@hyperframes/engine 0.6.119 → 0.6.120

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.
Files changed (73) hide show
  1. package/package.json +24 -7
  2. package/scripts/generate-lut-reference.py +0 -168
  3. package/scripts/test-fitTextFontSize-browser.ts +0 -135
  4. package/src/cdp-headless-experimental.d.ts +0 -54
  5. package/src/config.test.ts +0 -213
  6. package/src/config.ts +0 -417
  7. package/src/index.ts +0 -273
  8. package/src/services/audioMixer.test.ts +0 -326
  9. package/src/services/audioMixer.ts +0 -604
  10. package/src/services/audioMixer.types.ts +0 -35
  11. package/src/services/audioVolumeEnvelope.test.ts +0 -176
  12. package/src/services/audioVolumeEnvelope.ts +0 -138
  13. package/src/services/browserManager.test.ts +0 -330
  14. package/src/services/browserManager.ts +0 -670
  15. package/src/services/chunkEncoder.test.ts +0 -1415
  16. package/src/services/chunkEncoder.ts +0 -831
  17. package/src/services/chunkEncoder.types.ts +0 -60
  18. package/src/services/extractionCache.test.ts +0 -199
  19. package/src/services/extractionCache.ts +0 -216
  20. package/src/services/fileServer.ts +0 -110
  21. package/src/services/frameCapture-discardWarmup.test.ts +0 -183
  22. package/src/services/frameCapture-namePolyfill.test.ts +0 -78
  23. package/src/services/frameCapture-pollImagesReady.test.ts +0 -153
  24. package/src/services/frameCapture-staticDedupIndex.test.ts +0 -76
  25. package/src/services/frameCapture-warmupTicks.test.ts +0 -174
  26. package/src/services/frameCapture.test.ts +0 -192
  27. package/src/services/frameCapture.ts +0 -1934
  28. package/src/services/hdrCapture.test.ts +0 -159
  29. package/src/services/hdrCapture.ts +0 -315
  30. package/src/services/parallelCoordinator.test.ts +0 -139
  31. package/src/services/parallelCoordinator.ts +0 -437
  32. package/src/services/screenshotService.test.ts +0 -510
  33. package/src/services/screenshotService.ts +0 -615
  34. package/src/services/streamingEncoder.test.ts +0 -832
  35. package/src/services/streamingEncoder.ts +0 -594
  36. package/src/services/systemMemory.test.ts +0 -324
  37. package/src/services/systemMemory.ts +0 -180
  38. package/src/services/videoFrameExtractor.test.ts +0 -1062
  39. package/src/services/videoFrameExtractor.ts +0 -1139
  40. package/src/services/videoFrameInjector.test.ts +0 -300
  41. package/src/services/videoFrameInjector.ts +0 -687
  42. package/src/services/vp9Options.ts +0 -13
  43. package/src/types.ts +0 -191
  44. package/src/utils/alphaBlit.test.ts +0 -1349
  45. package/src/utils/alphaBlit.ts +0 -1015
  46. package/src/utils/assertSwiftShader.test.ts +0 -130
  47. package/src/utils/assertSwiftShader.ts +0 -126
  48. package/src/utils/ffmpegBinaries.test.ts +0 -43
  49. package/src/utils/ffmpegBinaries.ts +0 -63
  50. package/src/utils/ffprobe.test.ts +0 -342
  51. package/src/utils/ffprobe.ts +0 -457
  52. package/src/utils/gpuEncoder.test.ts +0 -140
  53. package/src/utils/gpuEncoder.ts +0 -268
  54. package/src/utils/hdr.test.ts +0 -191
  55. package/src/utils/hdr.ts +0 -137
  56. package/src/utils/hdrCompositing.test.ts +0 -130
  57. package/src/utils/htmlTemplate.test.ts +0 -42
  58. package/src/utils/htmlTemplate.ts +0 -42
  59. package/src/utils/layerCompositor.test.ts +0 -150
  60. package/src/utils/layerCompositor.ts +0 -58
  61. package/src/utils/parityContract.ts +0 -1
  62. package/src/utils/processTracker.test.ts +0 -74
  63. package/src/utils/processTracker.ts +0 -41
  64. package/src/utils/readWebGlVendorInfoFromCanvas.ts +0 -52
  65. package/src/utils/runFfmpeg.test.ts +0 -102
  66. package/src/utils/runFfmpeg.ts +0 -136
  67. package/src/utils/shaderTransitions.test.ts +0 -738
  68. package/src/utils/shaderTransitions.ts +0 -1130
  69. package/src/utils/uint16-alignment-audit.test.ts +0 -125
  70. package/src/utils/urlDownloader.test.ts +0 -65
  71. package/src/utils/urlDownloader.ts +0 -143
  72. package/tsconfig.json +0 -19
  73. package/vitest.config.ts +0 -7
