ブラウザ拡張開発日記

作ってるもの→ https://chrome.google.com/webstore/search/cside?_category=extensions

相変わらず Bootstrap を使い続けてしまっている

前回のあらすじ:Tailwind は最低限のデザインで構わない自分にとっては Not for me だった - ブラウザ拡張開発日記

拡張機能の有料機能を、買い切り or サブスク どちらで提供するか、悩んだメモ

実装はあらかた終わったが、最後の最後に、買い切りかサブスクかで迷いが生じてしまった。

相談できる同僚もいないので、ここで自分の考えを整理したい。

対象のプロダクト

chrome.google.com

一見簡単そうに見えるが、パーサーが非常に複雑で、コード行数 5,000 行超えの大作となっている。これだけ手間隙かけて無料はちょっとなぁ、と思い始め、一部の機能を有料で提供する決断をした。

リスク要因

2 つある。

1. 拡張が、Notion の「非公式」API に依存していること

  • Notion が公式に 3rd に向けて提供されている API ではなく、Notion の Web ページで使われている非公式な API を勝手に使っている
    • つまり、 公式が API の仕様を変更して、ある日突然 拡張が動かなくなる可能性がありうる
    • その場合は当然、新バージョンの API に対応する改修を行なう必要がある。

2. 僕の体調が不安定であること

  • 現在は 1 週間に平均して 4〜5 日くらいは(短い時間ながらも)コードを書けている
  • しかし、体調が悪化したり、強い副作用の薬を服用するようになった場合、1〜2 ヶ月、最悪それ以上の期間、コードが書けなくなる可能性がありうる。(過去にもそういう事があった)

最も悪いケースを考えれば、 「僕が体調不良でコードを書けない期間」と「 Notion の API の(後方互換性を壊す)仕様変更」が同時に起こった場合、その期間はサービスが停止する ことになる。

サービスが当面の間 停止する場合の対応

上記の最悪ケースが起こり、当面の間サービスが停止する緊急事態になった場合。(しかも、いつコードを再び書けるようになるかも見通せない場合)

買い切りの場合

  • 有料プランの導線をいったん閉じ、場合によっては、つい最近お金を払ってくれたユーザーに対して返金対応をする

サブスクの場合

  • 有料プランの導線をいったん閉じ、 サービスが再開できる日まで、サブスク加入ユーザーに対して「毎月」払い戻しの対応をする
    • 「サービスを再開できるまでの間、サブスク加入ユーザーにはいったん解約してもらう」ということは ExtensionPay / Stripe の仕様上できなさそう(せいぜい、お願いのメールを一件一件手動で送るくらい)なので、毎月払い戻しをする以外無いだろう

以上を踏まえ、買い切りか、サブスクか

  • サブスクはやめておいたほうが良さそう
    • コードか書けないくらい病気が悪化しているときに、払い戻しというタスクが毎月発生するのは、(1) 既存のサブスク加入ユーザーへの罪悪感 (2) 早く回復しなければ、という焦りが生じる という 2 点から、ただでさえ病気で余裕の無くなっている精神状態にさらに追い打ちをかけることになる 。それはかなり危険
  • よって、消去法的に、買い切りに決定する。

終わりに

  • 上記の 2 つのリスク要因が無ければ、迷わずサブスクにしていただろう
    • 生活上、一度しかお金が入ってこないよりも、毎月お金が入ってきた方が嬉しいに決まっている
    • それに、メンテナンスに今後も継続的に時間を取られるだろうし。
    • ユーザーとしても、無名の僕にいきなりまとまった額を支払うのには抵抗があるはずなので、試しやすい少額で使い始められるサブスクリプションが嬉しいユーザーはいるだろう
  • しかし、リスク要因を無視することは出来ないので、今回は買い切りという選択を選ぶこととする。
