@kubohiroya/turbowarp-extension-security-dialog 0.2.0 → 0.3.0
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/NOTICE.md +6 -0
- package/README.ja.md +96 -11
- package/README.md +120 -14
- package/dist/index.cjs +154 -2
- package/dist/index.d.ts +14 -0
- package/dist/index.mjs +154 -2
- package/dist/react.cjs +249 -13
- package/dist/react.d.ts +10 -1
- package/dist/react.mjs +249 -13
- package/package.json +1 -1
- package/styles.css +40 -0
- package/webpack4.css +40 -0
package/NOTICE.md
CHANGED
|
@@ -32,3 +32,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
32
32
|
`assets/screenshots/required-modes-{en,ja}-20261025.png` are unmodified browser captures of the public Editor on 25 October 2026, showing Camera Source 0.19.3, Asset Cache 0.3.1 and Bubble 0.13.4. Their origin, Editor revisions and hashes are recorded in `assets/screenshots/capture-20261025.json`. The captured UI, icons and banners retain their respective rights holders and applicable licenses; this package does not relicense those elements.
|
|
33
33
|
|
|
34
34
|
`assets/screenshots/localized-descriptions-{en,ja}-20261025.png` are unmodified captures of the updated public Editor on 25 October 2026, showing localized descriptions for Camera Source 0.19.4, Asset Cache 0.3.2 and Bubble 0.13.5. Their origin, revisions and hashes are recorded in `assets/screenshots/localized-descriptions-20261025.json`. The captured UI, icons and banners retain their respective rights holders and applicable licenses.
|
|
35
|
+
|
|
36
|
+
`assets/screenshots/required-approval-{en,ja}-20261028.png` are unmodified captures of the public Editor on 28 October 2026 after the required-mode approval update, showing Camera Source 0.19.4, Asset Cache 0.3.2 and Bubble 0.13.5 with Deny and With sandbox disabled. Their origin, verified distribution hashes, Editor revisions and image hashes are recorded in `assets/screenshots/required-approval-20261028.json`. The captured UI, icons and banners retain their respective rights holders and applicable licenses.
|
|
37
|
+
|
|
38
|
+
`assets/screenshots/preselected-required-{en,ja}-20261028.png` are unmodified captures of the public Editor on 28 October 2026 after automatic preselection was added when all listed extensions require unsandboxed execution. Camera Source 0.19.4, Asset Cache 0.3.2 and Bubble 0.13.5 have Without sandbox preselected and still require OK before execution. Origin, verified distribution hashes, Editor revisions and image hashes are recorded in `assets/screenshots/preselected-required-20261028.json`. The captured UI, icons and banners retain their respective rights holders and applicable licenses.
|
|
39
|
+
|
|
40
|
+
`assets/screenshots/hidden-bulk-required-{en,ja}-20261028.png` are unmodified captures of the public Editor on 28 October 2026 with Without sandbox preselected for all three required extensions and the redundant bulk selection section hidden. Camera Source 0.19.4, Asset Cache 0.3.2 and Bubble 0.13.5 still require OK before execution. Origin, verified distribution hashes, Editor revisions and image hashes are recorded in `assets/screenshots/hidden-bulk-required-20261028.json`. The captured UI, icons and banners retain their respective rights holders and applicable licenses.
|
package/README.ja.md
CHANGED
|
@@ -5,17 +5,99 @@
|
|
|
5
5
|
TurboWarpの複数の機能拡張を、1つの一覧で確認するための共通パッケージです。
|
|
6
6
|
関連タスクと受け入れ基準は [scratch-gui #33](https://github.com/kubohiroya/scratch-gui/issues/33) に記録しています。
|
|
7
7
|
|
|
8
|
+
## Pagesのカタログから機能拡張を選択する
|
|
9
|
+
|
|
10
|
+
`ExtensionSelectionList` は既存のバナー・メタデータ・詳細表示を共有し、各行の右端へチェックボックスを1つ表示します。
|
|
11
|
+
TurboWarp Plus一覧の対応範囲は [#28](https://github.com/kubohiroya/turbowarp-extension-security-dialog/issues/28) に記録しています。
|
|
12
|
+
ホスト側でGitHub PagesのJSONをHTTPS取得し、このコンポーネントへ渡します。
|
|
13
|
+
|
|
14
|
+
```jsx
|
|
15
|
+
import {loadExtensionCatalog} from '@kubohiroya/turbowarp-extension-security-dialog';
|
|
16
|
+
import {ExtensionSelectionList} from '@kubohiroya/turbowarp-extension-security-dialog/react';
|
|
17
|
+
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const entries = await loadExtensionCatalog(
|
|
20
|
+
'https://kubohiroya.github.io/turbowarp-plus-packages/extensions-v1.json',
|
|
21
|
+
controller.signal
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const list = <ExtensionSelectionList entries={entries} locale="ja"
|
|
25
|
+
filterQuery={searchText} loadedIds={loadedExtensionIds} busy={loading}
|
|
26
|
+
onCancel={closeLibrary}
|
|
27
|
+
onComplete={ordered => host.reviewAndLoadInOrder(ordered)} />;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
例の `host.reviewAndLoadInOrder` はホスト側で実装する実行許可確認・VM読み込み処理です。
|
|
31
|
+
`onComplete` は依存先を先に並べた重複のない配列を返し、`loadedIds` は除外します。
|
|
32
|
+
ある拡張を選ぶと推移的な依存先すべてがchecked・disabledになります。
|
|
33
|
+
親を解除すると依存関係を再計算し、ほかの選択やユーザー自身の明示選択が必要としている拡張は選択を保ちます。
|
|
34
|
+
読み込み済みの行はchecked・disabledです。検索と表示言語を切り替えても選択を保ちます。
|
|
35
|
+
`busy` で選択・フッター操作を無効にします。取得失敗・再試行・カタログの取得中止・VMエラーの表示と、
|
|
36
|
+
読み込み成功に応じた `loadedIds` 更新はホスト側の担当です。
|
|
37
|
+
|
|
38
|
+
各行は「手動で選択」(青)・「依存関係で自動選択」(紫)・「未選択」(中立色)を、背景・左枠・
|
|
39
|
+
アイコン・状態名で区別します。「読み込み済み」も独立した表示です。一覧の上部には凡例を表示します。
|
|
40
|
+
自動選択した行には「Bubble、Sprite に必要」のように、必要としている明示選択元すべてを表示します。
|
|
41
|
+
手動で選んだ拡張がほかの選択元の依存先になった場合も、「手動で選択」を維持し、依存元を併記して
|
|
42
|
+
チェックボックスを解除不可にします。DOMの `data-selection-state` は順に `selected` / `dependency` /
|
|
43
|
+
`unselected` / `loaded` です。
|
|
44
|
+
|
|
45
|
+
任意のカテゴリ定義がある場合、ダッシュボードと同じ順序でカテゴリ見出し(`h2`)を表示します。
|
|
46
|
+
日本語は `titleJa` があれば使用し、なければ `title` にフォールバックします。
|
|
47
|
+
検索で空になったカテゴリは非表示になりますが、選択状態と「詳細」の対象は維持します。
|
|
48
|
+
カテゴリのない旧JSONは従来の平坦な一覧です。カテゴリ付きと未指定の項目が混在する場合、未指定分は
|
|
49
|
+
末尾の「その他の機能拡張」へ表示します。配色はホストのモーダル背景色・文字色に従います。
|
|
50
|
+
強調色は `--twextsec-manual-accent` / `--twextsec-dependency-accent` /
|
|
51
|
+
`--twextsec-unselected-accent` / `--twextsec-loaded-accent` で調整できます。
|
|
52
|
+
|
|
53
|
+
一覧の表示・選択だけでは機能拡張のコードを読み込み・実行しません。
|
|
54
|
+
JSONのメタデータを直ちに表示し、「詳細」を開いたときだけソースを文字列として取得します。
|
|
55
|
+
取得に失敗してもカタログのメタデータと選択は利用できます。
|
|
56
|
+
実行許可確認には `ExtensionSecurityDialog context="catalog"` を使用でき、キャンセル文言が選択一覧へ戻る案内になります。
|
|
57
|
+
既定の `context="project"` はプロジェクト読み込み用の文言を維持します。
|
|
58
|
+
ホストは確定された実行モードと独自のセキュリティ方針を尊重して読み込んでください。
|
|
59
|
+
|
|
60
|
+
JSON契約は `schemaVersion: 1` と `extensions` 配列です。各要素の必須項目は次のとおりです。
|
|
61
|
+
|
|
62
|
+
| 項目 | 内容 |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `id` | ASCII英数字の機能拡張ID |
|
|
65
|
+
| `name` | 表示名 |
|
|
66
|
+
| `version` | 配布バージョン |
|
|
67
|
+
| `url` | バージョン固定の公開済み機能拡張JSへのHTTPS URL |
|
|
68
|
+
| `dependencies` | 同じJSONに含まれる依存先の機能拡張ID配列。npmパッケージ名ではない |
|
|
69
|
+
|
|
70
|
+
`package`・`description`・`descriptions`(`ja`等の小文字言語タグをキーとする説明)・`author`・`license`・
|
|
71
|
+
`banner`・`repository`・`unsandboxed`(例: `required`)は任意のメタデータです。
|
|
72
|
+
完全なJSON例は[英語版のAPI例](README.md#selecting-extensions-from-a-pages-catalog)を参照してください。
|
|
73
|
+
生成側でパッケージ依存を拡張IDへ変換し、ビルド専用パッケージを除外します。
|
|
74
|
+
|
|
75
|
+
カテゴリはトップレベルの任意項目 `categories: [{id, title, titleJa?}]` としてダッシュボード順に定義し、
|
|
76
|
+
各 `extensions[].category` にそのIDを指定します。カテゴリIDの重複・不正な定義・未定義IDの参照は拒否します。
|
|
77
|
+
`parseExtensionCatalog` の返り値は従来どおり配列で、カテゴリ参照は各要素の
|
|
78
|
+
`category: {id, title, titleJa?, order}` へ解決します。`order` は定義の配列順です。
|
|
79
|
+
|
|
80
|
+
`parseExtensionCatalog` は未対応schema、ID/URL重複、存在しない依存先、循環依存、HTTPS以外や認証情報付きのURLを拒否します。
|
|
81
|
+
`resolveExtensionSelection(entries, explicitIds, loadedIds)` はReactなしでも同じ選択集合・依存順を計算します。
|
|
82
|
+
返り値の `requiredBy` は依存先IDをキー、必要としている明示選択元IDの集合を値とするMapです。
|
|
83
|
+
推移的依存・共有依存を含め、選択を解除すると依存元の集合も再計算します。
|
|
84
|
+
`loadExtensionCatalog` は任意のAbortSignalを受け付け、認証情報・Refererを送らずに取得し、HTTP失敗や不正なJSON契約を例外として返します。
|
|
85
|
+
|
|
86
|
+
切り戻しはホストの起動時固定・既定OFFのフラグをOFFにして再読み込みするか、カタログ接続差分をrevertします。
|
|
87
|
+
既存の3つの実行モードを持つセキュリティ確認は、そのまま利用できます。
|
|
88
|
+
|
|
8
89
|
## 公開Editorのスクリーンショット
|
|
9
90
|
|
|
10
91
|
[英語版](./README.md)と日本語版は、同じダイアログの表示言語を切り替え、ラベルと拡張の説明文を各言語で表示しています。
|
|
11
|
-
|
|
12
|
-
Camera Source 0.19.4・Asset Cache 0.3.2・Bubble 0.13.5を開いた実画面です。
|
|
92
|
+
全件必須時の自動選択と一括選択欄の非表示に対応した[公開Editor](https://kubohiroya.github.io/turbowarp-extension-data-editor/editor.html?lang=ja)で、
|
|
93
|
+
2026年10月28日にCamera Source 0.19.4・Asset Cache 0.3.2・Bubble 0.13.5を開いた実画面です。
|
|
13
94
|
|
|
14
|
-

|
|
15
96
|
|
|
16
|
-
3拡張とも`Unsandboxed: required
|
|
17
|
-
|
|
18
|
-
|
|
97
|
+
3拡張とも`Unsandboxed: required`を宣言しています。「拒否」と「サンドボックスあり」は無効化され、
|
|
98
|
+
理由が表示されます。「サンドボックスなし」だけが有効で、「動作に必要」と表示します。
|
|
99
|
+
全拡張がこのモードを必要とする場合、全件のメタデータ取得後に選択済みになり、一括選択欄は非表示になります。
|
|
100
|
+
「OK」で続行するか、「キャンセル」でプロジェクトの読み込みを中止します。
|
|
19
101
|
必要なモードの表示は、安全性や配布元の信頼性の認定ではありません。
|
|
20
102
|
|
|
21
103
|
| 機能拡張 | サンドボックスなしが必要な理由 |
|
|
@@ -25,7 +107,7 @@ Camera Source 0.19.4・Asset Cache 0.3.2・Bubble 0.13.5を開いた実画面で
|
|
|
25
107
|
| Bubble | ホストの実行環境と描画機能へ直接アクセスして、ステージ上に重ねた吹き出しや立ち絵を描画・更新します。 |
|
|
26
108
|
|
|
27
109
|
画像はダイアログ部分をブラウザーで直接撮影したもので、加工していません。
|
|
28
|
-
取得元の版・Editor build・画像hashは[撮影台帳](./assets/screenshots/
|
|
110
|
+
取得元の版・Editor build・画像hashは[撮影台帳](./assets/screenshots/hidden-bulk-required-20261028.json)に記録しています。
|
|
29
111
|
|
|
30
112
|
## 連動するパッケージ
|
|
31
113
|
|
|
@@ -207,12 +289,15 @@ HTTP/HTTPSの出所URLは完全なURLを表示し、別タブで開きます。
|
|
|
207
289
|
個別選択・一括選択・一覧の見出しは、禁止アイコンの「拒否」、盾の「サンドボックスあり」、
|
|
208
290
|
警告の「サンドボックスなし」の順です。アイコンと色は文字ラベルを補助し、安全性の認定には使いません。
|
|
209
291
|
|
|
210
|
-
先頭の宣言コメントに `// Unsandboxed: required`
|
|
211
|
-
|
|
292
|
+
先頭の宣言コメントに `// Unsandboxed: required` があれば、「拒否」と「サンドボックスあり」を無効化して
|
|
293
|
+
理由を表示し、選択できる唯一のモード「サンドボックスなし」に「動作に必要」を付けます。
|
|
294
|
+
選択したモードを「OK」で承認するか、「キャンセル」でプロジェクトの読み込みを中止します。
|
|
212
295
|
この宣言は作者の自己申告であり、未宣言・取得失敗から必須モードを推測しません。
|
|
213
296
|
|
|
214
|
-
|
|
215
|
-
|
|
297
|
+
「拒否」を含む選択の確定はメタデータ取得の完了または失敗後です。全拡張の取得が成功し、すべてが
|
|
298
|
+
この必須モードを宣言していれば、非対応の保存済み選択も含めて「サンドボックスなし」を選択済みにします。
|
|
299
|
+
「OK」を押すまで拡張は実行せず、「キャンセル」で中止できます。取得中・未宣言・任意指定・取得失敗が
|
|
300
|
+
1件でもあれば、この一括初期選択は行いません。混在する一覧では、非対応の選択を解除して選び直しを案内します。
|
|
216
301
|
一括選択は対応する拡張だけに適用し、全件が非対応の一括ボタンは無効化します。
|
|
217
302
|
|
|
218
303
|
## 接続例
|
package/README.md
CHANGED
|
@@ -12,14 +12,15 @@ Related tasks and acceptance criteria are tracked in
|
|
|
12
12
|
[English](./README.md) and [Japanese](./README.ja.md) use the same dialog with localized labels and extension descriptions.
|
|
13
13
|
This capture shows Camera Source 0.19.4, Asset Cache 0.3.2 and Bubble 0.13.5 in the
|
|
14
14
|
[public Editor](https://kubohiroya.github.io/turbowarp-extension-data-editor/editor.html?lang=en)
|
|
15
|
-
on
|
|
15
|
+
on 28 October 2026, with **Without sandbox** already selected for all three extensions and the unnecessary bulk selection section hidden.
|
|
16
16
|
|
|
17
|
-

|
|
18
18
|
|
|
19
|
-
All three extensions declare `Unsandboxed: required`. **With sandbox**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
All three extensions declare `Unsandboxed: required`. **Deny** and **With sandbox** are
|
|
20
|
+
disabled, with the reason shown. **Without sandbox** is enabled and marked **Required to run**.
|
|
21
|
+
When every listed extension requires this mode, it is preselected after all metadata loads, and the bulk selection section is hidden.
|
|
22
|
+
Press OK to continue, or **Cancel** to stop loading the project.
|
|
23
|
+
These labels describe execution requirements, not a safety certification.
|
|
23
24
|
|
|
24
25
|
| Extension | Why it needs to run without a sandbox |
|
|
25
26
|
| --- | --- |
|
|
@@ -28,7 +29,7 @@ choice and OK. These labels describe execution requirements, not a safety certif
|
|
|
28
29
|
| Bubble | Accesses the host runtime and renderer directly to draw and update layered speech bubbles and portraits on the stage. |
|
|
29
30
|
|
|
30
31
|
The PNG is an unmodified, modal-only browser capture. Source versions, Editor build and image
|
|
31
|
-
hashes are recorded in [the capture manifest](./assets/screenshots/
|
|
32
|
+
hashes are recorded in [the capture manifest](./assets/screenshots/hidden-bulk-required-20261028.json).
|
|
32
33
|
|
|
33
34
|
## How it fits together
|
|
34
35
|
|
|
@@ -222,15 +223,19 @@ Per-row choices, the bulk choices and the list heading are ordered: **Deny** (pr
|
|
|
222
223
|
icon), **With sandbox** (shield), **Without sandbox** (warning). Icons and colour support the
|
|
223
224
|
text labels; they are not a safety certification.
|
|
224
225
|
|
|
225
|
-
If the leading declaration comment contains `// Unsandboxed: required`, *With sandbox*
|
|
226
|
-
disabled with the reason shown, and *Without sandbox* is
|
|
227
|
-
|
|
226
|
+
If the leading declaration comment contains `// Unsandboxed: required`, *Deny* and *With sandbox*
|
|
227
|
+
are disabled with the reason shown, and *Without sandbox* is the only available mode, marked
|
|
228
|
+
*Required to run*. Press OK to approve the selected modes, or Cancel to stop loading
|
|
229
|
+
the project. This declaration is the author's own claim; a required mode is never inferred from
|
|
228
230
|
a missing declaration or a failed fetch.
|
|
229
231
|
|
|
230
|
-
A choice
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
A choice, including Deny, is only committed once metadata has loaded or failed. When every
|
|
233
|
+
listed extension has loaded metadata declaring this requirement, *Without sandbox* is
|
|
234
|
+
preselected for all entries, including entries with incompatible saved choices. Nothing runs
|
|
235
|
+
until the user presses OK; Cancel still stops loading the project. Pending, missing, optional
|
|
236
|
+
or failed declarations prevent this batch default. In mixed batches, incompatible choices
|
|
237
|
+
are cleared and the user is asked to choose again. A bulk choice applies only to extensions
|
|
238
|
+
that support it, and a bulk button is disabled when no listed extension does.
|
|
234
239
|
|
|
235
240
|
## Wiring it up
|
|
236
241
|
|
|
@@ -251,6 +256,107 @@ surfacing as a VM error is identified with `isExtensionSecurityCancelled(error)`
|
|
|
251
256
|
Under Webpack 4 with CSS Modules, load `webpack4.css` instead of `styles.css`; root-level
|
|
252
257
|
compatibility entries resolve for older Webpack 4 setups that do not read package exports.
|
|
253
258
|
|
|
259
|
+
## Selecting extensions from a Pages catalog
|
|
260
|
+
|
|
261
|
+
`ExtensionSelectionList` shares the security dialog's banners, metadata and Details view,
|
|
262
|
+
with one checkbox at the right of each row. This API supports the TurboWarp Plus library
|
|
263
|
+
tracked in [#28](https://github.com/kubohiroya/turbowarp-extension-security-dialog/issues/28).
|
|
264
|
+
The host fetches a versioned JSON catalog over HTTPS:
|
|
265
|
+
|
|
266
|
+
```jsx
|
|
267
|
+
import {loadExtensionCatalog} from '@kubohiroya/turbowarp-extension-security-dialog';
|
|
268
|
+
import {ExtensionSelectionList} from '@kubohiroya/turbowarp-extension-security-dialog/react';
|
|
269
|
+
|
|
270
|
+
const controller = new AbortController();
|
|
271
|
+
const entries = await loadExtensionCatalog(
|
|
272
|
+
'https://kubohiroya.github.io/turbowarp-plus-packages/extensions-v1.json',
|
|
273
|
+
controller.signal
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const list = <ExtensionSelectionList entries={entries} locale="en"
|
|
277
|
+
filterQuery={searchText} loadedIds={loadedExtensionIds} busy={loading}
|
|
278
|
+
onCancel={closeLibrary}
|
|
279
|
+
onComplete={ordered => host.reviewAndLoadInOrder(ordered)} />;
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`host.reviewAndLoadInOrder` represents the host's permission review and VM loading path.
|
|
283
|
+
The callback receives dependencies before their dependants, each once, excluding
|
|
284
|
+
`loadedIds`. Selecting an extension checks and disables all transitive dependencies.
|
|
285
|
+
Removing its selection recomputes the closure; dependencies remain checked if another
|
|
286
|
+
selection needs them or the user selected them explicitly. Loaded rows are checked and
|
|
287
|
+
disabled. Search and locale changes preserve selection. `busy` disables selection and the
|
|
288
|
+
footer controls. The host owns fetch errors, retry, catalog cancellation, VM errors, and
|
|
289
|
+
updating `loadedIds` after each successful load.
|
|
290
|
+
|
|
291
|
+
Rows distinguish **Selected by you** (blue), **Selected automatically** (purple), and
|
|
292
|
+
**Not selected** (neutral), using a background, left border, icon and label together.
|
|
293
|
+
Already loaded rows have their own label and accent. A legend explains the states above
|
|
294
|
+
the list. Automatically selected dependencies name every selected root that needs them,
|
|
295
|
+
for example “Required by Bubble, Sprite”. An explicitly selected dependency keeps its
|
|
296
|
+
“Selected by you” state while another root requires it; its checkbox stays disabled and
|
|
297
|
+
the dependency reason is also shown. The row's `data-selection-state` is `selected`,
|
|
298
|
+
`dependency`, `unselected` or `loaded`.
|
|
299
|
+
|
|
300
|
+
Optional catalog categories render as semantic `h2` sections in dashboard order. Japanese
|
|
301
|
+
uses `titleJa` when available, otherwise `title`; other locales use `title`. Search hides
|
|
302
|
+
empty sections without changing selection or source controls. Catalogs without categories
|
|
303
|
+
keep a flat list. If categorized and uncategorized entries are mixed, the latter appear
|
|
304
|
+
under “Other extensions” at the end. Theme colors use the host's modal background and
|
|
305
|
+
foreground; accent overrides are `--twextsec-manual-accent`, `--twextsec-dependency-accent`,
|
|
306
|
+
`--twextsec-unselected-accent`, and `--twextsec-loaded-accent`.
|
|
307
|
+
|
|
308
|
+
The selection list never loads or runs extension code. Catalog metadata renders immediately;
|
|
309
|
+
only Details fetches the source as text. Source failures leave catalog metadata and selection
|
|
310
|
+
usable. `ExtensionSecurityDialog context="catalog"` provides permission review with cancel
|
|
311
|
+
wording that returns to this list; its default `context="project"` retains project wording.
|
|
312
|
+
The host must respect the committed execution modes and its own security policy.
|
|
313
|
+
|
|
314
|
+
The Pages document has the following contract (`schemaVersion: 1`):
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"schemaVersion": 1,
|
|
319
|
+
"categories": [{"id": "camera", "title": "Camera", "titleJa": "カメラ"}],
|
|
320
|
+
"extensions": [
|
|
321
|
+
{
|
|
322
|
+
"id": "cameraSource",
|
|
323
|
+
"name": "Camera Source",
|
|
324
|
+
"category": "camera",
|
|
325
|
+
"version": "1.2.3",
|
|
326
|
+
"package": "@example/camera-source",
|
|
327
|
+
"url": "https://cdn.jsdelivr.net/npm/@example/camera-source@1.2.3/dist/extension.js",
|
|
328
|
+
"description": "Share a camera stream.",
|
|
329
|
+
"descriptions": {"ja": "カメラ映像を共有します。"},
|
|
330
|
+
"repository": "https://github.com/example/camera-source",
|
|
331
|
+
"banner": "https://example.com/camera-source.svg",
|
|
332
|
+
"unsandboxed": "required",
|
|
333
|
+
"dependencies": []
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
IDs contain ASCII letters and digits. `name`, `version`, `url`, and `dependencies` are
|
|
340
|
+
required. Dependencies are extension IDs in this document, not npm package names;
|
|
341
|
+
the generator resolves package dependencies to these IDs. `package`, descriptions, author,
|
|
342
|
+
license, banner, repository and execution requirement are optional metadata. The generator
|
|
343
|
+
uses version-pinned, published distribution URLs and excludes build-only packages.
|
|
344
|
+
`parseExtensionCatalog` rejects unsupported schemas, duplicate IDs/URLs, missing dependencies,
|
|
345
|
+
cycles, and non-HTTPS or credential-bearing URLs before the UI or VM receives entries.
|
|
346
|
+
`resolveExtensionSelection(entries, explicitIds, loadedIds)` exposes the same closure and
|
|
347
|
+
load order independently of React. Its `requiredBy` map associates each dependency ID with
|
|
348
|
+
a set of the explicit root IDs that require it, including transitive and shared dependencies.
|
|
349
|
+
Optional `categories: [{id, title, titleJa?}]` are declared once, in dashboard order;
|
|
350
|
+
`extensions[].category` references one of those IDs. Duplicate category IDs, invalid
|
|
351
|
+
definitions and unknown references are rejected. `parseExtensionCatalog` retains its array
|
|
352
|
+
return type and resolves each reference to `entry.category: {id, title, titleJa?, order}`.
|
|
353
|
+
`loadExtensionCatalog` accepts an optional AbortSignal,
|
|
354
|
+
omits credentials/referrers, and rejects failed HTTP responses or invalid JSON contracts.
|
|
355
|
+
|
|
356
|
+
To roll back catalog integration, turn off the host's startup flag (off by default) and
|
|
357
|
+
reload, or revert the catalog wiring. The existing three-way security dialog remains
|
|
358
|
+
available without the catalog API.
|
|
359
|
+
|
|
254
360
|
## Stored preferences
|
|
255
361
|
|
|
256
362
|
Preferences are saved only when OK was pressed **and** the project then opened successfully.
|
package/dist/index.cjs
CHANGED
|
@@ -50,12 +50,15 @@ __export(index_exports, {
|
|
|
50
50
|
getExtensionDescription: () => getExtensionDescription,
|
|
51
51
|
getGitHubRepository: () => getGitHubRepository,
|
|
52
52
|
getPreviewCandidates: () => getPreviewCandidates,
|
|
53
|
+
loadExtensionCatalog: () => loadExtensionCatalog,
|
|
53
54
|
loadExtensionInfo: () => loadExtensionInfo,
|
|
54
55
|
messages: () => messages,
|
|
56
|
+
parseExtensionCatalog: () => parseExtensionCatalog,
|
|
55
57
|
parseExtensionMetadata: () => parseExtensionMetadata,
|
|
56
58
|
preferences: () => preferences_default,
|
|
57
59
|
projectFingerprint: () => projectFingerprint,
|
|
58
60
|
readPreferences: () => readPreferences,
|
|
61
|
+
resolveExtensionSelection: () => resolveExtensionSelection,
|
|
59
62
|
restoreChoices: () => restoreChoices,
|
|
60
63
|
writePreferences: () => writePreferences
|
|
61
64
|
});
|
|
@@ -334,8 +337,28 @@ var preferences_default = preferences;
|
|
|
334
337
|
// src/messages.js
|
|
335
338
|
var messages = {
|
|
336
339
|
"en": {
|
|
340
|
+
"selectionIntro": "Choose extensions to add. Required extensions are selected automatically.",
|
|
341
|
+
"selectionRequired": "Requires running without a sandbox.",
|
|
342
|
+
"selectedState": "Selected by you",
|
|
343
|
+
"dependencyState": "Selected automatically",
|
|
344
|
+
"unselectedState": "Not selected",
|
|
345
|
+
"loadedState": "Already loaded",
|
|
346
|
+
"selectionLegend": "Selection states",
|
|
347
|
+
"requiredBy": "Required by {names}",
|
|
348
|
+
"otherCategory": "Other extensions",
|
|
349
|
+
"catalogRequiredIntro": "All selected extensions require Without sandbox, which is already selected. Choose OK to load them, or Cancel to return to the selection list.",
|
|
350
|
+
"catalogRequired": "This extension cannot run inside the sandbox. Choose Without sandbox, or Cancel to return to the selection list.",
|
|
351
|
+
"catalogCancelQuestion": "Cancel loading the selected extensions and return to the selection list?",
|
|
352
|
+
"selectExtension": "Select",
|
|
353
|
+
"dependency": "Required by another selection",
|
|
354
|
+
"alreadyLoaded": "Already loaded",
|
|
355
|
+
"noResults": "No matching extensions",
|
|
356
|
+
"selectedSummary": "{count} extensions to load",
|
|
357
|
+
"loadSelected": "Load selected extensions",
|
|
358
|
+
"loadingExtensions": "Loading\u2026",
|
|
337
359
|
"title": "Extension Security",
|
|
338
360
|
"intro": "Choose how to run each extension (you can choose while metadata loads).",
|
|
361
|
+
"allRequiredIntro": "All extensions require Without sandbox, which is already selected. Choose OK to load the project, or Cancel to stop.",
|
|
339
362
|
"progress": "Extension information: {done}/{total}",
|
|
340
363
|
"bulk": "Apply to all listed extensions",
|
|
341
364
|
"extension": "Extension",
|
|
@@ -350,7 +373,7 @@ var messages = {
|
|
|
350
373
|
"embedded": "Embedded in this project",
|
|
351
374
|
"pending": "Getting metadata\u2026",
|
|
352
375
|
"failed": "Could not retrieve metadata",
|
|
353
|
-
"required": "This extension cannot run inside the sandbox.",
|
|
376
|
+
"required": "This extension cannot run inside the sandbox. Choose Without sandbox, or Cancel to stop loading the project.",
|
|
354
377
|
"requiredToRun": "Required to run",
|
|
355
378
|
"unsupported": "Unsupported",
|
|
356
379
|
"choiceReset": "The incompatible selection was cleared. Choose how to run this extension again.",
|
|
@@ -371,8 +394,28 @@ var messages = {
|
|
|
371
394
|
"warning": "Extensions without a sandbox can access your project and browser information."
|
|
372
395
|
},
|
|
373
396
|
"ja": {
|
|
397
|
+
"selectionIntro": "\u8FFD\u52A0\u3059\u308B\u6A5F\u80FD\u62E1\u5F35\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002\u4F9D\u5B58\u3059\u308B\u6A5F\u80FD\u62E1\u5F35\u3082\u81EA\u52D5\u3067\u9078\u629E\u3055\u308C\u307E\u3059\u3002",
|
|
398
|
+
"selectionRequired": "\u30B5\u30F3\u30C9\u30DC\u30C3\u30AF\u30B9\u306A\u3057\u3067\u306E\u5B9F\u884C\u304C\u5FC5\u8981\u3067\u3059\u3002",
|
|
399
|
+
"selectedState": "\u624B\u52D5\u3067\u9078\u629E",
|
|
400
|
+
"dependencyState": "\u4F9D\u5B58\u95A2\u4FC2\u3067\u81EA\u52D5\u9078\u629E",
|
|
401
|
+
"unselectedState": "\u672A\u9078\u629E",
|
|
402
|
+
"loadedState": "\u8AAD\u307F\u8FBC\u307F\u6E08\u307F",
|
|
403
|
+
"selectionLegend": "\u9078\u629E\u72B6\u614B\u306E\u51E1\u4F8B",
|
|
404
|
+
"requiredBy": "{names} \u306B\u5FC5\u8981",
|
|
405
|
+
"otherCategory": "\u305D\u306E\u4ED6\u306E\u6A5F\u80FD\u62E1\u5F35",
|
|
406
|
+
"catalogRequiredIntro": "\u9078\u629E\u3057\u305F\u3059\u3079\u3066\u306E\u6A5F\u80FD\u62E1\u5F35\u3067\u5FC5\u8981\u306A\u300C\u30B5\u30F3\u30C9\u30DC\u30C3\u30AF\u30B9\u306A\u3057\u300D\u3092\u9078\u629E\u6E08\u307F\u3067\u3059\u3002\u300COK\u300D\u3067\u8AAD\u307F\u8FBC\u3080\u304B\u3001\u300C\u30AD\u30E3\u30F3\u30BB\u30EB\u300D\u3067\u9078\u629E\u4E00\u89A7\u306B\u623B\u308A\u307E\u3059\u3002",
|
|
407
|
+
"catalogRequired": "\u3053\u306E\u62E1\u5F35\u306F\u30B5\u30F3\u30C9\u30DC\u30C3\u30AF\u30B9\u5185\u3067\u306F\u52D5\u4F5C\u3057\u307E\u305B\u3093\u3002\u300C\u30B5\u30F3\u30C9\u30DC\u30C3\u30AF\u30B9\u306A\u3057\u300D\u3092\u9078\u3076\u304B\u3001\u300C\u30AD\u30E3\u30F3\u30BB\u30EB\u300D\u3067\u9078\u629E\u4E00\u89A7\u306B\u623B\u308A\u307E\u3059\u3002",
|
|
408
|
+
"catalogCancelQuestion": "\u9078\u629E\u3057\u305F\u6A5F\u80FD\u62E1\u5F35\u306E\u8AAD\u307F\u8FBC\u307F\u3092\u53D6\u308A\u6D88\u3057\u3066\u3001\u9078\u629E\u4E00\u89A7\u306B\u623B\u308A\u307E\u3059\u304B\uFF1F",
|
|
409
|
+
"selectExtension": "\u9078\u629E",
|
|
410
|
+
"dependency": "\u9078\u629E\u3057\u305F\u62E1\u5F35\u306B\u5FC5\u8981",
|
|
411
|
+
"alreadyLoaded": "\u8AAD\u307F\u8FBC\u307F\u6E08\u307F",
|
|
412
|
+
"noResults": "\u4E00\u81F4\u3059\u308B\u6A5F\u80FD\u62E1\u5F35\u304C\u3042\u308A\u307E\u305B\u3093",
|
|
413
|
+
"selectedSummary": "\u8AAD\u307F\u8FBC\u3080\u6A5F\u80FD\u62E1\u5F35 {count} \u4EF6",
|
|
414
|
+
"loadSelected": "\u9078\u629E\u3057\u305F\u62E1\u5F35\u3092\u8AAD\u307F\u8FBC\u3080",
|
|
415
|
+
"loadingExtensions": "\u8AAD\u307F\u8FBC\u307F\u4E2D\u2026",
|
|
374
416
|
"title": "\u6A5F\u80FD\u62E1\u5F35\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3",
|
|
375
417
|
"intro": "\u5B9F\u884C\u65B9\u6CD5\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u53D6\u5F97\u4E2D\u3067\u3082\u9078\u629E\u3067\u304D\u307E\u3059\uFF09",
|
|
418
|
+
"allRequiredIntro": "\u3059\u3079\u3066\u306E\u6A5F\u80FD\u62E1\u5F35\u3067\u5FC5\u8981\u306A\u300C\u30B5\u30F3\u30C9\u30DC\u30C3\u30AF\u30B9\u306A\u3057\u300D\u3092\u9078\u629E\u6E08\u307F\u3067\u3059\u3002\u300COK\u300D\u3067\u8AAD\u307F\u8FBC\u3080\u304B\u3001\u300C\u30AD\u30E3\u30F3\u30BB\u30EB\u300D\u3067\u4E2D\u6B62\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
376
419
|
"progress": "\u6A5F\u80FD\u62E1\u5F35\u306E\u60C5\u5831\uFF1A{done}/{total} \u4EF6\u3092\u78BA\u8A8D",
|
|
377
420
|
"bulk": "\u8868\u793A\u4E2D\u306E\u3059\u3079\u3066\u306B\u9069\u7528",
|
|
378
421
|
"extension": "\u6A5F\u80FD\u62E1\u5F35",
|
|
@@ -387,7 +430,7 @@ var messages = {
|
|
|
387
430
|
"embedded": "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u5185\u306B\u57CB\u3081\u8FBC\u307F",
|
|
388
431
|
"pending": "\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u53D6\u5F97\u4E2D\u2026",
|
|
389
432
|
"failed": "\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F",
|
|
390
|
-
"required": "\u3053\u306E\u62E1\u5F35\u306F\u30B5\u30F3\u30C9\u30DC\u30C3\u30AF\u30B9\u5185\u3067\u306F\u52D5\u4F5C\u3057\u307E\u305B\u3093\u3002",
|
|
433
|
+
"required": "\u3053\u306E\u62E1\u5F35\u306F\u30B5\u30F3\u30C9\u30DC\u30C3\u30AF\u30B9\u5185\u3067\u306F\u52D5\u4F5C\u3057\u307E\u305B\u3093\u3002\u300C\u30B5\u30F3\u30C9\u30DC\u30C3\u30AF\u30B9\u306A\u3057\u300D\u3092\u9078\u3076\u304B\u3001\u300C\u30AD\u30E3\u30F3\u30BB\u30EB\u300D\u3067\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u8AAD\u307F\u8FBC\u307F\u3092\u4E2D\u6B62\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
391
434
|
"requiredToRun": "\u52D5\u4F5C\u306B\u5FC5\u8981",
|
|
392
435
|
"unsupported": "\u975E\u5BFE\u5FDC",
|
|
393
436
|
"choiceReset": "\u975E\u5BFE\u5FDC\u306E\u9078\u629E\u3092\u89E3\u9664\u3057\u307E\u3057\u305F\u3002\u5B9F\u884C\u65B9\u6CD5\u3092\u9078\u3073\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
@@ -413,3 +456,112 @@ var formatMessage = (locale, key, values = {}) => {
|
|
|
413
456
|
const template = messages[language][key] || messages.en[key] || key;
|
|
414
457
|
return template.replace(/\{(\w+)\}/g, (match, name) => String(values[name] === void 0 ? match : values[name]));
|
|
415
458
|
};
|
|
459
|
+
|
|
460
|
+
// src/selection.js
|
|
461
|
+
var httpsURL = (value) => {
|
|
462
|
+
if (typeof value !== "string" || !value.trim()) throw new Error("Expected an HTTPS URL");
|
|
463
|
+
const url = new URL(value);
|
|
464
|
+
if (url.protocol !== "https:" || url.username || url.password) throw new Error("Expected an HTTPS URL");
|
|
465
|
+
return url.href;
|
|
466
|
+
};
|
|
467
|
+
var resolveExtensionSelection = (entries, explicitIds = [], loadedIds = []) => {
|
|
468
|
+
const byId = /* @__PURE__ */ new Map();
|
|
469
|
+
for (const entry of entries) {
|
|
470
|
+
if (!entry || typeof entry.id !== "string" || !entry.id || byId.has(entry.id)) {
|
|
471
|
+
throw new Error(`Duplicate or missing extension ID: ${entry && entry.id}`);
|
|
472
|
+
}
|
|
473
|
+
if (entry.dependencies !== void 0 && (!Array.isArray(entry.dependencies) || entry.dependencies.some((id) => typeof id !== "string" || !id))) {
|
|
474
|
+
throw new Error(`Invalid extension dependencies: ${entry.id}`);
|
|
475
|
+
}
|
|
476
|
+
byId.set(entry.id, entry);
|
|
477
|
+
}
|
|
478
|
+
const selected = /* @__PURE__ */ new Set();
|
|
479
|
+
const required = /* @__PURE__ */ new Set();
|
|
480
|
+
const requiredBy = /* @__PURE__ */ new Map();
|
|
481
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
482
|
+
const ordered = [];
|
|
483
|
+
const visit = (id) => {
|
|
484
|
+
const entry = byId.get(id);
|
|
485
|
+
if (!entry) throw new Error(`Missing extension dependency: ${id}`);
|
|
486
|
+
if (visiting.has(id)) throw new Error(`Cyclic extension dependency: ${id}`);
|
|
487
|
+
if (selected.has(id)) return;
|
|
488
|
+
visiting.add(id);
|
|
489
|
+
for (const dependency of entry.dependencies || []) {
|
|
490
|
+
required.add(dependency);
|
|
491
|
+
visit(dependency);
|
|
492
|
+
}
|
|
493
|
+
visiting.delete(id);
|
|
494
|
+
selected.add(id);
|
|
495
|
+
ordered.push(entry);
|
|
496
|
+
};
|
|
497
|
+
const roots = [...explicitIds].filter((id) => byId.has(id));
|
|
498
|
+
for (const id of roots) visit(id);
|
|
499
|
+
for (const root of roots) {
|
|
500
|
+
const seen = /* @__PURE__ */ new Set();
|
|
501
|
+
const describeDependencies = (id) => {
|
|
502
|
+
for (const dependency of byId.get(id).dependencies || []) {
|
|
503
|
+
if (seen.has(dependency)) continue;
|
|
504
|
+
seen.add(dependency);
|
|
505
|
+
if (!requiredBy.has(dependency)) requiredBy.set(dependency, /* @__PURE__ */ new Set());
|
|
506
|
+
requiredBy.get(dependency).add(root);
|
|
507
|
+
describeDependencies(dependency);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
describeDependencies(root);
|
|
511
|
+
}
|
|
512
|
+
const loaded = new Set(loadedIds);
|
|
513
|
+
return { selected, required, requiredBy, loaded, ordered: ordered.filter((entry) => !loaded.has(entry.id)) };
|
|
514
|
+
};
|
|
515
|
+
var parseExtensionCatalog = (data) => {
|
|
516
|
+
if (!data || data.schemaVersion !== 1 || !Array.isArray(data.extensions)) {
|
|
517
|
+
throw new Error("Unsupported extension catalog");
|
|
518
|
+
}
|
|
519
|
+
const categories = /* @__PURE__ */ new Map();
|
|
520
|
+
if (data.categories !== void 0) {
|
|
521
|
+
if (!Array.isArray(data.categories)) throw new Error("Invalid extension catalog categories");
|
|
522
|
+
data.categories.forEach((category, order) => {
|
|
523
|
+
if (!category || typeof category.id !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(category.id) || typeof category.title !== "string" || !category.title.trim() || category.titleJa !== void 0 && (typeof category.titleJa !== "string" || !category.titleJa.trim())) {
|
|
524
|
+
throw new Error("Invalid extension catalog category");
|
|
525
|
+
}
|
|
526
|
+
if (categories.has(category.id)) throw new Error(`Duplicate extension category: ${category.id}`);
|
|
527
|
+
categories.set(category.id, __spreadValues({
|
|
528
|
+
id: category.id,
|
|
529
|
+
title: category.title,
|
|
530
|
+
order
|
|
531
|
+
}, category.titleJa !== void 0 ? { titleJa: category.titleJa } : {}));
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const urls = /* @__PURE__ */ new Set();
|
|
535
|
+
const entries = data.extensions.map((item) => {
|
|
536
|
+
if (!item || typeof item.id !== "string" || !/^[a-zA-Z0-9]+$/.test(item.id) || typeof item.name !== "string" || !item.name.trim() || typeof item.version !== "string" || !item.version || !Array.isArray(item.dependencies) || item.dependencies.some((id) => typeof id !== "string" || !/^[a-zA-Z0-9]+$/.test(id))) {
|
|
537
|
+
throw new Error("Invalid extension catalog entry");
|
|
538
|
+
}
|
|
539
|
+
const url = httpsURL(item.url);
|
|
540
|
+
if (urls.has(url)) throw new Error(`Duplicate extension URL: ${url}`);
|
|
541
|
+
urls.add(url);
|
|
542
|
+
const metadata = { name: item.name, id: item.id, version: item.version };
|
|
543
|
+
for (const field of ["description", "author", "license", "updatedAt", "unsandboxed"]) {
|
|
544
|
+
if (typeof item[field] === "string") metadata[field] = item[field];
|
|
545
|
+
}
|
|
546
|
+
if (item.descriptions && typeof item.descriptions === "object") {
|
|
547
|
+
metadata.descriptions = Object.fromEntries(Object.entries(item.descriptions).filter(([locale, text]) => /^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/.test(locale) && typeof text === "string"));
|
|
548
|
+
}
|
|
549
|
+
if (item.banner) metadata.banner = httpsURL(item.banner);
|
|
550
|
+
if (item.repository) metadata.repository = httpsURL(item.repository);
|
|
551
|
+
const entry = { id: item.id, ids: [item.id], url, dependencies: [...new Set(item.dependencies)], metadata };
|
|
552
|
+
if (item.category !== void 0) {
|
|
553
|
+
if (typeof item.category !== "string" || !categories.has(item.category)) {
|
|
554
|
+
throw new Error(`Missing extension category: ${item.category}`);
|
|
555
|
+
}
|
|
556
|
+
entry.category = categories.get(item.category);
|
|
557
|
+
}
|
|
558
|
+
return entry;
|
|
559
|
+
});
|
|
560
|
+
resolveExtensionSelection(entries, entries.map((entry) => entry.id));
|
|
561
|
+
return entries;
|
|
562
|
+
};
|
|
563
|
+
var loadExtensionCatalog = async (url, signal) => {
|
|
564
|
+
const response = await fetch(httpsURL(url), { signal, credentials: "omit", referrerPolicy: "no-referrer" });
|
|
565
|
+
if (!response.ok) throw new Error(`Extension catalog HTTP ${response.status}`);
|
|
566
|
+
return parseExtensionCatalog(await response.json());
|
|
567
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -24,3 +24,17 @@ export const preferences: {
|
|
|
24
24
|
};
|
|
25
25
|
export const messages: Record<string, Record<string, string>>;
|
|
26
26
|
export function formatMessage(locale: string, key: string, values?: Record<string, string | number>): string;
|
|
27
|
+
|
|
28
|
+
export interface ExtensionCategory {
|
|
29
|
+
id: string; title: string; titleJa?: string; order?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface SelectableExtension extends ExtensionEntry {
|
|
32
|
+
id: string; dependencies: string[]; metadata: ExtensionMetadata; category?: ExtensionCategory;
|
|
33
|
+
}
|
|
34
|
+
export interface ExtensionSelection {
|
|
35
|
+
selected: Set<string>; required: Set<string>; requiredBy: Map<string, Set<string>>;
|
|
36
|
+
loaded: Set<string>; ordered: SelectableExtension[];
|
|
37
|
+
}
|
|
38
|
+
export function resolveExtensionSelection(entries: SelectableExtension[], explicitIds?: Iterable<string>, loadedIds?: Iterable<string>): ExtensionSelection;
|
|
39
|
+
export function parseExtensionCatalog(data: unknown): SelectableExtension[];
|
|
40
|
+
export function loadExtensionCatalog(url: string, signal?: AbortSignal): Promise<SelectableExtension[]>;
|