はじめに
chrome.tabs.query() を叩き、何十ものタブ情報を取得してご満悦だったあの頃。chrome.storage.local.set() が成功したのを確認し、安心してブラウザを閉じたはずのあの夜。
そして訪れる「保存したセッションが、ある日突然すべて消える」という悪夢。
これは、多くのタブ管理ツール(OneTab, Session Buddy...)で今もなお報告され続けている、開発者にとってもユーザーにとっても最悪の事態です。
この記事は、そんなデータ消失の恐怖を根絶するため、私が生まれて初めて開発したChrome拡張機能「Tab Vault」の技術的な挑戦、学び、そして盛大な失敗の記録です。
この記事で話すこと
- なぜ既存のツールではなく、自作する道を選んだのか
- Manifest V3 と Vanilla JS を採用したアーキテクチャ設計
- 「絶対にデータを失わない」を実現するための具体的な実装
- ExtPay を使った買い切り決済の導入と、そこで得た知見
- 初めての個人開発でやらかした失敗談と、そこからの学び
なぜ作ったのか:課題は「機能」ではなく「信頼性」
開発の動機はシンプルです。既存のツールが信頼できなかったから。
- OneTab: シンプルだが、データ消失報告が多数。検索機能もない。
- Session Buddy: 高機能だが、こちらも同様にデータ消失の問題を抱える。
- Workona: 高機能だが、年$84という高額なサブスクリプションモデルに移行し、ユーザーの反発を招いた。
市場が求めているのは、目新しい機能ではなく「データを安全に保つ」という、ただ一つの基本的な約束を守れるツールだと確信し、開発に着手しました。
「絶対にデータを失わない」ためのアーキテクチャ設計
プロダクトのコンセプトは『あなたの仕事を絶対に失わせない、最も信頼できるタブ金庫』。このコンセプトを実現するため、技術選定と設計において以下の3つを絶対的なルールとしました。
1. Manifest V3への準拠:セキュリティと安定性の礎
言うまでもなく、現在のChrome拡張機能開発においてManifest V3(MV3)は必須です。MV3を採用することで、以下の恩恵を受け、堅牢な基盤を築きました。
-
Service Workerの採用:
イベント駆動で動作するため、不要なリソース消費を抑えられます。Tab Vaultでは、ブラウザ起動時 (chrome.runtime.onStartup) やタブの状態変化 (chrome.tabs.onUpdated) をフックし、必要な処理だけを実行します。 -
リモートコード実行の禁止:
拡張機能のパッケージに含まれるコードしか実行できないため、セキュリティリスクを大幅に低減できます。 -
最小権限の原則:
manifest.jsonで要求する権限を"storage","tabs","sessions"に限定。ユーザーデータを外部に送信するような権限は一切要求しないことで、プライバシーへの配慮を明確にしました。
{
"manifest_version": 3,
"name": "Tab Vault",
"version": "1.0.0",
"permissions": [
"storage",
"tabs",
"sessions"
],
"host_permissions": [
"https://extensionpay.com/*"
],
"background": {
"service_worker": "background.js"
}
}
2. Vanilla JSの採用:軽量性と依存ゼロへのこだわり
ReactやVue.jsといったフレームワークの利用も検討しましたが、最終的にライブラリ依存ゼロのVanilla JSを選択しました。
理由は、拡張機能の動作を可能な限り軽量かつ高速に保つためです。特にUIがシンプルなポップアップ画面において、仮想DOMのオーバーヘッドは不要と判断しました。結果として、ビルドプロセスがシンプルになり、将来的なライブラリの脆弱性を心配する必要もなくなりました。
非同期処理には async/await を全面的に採用し、Chrome APIのコールバック地獄を回避しています。
/**
* 現在のウィンドウの全タブをセッションとして保存します。
* @returns {Promise<void>} 保存処理が完了したら解決されるPromise。
*/
export async function saveCurrentWindow() {
try {
const tabs = await chrome.tabs.query({ currentWindow: true });
if (tabs.length === 0) {
return; // 保存対象なし
}
const newSession = {
id: `session-${Date.now()}`,
name: `Session at ${new Date().toLocaleString()}`,
tabs: tabs.map(tab => ({ url: tab.url, title: tab.title })),
};
await storageManager.addSession(newSession);
} catch (error) {
console.error('Failed to save session:', error);
}
}
3. chrome.storage.local への完全な閉結合
データ消失リスクを最小化するため、データストアは chrome.storage.local のみを利用し、外部サーバーとの通信を(決済を除き)一切行わない設計を徹底しました。
chrome.storage.local はブラウザが提供する堅牢なストレージであり、ユーザーのPC内にデータが保存されます。これにより、サーバー障害やネットワークエラーによるデータ損失の可能性を完全に排除しました。
技術的ハイライト:クラッシュ復元と決済機能の実装
自動クラッシュリカバリー機能の実装フロー
Tab Vaultの核となる機能の一つが、ブラウザクラッシュ後のセッション復元です。これは以下のフローで実現しています。
-
Service Worker (
background.js) がブラウザ起動を検知
chrome.runtime.onStartupイベントリスナーを使用します。 -
前回の終了がクラッシュによるものか判定
chrome.sessions.getRecentlyClosed()APIを呼び出します。このAPIは、直近で閉じられたタブやウィンドウのセッション情報を返してくれます。もしセッションリストにcrashed: trueという状態のウィンドウがあれば、それがクラッシュの証拠です。 -
リカバリー用のフラグを立てる
クラッシュを検知した場合、chrome.storage.session(メモリ上の一時ストレージ)に{ recoveryAvailable: true }のようなフラグを保存します。 -
ポップアップUIで復元を促す
ユーザーがポップアップを開いた際、まずこのフラグを確認。フラグがあれば、リカバリー用のUI(「直前のセッションを復元しますか?」)を表示します。
この機能により、「PCがフリーズした!」という絶望的な状況からでも、ユーザーの作業状態を救出できます。
ExtPayによる買い切り決済の導入
収益化には、Chrome拡張機能に特化した ExtensionPay を採用しました。サーバーサイドの実装なしに、$4.99 の買い切り決済を導入できます。
導入は驚くほど簡単でした。
-
公式のJSファイルを導入
-
manifest.jsonにhost_permissionsを追加"host_permissions": ["https://extensionpay.com/*"] -
決済処理を呼び出す
paymentmanager.jsimport ExtPay from './extpay.vendor.js'; const extpay = ExtPay('tabvault'); // あなたの拡張機能ID // 決済ページを開く function startPayment() { extpay.openPaymentPage(); } // ユーザー情報を取得してプレミアム状態を判定 async function checkPremiumStatus() { const user = await extpay.getUser(); if (user.paid) { // プレミアム機能を有効化 await storageManager.updateSettings({ isPremium: true }); } }
非常にシンプルですが、ここに落とし穴がありました(後述)。
初めての開発でやらかした失敗と学び
このプロジェクトは私の最初の成果物であり、当然ながら多くの失敗を経験しました。
-
【失敗談1】ExtPayのIDを取り違え、半日を溶かす
テスト実装中、ExtPayの初期化コードに、以前チュートリアルで使った別の拡張機能ID ('time-zone-hover') をハードコードしたままでした。当然、決済は成功せずNOT_PAIDエラーが頻発。原因がわからず、ExtPayのドキュメントを読み漁り、コードの隅々まで見直して半日を無駄にしました。- 学び: 環境変数やIDのような設定値は、真っ先に一元管理し、コードにベタ書きしない。 本番IDが確定したら、即座に関連ファイルすべてに反映させるべきでした。
-
【失敗談2】ドキュメントと実装の価格表記がバラバラ
当初$7.99で検討していた価格を$4.99に変更した際、コードは修正したものの、複数の設計書(docs/)に古い価格が残ったままでした。レビュー直前に気づき、慌てて修正する羽目に。-
学び: 設定値やマジックナンバーの変更時は、
grepやrgでプロジェクト全体を横断検索する癖をつける。rg '7\\.99'の一発で済む話でした。ドキュメントもコードの一部として、常に同期を保つ意識が重要です。
-
学び: 設定値やマジックナンバーの変更時は、
-
【失敗談3】モジュール責務の変更後、設計書が浦島太郎状態
paymentManager.jsの責務を変更したにもかかわらず、3_詳細設計書.mdの記述が古いまま(あるいは空欄)になっていました。コードレビューの際に「この関数の役割、設計書と違いますよね?」と指摘され、初めて気づきました。- 学び: コード変更とドキュメント更新は、必ず1つのコミット(またはPR)にまとめる。 「変更セット一覧」をメモに残し、関連ドキュメントの更新漏れを防ぐ仕組みを作りました。
まとめ
「Tab Vault」の開発は、データ消失という自分自身の「痛み」から始まった、初めての挑戦でした。
Manifest V3やVanilla JSといった技術的な選択は、すべて「ユーザーのデータを絶対に守る」という一点を達成するためのものです。その過程で多くの失敗も経験しましたが、一つひとつが今の自分にとってかけがえのない学びとなっています。
もし、あなたも既存のタブ管理ツールに一抹の不安を覚えていたり、Chrome拡張機能の個人開発に興味があったりするなら、この記事が何かのヒントになれば幸いです。
そして、よろしければ「Tab Vault」を一度お試しください。あなたのフィードバックが、この拡張機能をさらに信頼性の高いものへと成長させてくれます。
▼ Tab Vault (無料) - Chromeウェブストア
https://chromewebstore.google.com/detail/tab-vault/hjahlhfmfpdpbookdbdfcboihhmbifnk