----------------- 価格・課金スキームを考えるにあたり、モリゴンさんの [課金術 - portal shit!](https://portalshit.net/2023/10/04/how-to-charge-fees) を大変参考にさせていただきました(結果的に、彼の薦めるサブスク型は選べなかったけれど)。この場を借りて改めてお礼を申し上げます。

ブラウザ拡張機能を作る際に利用している外部サービス 3 つ

Sentry - エラー収集

https://sentry.io/

言わずとしれたエラー収集サービス。アラートを Slack に流してる。

💡エラーに個人情報が混じってる場合は適切にマスクした上で送信すること。

無料プランだと Slack 連携が使えないので

Sentry の Webhook → Vercel に立てているリクエスト中継アプリ → Slack の Incoming Webhook

みたいな感じで Sentry の Webhook を Slack までリレーしている。

Vercel のリクエスト変換アプリのコードはこんな感じ。(クリックで展開)

const { IncomingWebhook } = require("@slack/webhook");

export async function POST(request) {
  const params = await request.json();

  const webhook = new IncomingWebhook(
    "YOUR SLACK INCOMING WEBHOOK URL",
    {
      icon_url: "YOUR ICON URL",
      channel: "YOUR SLACK CHANNEL",
      username: "Sentry",
    }
  );
  await webhook.send(
    `${params.event.title}\n${params.url}\n( by ${params.project} )`
  );

  return new Response("OK");
}

リクエスト変換アプリを立てる場所はVercel でなくても、Google Cloud でも AWS でも何でも良いと思う。

canny.io - バグ報告/機能要望

https://canny.io/

バグレポートや機能要望をユーザーが投稿できる SaaS 。

以前までは Github Issues を使っていたが、言わずもがな Github アカウントが必要でユーザーにとって敷居が高かったので、Google などのアカウントでログインできるこちらのサービスに変えた。

ExtensionPay - 有料機能の実装

https://extensionpay.com/

決済機能をまるっと請け負ってくれるサービス。(正確にはまだ使っておらず、近々使う予定。)

これを使えば、サーバーを自前で持たなくても、クライアント JS のコードだけで有料機能の実装ができる。

ExtensionPay の内部では Stripe で決済を実行しているので、Stripe のアカウントが必要となる。(審査を通すために Web サイトなどをこしらえるのが割とだるい)

ただ、問題なのは競合サービスが存在しないことで、もし ExtensionPay がサ終した場合、決済のサーバーサイドを自前で用意しなければならないことになる。。サ終に怯えながら使ってる。

どうしたもんかなー。(昔は Google 公式が、拡張機能の決済システムを提供してくれていたらしいのだが、だいぶ昔に廃止されてしまった。復活してくれないかなー。無いか。。)

はてなブログを Markdown で書くとき、リスト(箇条書き)の先頭の記号を入力補完するブラウザ拡張機能

を作った。

インストール

Windows で動作検証できてないので、もし Win ユーザーで動かない人いたら教えてください。

仕様

  • 入力補完に対応してる記法
    • 順序なしリスト( - , * , + )
    • 順序付きリスト( 1. )
    • 引用( > )
  • 自動インデントも、勿論よしなにやる
  • Markdown 記法のみ対応。はてな記法では動きません

お気持ち

URL が動的に変わった場合に何かする( Chrome 拡張で)

追記

chrome.tabs.onUpdated の方がいいかも。

ユーザーに対して "ブラウザの履歴" 権限を要求しないのも、ユーザーの心象が良い。


追記前の文章ここから

URL が動的に変わった場合、とは要するに pushState された後に何かすることを指す。

window.popstate イベント というのがあるが、これは現在の Chrome などのブラウザでは使えない。

ではどうするか ... というと、chrome.webNavigation API の onHistoryStateUpdated イベントを listen すれば良い。

https://example.com/* でページの URL が変更された場合 *1 に何かするコードの例:

chrome.webNavigation.onHistoryStateUpdated.addListener(
  detail => { /* 何かする */ },
  { url: { urlPrefix: 'https://example.com/'} },
);

このコードは background service worker でしか動かないので、content script でなにかしたい場合は chrome.tabs.sendMessage() で content script にメッセージを投げる。

/* background.ts */
chrome.webNavigation.onHistoryStateUpdated.addListener(
  detail => chrome.tabs.sendMessage(detail.tabId),
  { url: { urlPrefix: 'https://example.com/'} },
);
/* content.ts */
chrome.runtime.onMessage.addListener(() => {
  /* 何かする */
});

ネック

この方法の唯一のネックは、ユーザーに webNavigation permission をインストール時に要求することになること。

「おいおい何で history にアクセスできる権限を要求するんだよ!!」とクレームが来たことが1度だけあるので、気にする人は気にするかもしれない。

*1:動的に変わった場合も、初回読み込み時も含む

複数の PJ で使う TypeScript のコードを共通ライブラリ化する

ライブラリを作るにあたり:tsup を使った

tsup

tsup とは:rollup みたいなバンドルツール。TypeScript で npm パッケージを作るのに特化している。

これを使わないと何が面倒臭いか:

  • CommonJS / ES Module 両対応させる場合、それぞれ別々の tsconfig.json を用意しなければならない
    • tsup だと、非常にシンプルな config ファイル 1 個だけで CommonJS / ES Module 両対応できる
  • スクリプト ( node_module/.bin/ に設置されるやつ) を作る場合、ES Module だと、 import './foo.js' みたいに import 文に .js を書かないと実行時エラーになってしまう
    • tsup を使っていると .js を書かなくても実行時エラーにならない
  • スクリプトを作る場合、ビルドした後に chmod +x しなければならない
    • tsup を使っていれば(ry

ライブラリをどこに上げるか→ Github Packages にした

GitHub Packagesのクイックスタート - GitHub Docs

Github Packages とは:パッケージをホストおよび管理する Github の機能。npm 互換がある。

ここに Github Actions を使ってパッケージをアップロードすれば、あとは npm パッケージをインストールするのと同じように、 npm install <package name> でそのパッケージを使えるようになる。

他に検討したがボツにしたもの:

  • 普通の素の Github リポジトリをホスト先として使う
    • 要は npm install https://github.com/Cside/some-package みたいなことをすること
    • 👍 npm publish とかすることなく、Push するだけで更新が反映されるのは楽
    • ❌ ビルドした .js ファイルも Git 管理しないといけない( Pull Request の差分とかが地獄になる)
  • npm の公開パッケージ
    • ❌ 非常に個人的なコードなので、公開パッケージとしてアップロードするのは躊躇われる
  • npm のプライベートパッケージ
    • ❌ 月額料金がかかる
  • Git submodule
    • 👍 .npmrc を書くことなくインストールできる
    • ❌ clone したあと、TypeScript を JS にビルドするタスクを何らかの方法で実行しなければならない
    • ❌ リポジトリを丸ごと clone するわけなので、Github Packages や npm のプライベートパッケージでインストールする場合と比べて、ファイルサイズが肥大化する
    • ❌ node_moduleじゃない場所に依存が入るのがキモい

Github Packages にもデメリットは 1 つあり、それは Github の Access Token を含んだ .npmrc を、リポジトリを触る全員が設置しないといけないこと。

これはメンテナにとってはだるそうなので、ここだけ気になるんだよなー。。(どうしようもならなそうだけど)

Plasmo がいまさら気になる(まだちゃんと触れていない)

割と前からあるっぽいが、社会と断絶した生活を送ってるため、昨日知った。。

www.plasmo.com

zenn.dev

公式サイトでは下記のように謳っている。

印象

  • 👍 魅力的に思えること
    • 複数ストアに自動デプロイする機能
      • Firefox ストア対応とか面倒くさかったので ...
    • chrome.runtime.sendMessage API 用いたメッセージングを型安全にできること
      • 何にも頼らずにメッセージングを型安全にやろうとすると、若干面倒だったので
    • 拡張を新規に作る際のブートストラップ、ボイラープレートコードはかなり少なく済みそう
  • 😥 懸念
    • かなり Plasmo Way でコードを書くことを強制してくること
      • 既存のプロダクトからの移行は大変そう
      • 万一 Plasmo が Deprecated になった場合に脱 Plasmo するのが大変そう
      • この手のフレームワークにありがちな、レールから少し外れたことをしたい場合に仰々しいコードを書く羽目にならないか不安
    • Twitter を見てると、まだ若干不安定な印象を受ける

結論

懸念点は触ってみないと何とも言えないので、どっかで腰を据えて触りたい。