@hyperframes/engine 0.6.118 → 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.
- package/package.json +24 -7
- package/scripts/generate-lut-reference.py +0 -168
- package/scripts/test-fitTextFontSize-browser.ts +0 -135
- package/src/cdp-headless-experimental.d.ts +0 -54
- package/src/config.test.ts +0 -213
- package/src/config.ts +0 -417
- package/src/index.ts +0 -273
- package/src/services/audioMixer.test.ts +0 -326
- package/src/services/audioMixer.ts +0 -604
- package/src/services/audioMixer.types.ts +0 -35
- package/src/services/audioVolumeEnvelope.test.ts +0 -176
- package/src/services/audioVolumeEnvelope.ts +0 -138
- package/src/services/browserManager.test.ts +0 -330
- package/src/services/browserManager.ts +0 -670
- package/src/services/chunkEncoder.test.ts +0 -1415
- package/src/services/chunkEncoder.ts +0 -831
- package/src/services/chunkEncoder.types.ts +0 -60
- package/src/services/extractionCache.test.ts +0 -199
- package/src/services/extractionCache.ts +0 -216
- package/src/services/fileServer.ts +0 -110
- package/src/services/frameCapture-discardWarmup.test.ts +0 -183
- package/src/services/frameCapture-namePolyfill.test.ts +0 -78
- package/src/services/frameCapture-pollImagesReady.test.ts +0 -153
- package/src/services/frameCapture-staticDedupIndex.test.ts +0 -76
- package/src/services/frameCapture-warmupTicks.test.ts +0 -174
- package/src/services/frameCapture.test.ts +0 -192
- package/src/services/frameCapture.ts +0 -1934
- package/src/services/hdrCapture.test.ts +0 -159
- package/src/services/hdrCapture.ts +0 -315
- package/src/services/parallelCoordinator.test.ts +0 -139
- package/src/services/parallelCoordinator.ts +0 -437
- package/src/services/screenshotService.test.ts +0 -510
- package/src/services/screenshotService.ts +0 -615
- package/src/services/streamingEncoder.test.ts +0 -832
- package/src/services/streamingEncoder.ts +0 -594
- package/src/services/systemMemory.test.ts +0 -324
- package/src/services/systemMemory.ts +0 -180
- package/src/services/videoFrameExtractor.test.ts +0 -1062
- package/src/services/videoFrameExtractor.ts +0 -1139
- package/src/services/videoFrameInjector.test.ts +0 -300
- package/src/services/videoFrameInjector.ts +0 -687
- package/src/services/vp9Options.ts +0 -13
- package/src/types.ts +0 -191
- package/src/utils/alphaBlit.test.ts +0 -1349
- package/src/utils/alphaBlit.ts +0 -1015
- package/src/utils/assertSwiftShader.test.ts +0 -130
- package/src/utils/assertSwiftShader.ts +0 -126
- package/src/utils/ffmpegBinaries.test.ts +0 -43
- package/src/utils/ffmpegBinaries.ts +0 -63
- package/src/utils/ffprobe.test.ts +0 -342
- package/src/utils/ffprobe.ts +0 -457
- package/src/utils/gpuEncoder.test.ts +0 -140
- package/src/utils/gpuEncoder.ts +0 -268
- package/src/utils/hdr.test.ts +0 -191
- package/src/utils/hdr.ts +0 -137
- package/src/utils/hdrCompositing.test.ts +0 -130
- package/src/utils/htmlTemplate.test.ts +0 -42
- package/src/utils/htmlTemplate.ts +0 -42
- package/src/utils/layerCompositor.test.ts +0 -150
- package/src/utils/layerCompositor.ts +0 -58
- package/src/utils/parityContract.ts +0 -1
- package/src/utils/processTracker.test.ts +0 -74
- package/src/utils/processTracker.ts +0 -41
- package/src/utils/readWebGlVendorInfoFromCanvas.ts +0 -52
- package/src/utils/runFfmpeg.test.ts +0 -102
- package/src/utils/runFfmpeg.ts +0 -136
- package/src/utils/shaderTransitions.test.ts +0 -738
- package/src/utils/shaderTransitions.ts +0 -1130
- package/src/utils/uint16-alignment-audit.test.ts +0 -125
- package/src/utils/urlDownloader.test.ts +0 -65
- package/src/utils/urlDownloader.ts +0 -143
- package/tsconfig.json +0 -19
- 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
|
-
}
|