@kubohiroya/turbowarp-extension-security-dialog 0.1.0 → 0.2.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 +2 -0
- package/README.ja.md +23 -7
- package/README.md +30 -7
- package/dist/index.cjs +26 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +26 -0
- package/dist/react.cjs +27 -1
- package/dist/react.mjs +27 -1
- package/package.json +7 -5
package/NOTICE.md
CHANGED
|
@@ -30,3 +30,5 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
30
30
|
## Public Editor screenshots
|
|
31
31
|
|
|
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
|
+
|
|
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.
|
package/README.ja.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# turbowarp-extension-security-dialog
|
|
2
2
|
|
|
3
|
-
[English](
|
|
3
|
+
[English](README.md) | 日本語
|
|
4
4
|
|
|
5
5
|
TurboWarpの複数の機能拡張を、1つの一覧で確認するための共通パッケージです。
|
|
6
6
|
関連タスクと受け入れ基準は [scratch-gui #33](https://github.com/kubohiroya/scratch-gui/issues/33) に記録しています。
|
|
7
7
|
|
|
8
8
|
## 公開Editorのスクリーンショット
|
|
9
9
|
|
|
10
|
-
[英語版](./README.md)
|
|
10
|
+
[英語版](./README.md)と日本語版は、同じダイアログの表示言語を切り替え、ラベルと拡張の説明文を各言語で表示しています。
|
|
11
11
|
2026年10月25日の[公開Editor](https://kubohiroya.github.io/turbowarp-extension-data-editor/editor.html?lang=ja)で、
|
|
12
|
-
Camera Source 0.19.
|
|
12
|
+
Camera Source 0.19.4・Asset Cache 0.3.2・Bubble 0.13.5を開いた実画面です。
|
|
13
13
|
|
|
14
|
-

|
|
15
15
|
|
|
16
16
|
3拡張とも`Unsandboxed: required`を宣言しています。「サンドボックスあり」は無効化され、
|
|
17
17
|
「この拡張はサンドボックス内では動作しません。」と理由が表示されます。
|
|
@@ -25,7 +25,7 @@ Camera Source 0.19.3・Asset Cache 0.3.1・Bubble 0.13.4を開いた実画面で
|
|
|
25
25
|
| Bubble | ホストの実行環境と描画機能へ直接アクセスして、ステージ上に重ねた吹き出しや立ち絵を描画・更新します。 |
|
|
26
26
|
|
|
27
27
|
画像はダイアログ部分をブラウザーで直接撮影したもので、加工していません。
|
|
28
|
-
取得元の版・Editor build・画像hashは[撮影台帳](./assets/screenshots/
|
|
28
|
+
取得元の版・Editor build・画像hashは[撮影台帳](./assets/screenshots/localized-descriptions-20261025.json)に記録しています。
|
|
29
29
|
|
|
30
30
|
## 連動するパッケージ
|
|
31
31
|
|
|
@@ -186,6 +186,22 @@ HTTP/HTTPSの出所URLは完全なURLを表示し、別タブで開きます。
|
|
|
186
186
|
プレビュー枠内をスクロールして確認できます。ポインターを外すかEscapeで閉じ、選択や詳細状態を保持します。
|
|
187
187
|
メタデータの非同期取得中も実行方法を選べます。ソースの一覧内表示は引き続き「詳細」を押したときだけです。
|
|
188
188
|
|
|
189
|
+
### 機能拡張の説明文の言語
|
|
190
|
+
|
|
191
|
+
拡張コードを実行せず、任意の言語別ヘッダーコメントを読み取ります。
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
// Description: Share one camera stream between extensions.
|
|
195
|
+
// Description[ja]: 1つのカメラ映像を複数の機能拡張で共有します。
|
|
196
|
+
// Description[ja-JP]: 日本語の説明(日本向け)。
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
言語タグの大文字・小文字は区別しません。Editorの言語の完全一致、基底言語、従来の`Description`の順で説明文を選びます。表示中に言語を変えると、ソースを再取得せず説明が切り替わり、許可の選択状態は保たれます。翻訳がない既存の拡張も従来どおり表示できます。
|
|
200
|
+
|
|
201
|
+
同梱メンバーのnoticeにある説明は親バンドルへ混ぜません。HTMLに見える説明も文字列として表示します。独自の`loadInfo`を使う場合は、任意の`ExtensionMetadata.descriptions`へ小文字の言語タグをキーとした説明を渡せます。`getExtensionDescription(metadata, locale)`でも同じ選択処理を利用できます。
|
|
202
|
+
|
|
203
|
+
上の画像は、日本語説明へ対応した公開Editorと公開済みの3拡張を使用しています。英語版では各拡張の従来の`Description`、日本語版では`Description[ja]`を表示します。対応する翻訳がない拡張は元の説明を表示します。説明の言語が変わっても、必要な実行モードや許可の選択は変わりません。パッケージを更新しても、既存SB3に埋め込まれたコードは自動で差し替わりません。
|
|
204
|
+
|
|
189
205
|
## 実行モードの選択
|
|
190
206
|
|
|
191
207
|
個別選択・一括選択・一覧の見出しは、禁止アイコンの「拒否」、盾の「サンドボックスあり」、
|
|
@@ -238,8 +254,8 @@ scratch-guiは既定OFFの起動時フラグで導入し、ホスト独自のセ
|
|
|
238
254
|
## 開発と検証
|
|
239
255
|
|
|
240
256
|
```sh
|
|
241
|
-
|
|
242
|
-
|
|
257
|
+
pnpm install
|
|
258
|
+
pnpm test
|
|
243
259
|
```
|
|
244
260
|
|
|
245
261
|
GUIとの統合では、実VMでの拒否後の表示・保存、エディター初期画面への遷移、
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# turbowarp-extension-security-dialog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [日本語](README.ja.md)
|
|
4
4
|
|
|
5
5
|
A shared package that reviews every custom extension a TurboWarp project is about to load
|
|
6
6
|
in **one single list**, instead of one prompt per extension.
|
|
@@ -9,12 +9,12 @@ Related tasks and acceptance criteria are tracked in
|
|
|
9
9
|
|
|
10
10
|
## Screenshots from the public Editor
|
|
11
11
|
|
|
12
|
-
[English](./README.md) and [Japanese](./README.ja.md) use the same dialog with localized labels.
|
|
13
|
-
This capture shows Camera Source 0.19.
|
|
12
|
+
[English](./README.md) and [Japanese](./README.ja.md) use the same dialog with localized labels and extension descriptions.
|
|
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
15
|
on 25 October 2026.
|
|
16
16
|
|
|
17
|
-

|
|
18
18
|
|
|
19
19
|
All three extensions declare `Unsandboxed: required`. **With sandbox** is disabled, with
|
|
20
20
|
**This extension cannot run inside the sandbox.** shown as the reason. **Without sandbox**
|
|
@@ -28,7 +28,7 @@ choice and OK. These labels describe execution requirements, not a safety certif
|
|
|
28
28
|
| Bubble | Accesses the host runtime and renderer directly to draw and update layered speech bubbles and portraits on the stage. |
|
|
29
29
|
|
|
30
30
|
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/
|
|
31
|
+
hashes are recorded in [the capture manifest](./assets/screenshots/localized-descriptions-20261025.json).
|
|
32
32
|
|
|
33
33
|
## How it fits together
|
|
34
34
|
|
|
@@ -193,6 +193,29 @@ or a file timestamp.
|
|
|
193
193
|
|
|
194
194
|
Run modes remain selectable while metadata is still loading.
|
|
195
195
|
|
|
196
|
+
### Localized extension descriptions
|
|
197
|
+
|
|
198
|
+
The dialog reads optional language-tagged header comments, without evaluating extension code:
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
// Description: Share one camera stream between extensions.
|
|
202
|
+
// Description[ja]: 1つのカメラ映像を複数の機能拡張で共有します。
|
|
203
|
+
// Description[ja-JP]: 日本語の説明(日本向け)。
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Tags are case-insensitive. Display selection uses the exact Editor locale, then its base
|
|
207
|
+
language, then the original `Description`. A locale change updates the text without refetching
|
|
208
|
+
source or changing permission choices. Missing translations keep existing extensions compatible.
|
|
209
|
+
Only the enclosing header is used; bundled member notices cannot supply its translation.
|
|
210
|
+
HTML-looking text is rendered as text. The optional `ExtensionMetadata.descriptions` record uses
|
|
211
|
+
lowercase keys, and `getExtensionDescription(metadata, locale)` exposes the same lookup.
|
|
212
|
+
|
|
213
|
+
The public Editor and the three published bundles shown above support localized descriptions.
|
|
214
|
+
The English capture uses each original `Description`; the Japanese capture uses `Description[ja]`.
|
|
215
|
+
Extensions without a matching translation retain their original description. The selected language
|
|
216
|
+
does not change required modes or permission choices. Updating a package does not replace code
|
|
217
|
+
already embedded in an existing SB3.
|
|
218
|
+
|
|
196
219
|
## Choosing a run mode
|
|
197
220
|
|
|
198
221
|
Per-row choices, the bulk choices and the list heading are ordered: **Deny** (prohibition
|
|
@@ -252,8 +275,8 @@ own security policy take precedence.
|
|
|
252
275
|
## Development and verification
|
|
253
276
|
|
|
254
277
|
```sh
|
|
255
|
-
|
|
256
|
-
|
|
278
|
+
pnpm install
|
|
279
|
+
pnpm test
|
|
257
280
|
```
|
|
258
281
|
|
|
259
282
|
GUI integration additionally verifies display and storage after a real-VM denial, the
|
package/dist/index.cjs
CHANGED
|
@@ -47,6 +47,7 @@ __export(index_exports, {
|
|
|
47
47
|
encodeChoices: () => encodeChoices,
|
|
48
48
|
fingerprint: () => fingerprint,
|
|
49
49
|
formatMessage: () => formatMessage,
|
|
50
|
+
getExtensionDescription: () => getExtensionDescription,
|
|
50
51
|
getGitHubRepository: () => getGitHubRepository,
|
|
51
52
|
getPreviewCandidates: () => getPreviewCandidates,
|
|
52
53
|
loadExtensionInfo: () => loadExtensionInfo,
|
|
@@ -125,6 +126,19 @@ var parseExtensionMetadata = (source) => {
|
|
|
125
126
|
};
|
|
126
127
|
const header = source.slice(0, 16384).match(/^\s*(?:(?:\/\/[^\n]*(?:\n|$))|(?:\/\*[\s\S]*?\*\/)\s*)*/)[0];
|
|
127
128
|
for (const line of header.split("\n")) {
|
|
129
|
+
if (/^\s*(?:\/\/|\/\*|\*)\s*Bundled extensions:\s*$/i.test(line)) break;
|
|
130
|
+
const localized = /^\s*(?:\/\/|\/\*|\*) ?Description\[([a-z]{2,8}(?:-[a-z0-9]{1,8})*)\]:\s*(.*?)\s*(?:\*\/)?$/i.exec(line);
|
|
131
|
+
if (localized) {
|
|
132
|
+
const locale = localized[1].toLowerCase();
|
|
133
|
+
const description = localized[2].slice(0, 1e3);
|
|
134
|
+
if (description.trim() && !/[\r\n\u2028\u2029]/.test(description)) {
|
|
135
|
+
if (!metadata.descriptions) metadata.descriptions = {};
|
|
136
|
+
if (!Object.prototype.hasOwnProperty.call(metadata.descriptions, locale)) {
|
|
137
|
+
metadata.descriptions[locale] = description;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
128
142
|
const match = /^\s*(?:\/\/|\/\*|\*)\s*([\w -]+):\s*(.*?)\s*(?:\*\/)?$/.exec(line);
|
|
129
143
|
const field = match && Object.prototype.hasOwnProperty.call(fields, match[1].toLowerCase()) && fields[match[1].toLowerCase()];
|
|
130
144
|
if (field && !Object.prototype.hasOwnProperty.call(metadata, field)) {
|
|
@@ -133,6 +147,18 @@ var parseExtensionMetadata = (source) => {
|
|
|
133
147
|
}
|
|
134
148
|
return metadata;
|
|
135
149
|
};
|
|
150
|
+
var getExtensionDescription = (metadata, locale) => {
|
|
151
|
+
const normalized = typeof locale === "string" ? locale.trim().toLowerCase() : "";
|
|
152
|
+
const descriptions = metadata.descriptions || {};
|
|
153
|
+
if (/^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/.test(normalized)) {
|
|
154
|
+
for (const candidate of [normalized, normalized.split("-")[0]]) {
|
|
155
|
+
if (Object.prototype.hasOwnProperty.call(descriptions, candidate) && typeof descriptions[candidate] === "string" && descriptions[candidate].trim()) {
|
|
156
|
+
return descriptions[candidate];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return metadata.description || "";
|
|
161
|
+
};
|
|
136
162
|
var decodeExtensionDataURL = (url) => {
|
|
137
163
|
const comma = url.indexOf(",");
|
|
138
164
|
if (comma < 0) throw new Error("Invalid extension data URL");
|
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ export interface ExtensionEntry { url: string; ids: string[]; banner?: string; }
|
|
|
3
3
|
export interface ExtensionMetadata {
|
|
4
4
|
name?: string; id?: string; description?: string; author?: string; version?: string; updatedAt?: string;
|
|
5
5
|
license?: string; repository?: string; banner?: string; unsandboxed?: string;
|
|
6
|
+
descriptions?: Record<string, string>;
|
|
6
7
|
}
|
|
7
8
|
export interface ExtensionInfo { source: string; metadata: ExtensionMetadata; }
|
|
8
9
|
export function parseExtensionMetadata(source: string): ExtensionMetadata;
|
|
10
|
+
export function getExtensionDescription(metadata: ExtensionMetadata, locale?: string): string;
|
|
9
11
|
export function decodeExtensionDataURL(url: string): string;
|
|
10
12
|
export function loadExtensionInfo(url: string, signal: AbortSignal): Promise<ExtensionInfo>;
|
|
11
13
|
export function getGitHubRepository(value?: string): string | null;
|
package/dist/index.mjs
CHANGED
|
@@ -80,6 +80,19 @@ var parseExtensionMetadata = (source) => {
|
|
|
80
80
|
};
|
|
81
81
|
const header = source.slice(0, 16384).match(/^\s*(?:(?:\/\/[^\n]*(?:\n|$))|(?:\/\*[\s\S]*?\*\/)\s*)*/)[0];
|
|
82
82
|
for (const line of header.split("\n")) {
|
|
83
|
+
if (/^\s*(?:\/\/|\/\*|\*)\s*Bundled extensions:\s*$/i.test(line)) break;
|
|
84
|
+
const localized = /^\s*(?:\/\/|\/\*|\*) ?Description\[([a-z]{2,8}(?:-[a-z0-9]{1,8})*)\]:\s*(.*?)\s*(?:\*\/)?$/i.exec(line);
|
|
85
|
+
if (localized) {
|
|
86
|
+
const locale = localized[1].toLowerCase();
|
|
87
|
+
const description = localized[2].slice(0, 1e3);
|
|
88
|
+
if (description.trim() && !/[\r\n\u2028\u2029]/.test(description)) {
|
|
89
|
+
if (!metadata.descriptions) metadata.descriptions = {};
|
|
90
|
+
if (!Object.prototype.hasOwnProperty.call(metadata.descriptions, locale)) {
|
|
91
|
+
metadata.descriptions[locale] = description;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
83
96
|
const match = /^\s*(?:\/\/|\/\*|\*)\s*([\w -]+):\s*(.*?)\s*(?:\*\/)?$/.exec(line);
|
|
84
97
|
const field = match && Object.prototype.hasOwnProperty.call(fields, match[1].toLowerCase()) && fields[match[1].toLowerCase()];
|
|
85
98
|
if (field && !Object.prototype.hasOwnProperty.call(metadata, field)) {
|
|
@@ -88,6 +101,18 @@ var parseExtensionMetadata = (source) => {
|
|
|
88
101
|
}
|
|
89
102
|
return metadata;
|
|
90
103
|
};
|
|
104
|
+
var getExtensionDescription = (metadata, locale) => {
|
|
105
|
+
const normalized = typeof locale === "string" ? locale.trim().toLowerCase() : "";
|
|
106
|
+
const descriptions = metadata.descriptions || {};
|
|
107
|
+
if (/^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/.test(normalized)) {
|
|
108
|
+
for (const candidate of [normalized, normalized.split("-")[0]]) {
|
|
109
|
+
if (Object.prototype.hasOwnProperty.call(descriptions, candidate) && typeof descriptions[candidate] === "string" && descriptions[candidate].trim()) {
|
|
110
|
+
return descriptions[candidate];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return metadata.description || "";
|
|
115
|
+
};
|
|
91
116
|
var decodeExtensionDataURL = (url) => {
|
|
92
117
|
const comma = url.indexOf(",");
|
|
93
118
|
if (comma < 0) throw new Error("Invalid extension data URL");
|
|
@@ -347,6 +372,7 @@ export {
|
|
|
347
372
|
encodeChoices,
|
|
348
373
|
fingerprint,
|
|
349
374
|
formatMessage,
|
|
375
|
+
getExtensionDescription,
|
|
350
376
|
getGitHubRepository,
|
|
351
377
|
getPreviewCandidates,
|
|
352
378
|
loadExtensionInfo,
|
package/dist/react.cjs
CHANGED
|
@@ -286,6 +286,19 @@ var parseExtensionMetadata = (source) => {
|
|
|
286
286
|
};
|
|
287
287
|
const header = source.slice(0, 16384).match(/^\s*(?:(?:\/\/[^\n]*(?:\n|$))|(?:\/\*[\s\S]*?\*\/)\s*)*/)[0];
|
|
288
288
|
for (const line of header.split("\n")) {
|
|
289
|
+
if (/^\s*(?:\/\/|\/\*|\*)\s*Bundled extensions:\s*$/i.test(line)) break;
|
|
290
|
+
const localized = /^\s*(?:\/\/|\/\*|\*) ?Description\[([a-z]{2,8}(?:-[a-z0-9]{1,8})*)\]:\s*(.*?)\s*(?:\*\/)?$/i.exec(line);
|
|
291
|
+
if (localized) {
|
|
292
|
+
const locale = localized[1].toLowerCase();
|
|
293
|
+
const description = localized[2].slice(0, 1e3);
|
|
294
|
+
if (description.trim() && !/[\r\n\u2028\u2029]/.test(description)) {
|
|
295
|
+
if (!metadata.descriptions) metadata.descriptions = {};
|
|
296
|
+
if (!Object.prototype.hasOwnProperty.call(metadata.descriptions, locale)) {
|
|
297
|
+
metadata.descriptions[locale] = description;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
289
302
|
const match = /^\s*(?:\/\/|\/\*|\*)\s*([\w -]+):\s*(.*?)\s*(?:\*\/)?$/.exec(line);
|
|
290
303
|
const field = match && Object.prototype.hasOwnProperty.call(fields, match[1].toLowerCase()) && fields[match[1].toLowerCase()];
|
|
291
304
|
if (field && !Object.prototype.hasOwnProperty.call(metadata, field)) {
|
|
@@ -294,6 +307,18 @@ var parseExtensionMetadata = (source) => {
|
|
|
294
307
|
}
|
|
295
308
|
return metadata;
|
|
296
309
|
};
|
|
310
|
+
var getExtensionDescription = (metadata, locale) => {
|
|
311
|
+
const normalized = typeof locale === "string" ? locale.trim().toLowerCase() : "";
|
|
312
|
+
const descriptions = metadata.descriptions || {};
|
|
313
|
+
if (/^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/.test(normalized)) {
|
|
314
|
+
for (const candidate of [normalized, normalized.split("-")[0]]) {
|
|
315
|
+
if (Object.prototype.hasOwnProperty.call(descriptions, candidate) && typeof descriptions[candidate] === "string" && descriptions[candidate].trim()) {
|
|
316
|
+
return descriptions[candidate];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return metadata.description || "";
|
|
321
|
+
};
|
|
297
322
|
var decodeExtensionDataURL = (url) => {
|
|
298
323
|
const comma = url.indexOf(",");
|
|
299
324
|
if (comma < 0) throw new Error("Invalid extension data URL");
|
|
@@ -637,6 +662,7 @@ var ExtensionSecurityDialog = class extends import_react2.default.Component {
|
|
|
637
662
|
renderRow(entry, index) {
|
|
638
663
|
const row = this.state.rows[entry.url] || {};
|
|
639
664
|
const metadata = row.metadata || {};
|
|
665
|
+
const description = getExtensionDescription(metadata, this.props.locale);
|
|
640
666
|
const required = requiresUnsandboxed(metadata);
|
|
641
667
|
const reasonId = `extension-mode-reason-${index}`;
|
|
642
668
|
const requiredId = `extension-mode-required-${index}`;
|
|
@@ -679,7 +705,7 @@ var ExtensionSecurityDialog = class extends import_react2.default.Component {
|
|
|
679
705
|
"data-index": index,
|
|
680
706
|
onError: this.handleImageError
|
|
681
707
|
}
|
|
682
|
-
), /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.info }, /* @__PURE__ */ import_react2.default.createElement("strong", null, name), /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.secondary }, this.t("id", { id: metadata.id || entry.ids.join(", ") })), metadata.version && /* @__PURE__ */ import_react2.default.createElement("span", { className: style_names_default.version }, `v${metadata.version}`), metadata.author && /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.secondary }, this.t("author", { author: metadata.author })), metadata.updatedAt && /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.secondary }, this.t("updated", { updated: metadata.updatedAt })),
|
|
708
|
+
), /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.info }, /* @__PURE__ */ import_react2.default.createElement("strong", null, name), /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.secondary }, this.t("id", { id: metadata.id || entry.ids.join(", ") })), metadata.version && /* @__PURE__ */ import_react2.default.createElement("span", { className: style_names_default.version }, `v${metadata.version}`), metadata.author && /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.secondary }, this.t("author", { author: metadata.author })), metadata.updatedAt && /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.secondary }, this.t("updated", { updated: metadata.updatedAt })), description && /* @__PURE__ */ import_react2.default.createElement("div", null, description), metadata.license && /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.secondary }, this.t("license", { license: metadata.license })), /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.secondary }, sourceLink), !row.status && /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.secondary }, this.t("pending")), row.status === "failed" && /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.error }, this.t("failed")), required && /* @__PURE__ */ import_react2.default.createElement("div", { id: reasonId, className: style_names_default.warningText }, this.t("required")), row.invalidated && !this.state.choices[entry.url] && /* @__PURE__ */ import_react2.default.createElement("div", { className: style_names_default.warningText, role: "status" }, this.t("choiceReset")))), MODES.map((mode) => /* @__PURE__ */ import_react2.default.createElement(
|
|
683
709
|
"label",
|
|
684
710
|
{
|
|
685
711
|
className: style_names_default.choice,
|
package/dist/react.mjs
CHANGED
|
@@ -255,6 +255,19 @@ var parseExtensionMetadata = (source) => {
|
|
|
255
255
|
};
|
|
256
256
|
const header = source.slice(0, 16384).match(/^\s*(?:(?:\/\/[^\n]*(?:\n|$))|(?:\/\*[\s\S]*?\*\/)\s*)*/)[0];
|
|
257
257
|
for (const line of header.split("\n")) {
|
|
258
|
+
if (/^\s*(?:\/\/|\/\*|\*)\s*Bundled extensions:\s*$/i.test(line)) break;
|
|
259
|
+
const localized = /^\s*(?:\/\/|\/\*|\*) ?Description\[([a-z]{2,8}(?:-[a-z0-9]{1,8})*)\]:\s*(.*?)\s*(?:\*\/)?$/i.exec(line);
|
|
260
|
+
if (localized) {
|
|
261
|
+
const locale = localized[1].toLowerCase();
|
|
262
|
+
const description = localized[2].slice(0, 1e3);
|
|
263
|
+
if (description.trim() && !/[\r\n\u2028\u2029]/.test(description)) {
|
|
264
|
+
if (!metadata.descriptions) metadata.descriptions = {};
|
|
265
|
+
if (!Object.prototype.hasOwnProperty.call(metadata.descriptions, locale)) {
|
|
266
|
+
metadata.descriptions[locale] = description;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
258
271
|
const match = /^\s*(?:\/\/|\/\*|\*)\s*([\w -]+):\s*(.*?)\s*(?:\*\/)?$/.exec(line);
|
|
259
272
|
const field = match && Object.prototype.hasOwnProperty.call(fields, match[1].toLowerCase()) && fields[match[1].toLowerCase()];
|
|
260
273
|
if (field && !Object.prototype.hasOwnProperty.call(metadata, field)) {
|
|
@@ -263,6 +276,18 @@ var parseExtensionMetadata = (source) => {
|
|
|
263
276
|
}
|
|
264
277
|
return metadata;
|
|
265
278
|
};
|
|
279
|
+
var getExtensionDescription = (metadata, locale) => {
|
|
280
|
+
const normalized = typeof locale === "string" ? locale.trim().toLowerCase() : "";
|
|
281
|
+
const descriptions = metadata.descriptions || {};
|
|
282
|
+
if (/^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/.test(normalized)) {
|
|
283
|
+
for (const candidate of [normalized, normalized.split("-")[0]]) {
|
|
284
|
+
if (Object.prototype.hasOwnProperty.call(descriptions, candidate) && typeof descriptions[candidate] === "string" && descriptions[candidate].trim()) {
|
|
285
|
+
return descriptions[candidate];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return metadata.description || "";
|
|
290
|
+
};
|
|
266
291
|
var decodeExtensionDataURL = (url) => {
|
|
267
292
|
const comma = url.indexOf(",");
|
|
268
293
|
if (comma < 0) throw new Error("Invalid extension data URL");
|
|
@@ -606,6 +631,7 @@ var ExtensionSecurityDialog = class extends React2.Component {
|
|
|
606
631
|
renderRow(entry, index) {
|
|
607
632
|
const row = this.state.rows[entry.url] || {};
|
|
608
633
|
const metadata = row.metadata || {};
|
|
634
|
+
const description = getExtensionDescription(metadata, this.props.locale);
|
|
609
635
|
const required = requiresUnsandboxed(metadata);
|
|
610
636
|
const reasonId = `extension-mode-reason-${index}`;
|
|
611
637
|
const requiredId = `extension-mode-required-${index}`;
|
|
@@ -648,7 +674,7 @@ var ExtensionSecurityDialog = class extends React2.Component {
|
|
|
648
674
|
"data-index": index,
|
|
649
675
|
onError: this.handleImageError
|
|
650
676
|
}
|
|
651
|
-
), /* @__PURE__ */ React2.createElement("div", { className: style_names_default.info }, /* @__PURE__ */ React2.createElement("strong", null, name), /* @__PURE__ */ React2.createElement("div", { className: style_names_default.secondary }, this.t("id", { id: metadata.id || entry.ids.join(", ") })), metadata.version && /* @__PURE__ */ React2.createElement("span", { className: style_names_default.version }, `v${metadata.version}`), metadata.author && /* @__PURE__ */ React2.createElement("div", { className: style_names_default.secondary }, this.t("author", { author: metadata.author })), metadata.updatedAt && /* @__PURE__ */ React2.createElement("div", { className: style_names_default.secondary }, this.t("updated", { updated: metadata.updatedAt })),
|
|
677
|
+
), /* @__PURE__ */ React2.createElement("div", { className: style_names_default.info }, /* @__PURE__ */ React2.createElement("strong", null, name), /* @__PURE__ */ React2.createElement("div", { className: style_names_default.secondary }, this.t("id", { id: metadata.id || entry.ids.join(", ") })), metadata.version && /* @__PURE__ */ React2.createElement("span", { className: style_names_default.version }, `v${metadata.version}`), metadata.author && /* @__PURE__ */ React2.createElement("div", { className: style_names_default.secondary }, this.t("author", { author: metadata.author })), metadata.updatedAt && /* @__PURE__ */ React2.createElement("div", { className: style_names_default.secondary }, this.t("updated", { updated: metadata.updatedAt })), description && /* @__PURE__ */ React2.createElement("div", null, description), metadata.license && /* @__PURE__ */ React2.createElement("div", { className: style_names_default.secondary }, this.t("license", { license: metadata.license })), /* @__PURE__ */ React2.createElement("div", { className: style_names_default.secondary }, sourceLink), !row.status && /* @__PURE__ */ React2.createElement("div", { className: style_names_default.secondary }, this.t("pending")), row.status === "failed" && /* @__PURE__ */ React2.createElement("div", { className: style_names_default.error }, this.t("failed")), required && /* @__PURE__ */ React2.createElement("div", { id: reasonId, className: style_names_default.warningText }, this.t("required")), row.invalidated && !this.state.choices[entry.url] && /* @__PURE__ */ React2.createElement("div", { className: style_names_default.warningText, role: "status" }, this.t("choiceReset")))), MODES.map((mode) => /* @__PURE__ */ React2.createElement(
|
|
652
678
|
"label",
|
|
653
679
|
{
|
|
654
680
|
className: style_names_default.choice,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubohiroya/turbowarp-extension-security-dialog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Batch extension security review, previews and per-project defaults for TurboWarp hosts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-only",
|
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
"files": ["dist", "react.js", "turbowarp.js", "styles.css", "webpack4.css", "README.md", "README.ja.md", "NOTICE.md", "LICENSE"],
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "node scripts/build.mjs",
|
|
23
|
-
"test": "
|
|
24
|
-
"
|
|
25
|
-
"
|
|
23
|
+
"test": "pnpm run build && node --test test/*.test.cjs",
|
|
24
|
+
"check": "pnpm run test && npm pack --dry-run --ignore-scripts",
|
|
25
|
+
"prepack": "pnpm run build",
|
|
26
|
+
"prepare": "pnpm run build"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {"@turbowarp/jszip": "3.11.1"},
|
|
28
29
|
"peerDependencies": {"react": "^16.14.0 || ^17 || ^18 || ^19", "react-dom": "^16.14.0 || ^17 || ^18 || ^19"},
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"react": "16.14.0",
|
|
34
35
|
"react-dom": "16.14.0"
|
|
35
36
|
},
|
|
36
|
-
"
|
|
37
|
+
"packageManager": "pnpm@11.28.8",
|
|
38
|
+
"engines": {"node": ">=24"},
|
|
37
39
|
"publishConfig": {"access": "public"}
|
|
38
40
|
}
|