WebSystemInjection

要件定義書: Web System Injection

プロジェクト概要

プロジェクト名: Web System Injection
バージョン: v1.0
作成日: 2026-04-12
形態: Chrome拡張機能(Manifest V3)

コンセプト

既存のWebサイトに対して、ドメイン単位でカスタム機能(プラグイン)を注入できるChrome拡張機能。
開発者がプラグインをZIP形式でインポートし、自分のブラウザ上でのみ動作させる。


機能要件

F-01: プラグイン管理機能

F-01-1: プラグインインポート

F-01-2: プラグイン一覧表示

F-01-3: プラグイン有効/無効切り替え

F-01-4: プラグイン削除

F-02: プラグイン実行機能

F-02-1: ドメインマッチング

F-02-2: スクリプト注入

F-02-3: プラグイン実行環境

F-03: ポップアップUI

F-03-1: メイン画面

F-03-2: プラグインインポート画面

F-03-3: 現在のページ向け表示


プラグイン定義フォーマット

plugin.json

{
  "id": "example-plugin",
  "name": "Example Plugin",
  "version": "1.0.0",
  "description": "プラグインの説明文",
  "author": "開発者名",
  "domains": [
    "example.com",
    "*.example.com"
  ],
  "scripts": {
    "main": "main.js",
    "runAt": "document_idle"
  },
  "styles": ["style.css"],
  "permissions": [],
  "config": {
    "buttonPosition": "bottom-right"
  }
}

plugin.json バリデーションルール

フィールド 必須 説明
id string 一意識別子。英数字・ハイフンのみ
name string 表示名
version string セマンティックバージョニング
description string - 説明文
author string - 作成者名
domains string[] 対象ドメインの配列(1つ以上)
scripts.main string メインスクリプトのファイル名
scripts.runAt string - 注入タイミング。デフォルト: document_idle
styles string[] - CSSファイル名の配列
permissions string[] - 将来拡張用
config object - プラグイン固有の設定値

技術仕様

ディレクトリ構成

web-system-injection/
├── manifest.json              # Chrome拡張マニフェスト(Manifest V3)
├── background.js              # Service Worker
├── content-loader.js          # Content Script(全ページに注入)
├── sdk.js                     # プラグイン向けユーティリティAPI(WSI.*)
├── popup/
│   ├── popup.html             # ポップアップUI
│   ├── popup.js               # ポップアップロジック
│   └── popup.css              # ポップアップスタイル
├── lib/
│   └── jszip.min.js           # ZIP解凍ライブラリ
└── icons/
    ├── icon16.png
    ├── icon48.png
    └── icon128.png

manifest.json

{
  "manifest_version": 3,
  "name": "Web System Injection",
  "version": "1.0.0",
  "description": "既存Webサイトにカスタム機能を注入するプラグインシステム",
  "permissions": [
    "storage",
    "activeTab",
    "scripting"
  ],
  "host_permissions": [
    "<all_urls>"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["sdk.js", "content-loader.js"],
      "run_at": "document_idle"
    }
  ],
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

データストレージ設計

chrome.storage.local

{
  "plugins": [
    {
      "id": "example-plugin",
      "name": "Example Plugin",
      "version": "1.0.0",
      "description": "...",
      "author": "...",
      "domains": ["example.com"],
      "runAt": "document_idle",
      "enabled": true,
      "code": "// main.js の内容(文字列)",
      "css": "/* style.css の内容(文字列)*/",
      "config": {},
      "installedAt": "2026-04-12T00:00:00Z",
      "updatedAt": "2026-04-12T00:00:00Z"
    }
  ],
  "pluginData_example-plugin": {
    "key1": "value1"
  }
}

SDK API仕様

WSI.addButton(options)

ページにフローティングボタンを追加する。

WSI.addButton({
  text: "実行",                    // ボタンテキスト
  icon: "",                      // アイコン文字(任意)
  position: "bottom-right",       // 表示位置: bottom-right / bottom-left / top-right / top-left
  onClick: () => { /* 処理 */ }   // クリック時のコールバック
});

WSI.addPanel(options)

ページにサイドパネルを追加する。

