diff --git a/package-lock.json b/package-lock.json index a9848e0..cdb127a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "dependencies": { "jszip": "^3.10.1", - "rrweb": "^2.0.0-alpha.4" + "rrweb": "^2.0.0-alpha.4", + "ts-ebml": "^3.0.2", + "webm-muxer": "^5.1.4" }, "devDependencies": { "@crxjs/vite-plugin": "^2.0.0-beta.25", @@ -1233,6 +1235,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/dom-webcodecs": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.18.tgz", + "integrity": "sha512-vAvE8C9DGWR+tkb19xyjk1TSUlJ7RUzzp4a9Anu7mwBT+fpyePWK1UxmH14tMO5zHmrnrRIMg5NutnnDztLxgg==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1264,6 +1272,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/wicg-file-system-access": { + "version": "2020.9.8", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.8.tgz", + "integrity": "sha512-ggMz8nOygG7d/stpH40WVaNvBwuyYLnrg5Mbyf6bmsj/8+gb6Ei4ZZ9/4PNpcPNTT8th9Q8sM8wYmWGjMWLX/A==", + "license": "MIT" + }, "node_modules/@vitest/expect": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", @@ -1435,6 +1449,14 @@ "node": ">=8" } }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1445,6 +1467,15 @@ "node": ">=18" } }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -1575,6 +1606,40 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/ebml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ebml/-/ebml-3.0.0.tgz", + "integrity": "sha512-Q6C1u4/TX1nYipT9HNIopp95YyyyI0zs1GXdNRKO7XL7k+oo+ZtDc1CaJjpCdmlLxWsnlKBOXJCXkYU0K/Anlg==", + "license": "MIT", + "dependencies": { + "buffers": "^0.1.1", + "debug": "~3.1.0" + }, + "engines": { + "node": ">= 6.4" + } + }, + "node_modules/ebml-block": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ebml-block/-/ebml-block-1.1.2.tgz", + "integrity": "sha512-HgNlIsRFP6D9VKU5atCeHRJY7XkJP8bOe8yEhd8NB7B3b4++VWTyauz6g650iiPmLfPLGlVpoJmGSgMfXDYusg==", + "license": "MIT" + }, + "node_modules/ebml/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/ebml/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1641,6 +1706,15 @@ "dev": true, "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -1769,6 +1843,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/int64-buffer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-1.1.0.tgz", + "integrity": "sha512-94smTCQOvigN4d/2R/YDjz8YVG0Sufvv2aAh8P5m42gwhCsDAJqnbNOrxJsrADuAFAA69Q/ptGzxvNcNuIJcvw==", + "license": "MIT" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2126,6 +2206,12 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/matroska-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/matroska-schema/-/matroska-schema-2.1.0.tgz", + "integrity": "sha512-6c1oFmDxf4Vc5J5lA+9wO7TKcw5M1w85HfzFhAFT4OuEUuqp/s/jqqC3OKlaWe1YwN5wTThJyTC7iwhyW7kQdg==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2597,6 +2683,23 @@ "node": ">=8.0" } }, + "node_modules/ts-ebml": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/ts-ebml/-/ts-ebml-3.0.2.tgz", + "integrity": "sha512-Br6DA/YbdpsiSQjc0KaF3ASQtkk3MCiA4q5kIA7ptv6adZa/MdYa2TXAXF2bAzRZIMWfyFEC9Gicr3nb51MgDA==", + "license": "MIT", + "dependencies": { + "commander": "^12.0.0", + "ebml": "^3.0.0", + "ebml-block": "^1.1.2", + "events": "^3.3.0", + "int64-buffer": "^1.0.1", + "matroska-schema": "^2.1.0" + }, + "bin": { + "ts-ebml": "lib/cli.js" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2964,6 +3067,17 @@ } } }, + "node_modules/webm-muxer": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webm-muxer/-/webm-muxer-5.1.4.tgz", + "integrity": "sha512-ditzgFVFbfqPaugkIr4mGhAdob5K9HY6Rzlh7TRsA368yA1sp/m5O7nQCcMLdgFDeNGtFPg8B+MeXLtpzKWX6Q==", + "deprecated": "This library is superseded by Mediabunny. Please migrate to it.", + "license": "MIT", + "dependencies": { + "@types/dom-webcodecs": "^0.1.4", + "@types/wicg-file-system-access": "^2020.9.5" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/package.json b/package.json index 7c7b9be..5fce969 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,10 @@ "test": "vitest run" }, "dependencies": { + "jszip": "^3.10.1", "rrweb": "^2.0.0-alpha.4", - "jszip": "^3.10.1" + "ts-ebml": "^3.0.2", + "webm-muxer": "^5.1.4" }, "devDependencies": { "@crxjs/vite-plugin": "^2.0.0-beta.25", @@ -20,4 +22,4 @@ "vite": "^5.4.2", "vitest": "^4" } -} \ No newline at end of file +} diff --git a/tests/background/webm-remux-deps.test.ts b/tests/background/webm-remux-deps.test.ts new file mode 100644 index 0000000..3074eba --- /dev/null +++ b/tests/background/webm-remux-deps.test.ts @@ -0,0 +1,137 @@ +// tests/background/webm-remux-deps.test.ts +// +// Plan 01-08 Task 1: SW-compatibility + presence contract for the two new +// runtime dependencies that the WebM remux pipeline rests on. Pins the +// architectural commitment that landed in CONTEXT.md amendment D-14-remux: +// - `ts-ebml` ^3.0.2 (MIT, parses each VideoSegment's EBML structure) +// - `webm-muxer` ^5.1.4 (MIT, writes the single-EBML-headered output) +// +// Both libraries were surveyed in `.planning/debug/d13-multi-ebml-concat- +// unplayable.md` (Evidence/library-survey, lines 380-410). They are pure +// JS, pure ESM/CJS, and were grep-verified at survey time to contain no +// hard DOM-global references on the hot path. Chrome's service-worker +// runtime (where `remuxSegments()` will execute) does not provide +// `window` or `document`; this test pins that compat at the +// devDependency-import surface so a future bump that accidentally adds +// a DOM global is caught at test time rather than at runtime in a +// production SW. +// +// Test 1 asserts named-export presence (RED until `npm install` lands). +// Test 2 asserts both libraries load under default Node globals without +// referencing `window` or `document` synchronously at import time. +// +// Skip discipline: none — these are pure import-shape tests, no external +// binaries, no fixtures. Vitest's default Node environment is sufficient. + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; + +interface GlobalSnapshot { + window: unknown; + document: unknown; + hadWindow: boolean; + hadDocument: boolean; +} + +/** + * Capture the current values of `window` and `document` on `globalThis` + * so they can be restored after a test that deletes them. Vitest's + * `vi.stubGlobal` is not used here because we want to assert the + * absence of the globals at import time, not just stub them. + * + * @returns Snapshot the caller passes back to {@link restoreGlobals}. + */ +function snapshotGlobals(): GlobalSnapshot { + const g = globalThis as unknown as Record; + return { + window: g.window, + document: g.document, + hadWindow: 'window' in g, + hadDocument: 'document' in g, + }; +} + +/** + * Restore the globals captured by {@link snapshotGlobals}. Idempotent. + * + * @param snap - Snapshot returned by {@link snapshotGlobals}. + */ +function restoreGlobals(snap: GlobalSnapshot): void { + const g = globalThis as unknown as Record; + if (snap.hadWindow) { + g.window = snap.window; + } else { + delete g.window; + } + if (snap.hadDocument) { + g.document = snap.document; + } else { + delete g.document; + } +} + +describe('webm-remux dependencies (Plan 01-08 Task 1)', () => { + it('exports Muxer + ArrayBufferTarget + Decoder', async () => { + // Dynamic import so a missing package surfaces as a precise test + // failure ("Cannot find module 'webm-muxer'") rather than a Vitest + // collection error that hides which dependency is the cause. + const webmMuxer = await import('webm-muxer'); + expect(webmMuxer.Muxer).toBeDefined(); + expect(webmMuxer.ArrayBufferTarget).toBeDefined(); + + const tsEbml = await import('ts-ebml'); + expect(tsEbml.Decoder).toBeDefined(); + }); + + describe('loads under default Node globals without DOM-global ReferenceErrors', () => { + let snap: GlobalSnapshot; + + beforeEach(() => { + snap = snapshotGlobals(); + const g = globalThis as unknown as Record; + delete g.window; + delete g.document; + }); + + afterEach(() => { + restoreGlobals(snap); + }); + + it('webm-muxer + ts-ebml do not throw on import when window/document are absent', async () => { + // The Chrome service-worker runtime provides neither `window` nor + // `document`. If either library's published dist references one + // of these synchronously at module evaluation time (e.g. a UMD + // wrapper falling through to `window`), the import below would + // throw a ReferenceError and this test would fail with a clear + // signal. + // + // ts-ebml's UMD wrapper does contain a `typeof window` check + // with a `self`/`global` fallback per the d13 library survey + // — `typeof` does NOT throw on undeclared identifiers per + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof + // so the fallback path resolves cleanly. + // + // webm-muxer is documented zero-DOM-ref. + let webmMuxerError: unknown = null; + try { + await import('webm-muxer'); + } catch (e) { + webmMuxerError = e; + } + expect( + webmMuxerError, + `webm-muxer threw at import time without window/document: ${String(webmMuxerError)}`, + ).toBeNull(); + + let tsEbmlError: unknown = null; + try { + await import('ts-ebml'); + } catch (e) { + tsEbmlError = e; + } + expect( + tsEbmlError, + `ts-ebml threw at import time without window/document: ${String(tsEbmlError)}`, + ).toBeNull(); + }); + }); +});