@@ -1,125 +0,0 @@
1
- /**
2
- * Audit test: Uint16Array vs Buffer.read/writeUInt16LE alignment.
3
- *
4
- * Captures the migration hazard documented in the HDR follow-up plan:
5
- * "Uint16Array over readUInt16LE / writeUInt16LE — ~105 touch points in the
6
- * hot path with an alignment-correctness concern (odd byteOffset on sliced
7
- * Buffers throws)."
8
- *
9
- * The hot path in `alphaBlit.ts` and `shaderTransitions.ts` reads/writes 16-bit
10
- * channels via `Buffer.readUInt16LE` / `Buffer.writeUInt16LE`. Those methods
11
- * accept arbitrary byte offsets — odd offsets are fine. A future perf PR may
12
- * migrate to `Uint16Array` views for ~2× throughput, but `Uint16Array` requires
13
- * 2-byte alignment of the underlying ArrayBuffer offset. If the source `Buffer`
14
- * was sliced from a parent at an odd byte offset, constructing a `Uint16Array`
15
- * view directly will throw `RangeError`.
16
- *
17
- * These tests pin down the contract so the migration PR can:
18
- * 1. Verify the migration is safe (current sub-buffers always start at even
19
- * byte offsets) — see `rgb48le row stride` test.
20
- * 2. Provide a reference safe-wrap pattern when alignment is not guaranteed.
21
- */
22
- import { describe, expect, it } from "vitest";
23
-
24
- describe("Uint16 alignment audit", () => {
25
- describe("Buffer.read/writeUInt16LE — current API", () => {
26
- it("accepts odd byte offsets without throwing", () => {
27
- const buf = Buffer.alloc(8);
28
- buf.writeUInt16LE(0xabcd, 1);
29
- expect(buf.readUInt16LE(1)).toBe(0xabcd);
30
- buf.writeUInt16LE(0x1234, 3);
31
- expect(buf.readUInt16LE(3)).toBe(0x1234);
32
- });
33
-
34
- it("preserves values across non-aligned slice round-trips", () => {
35
- const buf = Buffer.alloc(10);
36
- buf.writeUInt16LE(0xdead, 1);
37
- buf.writeUInt16LE(0xbeef, 5);
38
- const sub = buf.subarray(1);
39
- expect(sub.readUInt16LE(0)).toBe(0xdead);
40
- expect(sub.readUInt16LE(4)).toBe(0xbeef);
41
- });
42
- });
43
-
44
- describe("Uint16Array — migration hazard", () => {
45
- it("throws RangeError when byteOffset is odd", () => {
46
- const ab = new ArrayBuffer(8);
47
- expect(() => new Uint16Array(ab, 1, 2)).toThrow(RangeError);
48
- expect(() => new Uint16Array(ab, 3, 1)).toThrow(RangeError);
49
- });
50
-
51
- it("succeeds when byteOffset is even", () => {
52
- const ab = new ArrayBuffer(8);
53
- expect(() => new Uint16Array(ab, 0, 4)).not.toThrow();
54
- expect(() => new Uint16Array(ab, 2, 3)).not.toThrow();
55
- });
56
-
57
- it("a Buffer sliced at an odd offset cannot back a Uint16Array view directly", () => {
58
- const parent = Buffer.alloc(8);
59
- const odd = parent.subarray(1);
60
- expect(odd.byteOffset % 2).toBe(1);
61
- expect(
62
- () => new Uint16Array(odd.buffer, odd.byteOffset, Math.floor(odd.byteLength / 2)),
63
- ).toThrow(RangeError);
64
- });
65
-
66
- it("safe-wrap pattern: copy to a fresh aligned Buffer when offset is odd", () => {
67
- const parent = Buffer.alloc(8);
68
- parent.writeUInt16LE(0xfeed, 1);
69
- const odd = parent.subarray(1, 3);
70
-
71
- // Pattern the migration PR should use when alignment is not guaranteed:
72
- // realign by copying into a freshly allocated Buffer (always page-aligned).
73
- const aligned = odd.byteOffset % 2 === 0 ? odd : Buffer.from(odd);
74
- expect(aligned.byteOffset % 2).toBe(0);
75
- const view = new Uint16Array(
76
- aligned.buffer,
77
- aligned.byteOffset,
78
- Math.floor(aligned.byteLength / 2),
79
- );
80
- expect(view[0]).toBe(0xfeed);
81
- });
82
- });
83
-
84
- describe("HDR canvas/row alignment invariants", () => {
85
- it("rgb48le canvas row strides are always even-byte multiples", () => {
86
- // A row of an rgb48le canvas is `width * 6` bytes (3 channels × 2 bytes).
87
- // For any width, the row stride is even, so per-row subarrays inherit
88
- // even byte offsets when sliced from a Buffer whose byteOffset is also
89
- // even (true for `Buffer.alloc(N)`, which is fresh-allocator-aligned).
90
- for (const width of [1, 7, 33, 256, 1920]) {
91
- const stride = width * 6;
92
- expect(stride % 2).toBe(0);
93
- }
94
- });
95
-
96
- it("Buffer.alloc canvases produce subarrays with even byte offsets", () => {
97
- // This is the invariant the alphaBlit hot path relies on: as long as the
98
- // working canvas is built with `Buffer.alloc(width * height * 6)`, each
99
- // row subarray (`canvas.subarray(y * stride, (y + 1) * stride)`) starts
100
- // at an even byte offset, so a future Uint16Array migration is safe
101
- // without any realignment copy.
102
- const width = 17; // odd width; stride = 102 (still even)
103
- const height = 4;
104
- const canvas = Buffer.alloc(width * height * 6);
105
- const stride = width * 6;
106
- for (let y = 0; y < height; y++) {
107
- const row = canvas.subarray(y * stride, (y + 1) * stride);
108
- expect(row.byteOffset % 2).toBe(0);
109
- }
110
- });
111
-
112
- it("a 3-byte rgb24 stride would NOT be alignment-safe (counter-example)", () => {
113
- // Documents why the rgb48le format is migration-friendly: an rgba8 or
114
- // rgb24 canvas with odd width produces sub-buffers at odd byte offsets.
115
- // If a future PR wants to use Uint16Array views, it must keep the data
116
- // in an even-stride format (rgb48le ✓) or pay for a realignment copy.
117
- const width = 3;
118
- const height = 2;
119
- const rgb24 = Buffer.alloc(width * height * 3); // stride = 9, odd!
120
- const stride = width * 3;
121
- const row1 = rgb24.subarray(stride, stride * 2);
122
- expect(row1.byteOffset % 2).toBe(1);
123
- });
124
- });
125
- });
@@ -1,65 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { assertPublicHttpsUrl } from "./urlDownloader.js";
3
-
4
- describe("assertPublicHttpsUrl — SSRF guard", () => {
5
- it("accepts public HTTPS URLs", () => {
6
- expect(() =>
7
- assertPublicHttpsUrl("https://gen-os-static.s3.us-east-2.amazonaws.com/fonts/font.ttf"),
8
- ).not.toThrow();
9
- expect(() => assertPublicHttpsUrl("https://cdn.jsdelivr.net/npm/gsap.min.js")).not.toThrow();
10
- expect(() => assertPublicHttpsUrl("https://fonts.gstatic.com/s/font.woff2")).not.toThrow();
11
- });
12
-
13
- it("rejects http:// (non-HTTPS)", () => {
14
- expect(() => assertPublicHttpsUrl("http://example.com/font.ttf")).toThrow("Only HTTPS");
15
- });
16
-
17
- it("rejects AWS IMDS (169.254.169.254)", () => {
18
- expect(() =>
19
- assertPublicHttpsUrl("https://169.254.169.254/latest/meta-data/iam/security-credentials/"),
20
- ).toThrow("private/reserved");
21
- expect(() => assertPublicHttpsUrl("http://169.254.169.254/latest/user-data")).toThrow();
22
- });
23
-
24
- it("rejects loopback (127.x.x.x)", () => {
25
- expect(() => assertPublicHttpsUrl("https://127.0.0.1/font.ttf")).toThrow("private/reserved");
26
- expect(() => assertPublicHttpsUrl("https://127.1.2.3/secret")).toThrow("private/reserved");
27
- });
28
-
29
- it("rejects localhost", () => {
30
- expect(() => assertPublicHttpsUrl("https://localhost/font.ttf")).toThrow("private/reserved");
31
- expect(() => assertPublicHttpsUrl("http://localhost:3000/secret")).toThrow();
32
- });
33
-
34
- it("rejects RFC1918 — 10.x", () => {
35
- expect(() => assertPublicHttpsUrl("https://10.0.0.1/secret")).toThrow("private/reserved");
36
- expect(() => assertPublicHttpsUrl("https://10.255.255.255/secret")).toThrow("private/reserved");
37
- });
38
-
39
- it("rejects RFC1918 — 172.16–172.31", () => {
40
- expect(() => assertPublicHttpsUrl("https://172.16.0.1/secret")).toThrow("private/reserved");
41
- expect(() => assertPublicHttpsUrl("https://172.31.255.255/secret")).toThrow("private/reserved");
42
- });
43
-
44
- it("allows 172.0–172.15 and 172.32+ (not RFC1918)", () => {
45
- expect(() => assertPublicHttpsUrl("https://172.15.0.1/font.ttf")).not.toThrow();
46
- expect(() => assertPublicHttpsUrl("https://172.32.0.1/font.ttf")).not.toThrow();
47
- });
48
-
49
- it("rejects RFC1918 — 192.168.x", () => {
50
- expect(() => assertPublicHttpsUrl("https://192.168.1.1/secret")).toThrow("private/reserved");
51
- });
52
-
53
- it("rejects unspecified address (0.x)", () => {
54
- expect(() => assertPublicHttpsUrl("https://0.0.0.0/secret")).toThrow("private/reserved");
55
- });
56
-
57
- it("rejects loopback IPv6 ([::1])", () => {
58
- expect(() => assertPublicHttpsUrl("https://[::1]/secret")).toThrow("private/reserved");
59
- });
60
-
61
- it("rejects invalid URLs", () => {
62
- expect(() => assertPublicHttpsUrl("not-a-url")).toThrow("Invalid URL");
63
- expect(() => assertPublicHttpsUrl("")).toThrow("Invalid URL");
64
- });
65
- });
@@ -1,143 +0,0 @@
1
- import { createWriteStream, existsSync, mkdirSync } from "fs";
2
- import { createHash } from "crypto";
3
- import { join, extname } from "path";
4
- import { Readable } from "stream";
5
- import { finished } from "stream/promises";
6
-
7
- const downloadPathCache = new Map<string, string>();
8
- const inFlightDownloads = new Map<string, Promise<string>>();
9
-
10
- // SSRF guard: these prefixes identify non-public address space that
11
- // compositions (customer-supplied) must never be able to reach via the
12
- // download path. Blocks AWS IMDS (169.254.169.254), loopback, RFC1918,
13
- // and unspecified addresses. All comparisons are on the raw hostname
14
- // string; DNS resolution is NOT performed here, so DNS-rebinding bypasses
15
- // are not closed by this check — that gap is acceptable for the risk level.
16
- const BLOCKED_HOST_PREFIXES = [
17
- "169.254.", // link-local / AWS IMDS
18
- "127.", // loopback IPv4
19
- "10.", // RFC1918
20
- "192.168.", // RFC1918
21
- "0.", // unspecified
22
- "[::1]", // loopback IPv6
23
- "[fc", // RFC4193 unique-local IPv6
24
- "[fd", // RFC4193 unique-local IPv6
25
- ];
26
- // 172.16.0.0 – 172.31.255.255 (RFC1918)
27
- const BLOCKED_172_RANGE = { min: 16, max: 31 };
28
-
29
- function isBlockedHost(hostname: string): boolean {
30
- const h = hostname.toLowerCase();
31
- if (h === "localhost") return true;
32
- if (BLOCKED_HOST_PREFIXES.some((p) => h.startsWith(p))) return true;
33
- // 172.16–172.31
34
- const m = h.match(/^172\.(\d{1,3})\./);
35
- if (m) {
36
- const octet = parseInt(m[1] ?? "0", 10);
37
- if (octet >= BLOCKED_172_RANGE.min && octet <= BLOCKED_172_RANGE.max) return true;
38
- }
39
- return false;
40
- }
41
-
42
- /**
43
- * Validate that a URL is safe to fetch on behalf of customer-supplied
44
- * compositions. Throws if the URL is non-HTTPS or targets a private/reserved
45
- * address range (SSRF guard).
46
- */
47
- export function assertPublicHttpsUrl(url: string): void {
48
- let parsed: URL;
49
- try {
50
- parsed = new URL(url);
51
- } catch {
52
- throw new Error(`[URLDownloader] Invalid URL: ${url}`);
53
- }
54
- if (parsed.protocol !== "https:") {
55
- throw new Error(
56
- `[URLDownloader] Only HTTPS URLs are permitted in compositions (got ${parsed.protocol}): ${url}`,
57
- );
58
- }
59
- if (isBlockedHost(parsed.hostname)) {
60
- throw new Error(
61
- `[URLDownloader] URL targets a private/reserved address and is not permitted: ${url}`,
62
- );
63
- }
64
- }
65
-
66
- function getFilenameFromUrl(url: string): string {
67
- const hash = createHash("md5").update(url).digest("hex").slice(0, 12);
68
- const urlObj = new URL(url);
69
- const ext = extname(urlObj.pathname) || ".mp4";
70
- return `download_${hash}${ext}`;
71
- }
72
-
73
- export async function downloadToTemp(
74
- url: string,
75
- destDir: string,
76
- timeoutMs: number = 300000,
77
- ): Promise<string> {
78
- // Reject non-HTTPS URLs and private/reserved address ranges before
79
- // touching the cache or filesystem — customer-supplied compositions must
80
- // not be able to trigger outbound fetches to internal infrastructure.
81
- assertPublicHttpsUrl(url);
82
-
83
- const cachedPath = downloadPathCache.get(url);
84
- if (cachedPath && existsSync(cachedPath)) {
85
- return cachedPath;
86
- }
87
- const inFlight = inFlightDownloads.get(url);
88
- if (inFlight) {
89
- return inFlight;
90
- }
91
-
92
- if (!existsSync(destDir)) {
93
- mkdirSync(destDir, { recursive: true });
94
- }
95
-
96
- const filename = getFilenameFromUrl(url);
97
- const localPath = join(destDir, filename);
98
-
99
- if (existsSync(localPath)) {
100
- downloadPathCache.set(url, localPath);
101
- return localPath;
102
- }
103
-
104
- const downloadPromise = (async () => {
105
- try {
106
- const controller = new AbortController();
107
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
108
-
109
- const response = await fetch(url, { signal: controller.signal });
110
- clearTimeout(timeoutId);
111
-
112
- if (!response.ok) {
113
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
114
- }
115
-
116
- if (!response.body) {
117
- throw new Error("Response body is empty");
118
- }
119
-
120
- const fileStream = createWriteStream(localPath);
121
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
- const readableStream = Readable.fromWeb(response.body as any);
123
- await finished(readableStream.pipe(fileStream));
124
-
125
- downloadPathCache.set(url, localPath);
126
- return localPath;
127
- } catch (err) {
128
- const message = err instanceof Error ? err.message : String(err);
129
- if (message.includes("aborted")) {
130
- throw new Error(`[URLDownloader] Download timeout after ${timeoutMs / 1000}s: ${url}`);
131
- }
132
- throw new Error(`[URLDownloader] Download failed: ${message}`);
133
- } finally {
134
- inFlightDownloads.delete(url);
135
- }
136
- })();
137
- inFlightDownloads.set(url, downloadPromise);
138
- return downloadPromise;
139
- }
140
-
141
- export function isHttpUrl(path: string): boolean {
142
- return path.startsWith("http://") || path.startsWith("https://");
143
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "esModuleInterop": true,
7
- "strict": true,
8
- "noUncheckedIndexedAccess": true,
9
- "skipLibCheck": true,
10
- "outDir": "./dist",
11
- "rootDir": "./src",
12
- "declaration": true,
13
- "declarationMap": true,
14
- "sourceMap": true,
15
- "types": ["@webgpu/types"]
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist", "src/**/*.test.ts"]
19
- }
package/vitest.config.ts DELETED
@@ -1,7 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- test: {
5
- include: ["src/**/*.test.ts"],
6
- },
7
- });