WSI.addPanel({
  title: "パネルタイトル",
  width: "300px",                 // パネル幅
  position: "right",             // 表示位置: right / left
  content: "<div>HTML内容</div>", // パネル内のHTML
  onOpen: () => {},              // 開いたときのコールバック
  onClose: () => {}              // 閉じたときのコールバック
});

WSI.storage

プラグイン固有の永続ストレージ。

await WSI.storage.get("key");           // 値の取得
await WSI.storage.set("key", "value");  // 値の保存
await WSI.storage.remove("key");        // 値の削除
await WSI.storage.getAll();             // 全データ取得

WSI.log(message)

デバッグ用ログ出力。ブラウザのコンソールに [WSI:プラグインID] プレフィックス付きで出力される。

WSI.log("処理を開始しました");
// コンソール出力: [WSI:example-plugin] 処理を開始しました

WSI.onPageLoad(callback)

SPA等でのページ内遷移を検知して処理を実行する。

WSI.onPageLoad((url) => {
  console.log("ページ遷移:", url);
});

WSI.getConfig()

plugin.jsonconfig フィールドに定義された設定値を取得する。

const config = WSI.getConfig();
console.log(config.buttonPosition); // "bottom-right"

処理フロー

プラグインインポートフロー

1. ユーザーがポップアップUIで「プラグインを追加」をクリック
2. ファイルピッカーが開き、ZIPファイルを選択
3. JSZipでZIPを展開
4. plugin.json を読み取り、バリデーション実行
   - 必須フィールドの存在チェック
   - id の形式チェック(英数字・ハイフンのみ)
   - domains が1つ以上あるか
   - main.js ファイルがZIP内に存在するか
5. バリデーション成功 → プレビュー表示
6. 既存の同一IDプラグインがあれば上書き確認
7. ユーザーが「インポート」を実行
8. plugin.json + main.js + style.css の内容を chrome.storage.local に保存
9. 完了メッセージを表示

プラグイン実行フロー

1. ページ読み込み時に content-loader.js が実行される
2. chrome.storage.local から plugins 一覧を取得
3. 現在のドメイン(location.hostname)とプラグインの domains を照合
4. マッチ&有効なプラグインをフィルタリング
5. 各プラグインに対して:
   a. WSI SDK のインスタンスを生成(プラグインIDをスコープとして紐づけ)
   b. CSS があれば <style> タグとしてページに注入
   c. main.js のコードを即時実行関数でラップし、WSI を引数として渡して実行
6. 実行ログを WSI.log 経由でコンソールに出力

非機能要件

パフォーマンス

セキュリティ

互換性

保守性


開発方針

使用技術

開発フェーズ

Phase 1(MVP)

Phase 2(機能拡張)

Phase 3(将来構想)


サンプルプラグイン

hello-world プラグイン

動作確認用の最小プラグイン。対象ドメインにフローティングボタンを表示する。

plugin.json

{
  "id": "hello-world",
  "name": "Hello World",
  "version": "1.0.0",
  "description": "動作確認用のサンプルプラグイン",
  "author": "WSI Team",
  "domains": ["example.com"],
  "scripts": {
    "main": "main.js",
    "runAt": "document_idle"
  },
  "styles": [],
  "config": {
    "message": "Hello from WSI!"
  }
}

main.js

const config = WSI.getConfig();

WSI.addButton({
  text: "👋",
  position: "bottom-right",
  onClick: () => {
    alert(config.message);
    WSI.log("ボタンがクリックされました");
  }
});

WSI.log("Hello World プラグインが読み込まれました");

CLAUDE.md 向け補足

このプロジェクトを Claude Code で実装する際の注意事項:

  1. Manifest V3 厳守 — Service Worker ベース。永続的なバックグラウンドページは使えない
  2. 外部ライブラリ — JSZip は npm からダウンロードし lib/ にバンドルする。CDN参照不可
  3. テスト — Chrome拡張のテストは chrome://extensions での手動読み込みで行う
  4. コード注入方式 — プラグインコードは即時実行関数(IIFE)でラップし、グローバルスコープを汚染しない
  5. エラーハンドリング — プラグインの実行エラーが拡張機能本体やページに影響しないよう try-catch で囲む
  6. 日本語対応 — UIテキストは日本語。将来のi18n対応を見据え、文字列はテンプレートリテラルで管理