@hyperframes/engine 0.9.8 → 0.9.9
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.
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the page to expose window.__hf, then captures frames deterministically
|
|
7
7
|
* via Chrome's BeginFrame API or Page.captureScreenshot fallback.
|
|
8
8
|
*/
|
|
9
|
-
import { type Browser, type CDPSession, type Page, type Protocol } from "puppeteer-core";
|
|
9
|
+
import { type Browser, type CDPSession, type HTTPRequest, type Page, type Protocol } from "puppeteer-core";
|
|
10
10
|
import { type RawAuthoredTiming } from "@hyperframes/core";
|
|
11
11
|
import { type MotionBlurPlan } from "./motionBlur.js";
|
|
12
12
|
import { type BrowserLease, type CaptureMode } from "./browserManager.js";
|
|
@@ -89,6 +89,8 @@ export interface CaptureSession {
|
|
|
89
89
|
* were hitting that wall — a 705-render spike at the 45s setup bucket).
|
|
90
90
|
*/
|
|
91
91
|
scriptLoadFailures: string[];
|
|
92
|
+
/** Closed origin and error of each of `scriptLoadFailures`, same order. */
|
|
93
|
+
scriptLoadCauses?: ScriptLoadCause[];
|
|
92
94
|
/** Uncaught page errors; a timed-out timeline wait with any of these becomes a script failure. */
|
|
93
95
|
pageErrors: string[];
|
|
94
96
|
/** The first VFX chain error; its frames would lack the effect, so it stops the render. */
|
|
@@ -464,6 +466,15 @@ export declare class VfxFailureError extends Error {
|
|
|
464
466
|
readonly detail: string;
|
|
465
467
|
constructor(detail: string);
|
|
466
468
|
}
|
|
469
|
+
type ScriptLoadOrigin = "local" | "cdn" | "remote" | "unknown";
|
|
470
|
+
export interface ScriptLoadCause {
|
|
471
|
+
origin: ScriptLoadOrigin;
|
|
472
|
+
/** `net::ERR_*`, `http-<status>` or `unknown`. */
|
|
473
|
+
code: string;
|
|
474
|
+
}
|
|
475
|
+
/** Where a failed script came from and why, never its URL, host or path. */
|
|
476
|
+
export declare function classifyScriptLoadCause(url: string, serverUrl: string, failure: string | number): ScriptLoadCause;
|
|
477
|
+
export declare function recordFailedRequest(session: CaptureSession, request: Pick<HTTPRequest, "resourceType" | "url" | "method" | "failure">): void;
|
|
467
478
|
/** `runtime-error:<first line>` for the error a framework wrapper logs when a composition script throws. */
|
|
468
479
|
export declare function classifyConsoleScriptError(type: string, text: string): string | null;
|
|
469
480
|
export declare function classifyConsoleScriptFailure(type: string, text: string): string | null;
|
|
@@ -1686,6 +1686,7 @@ export function recordSubTimelineWarning(session, timeoutMs) {
|
|
|
1686
1686
|
const threwThenTimedOut = session.pageErrors.length > 0 && session.scriptLoadFailures.length === 0;
|
|
1687
1687
|
const scriptFailure = isScriptFailure(session);
|
|
1688
1688
|
const sources = [...session.scriptLoadFailures, ...session.pageErrors];
|
|
1689
|
+
const loadCauses = session.scriptLoadCauses ?? [];
|
|
1689
1690
|
const pending = session.pendingTimelineIds ?? [];
|
|
1690
1691
|
const pendingSuffix = pending.length > 0 ? ` (still unregistered: ${pending.join(", ")})` : "";
|
|
1691
1692
|
const isolated = session.isolatedCompositions ?? [];
|
|
@@ -1713,7 +1714,8 @@ export function recordSubTimelineWarning(session, timeoutMs) {
|
|
|
1713
1714
|
`${pendingSuffix} (${sources.join(", ")}). Fix the error; a composition animated by ` +
|
|
1714
1715
|
`CSS or rAF rather than a GSAP timeline must mark its host with data-no-timeline.`
|
|
1715
1716
|
: scriptFailure
|
|
1716
|
-
? `A sub-composition timeline script failed to load (${sources.join(", ")})`
|
|
1717
|
+
? `A sub-composition timeline script failed to load (${sources.join(", ")})` +
|
|
1718
|
+
scriptLoadCauseSuffix(loadCauses)
|
|
1717
1719
|
: `Sub-composition timelines did not become ready within ${timeoutMs}ms${pendingSuffix}. ` +
|
|
1718
1720
|
`This can be intentional: a composition driven by CSS animations or rAF never registers ` +
|
|
1719
1721
|
`window.__timelines[id], and marking its host with data-no-timeline skips the wait entirely. ` +
|
|
@@ -1891,11 +1893,44 @@ function recordPageError(session, error) {
|
|
|
1891
1893
|
// (e.g. a `requestfailed` following the 4xx), and repeated <script> tags for
|
|
1892
1894
|
// the same URL duplicate it further — dedupe so the fail-fast warning names
|
|
1893
1895
|
// each failed URL once.
|
|
1894
|
-
function recordScriptLoadFailure(session, url) {
|
|
1896
|
+
function recordScriptLoadFailure(session, url, cause) {
|
|
1895
1897
|
if (!session.scriptLoadFailures.includes(url)) {
|
|
1896
1898
|
session.scriptLoadFailures.push(url);
|
|
1899
|
+
(session.scriptLoadCauses ??= []).push(cause);
|
|
1897
1900
|
}
|
|
1898
1901
|
}
|
|
1902
|
+
const CDN_HOSTS = /^(?:cdn\.jsdelivr\.net|unpkg\.com|cdnjs\.cloudflare\.com)$/;
|
|
1903
|
+
/** Where a failed script came from and why, never its URL, host or path. */
|
|
1904
|
+
export function classifyScriptLoadCause(url, serverUrl, failure) {
|
|
1905
|
+
let origin = "unknown";
|
|
1906
|
+
if (url.startsWith(`${serverUrl}/`))
|
|
1907
|
+
origin = "local";
|
|
1908
|
+
else if (/^https?:\/\//.test(url)) {
|
|
1909
|
+
origin = CDN_HOSTS.test(new URL(url).hostname) ? "cdn" : "remote";
|
|
1910
|
+
}
|
|
1911
|
+
const code = typeof failure === "number"
|
|
1912
|
+
? `http-${failure}`
|
|
1913
|
+
: /^net::ERR_[A-Z0-9_]{1,40}$/.test(failure)
|
|
1914
|
+
? failure
|
|
1915
|
+
: "unknown";
|
|
1916
|
+
return { origin, code };
|
|
1917
|
+
}
|
|
1918
|
+
function scriptLoadCauseSuffix(causes) {
|
|
1919
|
+
const shown = causes.slice(0, 4).map((c) => `${c.origin}/${c.code}`);
|
|
1920
|
+
return shown.length === 0 ? "" : ` [script_load: ${shown.join(", ")}]`;
|
|
1921
|
+
}
|
|
1922
|
+
const CANCELLED_BY_THE_PAGE_NOT_REFUSED = "net::ERR_ABORTED";
|
|
1923
|
+
export function recordFailedRequest(session, request) {
|
|
1924
|
+
const resourceType = request.resourceType();
|
|
1925
|
+
const url = request.url();
|
|
1926
|
+
const failureText = request.failure()?.errorText ?? "unknown";
|
|
1927
|
+
if (resourceType === "script" && failureText !== CANCELLED_BY_THE_PAGE_NOT_REFUSED) {
|
|
1928
|
+
recordScriptLoadFailure(session, url, classifyScriptLoadCause(url, session.serverUrl, failureText));
|
|
1929
|
+
}
|
|
1930
|
+
if (shouldIgnoreRequestFailureDiagnostic({ resourceType, url, failureText }))
|
|
1931
|
+
return;
|
|
1932
|
+
appendBrowserDiagnostic(session, formatRequestFailureDiagnostic({ method: request.method(), resourceType, url, failureText }));
|
|
1933
|
+
}
|
|
1899
1934
|
const SCRIPT_ERROR_LABEL = "[HyperFrames] composition script error:";
|
|
1900
1935
|
/** `runtime-error:<first line>` for the error a framework wrapper logs when a composition script throws. */
|
|
1901
1936
|
export function classifyConsoleScriptError(type, text) {
|
|
@@ -1980,7 +2015,7 @@ export async function initializeSession(session) {
|
|
|
1980
2015
|
// A blocked script can never register its timeline (#3352); a thrown one may still, so it only counts on timeout.
|
|
1981
2016
|
const scriptFailure = classifyConsoleScriptFailure(type, text);
|
|
1982
2017
|
if (scriptFailure)
|
|
1983
|
-
recordScriptLoadFailure(session, scriptFailure);
|
|
2018
|
+
recordScriptLoadFailure(session, scriptFailure, { origin: "unknown", code: "unknown" });
|
|
1984
2019
|
recordConsoleScriptError(session, classifyConsoleScriptError(type, text));
|
|
1985
2020
|
});
|
|
1986
2021
|
page.on("pageerror", (err) => {
|
|
@@ -1991,22 +2026,7 @@ export async function initializeSession(session) {
|
|
|
1991
2026
|
console.error(text);
|
|
1992
2027
|
appendBrowserDiagnostic(session, text);
|
|
1993
2028
|
});
|
|
1994
|
-
page.on("requestfailed", (request) =>
|
|
1995
|
-
const resourceType = request.resourceType();
|
|
1996
|
-
const url = request.url();
|
|
1997
|
-
const failureText = request.failure()?.errorText ?? "unknown";
|
|
1998
|
-
if (resourceType === "script") {
|
|
1999
|
-
recordScriptLoadFailure(session, request.url());
|
|
2000
|
-
}
|
|
2001
|
-
if (shouldIgnoreRequestFailureDiagnostic({ resourceType, url, failureText }))
|
|
2002
|
-
return;
|
|
2003
|
-
appendBrowserDiagnostic(session, formatRequestFailureDiagnostic({
|
|
2004
|
-
method: request.method(),
|
|
2005
|
-
resourceType,
|
|
2006
|
-
url,
|
|
2007
|
-
failureText,
|
|
2008
|
-
}));
|
|
2009
|
-
});
|
|
2029
|
+
page.on("requestfailed", (request) => recordFailedRequest(session, request));
|
|
2010
2030
|
const projectScripts = new Set();
|
|
2011
2031
|
page.on("response", (response) => {
|
|
2012
2032
|
const status = response.status();
|
|
@@ -2018,8 +2038,9 @@ export async function initializeSession(session) {
|
|
|
2018
2038
|
projectScripts.add(responseUrl);
|
|
2019
2039
|
return;
|
|
2020
2040
|
}
|
|
2021
|
-
if (isScript)
|
|
2022
|
-
recordScriptLoadFailure(session, responseUrl);
|
|
2041
|
+
if (isScript) {
|
|
2042
|
+
recordScriptLoadFailure(session, responseUrl, classifyScriptLoadCause(responseUrl, serverUrl, status));
|
|
2043
|
+
}
|
|
2023
2044
|
appendBrowserDiagnostic(session, formatHttpErrorDiagnostic({
|
|
2024
2045
|
method: request.method(),
|
|
2025
2046
|
resourceType: request.resourceType(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"puppeteer": "~25.11.0",
|
|
60
60
|
"puppeteer-core": "^25.11.0",
|
|
61
61
|
"undici": "7.25.0",
|
|
62
|
-
"@hyperframes/core": "^0.9.
|
|
63
|
-
"@hyperframes/parsers": "^0.9.
|
|
62
|
+
"@hyperframes/core": "^0.9.9",
|
|
63
|
+
"@hyperframes/parsers": "^0.9.9"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^25.0.10",
|