memory-extract 0.1.0 → 0.1.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.
@@ -0,0 +1,737 @@
1
+ var MemoryExtract = (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.js
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ MAX_PAYLOAD_SIZE: () => MAX_PAYLOAD_SIZE,
24
+ MEMORY_CHUNK_TYPE: () => MEMORY_CHUNK_TYPE,
25
+ MEMORY_MAGIC: () => MEMORY_MAGIC,
26
+ MEMORY_VERSION: () => MEMORY_VERSION,
27
+ SUPPORTED_VERSIONS: () => SUPPORTED_VERSIONS,
28
+ assertSupportedVersion: () => assertSupportedVersion,
29
+ buildV2Manifest: () => buildV2Manifest,
30
+ createMemoryBlob: () => createMemoryBlob,
31
+ decodeManifest: () => decodeManifest,
32
+ decodeManifestFile: () => decodeManifestFile,
33
+ decodeMemoryPayload: () => decodeMemoryPayload,
34
+ decodeV2Archive: () => decodeV2Archive,
35
+ encodeV2Archive: () => encodeV2Archive,
36
+ extractMemory: () => extractMemory,
37
+ guessMimeType: () => guessMimeType,
38
+ listManifestFiles: () => listManifestFiles,
39
+ readManifestFile: () => readManifestFile,
40
+ readMemoryFile: () => readMemoryFile,
41
+ resolveLaunchAssetUrl: () => resolveLaunchAssetUrl,
42
+ resolveSafePath: () => resolveSafePath
43
+ });
44
+
45
+ // src/payloadFormat.js
46
+ var MEMORY_VERSION = 2;
47
+ var SUPPORTED_VERSIONS = [1, 2];
48
+ var MAX_PAYLOAD_SIZE = 64 * 1024 * 1024;
49
+ var readUint32BE = (view, offset) => view[offset] << 24 | view[offset + 1] << 16 | view[offset + 2] << 8 | view[offset + 3];
50
+ var writeUint32BE = (view, offset, value) => {
51
+ view[offset] = value >>> 24 & 255;
52
+ view[offset + 1] = value >>> 16 & 255;
53
+ view[offset + 2] = value >>> 8 & 255;
54
+ view[offset + 3] = value & 255;
55
+ };
56
+ var assertSupportedVersion = (version) => {
57
+ if (!SUPPORTED_VERSIONS.includes(version)) {
58
+ throw new Error(`Unsupported memory version: ${version}`);
59
+ }
60
+ };
61
+ var guessMimeType = (filePath) => {
62
+ const lower = filePath.toLowerCase();
63
+ if (lower.endsWith(".html")) return "text/html";
64
+ if (lower.endsWith(".css")) return "text/css";
65
+ if (lower.endsWith(".js")) return "text/javascript";
66
+ if (lower.endsWith(".svg")) return "image/svg+xml";
67
+ if (lower.endsWith(".png")) return "image/png";
68
+ if (lower.endsWith(".json")) return "application/json";
69
+ if (lower.endsWith(".woff2")) return "font/woff2";
70
+ if (lower.endsWith(".woff")) return "font/woff";
71
+ return "application/octet-stream";
72
+ };
73
+ var listManifestFiles = (manifest) => {
74
+ if (Array.isArray(manifest.files)) {
75
+ return manifest.files.map((file) => file.path);
76
+ }
77
+ return Object.keys(manifest.files ?? {});
78
+ };
79
+ var readManifestFile = (manifest, filePath, fileBytes = null, decodeV1File = null) => {
80
+ if (fileBytes?.[filePath]) {
81
+ return fileBytes[filePath];
82
+ }
83
+ const record = manifest.files?.[filePath];
84
+ if (record && decodeV1File) {
85
+ return decodeV1File(record);
86
+ }
87
+ throw new Error(`Manifest file not found: ${filePath}`);
88
+ };
89
+ var concatBytes = (parts) => {
90
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
91
+ const output = new Uint8Array(total);
92
+ let offset = 0;
93
+ for (const part of parts) {
94
+ output.set(part, offset);
95
+ offset += part.length;
96
+ }
97
+ return output;
98
+ };
99
+ var toUint8Array = (value) => value instanceof Uint8Array ? value : new Uint8Array(value);
100
+ var buildV2Manifest = ({
101
+ name,
102
+ entry,
103
+ files,
104
+ kind = "web-app",
105
+ runtime = "iframe-sandbox",
106
+ source = ""
107
+ }) => {
108
+ const sortedPaths = Object.keys(files).sort();
109
+ return {
110
+ manifest: {
111
+ v: 2,
112
+ kind,
113
+ name,
114
+ entry,
115
+ runtime,
116
+ ...source ? { source } : {},
117
+ files: sortedPaths.map((filePath) => {
118
+ const bytes = toUint8Array(files[filePath]);
119
+ return {
120
+ path: filePath,
121
+ mime: guessMimeType(filePath),
122
+ size: bytes.length
123
+ };
124
+ })
125
+ },
126
+ sortedPaths
127
+ };
128
+ };
129
+ var encodeV2Archive = ({ name, entry, files, kind, runtime, source }) => {
130
+ const { manifest, sortedPaths } = buildV2Manifest({
131
+ name,
132
+ entry,
133
+ files,
134
+ kind,
135
+ runtime,
136
+ source
137
+ });
138
+ const json = new TextEncoder().encode(JSON.stringify(manifest));
139
+ const header = new Uint8Array(4);
140
+ writeUint32BE(header, 0, json.length);
141
+ return concatBytes([
142
+ header,
143
+ json,
144
+ ...sortedPaths.map((filePath) => toUint8Array(files[filePath]))
145
+ ]);
146
+ };
147
+ var decodeV2Archive = (decompressed) => {
148
+ const view = toUint8Array(decompressed);
149
+ const jsonLength = readUint32BE(view, 0);
150
+ const jsonStart = 4;
151
+ const jsonEnd = jsonStart + jsonLength;
152
+ if (jsonEnd > view.length) {
153
+ throw new Error("Memory manifest length exceeds payload size.");
154
+ }
155
+ const manifest = JSON.parse(new TextDecoder().decode(view.slice(jsonStart, jsonEnd)));
156
+ const fileBytes = {};
157
+ let offset = jsonEnd;
158
+ for (const file of manifest.files ?? []) {
159
+ const end = offset + file.size;
160
+ if (end > view.length) {
161
+ throw new Error(`Memory file exceeds payload size: ${file.path}`);
162
+ }
163
+ fileBytes[file.path] = view.slice(offset, end);
164
+ offset = end;
165
+ }
166
+ if (offset !== view.length) {
167
+ throw new Error("Memory payload trailing bytes remain after decode.");
168
+ }
169
+ return { manifest, fileBytes };
170
+ };
171
+ var resolveSafePath = (outDir, filePath, pathSep, pathResolve) => {
172
+ const resolvedOutDir = pathResolve(outDir);
173
+ const target = pathResolve(resolvedOutDir, filePath);
174
+ const prefix = resolvedOutDir.endsWith(pathSep) ? resolvedOutDir : `${resolvedOutDir}${pathSep}`;
175
+ if (target !== resolvedOutDir && !target.startsWith(prefix)) {
176
+ throw new Error(`Unsafe path in manifest: ${filePath}`);
177
+ }
178
+ return target;
179
+ };
180
+
181
+ // src/memoryFormat.js
182
+ var MEMORY_MAGIC = new Uint8Array([87, 76, 70, 67]);
183
+ var MEMORY_CHUNK_TYPE = new Uint8Array([119, 76, 70, 67]);
184
+ var PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
185
+ var textDecoder = new TextDecoder();
186
+ var bytesMatch = (view, offset, magic) => {
187
+ if (offset < 0 || offset + magic.length > view.length) return false;
188
+ for (let index = 0; index < magic.length; index += 1) {
189
+ if (view[offset + index] !== magic[index]) return false;
190
+ }
191
+ return true;
192
+ };
193
+ var readUint32BE2 = (view, offset) => view[offset] << 24 | view[offset + 1] << 16 | view[offset + 2] << 8 | view[offset + 3];
194
+ var parsePngChunks = (view) => {
195
+ if (view.length < 8 || !bytesMatch(view, 0, PNG_SIGNATURE)) {
196
+ throw new Error("Invalid PNG signature.");
197
+ }
198
+ const chunks = [];
199
+ let offset = 8;
200
+ while (offset + 12 <= view.length) {
201
+ const length = readUint32BE2(view, offset);
202
+ const type = view.slice(offset + 4, offset + 8);
203
+ const dataStart = offset + 8;
204
+ const dataEnd = dataStart + length;
205
+ if (dataEnd + 4 > view.length) {
206
+ throw new Error("PNG chunk exceeds file size.");
207
+ }
208
+ chunks.push({
209
+ type: String.fromCharCode(...type),
210
+ data: view.slice(dataStart, dataEnd),
211
+ start: offset,
212
+ end: dataEnd + 4
213
+ });
214
+ offset = dataEnd + 4;
215
+ if (chunks[chunks.length - 1].type === "IEND") {
216
+ break;
217
+ }
218
+ }
219
+ return { view, chunks, trailing: view.slice(offset) };
220
+ };
221
+ var readMemoryPayload = (chunkData) => {
222
+ if (chunkData.length < 8 || !bytesMatch(chunkData, 0, MEMORY_MAGIC)) {
223
+ throw new Error("Memory chunk magic mismatch.");
224
+ }
225
+ const version = readUint32BE2(chunkData, 4);
226
+ assertSupportedVersion(version);
227
+ const payload = chunkData.slice(8);
228
+ if (payload.length > MAX_PAYLOAD_SIZE) {
229
+ throw new Error(`Memory payload exceeds ${MAX_PAYLOAD_SIZE} bytes.`);
230
+ }
231
+ return { version, payload };
232
+ };
233
+ var gunzip = async (payload) => {
234
+ const stream = new Blob([payload]).stream().pipeThrough(new DecompressionStream("gzip"));
235
+ const buffer = await new Response(stream).arrayBuffer();
236
+ return new Uint8Array(buffer);
237
+ };
238
+ var decodeManifest = async (payload) => {
239
+ const decompressed = await gunzip(payload);
240
+ return JSON.parse(textDecoder.decode(decompressed));
241
+ };
242
+ var decodeMemoryPayload = async (payload, version) => {
243
+ assertSupportedVersion(version);
244
+ if (version === 1) {
245
+ return {
246
+ manifest: await decodeManifest(payload),
247
+ fileBytes: null
248
+ };
249
+ }
250
+ return decodeV2Archive(await gunzip(payload));
251
+ };
252
+ var extractLegacyFooter = async (view) => {
253
+ let magicIndex = -1;
254
+ for (let index = view.length - MEMORY_MAGIC.length; index >= 0; index -= 1) {
255
+ if (bytesMatch(view, index, MEMORY_MAGIC)) {
256
+ magicIndex = index;
257
+ break;
258
+ }
259
+ }
260
+ if (magicIndex < 0) {
261
+ throw new Error("Memory footer not found.");
262
+ }
263
+ const version = readUint32BE2(view, magicIndex + 4);
264
+ const payloadLength = readUint32BE2(view, magicIndex + 8);
265
+ const payloadStart = magicIndex + 12;
266
+ const payloadEnd = payloadStart + payloadLength;
267
+ if (version !== 1) {
268
+ throw new Error(`Unsupported legacy memory version: ${version}`);
269
+ }
270
+ if (payloadEnd > view.length) {
271
+ throw new Error("Memory payload length exceeds file size.");
272
+ }
273
+ const payload = view.slice(payloadStart, payloadEnd);
274
+ const decoded = await decodeMemoryPayload(payload, version);
275
+ return {
276
+ png: view.slice(0, magicIndex),
277
+ payload,
278
+ ...decoded,
279
+ version
280
+ };
281
+ };
282
+ var decodeExtractedMemory = async (png, payload, version) => {
283
+ const decoded = await decodeMemoryPayload(payload, version);
284
+ return {
285
+ png,
286
+ payload,
287
+ ...decoded,
288
+ version
289
+ };
290
+ };
291
+ var extractMemory = async (input) => {
292
+ const view = input instanceof Uint8Array ? input : new Uint8Array(input);
293
+ if (view.length < 12) {
294
+ throw new Error("File is too small to be a memory PNG.");
295
+ }
296
+ try {
297
+ const { view: pngView, chunks, trailing } = parsePngChunks(view);
298
+ const memoryChunk = chunks.find((chunk) => chunk.type === "wLFC");
299
+ if (memoryChunk) {
300
+ const { version, payload } = readMemoryPayload(memoryChunk.data);
301
+ return decodeExtractedMemory(pngView, payload, version);
302
+ }
303
+ if (trailing.length > 0) {
304
+ return extractLegacyFooter(view);
305
+ }
306
+ } catch (error) {
307
+ if (error.message?.includes("Memory footer not found")) {
308
+ throw error;
309
+ }
310
+ return extractLegacyFooter(view);
311
+ }
312
+ throw new Error("Memory chunk not found.");
313
+ };
314
+ var decodeManifestFile = (fileRecord) => {
315
+ if (!fileRecord?.data) {
316
+ throw new Error("Invalid manifest file record.");
317
+ }
318
+ if (typeof Uint8Array.fromBase64 === "function") {
319
+ return Uint8Array.fromBase64(fileRecord.data, { alphabet: "base64" });
320
+ }
321
+ const binary = atob(fileRecord.data);
322
+ const bytes = new Uint8Array(binary.length);
323
+ for (let index = 0; index < binary.length; index += 1) {
324
+ bytes[index] = binary.charCodeAt(index);
325
+ }
326
+ return bytes;
327
+ };
328
+ var readMemoryFile = (manifest, filePath, fileBytes = null) => readManifestFile(manifest, filePath, fileBytes, decodeManifestFile);
329
+
330
+ // src/memoryPlay.js
331
+ var isHtmlEntry = (filePath) => filePath.endsWith(".html") || filePath.endsWith(".htm");
332
+ var resolveMemoryPlayMode = (manifest, filePaths) => {
333
+ if (filePaths.length === 1) {
334
+ return { mode: "file", path: filePaths[0] };
335
+ }
336
+ const indexEntry = filePaths.find(
337
+ (filePath) => filePath === "index.html" || filePath.endsWith("/index.html") || filePath === "index.htm" || filePath.endsWith("/index.htm")
338
+ );
339
+ if (indexEntry) {
340
+ return { mode: "play", entry: indexEntry };
341
+ }
342
+ if (filePaths.includes(manifest.entry) && isHtmlEntry(manifest.entry)) {
343
+ return { mode: "play", entry: manifest.entry };
344
+ }
345
+ return { mode: "browse" };
346
+ };
347
+ var urlPathToPrefix = (urlPath) => urlPath === "/" ? "" : urlPath.slice(1);
348
+ var listVirtualEntries = (filePaths, urlPath = "/") => {
349
+ const prefix = urlPathToPrefix(urlPath);
350
+ const entries = /* @__PURE__ */ new Map();
351
+ for (const filePath of filePaths) {
352
+ if (prefix && !filePath.startsWith(`${prefix}/`)) {
353
+ continue;
354
+ }
355
+ const rest = prefix ? filePath.slice(prefix.length + 1) : filePath;
356
+ if (!rest) {
357
+ continue;
358
+ }
359
+ const slash = rest.indexOf("/");
360
+ if (slash === -1) {
361
+ entries.set(rest, { name: rest, isDirectory: false });
362
+ continue;
363
+ }
364
+ entries.set(rest.slice(0, slash), {
365
+ name: rest.slice(0, slash),
366
+ isDirectory: true
367
+ });
368
+ }
369
+ return [...entries.values()];
370
+ };
371
+ var collectVirtualDirectoryPaths = (filePaths) => {
372
+ const dirs = /* @__PURE__ */ new Set(["/"]);
373
+ for (const filePath of filePaths) {
374
+ const parts = filePath.split("/");
375
+ for (let index = 1; index < parts.length; index += 1) {
376
+ dirs.add(`/${parts.slice(0, index).join("/")}`);
377
+ }
378
+ }
379
+ return [...dirs].sort(
380
+ (left, right) => right.split("/").length - left.split("/").length
381
+ );
382
+ };
383
+ var buildMemoryBrowseListings = (filePaths, fileUrlByPath) => {
384
+ const listings = {};
385
+ for (const urlPath of collectVirtualDirectoryPaths(filePaths)) {
386
+ const prefix = urlPath === "/" ? "" : urlPath.slice(1);
387
+ listings[urlPath] = listVirtualEntries(filePaths, urlPath).map((entry) => {
388
+ const fullPath = prefix ? `${prefix}/${entry.name}` : entry.name;
389
+ if (entry.isDirectory) {
390
+ return {
391
+ name: entry.name,
392
+ isDirectory: true,
393
+ path: `/${fullPath}`.replace(/\/+/g, "/")
394
+ };
395
+ }
396
+ return {
397
+ name: entry.name,
398
+ isDirectory: false,
399
+ href: fileUrlByPath.get(fullPath) ?? "#"
400
+ };
401
+ });
402
+ }
403
+ return listings;
404
+ };
405
+ var buildInteractiveBrowseDocument = (listings, rootPath = "/") => `<!DOCTYPE html>
406
+ <html lang="en">
407
+ <head>
408
+ <meta charset="utf-8" />
409
+ <title>Index of ${rootPath}</title>
410
+ <style>
411
+ body { font-family: system-ui, sans-serif; margin: 2rem; }
412
+ h1 { font-size: 1.1rem; font-weight: 600; }
413
+ ul { list-style: none; padding: 0; }
414
+ li { margin: 0.35rem 0; }
415
+ a { text-decoration: none; color: inherit; }
416
+ a:hover { text-decoration: underline; }
417
+ </style>
418
+ </head>
419
+ <body>
420
+ <h1 id="title">Index of ${rootPath}</h1>
421
+ <ul id="list"></ul>
422
+ <script>
423
+ const listings = ${JSON.stringify(listings)};
424
+ const title = document.getElementById("title");
425
+ const list = document.getElementById("list");
426
+
427
+ const parentPath = (path) => {
428
+ if (path === "/") return "/";
429
+ const trimmed = path.replace(/\\/+$/, "");
430
+ const slash = trimmed.lastIndexOf("/");
431
+ return slash <= 0 ? "/" : trimmed.slice(0, slash);
432
+ };
433
+
434
+ const render = (path) => {
435
+ const entries = listings[path] || [];
436
+ title.textContent = "Index of " + path;
437
+ list.replaceChildren();
438
+
439
+ if (path !== "/") {
440
+ const parentItem = document.createElement("li");
441
+ const parentLink = document.createElement("a");
442
+ parentLink.href = "#";
443
+ parentLink.textContent = "../";
444
+ parentLink.addEventListener("click", (event) => {
445
+ event.preventDefault();
446
+ render(parentPath(path));
447
+ });
448
+ parentItem.appendChild(parentLink);
449
+ list.appendChild(parentItem);
450
+ }
451
+
452
+ entries
453
+ .slice()
454
+ .sort((left, right) => {
455
+ if (left.isDirectory !== right.isDirectory) {
456
+ return left.isDirectory ? -1 : 1;
457
+ }
458
+ return left.name.localeCompare(right.name);
459
+ })
460
+ .forEach((entry) => {
461
+ const item = document.createElement("li");
462
+ const link = document.createElement("a");
463
+
464
+ if (entry.isDirectory) {
465
+ link.href = "#";
466
+ link.textContent = entry.name + "/";
467
+ link.addEventListener("click", (event) => {
468
+ event.preventDefault();
469
+ render(entry.path);
470
+ });
471
+ } else {
472
+ link.href = entry.href;
473
+ link.textContent = entry.name;
474
+ }
475
+
476
+ item.appendChild(link);
477
+ list.appendChild(item);
478
+ });
479
+ };
480
+
481
+ render(${JSON.stringify(rootPath)});
482
+ <\/script>
483
+ </body>
484
+ </html>
485
+ `;
486
+
487
+ // src/loadMemory.js
488
+ var textDecoder2 = new TextDecoder();
489
+ var REWRITABLE_ATTRIBUTES = [
490
+ ["script", "src"],
491
+ ["link", "href"],
492
+ ["img", "src"],
493
+ ["source", "src"],
494
+ ["video", "src"],
495
+ ["audio", "src"],
496
+ ["image", "href"]
497
+ ];
498
+ var CSS_URL_PATTERN = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g;
499
+ var readInput = async (input) => {
500
+ if (input instanceof Uint8Array) {
501
+ return input;
502
+ }
503
+ if (input instanceof ArrayBuffer) {
504
+ return new Uint8Array(input);
505
+ }
506
+ if (input instanceof Blob) {
507
+ return new Uint8Array(await input.arrayBuffer());
508
+ }
509
+ throw new Error("Expected a PNG memory image.");
510
+ };
511
+ var normalizeAssetPath = (value) => value.replace(/^\.\//, "");
512
+ var stripLeadingSlash = (value) => value.startsWith("/") ? value.slice(1) : value;
513
+ var guessMimeType2 = (filePath, record) => {
514
+ if (record?.mime) return record.mime;
515
+ if (filePath.endsWith(".html")) return "text/html";
516
+ if (filePath.endsWith(".css")) return "text/css";
517
+ if (filePath.endsWith(".js")) return "text/javascript";
518
+ if (filePath.endsWith(".svg")) return "image/svg+xml";
519
+ if (filePath.endsWith(".png")) return "image/png";
520
+ if (filePath.endsWith(".json")) return "application/json";
521
+ if (filePath.endsWith(".woff2")) return "font/woff2";
522
+ if (filePath.endsWith(".woff")) return "font/woff";
523
+ return "application/octet-stream";
524
+ };
525
+ var resolveAssetUrl = (rawPath, urlByPath) => {
526
+ if (!rawPath) return null;
527
+ if (/^(?:[a-z]+:|\/\/|#|data:)/i.test(rawPath)) return null;
528
+ const normalized = normalizeAssetPath(rawPath);
529
+ return urlByPath.get(normalized) ?? urlByPath.get(rawPath) ?? null;
530
+ };
531
+ var resolveManifestPath = (basePath, rawPath, manifestPaths) => {
532
+ const manifestSet = new Set(manifestPaths);
533
+ const normalized = stripLeadingSlash(normalizeAssetPath(rawPath));
534
+ if (manifestSet.has(normalized)) {
535
+ return normalized;
536
+ }
537
+ if (manifestSet.has(rawPath)) {
538
+ return rawPath;
539
+ }
540
+ if (!basePath.includes("/")) {
541
+ return manifestSet.has(normalized) ? normalized : null;
542
+ }
543
+ const baseDir = basePath.slice(0, basePath.lastIndexOf("/"));
544
+ const joined = normalizeAssetPath(`${baseDir}/${normalized}`);
545
+ return manifestSet.has(joined) ? joined : null;
546
+ };
547
+ var extractCssAssetPaths = (cssText) => {
548
+ const paths = [];
549
+ for (const match of cssText.matchAll(CSS_URL_PATTERN)) {
550
+ paths.push(match[2]);
551
+ }
552
+ return paths;
553
+ };
554
+ var collectRequiredFiles = (manifest, fileBytes, entryPath) => {
555
+ const manifestPaths = listManifestFiles(manifest);
556
+ const required = /* @__PURE__ */ new Set([entryPath]);
557
+ for (const filePath of manifestPaths) {
558
+ if (filePath.endsWith(".js") || filePath.endsWith(".css")) {
559
+ required.add(filePath);
560
+ }
561
+ }
562
+ let previousSize = 0;
563
+ while (required.size !== previousSize) {
564
+ previousSize = required.size;
565
+ for (const filePath of [...required]) {
566
+ const bytes = readMemoryFile(manifest, filePath, fileBytes);
567
+ const text = textDecoder2.decode(bytes);
568
+ const refs = [];
569
+ if (filePath.endsWith(".html")) {
570
+ const doc = new DOMParser().parseFromString(text, "text/html");
571
+ REWRITABLE_ATTRIBUTES.forEach(([tag, attribute]) => {
572
+ doc.querySelectorAll(`${tag}[${attribute}]`).forEach((element) => {
573
+ refs.push(element.getAttribute(attribute));
574
+ });
575
+ });
576
+ }
577
+ if (filePath.endsWith(".css")) {
578
+ refs.push(...extractCssAssetPaths(text));
579
+ }
580
+ refs.forEach((ref) => {
581
+ const resolved = resolveManifestPath(filePath, ref, manifestPaths);
582
+ if (resolved) {
583
+ required.add(resolved);
584
+ }
585
+ });
586
+ }
587
+ }
588
+ return required;
589
+ };
590
+ var BLOB_LAUNCH_BASE = "http://localhost/";
591
+ var injectBlobLaunchShim = (doc) => {
592
+ const base = doc.createElement("base");
593
+ base.setAttribute("href", BLOB_LAUNCH_BASE);
594
+ doc.head.prepend(base);
595
+ const shim = doc.createElement("script");
596
+ shim.textContent = `(function () {
597
+ var base = ${JSON.stringify(BLOB_LAUNCH_BASE)};
598
+ var NativeURL = URL;
599
+ var invalidBase = function (value) {
600
+ if (!value) return true;
601
+ var text = String(value);
602
+ return text === "null" || text.indexOf("blob:") === 0;
603
+ };
604
+ URL = function (url, baseUrl) {
605
+ if (typeof url === "string" && url.charAt(0) === "/" && invalidBase(baseUrl)) {
606
+ return new NativeURL(url, base);
607
+ }
608
+ return new NativeURL(url, baseUrl);
609
+ };
610
+ URL.prototype = NativeURL.prototype;
611
+ URL.createObjectURL = NativeURL.createObjectURL.bind(NativeURL);
612
+ URL.revokeObjectURL = NativeURL.revokeObjectURL.bind(NativeURL);
613
+ })();`;
614
+ doc.head.prepend(shim);
615
+ };
616
+ var resolveLaunchAssetUrl = (rawPath, urlByPath, entryPath, manifestPaths) => {
617
+ const direct = resolveAssetUrl(rawPath, urlByPath);
618
+ if (direct) {
619
+ return direct;
620
+ }
621
+ if (!rawPath || !entryPath || !manifestPaths) {
622
+ return null;
623
+ }
624
+ const resolved = resolveManifestPath(entryPath, rawPath, manifestPaths);
625
+ return resolved ? urlByPath.get(resolved) ?? null : null;
626
+ };
627
+ var buildLaunchDocument = (html, urlByPath, entryPath, manifestPaths) => {
628
+ const doc = new DOMParser().parseFromString(html, "text/html");
629
+ REWRITABLE_ATTRIBUTES.forEach(([tag, attribute]) => {
630
+ doc.querySelectorAll(`${tag}[${attribute}]`).forEach((element) => {
631
+ const rawPath = element.getAttribute(attribute);
632
+ const assetUrl = resolveLaunchAssetUrl(
633
+ rawPath,
634
+ urlByPath,
635
+ entryPath,
636
+ manifestPaths
637
+ );
638
+ if (assetUrl) {
639
+ element.setAttribute(attribute, assetUrl);
640
+ }
641
+ element.removeAttribute("crossorigin");
642
+ });
643
+ });
644
+ doc.documentElement.querySelectorAll("[crossorigin]").forEach((element) => {
645
+ element.removeAttribute("crossorigin");
646
+ });
647
+ injectBlobLaunchShim(doc);
648
+ return `<!DOCTYPE html>
649
+ ${doc.documentElement.outerHTML}`;
650
+ };
651
+ var getManifestFileMeta = (manifest, filePath) => {
652
+ if (Array.isArray(manifest.files)) {
653
+ return manifest.files.find((file) => file.path === filePath) ?? null;
654
+ }
655
+ return manifest.files?.[filePath] ?? null;
656
+ };
657
+ var attachBlobDispose = (blob, blobUrls) => {
658
+ blob.dispose = () => {
659
+ blobUrls.forEach((url) => URL.revokeObjectURL(url));
660
+ };
661
+ return blob;
662
+ };
663
+ var createFileLaunchBlob = (manifest, fileBytes, filePath, blobUrls) => {
664
+ const fileMeta = getManifestFileMeta(manifest, filePath);
665
+ const fileContent = readMemoryFile(manifest, filePath, fileBytes);
666
+ const mime = guessMimeType2(filePath, fileMeta);
667
+ const launchBlob = new Blob([fileContent], { type: mime });
668
+ return attachBlobDispose(launchBlob, blobUrls);
669
+ };
670
+ var createBrowseLaunchBlob = (manifest, fileBytes, filePaths, blobUrls) => {
671
+ const fileUrlByPath = /* @__PURE__ */ new Map();
672
+ for (const filePath of filePaths) {
673
+ const fileMeta = getManifestFileMeta(manifest, filePath);
674
+ const fileContent = readMemoryFile(manifest, filePath, fileBytes);
675
+ const mime = guessMimeType2(filePath, fileMeta);
676
+ const assetUrl = URL.createObjectURL(new Blob([fileContent], { type: mime }));
677
+ blobUrls.push(assetUrl);
678
+ fileUrlByPath.set(filePath, assetUrl);
679
+ }
680
+ const listings = buildMemoryBrowseListings(filePaths, fileUrlByPath);
681
+ const launchDocument = buildInteractiveBrowseDocument(listings);
682
+ const launchBlob = new Blob([launchDocument], { type: "text/html" });
683
+ return attachBlobDispose(launchBlob, blobUrls);
684
+ };
685
+ var createPlayLaunchBlob = (manifest, fileBytes, entryPath, blobUrls) => {
686
+ const urlByPath = /* @__PURE__ */ new Map();
687
+ const requiredFiles = collectRequiredFiles(manifest, fileBytes, entryPath);
688
+ for (const filePath of requiredFiles) {
689
+ const fileMeta = getManifestFileMeta(manifest, filePath);
690
+ const fileContent = readMemoryFile(manifest, filePath, fileBytes);
691
+ const mime = guessMimeType2(filePath, fileMeta);
692
+ const assetUrl = URL.createObjectURL(new Blob([fileContent], { type: mime }));
693
+ blobUrls.push(assetUrl);
694
+ urlByPath.set(filePath, assetUrl);
695
+ urlByPath.set(normalizeAssetPath(filePath), assetUrl);
696
+ }
697
+ if (!requiredFiles.has(entryPath)) {
698
+ throw new Error(`Manifest entry not found: ${entryPath}`);
699
+ }
700
+ const htmlBytes = readMemoryFile(manifest, entryPath, fileBytes);
701
+ const html = textDecoder2.decode(htmlBytes);
702
+ const manifestPaths = listManifestFiles(manifest);
703
+ const launchDocument = buildLaunchDocument(
704
+ html,
705
+ urlByPath,
706
+ entryPath,
707
+ manifestPaths
708
+ );
709
+ const launchBlob = new Blob([launchDocument], { type: "text/html" });
710
+ return attachBlobDispose(launchBlob, blobUrls);
711
+ };
712
+ var createMemoryBlob = async (input) => {
713
+ const bytes = await readInput(input);
714
+ const { manifest, fileBytes } = await extractMemory(bytes);
715
+ const filePaths = listManifestFiles(manifest);
716
+ const playMode = resolveMemoryPlayMode(manifest, filePaths);
717
+ const blobUrls = [];
718
+ try {
719
+ if (playMode.mode === "file") {
720
+ return createFileLaunchBlob(manifest, fileBytes, playMode.path, blobUrls);
721
+ }
722
+ if (playMode.mode === "browse") {
723
+ return createBrowseLaunchBlob(manifest, fileBytes, filePaths, blobUrls);
724
+ }
725
+ return createPlayLaunchBlob(
726
+ manifest,
727
+ fileBytes,
728
+ playMode.entry ?? manifest.entry,
729
+ blobUrls
730
+ );
731
+ } catch (error) {
732
+ blobUrls.forEach((url) => URL.revokeObjectURL(url));
733
+ throw error;
734
+ }
735
+ };
736
+ return __toCommonJS(index_exports);
737
+ })();