@kubohiroya/turbowarp-extension-loader 0.2.0 → 0.2.1
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/CHANGELOG.md +20 -0
- package/README.ja.md +5 -5
- package/README.md +5 -5
- package/dist/index.js +2 -1
- package/dist/indexeddb-cache.d.ts +1 -1
- package/dist/indexeddb-cache.js +2 -1
- package/dist/limits.d.ts +19 -0
- package/dist/limits.js +16 -0
- package/dist/memory-cache.js +2 -1
- package/dist/packager.js +4 -2
- package/dist/project-authoring.js +2 -1
- package/dist/project-catalog.js +3 -3
- package/dist/project-codec.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.1
|
|
6
|
+
|
|
7
|
+
Default size limits that fit real extensions. A bundled story runtime of about 7 MiB was rejected in
|
|
8
|
+
every Plus form (`INVALID_ARCHIVE` for embedded, `INVALID_BYTES` for remote and cached) although
|
|
9
|
+
the same project loaded from a Data URL, because the loader read at most 1 MiB per extension while
|
|
10
|
+
the project codec stored entries up to 16 MiB.
|
|
11
|
+
|
|
12
|
+
- The loader, `./project-catalog` and `./project-authoring` default to 16 MiB per extension and
|
|
13
|
+
64 MiB in total, the codec's per-entry and expansion limits. All defaults now come from one module,
|
|
14
|
+
so an entry the codec accepts is one the loader reads. Hosts can still lower them.
|
|
15
|
+
- `./project-codec` accepts a `project.json` of up to 32 MiB by default (it was 4 MiB), the value
|
|
16
|
+
the TurboWarp Plus editor already passed; the same project's `project.json` is 6.8 MB once its
|
|
17
|
+
runtime moves out to an archive entry, so a host using the defaults, such as the packager page,
|
|
18
|
+
rejected it before any extension was read.
|
|
19
|
+
- `createMemoryContentCache` defaults to 32 MiB and the IndexedDB cache to a 16 MiB decompressed
|
|
20
|
+
maximum, so a cache holds an extension of the largest default size.
|
|
21
|
+
- `migrateLegacyDataURL` accepts a Data URL long enough for a 16 MiB extension (it stopped at
|
|
22
|
+
4,194,304 characters), and its base64 check is linear: the previous pattern overflowed the regex
|
|
23
|
+
stack on a payload of a few MiB and reported `INVALID_ENCODING`.
|
|
24
|
+
|
|
5
25
|
## 0.2.0
|
|
6
26
|
|
|
7
27
|
A third source form for extensions whose bytes should be reachable both from a canonical published
|
package/README.ja.md
CHANGED
|
@@ -74,7 +74,7 @@ await loader.unload();
|
|
|
74
74
|
- `./packager` はEmbedded / Remote変換、明示承認付きData URL移行、lock/license metadata保持、offline SB3準備を提供します。外部依存資源がある拡張は同梱を推測せず拒否します。署名・発行者鍵のpinningはホスト側で追加できる設計で、初期実装は内容・取得元に束縛したセッション承認です。
|
|
75
75
|
- ZIP の実装は `createProjectIO({zip: JSZip})` に注入します。core の codec が ZIP central directory を直接検査し、重複・経路・宣言容量を検証してから、ストリーム展開の実容量と CRC32 を検査します。外部実装の archive port を使う場合も同等の検証が必要です。
|
|
76
76
|
|
|
77
|
-
既定値は最大64拡張、1拡張
|
|
77
|
+
既定値は最大64拡張、1拡張16 MiB、合計64 MiB、取得・認可30秒です。1拡張と合計の既定値は project codec の1 entry と全展開量の上限に揃えてあり、作品に格納できる拡張ファイルは Loader でも読めます。ホストは loader options で下げられます。データURLやHTTPの開発モード例外はありません。標準 TurboWarp / 非対応エディターが独自 Manifest と ZIP エントリを破棄する可能性があるため、保持対応のないホストでの再保存は移行手段になりません。
|
|
78
78
|
|
|
79
79
|
## IndexedDB 永続キャッシュ(ブラウザホスト用)
|
|
80
80
|
|
|
@@ -90,13 +90,13 @@ import {createIndexedDBContentCache}
|
|
|
90
90
|
const cache = createIndexedDBContentCache({
|
|
91
91
|
databaseName: 'turbowarp-plus-extension-cache',
|
|
92
92
|
maxBytes: 32 * 1024 * 1024, // compressed storage quota
|
|
93
|
-
maxDecompressedBytes:
|
|
93
|
+
maxDecompressedBytes: 16 * 1024 * 1024
|
|
94
94
|
});
|
|
95
95
|
const loader = createExtensionLoader({...hostPorts, cache});
|
|
96
96
|
// 終了・ログアウトなどの明示操作で await cache.clear() / await cache.close()
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
- キーは lowercase SHA-256 hex。**保存する値は生JSではなく gzip 圧縮されたバイト列**(format: `gzip-v1`、元サイズを併記)。取得時に展開して Loader に返します。既定32 MiB は**圧縮後の総保存容量**、展開後の最大サイズは
|
|
99
|
+
- キーは lowercase SHA-256 hex。**保存する値は生JSではなく gzip 圧縮されたバイト列**(format: `gzip-v1`、元サイズを併記)。取得時に展開して Loader に返します。既定32 MiB は**圧縮後の総保存容量**、展開後の最大サイズは16 MiB(1拡張の既定値)、有効期限なし。容量超過時は最終アクセス順LRUで削除します。旧形式の非圧縮エントリは読み込まず削除します。
|
|
100
100
|
- 展開は出力サイズを制限し、改ざん・gzip破損や展開超過を拒否します。ブラウザの `CompressionStream` / `DecompressionStream` が必要です。非対応環境で生JSへ黙って切り替えて保存することはありません。
|
|
101
101
|
- キャッシュを利用した際にも Loader コアが **取得バイト列の SHA-256 を再検証** し、毎回 trustProvider の権限判定を適用します。キャッシュが信頼判断を代行することはありません。
|
|
102
102
|
- キャッシュは IndexedDB を使用できるブラウザ origin に紐付き、ブラウザのストレージ削除・プライベートモード・容量不足等で消える場合があります。ブラウザからの永続化保証はありません。
|
|
@@ -130,13 +130,13 @@ npm run check
|
|
|
130
130
|
|
|
131
131
|
`prepare` は JSON object / string / UTF-8 bytes / ArrayBuffer / Blob と SB3 ZIP を受け取り、`{manifest, input, context}` を返します。Manifest があれば core で検証し、legacy input の Plus ID に対する `extensionURLs` を除去して自動 URL 実行を防ぎます。ID 自体は GUI の登録 hook 用に残します。保存時は Manifest・元の Plus URL metadata・embedded bytes・未知の ZIP entry・未知の top-level metadata を復元します。`restoreArchive` は GUI の `toJSON` hook が `project.json` を復元済みであることを前提とします。
|
|
132
132
|
|
|
133
|
-
未対応形式は ZIP64、multidisk、暗号化、STORE/DEFLATE 以外、UTF-8 以外の非 ASCII entry 名、Unicode path extra field です。既定上限は archive と全展開量64 MiB、1 entry16 MiB、project.json
|
|
133
|
+
未対応形式は ZIP64、multidisk、暗号化、STORE/DEFLATE 以外、UTF-8 以外の非 ASCII entry 名、Unicode path extra field です。既定上限は archive と全展開量64 MiB、1 entry16 MiB、project.json 32 MiB、4096 entry、圧縮比1000です。上限はホスト起動設定で変更できます。Manifest のない ZIP もこの安全検査を受け、通過後は元 input をそのまま返します。このため、従来 VM が許していた未対応 ZIP もフラグ有効時には拒否されます。フラグ OFF では codec 自体を呼びません。
|
|
134
134
|
|
|
135
135
|
## 追加のホスト向けAPI
|
|
136
136
|
|
|
137
137
|
- `isAuthorizedCode(value)` はコアが発行し現在登録中の認可証跡だけを識別します。コピー・作品データによる偽造を拒否し、transaction終了時に失効します。
|
|
138
138
|
- `./session-trust` の `createSessionTrustProvider` はid/digest/source/version/publisher/capabilities/dependenciesへ束縛した承認を保持します。明示された外部ホストのURL/execution方針をUI前に毎回適用し、判定できないEmbedded要求は拒否します。
|
|
139
|
-
- `./memory-cache` の `createMemoryContentCache`
|
|
139
|
+
- `./memory-cache` の `createMemoryContentCache` はコピー・容量(既定32 MiB、既定最大サイズの拡張2件分)・TTL・LRUを提供し、コアはヒット時にも内容を再検証します。
|
|
140
140
|
- `./webpack4` はGUI/Packager用の同一コアをwebpack 4でビルドするための構成補助です。
|
|
141
141
|
|
|
142
142
|
実GUI、実Packager 3.14.0の生成HTML、Desktop 1.17.0の互換性はEditor側のE2Eが担当します。標準Desktopや標準WebへLoaderを無断インストールしません。標準Desktopの保存は独自Manifest/ZIPエントリを破棄することを実測しているため、保持対応エディターで保存してください。
|
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ The real VM adapter is owned by `scratch-gui/src/lib/tw-plus-extension-registrar
|
|
|
74
74
|
- `./packager` provides Embedded / Remote conversion, Data URL migration with explicit approval, lock and license metadata preservation, and offline SB3 preparation. An extension with external dependent resources is rejected rather than guessed into a bundle. Signature and publisher key pinning can be added by the host; the initial implementation is session approval bound to content and source.
|
|
75
75
|
- The ZIP implementation is injected through `createProjectIO({zip: JSZip})`. The core codec inspects the ZIP central directory directly and validates duplicates, paths and declared capacity before checking the real expanded size and CRC32 of the stream. An externally implemented archive port needs equivalent validation.
|
|
76
76
|
|
|
77
|
-
The defaults are at most 64 extensions,
|
|
77
|
+
The defaults are at most 64 extensions, 16 MiB per extension, 64 MiB in total, and 30 seconds for fetching and authorization. The per-extension and total defaults equal the project codec's per-entry and expansion limits, so any extension file a project can carry is also one the Loader reads; a host can lower them through its loader options. There is no development-mode exception for Data URLs or HTTP. Because standard TurboWarp and unsupported editors may discard the non-standard Manifest and ZIP entries, re-saving in a host without preservation support is not a migration path.
|
|
78
78
|
|
|
79
79
|
## IndexedDB persistent cache (for browser hosts)
|
|
80
80
|
|
|
@@ -89,13 +89,13 @@ import {createIndexedDBContentCache}
|
|
|
89
89
|
const cache = createIndexedDBContentCache({
|
|
90
90
|
databaseName: 'turbowarp-plus-extension-cache',
|
|
91
91
|
maxBytes: 32 * 1024 * 1024, // compressed storage quota
|
|
92
|
-
maxDecompressedBytes:
|
|
92
|
+
maxDecompressedBytes: 16 * 1024 * 1024
|
|
93
93
|
});
|
|
94
94
|
const loader = createExtensionLoader({...hostPorts, cache});
|
|
95
95
|
// On an explicit action such as exit or sign-out: await cache.clear() / await cache.close()
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
- The key is a lowercase SHA-256 hex digest. **The stored value is gzip-compressed bytes, not raw JS** (format `gzip-v1`, with the original size recorded alongside). It is decompressed on read and returned to the Loader. The default 32 MiB is the **total compressed storage quota**, the maximum decompressed size is
|
|
98
|
+
- The key is a lowercase SHA-256 hex digest. **The stored value is gzip-compressed bytes, not raw JS** (format `gzip-v1`, with the original size recorded alongside). It is decompressed on read and returned to the Loader. The default 32 MiB is the **total compressed storage quota**, the maximum decompressed size is 16 MiB (the per-extension default), and entries do not expire. On quota overflow, entries are evicted in least-recently-accessed order. Uncompressed entries in the old format are deleted rather than read.
|
|
99
99
|
- Decompression bounds the output size and rejects tampering, gzip corruption and oversized expansion. The browser's `CompressionStream` / `DecompressionStream` is required. The adapter never silently falls back to storing raw JS in an unsupported environment.
|
|
100
100
|
- Even on a cache hit, the Loader core **re-verifies the SHA-256 of the returned bytes** and applies the `trustProvider` decision every time. The cache never stands in for a trust decision.
|
|
101
101
|
- The cache is bound to a browser origin that can use IndexedDB, and may disappear when the browser clears storage, in private mode, when storage runs out, and so on. The browser offers no persistence guarantee.
|
|
@@ -128,13 +128,13 @@ Wire the Loader inside a host feature flag that is fixed at startup and off by d
|
|
|
128
128
|
|
|
129
129
|
`prepare` accepts a JSON object, a string, UTF-8 bytes, an ArrayBuffer, a Blob and an SB3 ZIP, and returns `{manifest, input, context}`. When a Manifest is present, the core validates it and strips `extensionURLs` for Plus IDs in legacy input to prevent automatic URL execution. The IDs themselves remain for the GUI's registration hook. On save it restores the Manifest, the original Plus URL metadata, embedded bytes, unknown ZIP entries and unknown top-level metadata. `restoreArchive` assumes the GUI's `toJSON` hook has already restored `project.json`.
|
|
130
130
|
|
|
131
|
-
Unsupported formats are ZIP64, multidisk, encryption, compression other than STORE/DEFLATE, non-ASCII entry names that are not UTF-8, and the Unicode path extra field. The default limits are 64 MiB for the archive and for total expansion, 16 MiB per entry,
|
|
131
|
+
Unsupported formats are ZIP64, multidisk, encryption, compression other than STORE/DEFLATE, non-ASCII entry names that are not UTF-8, and the Unicode path extra field. The default limits are 64 MiB for the archive and for total expansion, 16 MiB per entry, 32 MiB for `project.json`, 4096 entries, and a compression ratio of 1000. The limits are configurable through the host's startup settings. A ZIP without a Manifest goes through the same safety checks, and the original input is returned unchanged once it passes. As a result, an unsupported ZIP that the previous VM accepted is rejected while the flag is on. With the flag off, the codec is not called at all.
|
|
132
132
|
|
|
133
133
|
## Additional host-facing API
|
|
134
134
|
|
|
135
135
|
- `isAuthorizedCode(value)` identifies only an authorization trail issued by the core and currently being registered. Copies and forgeries from project data are rejected, and the trail expires when the transaction ends.
|
|
136
136
|
- `createSessionTrustProvider` in `./session-trust` keeps approval bound to id, digest, source, version, publisher, capabilities and dependencies. It applies the explicit external host URL / execution policy before the UI every time, and rejects an Embedded request it cannot decide.
|
|
137
|
-
- `createMemoryContentCache` in `./memory-cache` provides copying, capacity, TTL and LRU, and the core re-verifies the content even on a hit.
|
|
137
|
+
- `createMemoryContentCache` in `./memory-cache` provides copying, capacity (32 MiB by default, two extensions of the largest default size), TTL and LRU, and the core re-verifies the content even on a hit.
|
|
138
138
|
- `./webpack4` is a configuration helper for building the same core with webpack 4 for the GUI and Packager.
|
|
139
139
|
|
|
140
140
|
Compatibility with the real GUI, with the HTML generated by the real Packager 3.14.0, and with Desktop 1.17.0 is covered by E2E tests on the Editor side. Do not install the Loader into standard Desktop or standard Web without permission. Saving in standard Desktop has been measured to discard the non-standard Manifest and ZIP entries, so save in an editor that preserves them.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { DEFAULT_LOADER_LIMITS } from './limits.js';
|
|
2
|
+
const DEFAULTS = DEFAULT_LOADER_LIMITS;
|
|
2
3
|
const RESERVED_IDS = new Set(['pen', 'music', 'videoSensing', 'text2speech', 'translate', 'microbit', 'ev3', 'boost', 'wedo2', 'gdxfor', 'makeymakey']);
|
|
3
4
|
const ID = /^[a-zA-Z][a-zA-Z0-9]{0,63}$/;
|
|
4
5
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
@@ -3,7 +3,7 @@ export interface IndexedDBContentCacheOptions {
|
|
|
3
3
|
databaseName?: string;
|
|
4
4
|
/** Compressed bytes quota (default 32 MiB). */
|
|
5
5
|
maxBytes?: number;
|
|
6
|
-
/** Maximum decompressed JS size (default
|
|
6
|
+
/** Maximum decompressed JS size (default 16 MiB, the largest default extension). */
|
|
7
7
|
maxDecompressedBytes?: number;
|
|
8
8
|
now?: () => number;
|
|
9
9
|
}
|
package/dist/indexeddb-cache.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { gzipBytes, gunzipBytes } from './cache-gzip.js';
|
|
2
|
+
import { DEFAULT_ENTRY_BYTES } from './limits.js';
|
|
2
3
|
/** Persistent, content-addressed cache. The Loader independently verifies SHA-256 on every hit.
|
|
3
4
|
* Storage is untrusted: this adapter is not an authorization or integrity boundary.
|
|
4
5
|
*/
|
|
5
|
-
export function createIndexedDBContentCache({ indexedDB = globalThis.indexedDB, databaseName = 'turbowarp-plus-extension-cache', maxBytes = 32 * 1024 * 1024, maxDecompressedBytes =
|
|
6
|
+
export function createIndexedDBContentCache({ indexedDB = globalThis.indexedDB, databaseName = 'turbowarp-plus-extension-cache', maxBytes = 32 * 1024 * 1024, maxDecompressedBytes = DEFAULT_ENTRY_BYTES, now = Date.now } = {}) {
|
|
6
7
|
if (!indexedDB || typeof indexedDB.open !== 'function')
|
|
7
8
|
throw new Error('IndexedDB unavailable');
|
|
8
9
|
if (typeof databaseName !== 'string' || !databaseName || !Number.isSafeInteger(maxBytes) || maxBytes < 1 ||
|
package/dist/limits.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one source of the default size limits. A file the project codec accepts as an archive entry
|
|
3
|
+
* must also be one the loader reads, so the per-extension and total defaults are the codec's entry
|
|
4
|
+
* and expansion limits; real extensions such as a bundled story runtime reach several MiB.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_ENTRY_BYTES: number;
|
|
7
|
+
export declare const DEFAULT_TOTAL_BYTES: number;
|
|
8
|
+
/** A project.json may itself carry large scripts, such as legacy Data URL extensions or story data. */
|
|
9
|
+
export declare const DEFAULT_PROJECT_JSON_BYTES: number;
|
|
10
|
+
export declare const DEFAULT_LOADER_LIMITS: Readonly<{
|
|
11
|
+
maxExtensions: 64;
|
|
12
|
+
maxBytes: number;
|
|
13
|
+
maxTotalBytes: number;
|
|
14
|
+
timeoutMs: 30000;
|
|
15
|
+
}>;
|
|
16
|
+
/** A cache holds at least one extension of the largest default size. */
|
|
17
|
+
export declare const DEFAULT_MEMORY_CACHE_BYTES: number;
|
|
18
|
+
/** Base64 of the largest default extension, plus room for the `data:` prefix. */
|
|
19
|
+
export declare const MAX_LEGACY_DATA_URL_LENGTH: number;
|
package/dist/limits.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const MiB = 1024 * 1024;
|
|
2
|
+
/**
|
|
3
|
+
* The one source of the default size limits. A file the project codec accepts as an archive entry
|
|
4
|
+
* must also be one the loader reads, so the per-extension and total defaults are the codec's entry
|
|
5
|
+
* and expansion limits; real extensions such as a bundled story runtime reach several MiB.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_ENTRY_BYTES = 16 * MiB;
|
|
8
|
+
export const DEFAULT_TOTAL_BYTES = 64 * MiB;
|
|
9
|
+
/** A project.json may itself carry large scripts, such as legacy Data URL extensions or story data. */
|
|
10
|
+
export const DEFAULT_PROJECT_JSON_BYTES = 32 * MiB;
|
|
11
|
+
export const DEFAULT_LOADER_LIMITS = Object.freeze({ maxExtensions: 64, maxBytes: DEFAULT_ENTRY_BYTES,
|
|
12
|
+
maxTotalBytes: DEFAULT_TOTAL_BYTES, timeoutMs: 30000 });
|
|
13
|
+
/** A cache holds at least one extension of the largest default size. */
|
|
14
|
+
export const DEFAULT_MEMORY_CACHE_BYTES = 2 * DEFAULT_ENTRY_BYTES;
|
|
15
|
+
/** Base64 of the largest default extension, plus room for the `data:` prefix. */
|
|
16
|
+
export const MAX_LEGACY_DATA_URL_LENGTH = Math.ceil(DEFAULT_ENTRY_BYTES / 3) * 4 + 1024;
|
package/dist/memory-cache.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { DEFAULT_MEMORY_CACHE_BYTES } from './limits.js';
|
|
1
2
|
/** Bounded host-session storage; the Loader re-verifies bytes on every cache hit. */
|
|
2
|
-
export function createMemoryContentCache({ maxBytes =
|
|
3
|
+
export function createMemoryContentCache({ maxBytes = DEFAULT_MEMORY_CACHE_BYTES, ttlMs = 60 * 60 * 1000, now = Date.now } = {}) {
|
|
3
4
|
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || !Number.isSafeInteger(ttlMs) || ttlMs < 1 || typeof now !== 'function') {
|
|
4
5
|
throw new TypeError('Invalid cache capacity or expiry');
|
|
5
6
|
}
|
package/dist/packager.js
CHANGED
|
@@ -2,6 +2,7 @@ import { LoaderError, validateManifest } from './index.js';
|
|
|
2
2
|
import { createProjectIO } from './project-codec.js';
|
|
3
3
|
import { collectVerifiedCodes as collect } from './verified-code.js';
|
|
4
4
|
import { readPreparedProject as decodePrepared } from './project-data.js';
|
|
5
|
+
import { MAX_LEGACY_DATA_URL_LENGTH } from './limits.js';
|
|
5
6
|
const encoder = new TextEncoder();
|
|
6
7
|
function fail(code, message) { throw new LoaderError(code, message); }
|
|
7
8
|
const cancelled = (signal) => { if (signal?.aborted)
|
|
@@ -118,7 +119,7 @@ export async function prepareOfflinePackage(input, ports, options = {}) {
|
|
|
118
119
|
}
|
|
119
120
|
/** Migrate a legacy Data URL only after the host explicitly pins and approves its bytes. */
|
|
120
121
|
export async function migrateLegacyDataURL({ id, url, sha256, metadata }, ports, options = {}) {
|
|
121
|
-
if (typeof url !== 'string' || url.length >
|
|
122
|
+
if (typeof url !== 'string' || url.length > MAX_LEGACY_DATA_URL_LENGTH)
|
|
122
123
|
fail('INVALID_SOURCE', 'Invalid or oversized legacy Data URL');
|
|
123
124
|
const match = /^data:(?:application|text)\/(?:javascript|x-javascript)(?:;charset=utf-8)?(;base64)?;?,([\s\S]*)$/i.exec(url);
|
|
124
125
|
if (!match)
|
|
@@ -126,7 +127,8 @@ export async function migrateLegacyDataURL({ id, url, sha256, metadata }, ports,
|
|
|
126
127
|
let bytes;
|
|
127
128
|
try {
|
|
128
129
|
if (match[1]) {
|
|
129
|
-
|
|
130
|
+
// A repeated group overflows the regex stack on a multi-MiB payload; this form is linear.
|
|
131
|
+
if (match[2].length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(match[2]))
|
|
130
132
|
throw new Error('Invalid base64');
|
|
131
133
|
bytes = Uint8Array.from(atob(match[2]), character => character.charCodeAt(0));
|
|
132
134
|
}
|
|
@@ -2,6 +2,7 @@ import { LoaderError, validateManifest } from './index.js';
|
|
|
2
2
|
import { createProjectIO } from './project-codec.js';
|
|
3
3
|
import { readPreparedProject } from './project-data.js';
|
|
4
4
|
import { collectVerifiedCodes } from './verified-code.js';
|
|
5
|
+
import { DEFAULT_LOADER_LIMITS } from './limits.js';
|
|
5
6
|
const encoder = new TextEncoder();
|
|
6
7
|
function fail(code, message, id) { throw new LoaderError(code, message, id); }
|
|
7
8
|
const plainObject = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value) &&
|
|
@@ -28,7 +29,7 @@ export async function prepareExtensionAddition(input, request, ports, { zip: Zip
|
|
|
28
29
|
}
|
|
29
30
|
if (typeof ports?.trustProvider?.authorize !== 'function')
|
|
30
31
|
fail('INVALID_PORT', 'An explicit host trust provider is required');
|
|
31
|
-
const limits = { maxBytes:
|
|
32
|
+
const limits = { maxBytes: DEFAULT_LOADER_LIMITS.maxBytes, timeoutMs: DEFAULT_LOADER_LIMITS.timeoutMs, ...loaderOptions };
|
|
32
33
|
for (const key of ['maxBytes', 'maxTotalBytes', 'maxExtensions', 'timeoutMs']) {
|
|
33
34
|
const value = limits[key];
|
|
34
35
|
if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))
|
package/dist/project-catalog.js
CHANGED
|
@@ -2,6 +2,7 @@ import { LoaderError, validateManifest } from './index.js';
|
|
|
2
2
|
import { createProjectIO } from './project-codec.js';
|
|
3
3
|
import { readPreparedProject } from './project-data.js';
|
|
4
4
|
import { collectVerifiedCodes } from './verified-code.js';
|
|
5
|
+
import { DEFAULT_LOADER_LIMITS, DEFAULT_TOTAL_BYTES } from './limits.js';
|
|
5
6
|
const encoder = new TextEncoder();
|
|
6
7
|
const digest = async (bytes) => Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', bytes)), value => value.toString(16).padStart(2, '0')).join('');
|
|
7
8
|
function fail(code, message, id) { throw new LoaderError(code, message, id); }
|
|
@@ -11,8 +12,7 @@ export async function readProjectAuthoringState(input, { zip, signal, archiveOpt
|
|
|
11
12
|
return prepared.manifest;
|
|
12
13
|
}
|
|
13
14
|
export async function prepareCatalogExtensionChanges(input, requests, ports, { zip: Zip, signal: externalSignal, loaderOptions = {}, archiveOptions } = {}) {
|
|
14
|
-
const limits = {
|
|
15
|
-
timeoutMs: 30000, ...loaderOptions };
|
|
15
|
+
const limits = { ...DEFAULT_LOADER_LIMITS, ...loaderOptions };
|
|
16
16
|
for (const key of ['maxExtensions', 'maxBytes', 'maxTotalBytes', 'timeoutMs']) {
|
|
17
17
|
if (!Number.isSafeInteger(limits[key]) || limits[key] < 1)
|
|
18
18
|
fail('INVALID_OPTIONS', `Invalid ${key}`);
|
|
@@ -21,7 +21,7 @@ export async function prepareCatalogExtensionChanges(input, requests, ports, { z
|
|
|
21
21
|
fail('INVALID_ADDITION', 'A bounded catalog selection is required');
|
|
22
22
|
}
|
|
23
23
|
if (input instanceof Uint8Array || input instanceof ArrayBuffer) {
|
|
24
|
-
if (input.byteLength > (archiveOptions?.maxArchiveBytes ??
|
|
24
|
+
if (input.byteLength > (archiveOptions?.maxArchiveBytes ?? DEFAULT_TOTAL_BYTES)) {
|
|
25
25
|
fail('ARCHIVE_SIZE_LIMIT', 'Project exceeds the archive byte limit');
|
|
26
26
|
}
|
|
27
27
|
input = input.slice(0);
|
package/dist/project-codec.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { discoverManifest, LoaderError } from './index.js';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { DEFAULT_ENTRY_BYTES, DEFAULT_PROJECT_JSON_BYTES, DEFAULT_TOTAL_BYTES } from './limits.js';
|
|
3
|
+
const DEFAULTS = Object.freeze({ maxArchiveBytes: DEFAULT_TOTAL_BYTES, maxTotalBytes: DEFAULT_TOTAL_BYTES,
|
|
4
|
+
maxEntryBytes: DEFAULT_ENTRY_BYTES, maxProjectBytes: DEFAULT_PROJECT_JSON_BYTES, maxEntries: 4096, maxRatio: 1000 });
|
|
4
5
|
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
5
6
|
const encoder = new TextEncoder();
|
|
6
7
|
function fail(message) { throw new LoaderError('INVALID_ARCHIVE', message); }
|