hyperframes 0.4.5 → 0.4.6

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/dist/cli.js CHANGED
@@ -54,7 +54,7 @@ var VERSION;
54
54
  var init_version = __esm({
55
55
  "src/version.ts"() {
56
56
  "use strict";
57
- VERSION = true ? "0.4.5" : "0.0.0-dev";
57
+ VERSION = true ? "0.4.6" : "0.0.0-dev";
58
58
  }
59
59
  });
60
60
 
@@ -20938,6 +20938,15 @@ function isFontResourceError(type, text, locationUrl) {
20938
20938
  `${locationUrl} ${text}`
20939
20939
  );
20940
20940
  }
20941
+ async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100) {
20942
+ const deadline = Date.now() + timeoutMs;
20943
+ while (Date.now() < deadline) {
20944
+ const ready = Boolean(await page.evaluate(expression));
20945
+ if (ready) return true;
20946
+ await new Promise((resolve35) => setTimeout(resolve35, intervalMs));
20947
+ }
20948
+ return Boolean(await page.evaluate(expression));
20949
+ }
20941
20950
  async function initializeSession(session) {
20942
20951
  const { page, serverUrl } = session;
20943
20952
  page.on("console", (msg) => {
@@ -20967,14 +20976,26 @@ async function initializeSession(session) {
20967
20976
  if (session.captureMode === "screenshot") {
20968
20977
  await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
20969
20978
  const pageReadyTimeout2 = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG2.playerReadyTimeout;
20970
- await page.waitForFunction(
20979
+ const pageReady2 = await pollPageExpression(
20980
+ page,
20971
20981
  `!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`,
20972
- { timeout: pageReadyTimeout2 }
20982
+ pageReadyTimeout2
20973
20983
  );
20974
- await page.waitForFunction(
20984
+ if (!pageReady2) {
20985
+ throw new Error(
20986
+ `[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
20987
+ );
20988
+ }
20989
+ const videosReady = await pollPageExpression(
20990
+ page,
20975
20991
  `document.querySelectorAll("video").length === 0 || Array.from(document.querySelectorAll("video")).every(v => v.readyState >= 1)`,
20976
- { timeout: pageReadyTimeout2 }
20992
+ pageReadyTimeout2
20977
20993
  );
20994
+ if (!videosReady) {
20995
+ throw new Error(
20996
+ `[FrameCapture] video metadata not ready after ${pageReadyTimeout2}ms. Video elements must load metadata before capture starts.`
20997
+ );
20998
+ }
20978
20999
  await page.evaluate(`document.fonts?.ready`);
20979
21000
  session.isInitialized = true;
20980
21001
  return;
@@ -24079,8 +24100,22 @@ function injectScriptsIntoHtml2(html, headScripts, bodyScripts, stripEmbedded) {
24079
24100
  }
24080
24101
  return html;
24081
24102
  }
24103
+ function injectScriptsAtHeadStart(html, scripts) {
24104
+ if (scripts.length === 0) return html;
24105
+ const headTags = scripts.map((src) => `<script>${src}</script>`).join("\n");
24106
+ if (html.includes("<head")) {
24107
+ return html.replace(/<head\b[^>]*>/i, (match) => `${match}
24108
+ ${headTags}`);
24109
+ }
24110
+ if (html.includes("<body")) {
24111
+ return html.replace("<body", () => `${headTags}
24112
+ <body`);
24113
+ }
24114
+ return headTags + "\n" + html;
24115
+ }
24082
24116
  function createFileServer2(options) {
24083
24117
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
24118
+ const preHeadScripts = options.preHeadScripts ?? [];
24084
24119
  const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
24085
24120
  const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
24086
24121
  const app = new Hono3();
@@ -24104,7 +24139,11 @@ function createFileServer2(options) {
24104
24139
  if (ext === ".html") {
24105
24140
  const rawHtml = readFileSync17(filePath, "utf-8");
24106
24141
  const isIndex = relativePath === "index.html";
24107
- const html = isIndex ? injectScriptsIntoHtml2(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
24142
+ let html = rawHtml;
24143
+ if (preHeadScripts.length > 0) {
24144
+ html = injectScriptsAtHeadStart(html, preHeadScripts);
24145
+ }
24146
+ html = isIndex ? injectScriptsIntoHtml2(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
24108
24147
  return c2.text(html, 200, { "Content-Type": contentType });
24109
24148
  }
24110
24149
  const content = readFileSync17(filePath);
@@ -24132,7 +24171,7 @@ function createFileServer2(options) {
24132
24171
  });
24133
24172
  });
24134
24173
  }
24135
- var MIME_TYPES3, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT;
24174
+ var MIME_TYPES3, VIRTUAL_TIME_SHIM, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT;
24136
24175
  var init_fileServer2 = __esm({
24137
24176
  "../producer/src/services/fileServer.ts"() {
24138
24177
  "use strict";
@@ -24159,6 +24198,102 @@ var init_fileServer2 = __esm({
24159
24198
  ".ttf": "font/ttf",
24160
24199
  ".otf": "font/otf"
24161
24200
  };
24201
+ VIRTUAL_TIME_SHIM = String.raw`(function() {
24202
+ if (window.__HF_VIRTUAL_TIME__) return;
24203
+
24204
+ var virtualNowMs = 0;
24205
+ var rafId = 1;
24206
+ var rafQueue = [];
24207
+ var OriginalDate = Date;
24208
+ var originalSetTimeout = window.setTimeout.bind(window);
24209
+ var originalClearTimeout = window.clearTimeout.bind(window);
24210
+ var originalSetInterval = window.setInterval.bind(window);
24211
+ var originalClearInterval = window.clearInterval.bind(window);
24212
+ var originalRequestAnimationFrame = window.requestAnimationFrame
24213
+ ? window.requestAnimationFrame.bind(window)
24214
+ : null;
24215
+ var originalCancelAnimationFrame = window.cancelAnimationFrame
24216
+ ? window.cancelAnimationFrame.bind(window)
24217
+ : null;
24218
+
24219
+ function flushAnimationFrame() {
24220
+ if (!rafQueue.length) return;
24221
+ var current = rafQueue.slice();
24222
+ rafQueue.length = 0;
24223
+ for (var i = 0; i < current.length; i++) {
24224
+ var entry = current[i];
24225
+ if (entry.cancelled) continue;
24226
+ try {
24227
+ entry.callback(virtualNowMs);
24228
+ } catch {}
24229
+ }
24230
+ }
24231
+
24232
+ function VirtualDate() {
24233
+ var args = Array.prototype.slice.call(arguments);
24234
+ if (!(this instanceof VirtualDate)) {
24235
+ return OriginalDate.apply(null, args.length ? args : [virtualNowMs]);
24236
+ }
24237
+ var instance = args.length ? new (Function.prototype.bind.apply(OriginalDate, [null].concat(args)))() : new OriginalDate(virtualNowMs);
24238
+ Object.setPrototypeOf(instance, VirtualDate.prototype);
24239
+ return instance;
24240
+ }
24241
+
24242
+ VirtualDate.prototype = OriginalDate.prototype;
24243
+ Object.setPrototypeOf(VirtualDate, OriginalDate);
24244
+ VirtualDate.now = function() { return virtualNowMs; };
24245
+ VirtualDate.parse = OriginalDate.parse.bind(OriginalDate);
24246
+ VirtualDate.UTC = OriginalDate.UTC.bind(OriginalDate);
24247
+
24248
+ try {
24249
+ Object.defineProperty(window, "Date", {
24250
+ configurable: true,
24251
+ writable: true,
24252
+ value: VirtualDate,
24253
+ });
24254
+ } catch {}
24255
+
24256
+ if (window.performance && typeof window.performance.now === "function") {
24257
+ try {
24258
+ Object.defineProperty(window.performance, "now", {
24259
+ configurable: true,
24260
+ value: function() { return virtualNowMs; },
24261
+ });
24262
+ } catch {}
24263
+ }
24264
+
24265
+ window.requestAnimationFrame = function(callback) {
24266
+ if (typeof callback !== "function") return 0;
24267
+ var entry = { id: rafId++, callback: callback, cancelled: false };
24268
+ rafQueue.push(entry);
24269
+ return entry.id;
24270
+ };
24271
+ window.cancelAnimationFrame = function(id) {
24272
+ for (var i = 0; i < rafQueue.length; i++) {
24273
+ if (rafQueue[i].id === id) {
24274
+ rafQueue[i].cancelled = true;
24275
+ }
24276
+ }
24277
+ };
24278
+
24279
+ window.__HF_VIRTUAL_TIME__ = {
24280
+ originalSetTimeout: originalSetTimeout,
24281
+ originalClearTimeout: originalClearTimeout,
24282
+ originalSetInterval: originalSetInterval,
24283
+ originalClearInterval: originalClearInterval,
24284
+ originalRequestAnimationFrame: originalRequestAnimationFrame,
24285
+ originalCancelAnimationFrame: originalCancelAnimationFrame,
24286
+ seekToTime: function(nextTimeMs) {
24287
+ var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
24288
+ virtualNowMs = safeTimeMs;
24289
+ flushAnimationFrame();
24290
+ return virtualNowMs;
24291
+ },
24292
+ getTime: function() {
24293
+ return virtualNowMs;
24294
+ },
24295
+ };
24296
+ })();`;
24162
24297
  RENDER_SEEK_MODE = process.env.PRODUCER_RUNTIME_RENDER_SEEK_MODE === "strict-boundary" ? "strict-boundary" : "preview-phase";
24163
24298
  RENDER_SEEK_DIAGNOSTICS = process.env.PRODUCER_DEBUG_SEEK_DIAGNOSTICS === "true";
24164
24299
  RENDER_SEEK_STEP = Math.max(
@@ -24170,6 +24305,10 @@ var init_fileServer2 = __esm({
24170
24305
  Math.min(0.95, Number(process.env.PRODUCER_RUNTIME_RENDER_SEEK_OFFSET_FRACTION || 0.5))
24171
24306
  );
24172
24307
  RENDER_MODE_SCRIPT = `(function() {
24308
+ var __realSetTimeout =
24309
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetTimeout === "function"
24310
+ ? window.__HF_VIRTUAL_TIME__.originalSetTimeout
24311
+ : window.setTimeout.bind(window);
24173
24312
  var __seekMode = ${JSON.stringify(RENDER_SEEK_MODE)};
24174
24313
  var __seekDiagnostics = ${RENDER_SEEK_DIAGNOSTICS ? "true" : "false"};
24175
24314
  var __seekStep = ${RENDER_SEEK_STEP};
@@ -24263,23 +24402,56 @@ var init_fileServer2 = __esm({
24263
24402
  window.__renderReady = true;
24264
24403
  return;
24265
24404
  }
24266
- setTimeout(waitForPlayer, 50);
24405
+ __realSetTimeout(waitForPlayer, 50);
24267
24406
  return;
24268
24407
  }
24269
24408
  if (installMediaFallbackPlayer()) {
24270
24409
  return;
24271
24410
  }
24272
- setTimeout(waitForPlayer, 50);
24411
+ __realSetTimeout(waitForPlayer, 50);
24273
24412
  }
24274
24413
  waitForPlayer();
24275
24414
  })();`;
24276
24415
  HF_BRIDGE_SCRIPT = `(function() {
24416
+ var __realSetInterval =
24417
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === "function"
24418
+ ? window.__HF_VIRTUAL_TIME__.originalSetInterval
24419
+ : window.setInterval.bind(window);
24420
+ var __realClearInterval =
24421
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalClearInterval === "function"
24422
+ ? window.__HF_VIRTUAL_TIME__.originalClearInterval
24423
+ : window.clearInterval.bind(window);
24277
24424
  function getDeclaredDuration() {
24278
24425
  var root = document.querySelector('[data-composition-id]');
24279
24426
  if (!root) return 0;
24280
24427
  var d = Number(root.getAttribute('data-duration'));
24281
24428
  return Number.isFinite(d) && d > 0 ? d : 0;
24282
24429
  }
24430
+ function seekSameOriginChildFrames(frameWindow, nextTimeMs) {
24431
+ var frames;
24432
+ try {
24433
+ frames = frameWindow.frames;
24434
+ } catch (_error) {
24435
+ return;
24436
+ }
24437
+ if (!frames || typeof frames.length !== "number") return;
24438
+ for (var i = 0; i < frames.length; i++) {
24439
+ var childWindow = null;
24440
+ try {
24441
+ childWindow = frames[i];
24442
+ if (!childWindow || childWindow === frameWindow) continue;
24443
+ if (
24444
+ childWindow.__HF_VIRTUAL_TIME__ &&
24445
+ typeof childWindow.__HF_VIRTUAL_TIME__.seekToTime === "function"
24446
+ ) {
24447
+ childWindow.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
24448
+ }
24449
+ } catch (_error) {
24450
+ continue;
24451
+ }
24452
+ seekSameOriginChildFrames(childWindow, nextTimeMs);
24453
+ }
24454
+ }
24283
24455
  function bridge() {
24284
24456
  var p = window.__player;
24285
24457
  if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
@@ -24290,13 +24462,20 @@ var init_fileServer2 = __esm({
24290
24462
  var d = p.getDuration();
24291
24463
  return d > 0 ? d : getDeclaredDuration();
24292
24464
  },
24293
- seek: function(t) { p.renderSeek(t); },
24465
+ seek: function(t) {
24466
+ p.renderSeek(t);
24467
+ var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;
24468
+ if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === "function") {
24469
+ window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
24470
+ }
24471
+ seekSameOriginChildFrames(window, nextTimeMs);
24472
+ },
24294
24473
  };
24295
24474
  return true;
24296
24475
  }
24297
24476
  if (bridge()) return;
24298
- var iv = setInterval(function() {
24299
- if (bridge()) clearInterval(iv);
24477
+ var iv = __realSetInterval(function() {
24478
+ if (bridge()) __realClearInterval(iv);
24300
24479
  }, 50);
24301
24480
  })();`;
24302
24481
  }
@@ -24753,6 +24932,36 @@ function dedupeElementsById(elements) {
24753
24932
  }
24754
24933
  return Array.from(deduped.values());
24755
24934
  }
24935
+ function stripJsComments(source) {
24936
+ return source.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
24937
+ }
24938
+ function detectRenderModeHints(html) {
24939
+ const reasons = [];
24940
+ const { document: document2 } = parseHTML(html);
24941
+ if (document2.querySelector("iframe")) {
24942
+ reasons.push({
24943
+ code: "iframe",
24944
+ message: "Detected <iframe> in the composition DOM. Nested iframe animation is routed through screenshot capture mode for compatibility."
24945
+ });
24946
+ }
24947
+ let scriptMatch;
24948
+ const scriptPattern = new RegExp(INLINE_SCRIPT_PATTERN.source, INLINE_SCRIPT_PATTERN.flags);
24949
+ while ((scriptMatch = scriptPattern.exec(html)) !== null) {
24950
+ const attrs = scriptMatch[1] || "";
24951
+ if (/\bsrc\s*=/i.test(attrs)) continue;
24952
+ const content = stripJsComments(scriptMatch[2] || "");
24953
+ if (!/requestAnimationFrame\s*\(/.test(content)) continue;
24954
+ reasons.push({
24955
+ code: "requestAnimationFrame",
24956
+ message: "Detected raw requestAnimationFrame() in an inline script. This render is routed through screenshot capture mode with virtual time enabled."
24957
+ });
24958
+ break;
24959
+ }
24960
+ return {
24961
+ recommendScreenshot: reasons.length > 0,
24962
+ reasons
24963
+ };
24964
+ }
24756
24965
  async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
24757
24966
  let filePath = src;
24758
24967
  if (isHttpUrl(src)) {
@@ -25316,6 +25525,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
25316
25525
  /(<(?:video|audio)\b[^>]*?)\s+preload\s*=\s*["']none["']/gi,
25317
25526
  "$1"
25318
25527
  );
25528
+ const renderModeHints = detectRenderModeHints(sanitizedHtml);
25319
25529
  const coalescedHtml = await injectDeterministicFontFaces(
25320
25530
  coalesceHeadStylesAndBodyScripts2(promoteCssImportsToLinkTags(sanitizedHtml))
25321
25531
  );
@@ -25359,7 +25569,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
25359
25569
  externalAssets,
25360
25570
  width,
25361
25571
  height,
25362
- staticDuration
25572
+ staticDuration,
25573
+ renderModeHints
25363
25574
  };
25364
25575
  }
25365
25576
  async function discoverMediaFromBrowser(page) {
@@ -25453,9 +25664,11 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
25453
25664
  subCompositions,
25454
25665
  videos,
25455
25666
  audios,
25456
- unresolvedCompositions: remaining
25667
+ unresolvedCompositions: remaining,
25668
+ renderModeHints: compiled.renderModeHints
25457
25669
  };
25458
25670
  }
25671
+ var INLINE_SCRIPT_PATTERN;
25459
25672
  var init_htmlCompiler2 = __esm({
25460
25673
  "../producer/src/services/htmlCompiler.ts"() {
25461
25674
  "use strict";
@@ -25466,6 +25679,7 @@ var init_htmlCompiler2 = __esm({
25466
25679
  init_src2();
25467
25680
  init_urlDownloader2();
25468
25681
  init_deterministicFonts();
25682
+ INLINE_SCRIPT_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
25469
25683
  }
25470
25684
  });
25471
25685
 
@@ -25613,11 +25827,20 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
25613
25827
  end: a.end,
25614
25828
  mediaStart: a.mediaStart
25615
25829
  })),
25616
- subCompositions: Array.from(compiled.subCompositions.keys())
25830
+ subCompositions: Array.from(compiled.subCompositions.keys()),
25831
+ renderModeHints: compiled.renderModeHints
25617
25832
  };
25618
25833
  writeFileSync10(join30(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
25619
25834
  }
25620
25835
  }
25836
+ function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
25837
+ if (cfg.forceScreenshot || !compiled.renderModeHints.recommendScreenshot) return;
25838
+ cfg.forceScreenshot = true;
25839
+ log.warn("Auto-selected screenshot capture mode for render compatibility", {
25840
+ reasonCodes: compiled.renderModeHints.reasons.map((reason) => reason.code),
25841
+ reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
25842
+ });
25843
+ }
25621
25844
  function createRenderJob(config) {
25622
25845
  return {
25623
25846
  id: randomUUID2(),
@@ -25730,6 +25953,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25730
25953
  let compiled = await compileForRender(projectDir, htmlPath, join30(workDir, "downloads"));
25731
25954
  assertNotAborted();
25732
25955
  perfStages.compileOnlyMs = Date.now() - compileStart;
25956
+ applyRenderModeHints(cfg, compiled, log);
25733
25957
  writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
25734
25958
  log.info("Compiled composition metadata", {
25735
25959
  entryFile,
@@ -25737,7 +25961,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25737
25961
  width: compiled.width,
25738
25962
  height: compiled.height,
25739
25963
  videoCount: compiled.videos.length,
25740
- audioCount: compiled.audios.length
25964
+ audioCount: compiled.audios.length,
25965
+ renderModeHints: compiled.renderModeHints
25741
25966
  });
25742
25967
  const composition = {
25743
25968
  duration: compiled.staticDuration,
@@ -25757,7 +25982,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25757
25982
  fileServer = await createFileServer2({
25758
25983
  projectDir,
25759
25984
  compiledDir: join30(workDir, "compiled"),
25760
- port: 0
25985
+ port: 0,
25986
+ preHeadScripts: [VIRTUAL_TIME_SHIM]
25761
25987
  });
25762
25988
  assertNotAborted();
25763
25989
  const captureOpts = {
@@ -26000,7 +26226,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
26000
26226
  fileServer = await createFileServer2({
26001
26227
  projectDir,
26002
26228
  compiledDir: join30(workDir, "compiled"),
26003
- port: 0
26229
+ port: 0,
26230
+ preHeadScripts: [VIRTUAL_TIME_SHIM]
26004
26231
  });
26005
26232
  assertNotAborted();
26006
26233
  }