@hyperframes/engine 0.8.203 → 0.8.204

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.
@@ -48,6 +48,7 @@ interface BeginFrameProbeResult {
48
48
  supported: boolean;
49
49
  detail: string;
50
50
  durationMs: number;
51
+ timedOut?: boolean;
51
52
  }
52
53
  declare function closeBrowserAfterFailedProbe(browser: Browser, timeoutMs?: number): Promise<void>;
53
54
  declare function probeBeginFrameSupport(browser: Browser, timeoutMs?: number): Promise<BeginFrameProbeResult>;
@@ -245,22 +245,25 @@ const BEGINFRAME_ONLY_FLAGS = new Set([
245
245
  function stripBeginFrameFlags(args) {
246
246
  return args.filter((a) => !BEGINFRAME_ONLY_FLAGS.has(a));
247
247
  }
248
+ class BeginFrameProbeTimeout extends Error {
249
+ }
248
250
  const BEGINFRAME_PROBE_TIMEOUT_MS = 2000;
251
+ const BEGINFRAME_PROBE_RETRY_TIMEOUT_MS = 8000;
249
252
  const BEGINFRAME_PROBE_CLEANUP_TIMEOUT_MS = 250;
250
253
  async function awaitBeforeDeadline(operation, deadline, label) {
251
254
  const remainingMs = deadline - Date.now();
252
255
  if (remainingMs <= 0) {
253
- throw new Error(`beginFrame probe timeout before ${label}`);
256
+ throw new BeginFrameProbeTimeout(`beginFrame probe timeout before ${label}`);
254
257
  }
255
- return rejectAfter(operation, remainingMs, `beginFrame probe timeout during ${label}`);
258
+ return rejectAfter(operation, remainingMs, new BeginFrameProbeTimeout(`beginFrame probe timeout during ${label}`));
256
259
  }
257
- async function rejectAfter(operation, timeoutMs, message) {
260
+ async function rejectAfter(operation, timeoutMs, error) {
258
261
  let timeout;
259
262
  try {
260
263
  return await Promise.race([
261
264
  operation,
262
265
  new Promise((_, reject) => {
263
- timeout = setTimeout(() => reject(new Error(message)), timeoutMs);
266
+ timeout = setTimeout(() => reject(typeof error === "string" ? new Error(error) : error), timeoutMs);
264
267
  }),
265
268
  ]);
266
269
  }
@@ -345,7 +348,8 @@ async function probeBeginFrameSupport(browser, timeoutMs = BEGINFRAME_PROBE_TIME
345
348
  await awaitBeforeDeadline(new Promise((resolveDelay) => setTimeout(resolveDelay, 10)), deadline, `screenshot retry delay ${attempts}`);
346
349
  }
347
350
  if (!isPng) {
348
- throw new Error(`beginFrame screenshot returned ${bytes.length} bytes after ` +
351
+ const Failure = bytes.length === 0 ? BeginFrameProbeTimeout : Error;
352
+ throw new Failure(`beginFrame screenshot returned ${bytes.length} bytes after ` +
349
353
  `${attempts} attempts in ${Date.now() - started}ms with signature ` +
350
354
  `${bytes.length >= 4 ? bytes.subarray(0, 4).toString("hex") : "<empty>"}`);
351
355
  }
@@ -362,6 +366,7 @@ async function probeBeginFrameSupport(browser, timeoutMs = BEGINFRAME_PROBE_TIME
362
366
  supported: false,
363
367
  detail: error instanceof Error ? error.message : String(error),
364
368
  durationMs: Date.now() - started,
369
+ timedOut: error instanceof BeginFrameProbeTimeout,
365
370
  };
366
371
  }
367
372
  if (page && !(await settleWithin(page.close(), BEGINFRAME_PROBE_CLEANUP_TIMEOUT_MS))) {
@@ -653,42 +658,43 @@ async function launchBrowser(fingerprint) {
653
658
  const env = launchEnv(fingerprint);
654
659
  let captureMode = fingerprint.requestedCaptureMode;
655
660
  let browser;
661
+ const launch = (args) => ppt.launch({
662
+ ...sigintLaunchOptions(),
663
+ headless: true,
664
+ args: withHyperframesBrowserMarker(args),
665
+ defaultViewport: null,
666
+ executablePath: fingerprint.executablePath,
667
+ env,
668
+ timeout: fingerprint.browserTimeoutMs,
669
+ protocolTimeout: fingerprint.protocolTimeoutMs,
670
+ });
671
+ const probe = (target, timeoutMs) => probeBeginFrameSupport(target, timeoutMs).catch((error) => ({
672
+ supported: true,
673
+ detail: `probe harness error ignored: ${error instanceof Error ? error.message : String(error)}`,
674
+ durationMs: 0,
675
+ timedOut: false,
676
+ }));
656
677
  try {
657
- browser = await ppt.launch({
658
- ...sigintLaunchOptions(),
659
- headless: true,
660
- args: withHyperframesBrowserMarker(fingerprint.args),
661
- defaultViewport: null,
662
- executablePath: fingerprint.executablePath,
663
- env,
664
- timeout: fingerprint.browserTimeoutMs,
665
- protocolTimeout: fingerprint.protocolTimeoutMs,
666
- });
678
+ browser = await launch(fingerprint.args);
667
679
  const browserVersion = await browser.version().catch(() => "unknown");
668
680
  const gpuFlags = fingerprint.args.filter((a) => a.startsWith("--use-gl=") || a.startsWith("--use-angle="));
669
681
  console.error(`[BrowserManager] Browser launched (${browserVersion}, ${captureMode}, gl=${gpuFlags.join(" ") || "default"}, headlessShell=${!!fingerprint.executablePath}, platform=${process.platform})`);
670
682
  if (captureMode === "beginframe") {
671
- const probe = await probeBeginFrameSupport(browser).catch((error) => ({
672
- supported: true,
673
- detail: `probe harness error ignored: ${error instanceof Error ? error.message : String(error)}`,
674
- durationMs: 0,
675
- }));
676
- if (!probe.supported) {
683
+ let result = await probe(browser);
684
+ if (!result.supported && result.timedOut) {
685
+ console.warn(`[BrowserManager] HeadlessExperimental.beginFrame probe timed out after ${result.durationMs}ms: ` +
686
+ `${result.detail}; retrying once with a longer budget.`);
687
+ await closeBrowserAfterFailedProbe(browser);
688
+ browser = await launch(fingerprint.args);
689
+ result = await probe(browser, BEGINFRAME_PROBE_RETRY_TIMEOUT_MS);
690
+ }
691
+ if (!result.supported) {
677
692
  await closeBrowserAfterFailedProbe(browser);
678
693
  browser = undefined;
679
- console.warn(`[BrowserManager] HeadlessExperimental.beginFrame probe failed after ${probe.durationMs}ms: ` +
680
- `${probe.detail}; falling back to screenshot mode.`);
694
+ console.warn(`[BrowserManager] HeadlessExperimental.beginFrame probe failed after ${result.durationMs}ms: ` +
695
+ `${result.detail}; falling back to screenshot mode.`);
681
696
  captureMode = "screenshot";
682
- browser = await ppt.launch({
683
- ...sigintLaunchOptions(),
684
- headless: true,
685
- args: withHyperframesBrowserMarker(stripBeginFrameFlags([...fingerprint.args])),
686
- defaultViewport: null,
687
- executablePath: fingerprint.executablePath,
688
- env,
689
- timeout: fingerprint.browserTimeoutMs,
690
- protocolTimeout: fingerprint.protocolTimeoutMs,
691
- });
697
+ browser = await launch(stripBeginFrameFlags([...fingerprint.args]));
692
698
  }
693
699
  }
694
700
  return { browser, captureMode };
@@ -834,7 +834,6 @@ async function installMediaPageHooks(page, options) {
834
834
  await page.evaluateOnNewDocument(holdRenderFrameVideos, options.skipReadinessVideoIds, MEDIA_RENDER_ID_ATTR);
835
835
  }
836
836
  }
837
- // fallow-ignore-next-line unit-size
838
837
  export async function createCaptureSession(serverUrl, outputDir, options, onBeforeCapture = null, config) {
839
838
  if (!existsSync(outputDir))
840
839
  mkdirSync(outputDir, { recursive: true });
@@ -1845,7 +1844,6 @@ export function classifyPageError(details, projectScripts) {
1845
1844
  const owned = (url) => url !== undefined && (projectScripts.has(url) || url.startsWith(COMPOSITION_SOURCE_URL));
1846
1845
  return urls.some(owned) ? `runtime-error:${firstLine}` : null;
1847
1846
  }
1848
- // fallow-ignore-next-line unit-size
1849
1847
  export async function initializeSession(session) {
1850
1848
  const { page, serverUrl } = session;
1851
1849
  // Forward browser console to host. HyperFrames runtime logs get a dedicated
@@ -3765,7 +3763,7 @@ export async function closeCaptureSession(session) {
3765
3763
  session.chromeMemory.stop();
3766
3764
  // One last sample so the summary reflects the session's end state, not a
3767
3765
  // point up to `intervalMs` earlier. Best effort; the page may be gone.
3768
- await session.chromeMemory.sampleOnce();
3766
+ await waitForCloseWithTimeout(session.chromeMemory.sampleOnce());
3769
3767
  }
3770
3768
  if (session.page && !session.pageReleased) {
3771
3769
  const blur = await describePageMotionBlurAtEnd(session.page);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.8.203",
3
+ "version": "0.8.204",
4
4
  "description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,8 +50,8 @@
50
50
  "linkedom": "^0.18.12",
51
51
  "puppeteer": "^25.8.0",
52
52
  "puppeteer-core": "^25.8.0",
53
- "@hyperframes/core": "^0.8.203",
54
- "@hyperframes/parsers": "^0.8.203"
53
+ "@hyperframes/parsers": "^0.8.204",
54
+ "@hyperframes/core": "^0.8.204"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^25.0.10",