@midra/webext 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -2
- package/dist/index.d.ts +899 -0
- package/dist/index.js +738 -0
- package/package.json +45 -4
package/README.md
CHANGED
|
@@ -1,3 +1,280 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @midra/webext
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Chrome / Firefox の WebExtensions API を共通のインターフェースで扱うためのライブラリです。足りないメソッドの補完、API 名の統一、タブ取得やポップアウトなどの実用ヘルパーを提供します。特定ブラウザの API の完全な模倣は目指しません。
|
|
4
|
+
|
|
5
|
+
> **AI 生成について**:本ライブラリのコードおよび本 README は、AI によって生成されています。利用時は、対象ブラウザでの動作・権限・セキュリティ要件を確認してください。
|
|
6
|
+
|
|
7
|
+
## 主な機能
|
|
8
|
+
|
|
9
|
+
- **サイドパネル**:`webext.side` で Chrome / Firefox の操作を共通化。
|
|
10
|
+
- **タブ取得・ポップアウト**:コンテンツスクリプト自身のタブ取得と、タブに関連付けた別ウィンドウの作成。
|
|
11
|
+
- **ストレージ**:不足メソッドの補完、値の取得・保存、変更監視。
|
|
12
|
+
- **型付きメッセージング**:チャンネルごとのリクエスト・レスポンスの型定義。
|
|
13
|
+
- **メニュー**:`menus` で Firefox の `menus` / Chrome の `contextMenus` の差異を吸収。
|
|
14
|
+
|
|
15
|
+
グローバルの `chrome` / `browser` は変更しません。WXT、ビルド時のブラウザ指定、追加のランタイム依存は不要です。
|
|
16
|
+
|
|
17
|
+
## 目次
|
|
18
|
+
|
|
19
|
+
- [対応方針](#対応方針)
|
|
20
|
+
- [基本と初期化](#基本と初期化)
|
|
21
|
+
- [サイドパネル](#サイドパネル)
|
|
22
|
+
- [タブ](#タブ)
|
|
23
|
+
- [ポップアウト](#ポップアウト)
|
|
24
|
+
- [ストレージ](#ストレージ)
|
|
25
|
+
- [型付きメッセージング](#型付きメッセージング)
|
|
26
|
+
- [ネイティブ API と移行](#ネイティブ-api-と移行)
|
|
27
|
+
- [デモ拡張](#デモ拡張)
|
|
28
|
+
- [開発](#開発)
|
|
29
|
+
|
|
30
|
+
## 対応方針
|
|
31
|
+
|
|
32
|
+
Manifest V3 と Promise 版のネイティブ API がある、最新版の Chrome / Firefox を対象とします。
|
|
33
|
+
|
|
34
|
+
- 個別の API には、バージョン・権限・マニフェスト設定の要件があります。
|
|
35
|
+
- Chrome のサイドパネルの `close` は、Chrome 141 以降で利用できます。
|
|
36
|
+
- 共通 API は Promise 形式です。ネイティブ API 全体を旧バージョン向けに Promise 化するものではありません。
|
|
37
|
+
- コールバック形式のみの古い API や、OS・ブラウザに機能自体がない API まで補完できるとは限りません。
|
|
38
|
+
|
|
39
|
+
## 基本と初期化
|
|
40
|
+
|
|
41
|
+
### 1. バックグラウンドで初期化
|
|
42
|
+
|
|
43
|
+
**バックグラウンドのトップレベルで `webext.initialize()` を同期的に呼んでください。** コンテンツスクリプト用のタブ取得ブリッジを登録します。サービスワーカーの再起動時も、トップレベルで再登録されます。
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { webext } from '@midra/webext'
|
|
47
|
+
|
|
48
|
+
webext.initialize()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. 共通 API を利用
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { webext } from '@midra/webext'
|
|
55
|
+
|
|
56
|
+
const tab = await webext.tabs.getTarget()
|
|
57
|
+
const keys = await webext.storage.local.getKeys()
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`webext` は最初のアクセス時に初期化します。モジュールのインポートだけなら拡張機能以外でも可能ですが、API へのアクセスには拡張機能の環境が必要です。
|
|
61
|
+
|
|
62
|
+
### 実行コンテキストを明示する場合
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { createWebExt } from '@midra/webext'
|
|
66
|
+
|
|
67
|
+
const webext = createWebExt({ context: 'sidepanel' })
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`api`、`url`、`browser` も明示指定できます。テストでは `api` を注入できます。
|
|
71
|
+
|
|
72
|
+
| プロパティ | 値・用途 |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `context.type` | `background` / `content-script` / `popup` / `sidepanel` / `null`。画面の役割はマニフェストから推測しますが、動的に変更したパスや独自画面では明示指定してください |
|
|
75
|
+
| `context.browser` | `chrome`(Chromium 系)/ `firefox` / `unknown`。通常の処理では、この値による分岐は不要です |
|
|
76
|
+
|
|
77
|
+
## サイドパネル
|
|
78
|
+
|
|
79
|
+
`webext.side` で、Chrome の `sidePanel` / Firefox の `sidebarAction` を共通の操作として利用できます。
|
|
80
|
+
|
|
81
|
+
| API | 動作 |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `available` | 開くためのAPIがあるか。表示状態ではありません |
|
|
84
|
+
| `capabilities` | `open` / `close` / `path` / `isOpen` / `targetedOpen` / `actionClick` の対応状況 |
|
|
85
|
+
| `open()` | 現在のウィンドウで開く |
|
|
86
|
+
| `close()` | 閉じる。パネルを無効化したりパスを変更したりしません |
|
|
87
|
+
| `getPath({ tabId? }?)` | 設定中の拡張ルート相対パスを返す。クエリ・ハッシュを保持 |
|
|
88
|
+
| `setPath(path, { tabId? }?)` | パスを変更。対象省略時はグローバル設定 |
|
|
89
|
+
| `isOpen({ windowId? }?)` | 指定ウィンドウ(省略時は現在)で表示されているか |
|
|
90
|
+
| `openPopout(options?)` | 対象タブに設定中のパネルを別ウィンドウで開く |
|
|
91
|
+
| `bindActionClick(onError)` | actionクリック時に開く。解除関数を返す |
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// backgroundのトップレベル。actionに default_popup があるとクリックイベントは発火しません。
|
|
95
|
+
const unbind = webext.side.bindActionClick(console.error)
|
|
96
|
+
|
|
97
|
+
// 拡張ページのボタンから開く場合
|
|
98
|
+
button.addEventListener('click', () => {
|
|
99
|
+
void webext.side.open().catch(console.error)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// パス設定はユーザー操作より前の初期設定時などに実行
|
|
103
|
+
await webext.side.setPath('ui/side.html')
|
|
104
|
+
await webext.side.openPopout({ width: 420, height: 720 })
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### マニフェスト設定
|
|
108
|
+
|
|
109
|
+
Chrome の設定(関連部分のみ):
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"manifest_version": 3,
|
|
114
|
+
"action": {},
|
|
115
|
+
"permissions": ["sidePanel"],
|
|
116
|
+
"side_panel": { "default_path": "ui/side.html" }
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Firefox の設定(関連部分のみ):
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"manifest_version": 3,
|
|
125
|
+
"action": {},
|
|
126
|
+
"sidebar_action": { "default_panel": "ui/side.html" }
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
パネル用 HTML は利用側で用意してください。マニフェストのビルド時変換は行いません。
|
|
131
|
+
|
|
132
|
+
### サイドパネルの制約
|
|
133
|
+
|
|
134
|
+
- `open()` はユーザー操作から直接呼びます。内部でネイティブ呼び出し前の非同期検索・設定更新は行いません。
|
|
135
|
+
- Chromeの対象省略は現在のウィンドウのグローバルパネル。タブ固有のパネルには `{ tabId }` を指定します。`open()` は同期的に `WINDOW_ID_CURRENT` を渡します。この定数に未対応の古いChromeでは、クリックイベントの `tab.windowId` など実際のIDを指定してください。
|
|
136
|
+
- Firefoxは明示的な開閉対象指定に対応しません(`windowId: -2` は現在のウィンドウとして扱います)。未対応操作は `UnsupportedOperationError` になります。
|
|
137
|
+
- `getPath()` は両ブラウザでルート相対パスに統一。`setPath()` は同じ拡張内の絶対URLも受け付けます。外部URLは拒否します。
|
|
138
|
+
- `isOpen()` はFirefoxの `sidebarAction.isOpen()` / Chromeの `runtime.getContexts()` を利用します。`capabilities.isOpen` は `native` / `document` / `false`。Chromeではパネルドキュメントの存在を観測するため、切り替え・終了途中などの厳密なUI表示状態とは一致しない可能性があります。未対応環境ではエラーにします。
|
|
139
|
+
- `available` / `capabilities` はAPIの存在確認です。権限やmanifest設定が正しいことまでは保証しません。
|
|
140
|
+
- `bindActionClick()` はbackground起動時に登録してください。解除は戻り値、または `webext.dispose()` で行えます。
|
|
141
|
+
- 配置、無効化、ブラウザ固有の開閉イベントなどはネイティブAPIを利用してください。ダミーイベントは提供しません。
|
|
142
|
+
|
|
143
|
+
## タブ
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
await webext.tabs.getCurrentActive() // 現在のウィンドウのアクティブタブ
|
|
147
|
+
await webext.tabs.getCurrentActiveId()
|
|
148
|
+
await webext.tabs.getSelf() // コンテンツスクリプト自身を含むタブ
|
|
149
|
+
await webext.tabs.getSelfId()
|
|
150
|
+
await webext.tabs.getTarget() // リンク先 → コンテンツスクリプト自身 → アクティブタブ
|
|
151
|
+
await webext.tabs.getTargetId()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
コンテンツスクリプトでも共通ヘルパーを使用できます。background側の `initialize()` が必要です。`getCurrentActive()` は送信元タブのウィンドウに限定して検索します。
|
|
155
|
+
|
|
156
|
+
`tabs.available` はネイティブのtabs APIが利用可能かどうかです。コンテンツスクリプトで `false` でも上記ヘルパーはブリッジ経由で動きます。`query()` などネイティブメソッドは直接使用できません。
|
|
157
|
+
|
|
158
|
+
タブがなければ `undefined`。リンク先が閉じられていた場合はエラーを伝播し、別のタブへ自動的に切り替えません。URLなどの情報には `tabs` やホスト権限が必要です。
|
|
159
|
+
|
|
160
|
+
## ポップアウト
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
await webext.action.openPopout({ width: 420, tabId: 123 })
|
|
164
|
+
await webext.side.openPopout({ width: 420 })
|
|
165
|
+
await webext.side.openPopout({ tabId: null }) // リンクなし
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`tabId` は関連付けるタブのIDで、既存タブを移動する指定ではありません。省略時は `getTarget()` の対象を使います。現在のaction/panel設定を取得し、URLのクエリ・ハッシュを保持します。actionのpopupが空に設定されている場合はエラーで、manifestの初期値へ勝手に戻しません。
|
|
169
|
+
|
|
170
|
+
`context.linkedTabId` はIDまたは `null`。`context.isPopout` はリンクなしでも `true` です。内部クエリパラメータは拡張ページでのみ解釈します。これらは便宜上の情報であり、認証・アクセス制御には利用できません。
|
|
171
|
+
|
|
172
|
+
## ストレージ
|
|
173
|
+
|
|
174
|
+
存在する `local` / `sync` / `managed` / `session` 領域に同じヘルパーを提供します。権限が必要です。未対応のsession領域はメモリや永続領域で代用しません。managed領域の書き込みはネイティブ側で拒否されます。
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const theme = await webext.storage.local.getValue('theme', 'system')
|
|
178
|
+
await webext.storage.local.setValue('theme', 'dark')
|
|
179
|
+
const stop = webext.storage.local.watch<string>('theme', (value, previous) => {
|
|
180
|
+
console.log(previous, value)
|
|
181
|
+
})
|
|
182
|
+
stop()
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- `getKeys()`:ネイティブ優先。補完時は `get(null)` で全値を読んでキーを取得。
|
|
186
|
+
- `getBytesInUse(keys?)`:ネイティブ優先。補完時はキーとJSON化した値のUTF-8バイト数の合計。**推定値で、ディスク使用量やクォータ判定には使えません。**
|
|
187
|
+
- `capabilities.getKeys`:`native` / `polyfilled`。
|
|
188
|
+
- `capabilities.getBytesInUse`:`native` / `estimated`。
|
|
189
|
+
- `watch()`:値の変更・削除を監視。削除時は `undefined`。解除関数を返し、`webext.dispose()` でも解除。
|
|
190
|
+
- `getValue<T>()` の型指定は実行時検証ではありません。保存データの検証が必要なら利用側で行ってください。
|
|
191
|
+
|
|
192
|
+
## 型付きメッセージング
|
|
193
|
+
|
|
194
|
+
同じチャンネル名・スキーマを送受信側で共有します。1つのリクエストは1つのコンテキストで処理してください。
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
interface Messages {
|
|
198
|
+
greet: { request: { name: string }; response: string }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const channel = webext.messaging.channel<Messages>('app/background')
|
|
202
|
+
|
|
203
|
+
// background(トップレベルで登録)
|
|
204
|
+
const stop = channel.handle('greet', ({ name }, sender) => `Hello, ${name}`)
|
|
205
|
+
|
|
206
|
+
// popup / content scriptなど別のコンテキスト
|
|
207
|
+
const result = await channel.send('greet', { name: 'Midra' })
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`send()` の第3引数で `tabId`、`frameId`、`documentId`、`timeoutMs`、`signal` を指定できます。タブへの送信にはネイティブtabs APIが必要です。
|
|
211
|
+
|
|
212
|
+
- 応答待ちはデフォルト10秒。`timeoutMs` は正の有限数で、上限は `2_147_483_647` ミリ秒です。範囲外の指定は送信前に `TypeError`。タイムアウトは `MessageTimeoutError`。
|
|
213
|
+
- handlerの例外は `RemoteError` として伝播(元の名前は `remoteName`)。例外値を文字列化できない場合は、`remoteName: 'Error'`、メッセージ `'Remote handler failed'` にフォールバックします。
|
|
214
|
+
- `AbortSignal` とタイムアウトは送信側の待機のみ終了し、受信側の処理をキャンセルしません。
|
|
215
|
+
- Chrome / Firefoxで同じcallback応答方式を使用。handlerは同期的に呼び出し、非同期結果も応答できます。
|
|
216
|
+
- JSON互換値のみ送れます。Date、Map、BigInt、循環参照などは拒否。オブジェクト内の `undefined` プロパティは省略されます。
|
|
217
|
+
- 同じ拡張IDからの内部メッセージだけを受け付けます。未登録のメッセージを横取りしません。
|
|
218
|
+
- 型は実行時検証ではありません。受信値やsenderの追加検証は必要に応じてhandler内で行ってください。
|
|
219
|
+
- `stop()` / `channel.dispose()` / `webext.dispose()` でリスナーを解除。
|
|
220
|
+
|
|
221
|
+
## ネイティブ API と移行
|
|
222
|
+
|
|
223
|
+
ブラウザ固有の操作には `webext.native` を使用できます。`webext.sidePanel` / `webext.sidebarAction` もネイティブのままです。
|
|
224
|
+
|
|
225
|
+
旧実装から移行する場合は、次のように置き換えてください。
|
|
226
|
+
|
|
227
|
+
| 旧実装 | 移行先 |
|
|
228
|
+
| --- | --- |
|
|
229
|
+
| サイドパネル操作 | `webext.side` |
|
|
230
|
+
| `sidePanel.path` | `side.getPath()` / `side.setPath()` |
|
|
231
|
+
| `action.path` | `action.getPopup()` で実際の設定を取得 |
|
|
232
|
+
| リンク先優先の `tabs.getCurrentActive()` | `tabs.getTarget()` |
|
|
233
|
+
|
|
234
|
+
## デモ拡張
|
|
235
|
+
|
|
236
|
+
Chrome / Firefox 向けの試験用拡張を生成できます。
|
|
237
|
+
|
|
238
|
+
```sh
|
|
239
|
+
bun run demo:build
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
読み込み方と操作手順は [demo/README.md](demo/README.md) を参照してください。
|
|
243
|
+
|
|
244
|
+
## 開発
|
|
245
|
+
|
|
246
|
+
依存関係をインストールしてから、必要なコマンドを実行してください。
|
|
247
|
+
|
|
248
|
+
```sh
|
|
249
|
+
bun install
|
|
250
|
+
bun run validate
|
|
251
|
+
bun run build
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
| コマンド | 内容 |
|
|
255
|
+
| --- | --- |
|
|
256
|
+
| `bun run validate` | 書式・静的解析・型チェック・全テスト(ファイルは変更しない) |
|
|
257
|
+
| `bun run check` | 書式・静的解析の検証のみ |
|
|
258
|
+
| `bun run check:fix` | 書式・静的解析の自動修正(ファイルを書き換える) |
|
|
259
|
+
| `bun run compile` | 型チェック |
|
|
260
|
+
| `bun test` | 全テスト |
|
|
261
|
+
| `bun run build` | ライブラリのビルド |
|
|
262
|
+
| `bun run demo:build` | デモ拡張のビルド |
|
|
263
|
+
|
|
264
|
+
リリース用ワークフローでも、ビルド前に `validate` を実行します。
|
|
265
|
+
|
|
266
|
+
### ソース構成
|
|
267
|
+
|
|
268
|
+
- `src/core.ts`:公開APIの組み立てと初期化。
|
|
269
|
+
- `src/context.ts` / `src/paths.ts`:実行環境判定、拡張内URLの検証。
|
|
270
|
+
- `src/tabs.ts` / `src/popout.ts`:タブ取得ブリッジ、ポップアウトの対象解決と作成。
|
|
271
|
+
- `src/side.ts` / `src/storage.ts`:ブラウザ差異の吸収と共通ヘルパー。
|
|
272
|
+
- `src/messaging/`:公開型、JSON検証、送信・待機処理、チャンネルのルーティング。
|
|
273
|
+
- `src/disposables.ts` / `src/facade.ts`:解除処理の管理、ネイティブAPIを変更しないラッパー。
|
|
274
|
+
- `demo/src/operations.ts`:ユーザー操作を維持した実行と、ボタンごとの実行中状態の管理。
|
|
275
|
+
|
|
276
|
+
メッセージの受信リスナーは `createWebExt()` のインスタンスごとに1つを共有し、handlerが存在する間だけ登録します。ストレージの監視とactionクリックの解除関数は、繰り返し呼んでも解除処理を重複実行しません。
|
|
277
|
+
|
|
278
|
+
### 検証範囲
|
|
279
|
+
|
|
280
|
+
テストでは API のモックによる動作・境界条件・解除処理と、公開用スクリプトの出力を確認します。実際の表示・ユーザー操作の有効期間・権限は、ブラウザでの確認が必要です。
|