Skip to content

Commit 30d2456

Browse files
committed
fix: bound Jam recorder startup
1 parent ee3845b commit 30d2456

3 files changed

Lines changed: 35 additions & 4 deletions

File tree

‎.audit/agent-verification-loop.tsv‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ ts phase decision why evidence result
1616
2026-08-31T16:42:00Z webkit reran the full Safari-engine matrix after rebase main changed routing and AI code since the first WebKit proof Playwright 1.60 Noble container with Node 22 56 WebKit cases passed
1717
2026-08-31T16:56:00Z CodeRabbit fixed all four current-head review comments restricted trusted staging triggers and loopback auth, updated plan state, and sequenced Jam before app startup PR 21 review on 5f0f67a all findings implemented or bounded with deterministic proof
1818
2026-08-31T16:59:00Z local gates reran CI-equivalent checks after CodeRabbit fixes review changes affected workflow security and application bootstrap npm run verify, Playwright 1.60 Noble container 89 files, 930 tests, coverage, build, 112 Chromium and 56 WebKit cases passed
19+
2026-08-31T17:06:00Z CodeRabbit added a bounded Jam CDN timeout prevented a stalled recorder request from blocking application startup src/jam-loader.ts, src/test/jam-loader.test.ts 20 focused tests, lint, typecheck, build, Chromium and WebKit startup passed

‎src/jam-loader.ts‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const JAM_SCRIPT_URLS = [
22
'https://js.jam.dev/recorder.js',
33
'https://js.jam.dev/capture.js',
44
] as const;
5+
const JAM_SCRIPT_TIMEOUT_MS = 3_000;
56

67
interface JamLoaderOptions {
78
documentRef?: Document;
@@ -14,14 +15,27 @@ function appendJamModule(documentRef: Document, source: string): Promise<void> {
1415
).find((script) => script.src === source);
1516
if (existing) return Promise.resolve();
1617

17-
return new Promise((resolve) => {
18+
return new Promise((resolve, reject) => {
1819
const script = documentRef.createElement('script');
1920
script.type = 'module';
2021
script.async = false;
2122
script.src = source;
2223
script.dataset.jamRecorder = 'true';
23-
script.addEventListener('load', () => resolve(), { once: true });
24-
script.addEventListener('error', () => resolve(), { once: true });
24+
25+
let settled = false;
26+
const finish = (outcome: () => void) => {
27+
if (settled) return;
28+
settled = true;
29+
clearTimeout(timeoutId);
30+
outcome();
31+
};
32+
const timeoutId = setTimeout(() => {
33+
script.remove();
34+
finish(() => reject(new Error(`Jam module load timed out: ${source}`)));
35+
}, JAM_SCRIPT_TIMEOUT_MS);
36+
37+
script.addEventListener('load', () => finish(resolve), { once: true });
38+
script.addEventListener('error', () => finish(resolve), { once: true });
2539
documentRef.head.append(script);
2640
});
2741
}

‎src/test/jam-loader.test.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { describe, expect, it } from 'vitest';
1+
import { afterEach, describe, expect, it, vi } from 'vitest';
22
import { loadJamRecorder } from '@/jam-loader';
33

44
function jamScripts() {
55
return Array.from(document.head.querySelectorAll<HTMLScriptElement>('script[data-jam-recorder]'));
66
}
77

88
describe('Jam recorder loader', () => {
9+
afterEach(() => {
10+
vi.useRealTimers();
11+
});
12+
913
it('does not load recorder scripts when MediaRecorder is unavailable', async () => {
1014
jamScripts().forEach((element) => element.remove());
1115

@@ -33,4 +37,16 @@ describe('Jam recorder loader', () => {
3337
await loadJamRecorder({ documentRef: document, mediaRecorderAvailable: true });
3438
expect(jamScripts()).toHaveLength(2);
3539
});
40+
41+
it('rejects a stalled Jam load so application bootstrap can continue', async () => {
42+
jamScripts().forEach((element) => element.remove());
43+
vi.useFakeTimers();
44+
45+
const loading = loadJamRecorder({ documentRef: document, mediaRecorderAvailable: true });
46+
const rejection = expect(loading).rejects.toThrow('Jam module load timed out');
47+
await vi.advanceTimersByTimeAsync(3_000);
48+
49+
await rejection;
50+
expect(jamScripts()).toHaveLength(0);
51+
});
3652
});

0 commit comments

Comments
 (0)