@grabbit-labs/dynafetch 0.2.3 → 0.2.5

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.
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/index.js CHANGED
@@ -325,7 +325,8 @@ async function withDynafetchSession(options, run) {
325
325
  try {
326
326
  return await sessionStore.run({ sessionId: session.sessionId }, run);
327
327
  } finally {
328
- callWorker("closeSession", { sessionId: session.sessionId }).catch(() => {
328
+ const closeTimeoutMs = Math.min(resolveRpcTimeoutMs(options), 5e3);
329
+ await callWorker("closeSession", { sessionId: session.sessionId }, closeTimeoutMs).catch(() => {
329
330
  });
330
331
  transport.release();
331
332
  }
@@ -763,8 +764,19 @@ var TELEMETRY_INLINE_PATTERNS = [
763
764
  /\bhj\s*\(/i,
764
765
  /\bnewrelic\b/i,
765
766
  /\bdatadog\b/i,
766
- /\bSentry\b/i,
767
- /\bLogRocket\b/i
767
+ /\bSentry\.(?:init|capture|captureException|configureScope|withScope)\b/i,
768
+ /\bLogRocket\.(?:init|identify|track)\b/i
769
+ ];
770
+ var INLINE_APPLICATION_PATTERNS = [
771
+ /\bwebpackChunk\w*/i,
772
+ /\b__webpack_require__\b/i,
773
+ /\b__SCRIPTS_LOADED__\b/i,
774
+ /\b__LOADABLE_LOADED_CHUNKS__\b/i,
775
+ /\bparcelRequire\b/i,
776
+ /\bwindow\.__[A-Z0-9_]{3,}\s*=/i,
777
+ /\bglobalThis\.__[A-Z0-9_]{3,}\s*=/i,
778
+ /\bself\.__[A-Z0-9_]{3,}\s*=/i,
779
+ /\bperformance\.mark\s*\(/i
768
780
  ];
769
781
  function siteKey(hostname) {
770
782
  const parts = hostname.split(".").filter(Boolean);
@@ -798,6 +810,7 @@ function classifyScriptAsset(script, pageUrl) {
798
810
  if (script.scriptKind === "module") return "application";
799
811
  return "unknown";
800
812
  }
813
+ if (matchesAny(content, INLINE_APPLICATION_PATTERNS)) return "application";
801
814
  if (matchesAny(content, TELEMETRY_INLINE_PATTERNS)) return "telemetry";
802
815
  return "application";
803
816
  }
@@ -876,6 +889,7 @@ var Executor = class {
876
889
  this.esbuildModule = null;
877
890
  this.originalGlobalMessageChannel = void 0;
878
891
  this.originalGlobalMessagePort = void 0;
892
+ this.activeTimerHandles = /* @__PURE__ */ new Set();
879
893
  // Simple telemetry counters (useful for debugging).
880
894
  this.telemetry_stubbed = 0;
881
895
  this.telemetry_proxy = 0;
@@ -977,6 +991,76 @@ var Executor = class {
977
991
  }
978
992
  }
979
993
  }
994
+ clearTrackedTimers(window) {
995
+ for (const handle of this.activeTimerHandles) {
996
+ try {
997
+ window.clearTimeout?.(handle);
998
+ } catch {
999
+ }
1000
+ try {
1001
+ window.clearInterval?.(handle);
1002
+ } catch {
1003
+ }
1004
+ try {
1005
+ handle?.unref?.();
1006
+ } catch {
1007
+ }
1008
+ }
1009
+ this.activeTimerHandles.clear();
1010
+ }
1011
+ async performAutoScroll(window) {
1012
+ const steps = this.clampMs(Number(process.env.PHANTOM_AUTO_SCROLL_STEPS ?? 3), 0, 12);
1013
+ if (steps === 0) return false;
1014
+ const stepPx = this.clampMs(
1015
+ Number(process.env.PHANTOM_AUTO_SCROLL_STEP_PX ?? Math.max(Number(window.innerHeight) || 0, 900)),
1016
+ 100,
1017
+ 1e4
1018
+ );
1019
+ const delayMs = this.clampMs(Number(process.env.PHANTOM_AUTO_SCROLL_DELAY_MS ?? 150), 0, 5e3);
1020
+ const dispatchScroll = () => {
1021
+ try {
1022
+ window.dispatchEvent(new window.Event("scroll"));
1023
+ } catch {
1024
+ }
1025
+ try {
1026
+ window.document?.dispatchEvent?.(new window.Event("scroll"));
1027
+ } catch {
1028
+ }
1029
+ try {
1030
+ window.document?.documentElement?.dispatchEvent?.(new window.Event("scroll"));
1031
+ } catch {
1032
+ }
1033
+ };
1034
+ let didScroll = false;
1035
+ for (let step = 0; step < steps && !this.windowClosed; step++) {
1036
+ const nextY = (Number(window.scrollY) || 0) + stepPx;
1037
+ try {
1038
+ window.scrollTo(0, nextY);
1039
+ } catch {
1040
+ try {
1041
+ window.scrollY = nextY;
1042
+ } catch {
1043
+ }
1044
+ }
1045
+ dispatchScroll();
1046
+ didScroll = true;
1047
+ if (delayMs > 0) {
1048
+ await new Promise((resolve) => window.setTimeout(resolve, delayMs));
1049
+ }
1050
+ }
1051
+ if (didScroll) {
1052
+ try {
1053
+ window.scrollTo(0, 0);
1054
+ } catch {
1055
+ try {
1056
+ window.scrollY = 0;
1057
+ } catch {
1058
+ }
1059
+ }
1060
+ dispatchScroll();
1061
+ }
1062
+ return didScroll;
1063
+ }
980
1064
  applyDefaults(quiescence, moduleWaitMsOverride) {
981
1065
  const hardMaxCap = this.clampMs(Number(process.env.PHANTOM_QUIESCENCE_MAX_CAP_MS ?? 8e3), 500, 6e4);
982
1066
  const minWaitMs = this.clampMs(quiescence?.minWaitMs ?? 75, 0, 1e4);
@@ -1414,14 +1498,66 @@ var Executor = class {
1414
1498
  };
1415
1499
  }
1416
1500
  const g = globalThis;
1417
- if (!window.Headers && g.Headers) window.Headers = g.Headers;
1418
- if (!window.Request && g.Request) window.Request = g.Request;
1419
- if (!window.Response && g.Response) window.Response = g.Response;
1420
- if (!window.AbortController && g.AbortController) window.AbortController = g.AbortController;
1421
- if (!window.AbortSignal && g.AbortSignal) window.AbortSignal = g.AbortSignal;
1501
+ if (g.Headers) window.Headers = g.Headers;
1502
+ if (g.Response) window.Response = g.Response;
1503
+ if (g.AbortController) window.AbortController = g.AbortController;
1504
+ if (g.AbortSignal) window.AbortSignal = g.AbortSignal;
1422
1505
  if (!window.TextEncoder && g.TextEncoder) window.TextEncoder = g.TextEncoder;
1423
1506
  if (!window.TextDecoder && g.TextDecoder) window.TextDecoder = g.TextDecoder;
1424
1507
  if (!window.structuredClone && g.structuredClone) window.structuredClone = g.structuredClone.bind(g);
1508
+ const normalizeUrlForRequest = (value) => {
1509
+ if (typeof value !== "string") return value;
1510
+ try {
1511
+ return new URL(value, window.location.href).toString();
1512
+ } catch {
1513
+ return value;
1514
+ }
1515
+ };
1516
+ const normalizeAbortSignalForRequest = (signal) => {
1517
+ if (!signal || !g.AbortController || !g.AbortSignal) return signal;
1518
+ if (signal instanceof g.AbortSignal) return signal;
1519
+ if (typeof signal.aborted !== "boolean") return void 0;
1520
+ const controller = new g.AbortController();
1521
+ if (signal.aborted) {
1522
+ try {
1523
+ controller.abort(signal.reason);
1524
+ } catch {
1525
+ controller.abort();
1526
+ }
1527
+ return controller.signal;
1528
+ }
1529
+ if (typeof signal.addEventListener === "function") {
1530
+ try {
1531
+ signal.addEventListener("abort", () => {
1532
+ try {
1533
+ controller.abort(signal.reason);
1534
+ } catch {
1535
+ controller.abort();
1536
+ }
1537
+ }, { once: true });
1538
+ } catch {
1539
+ }
1540
+ }
1541
+ return controller.signal;
1542
+ };
1543
+ if (g.Request) {
1544
+ const NativeRequest = g.Request;
1545
+ const RequestShim = class Request extends NativeRequest {
1546
+ constructor(input, init) {
1547
+ const normalizedInput = typeof input === "string" ? normalizeUrlForRequest(input) : input instanceof URL ? normalizeUrlForRequest(input.toString()) : input;
1548
+ const normalizedInit = init && typeof init === "object" ? {
1549
+ ...init,
1550
+ signal: normalizeAbortSignalForRequest(init.signal)
1551
+ } : init;
1552
+ super(normalizedInput, normalizedInit);
1553
+ }
1554
+ };
1555
+ try {
1556
+ Object.setPrototypeOf(RequestShim, NativeRequest);
1557
+ } catch {
1558
+ }
1559
+ window.Request = RequestShim;
1560
+ }
1425
1561
  const makeStorage = () => {
1426
1562
  const store = /* @__PURE__ */ new Map();
1427
1563
  return {
@@ -1468,9 +1604,10 @@ var Executor = class {
1468
1604
  };
1469
1605
  }
1470
1606
  if (!window.crypto) window.crypto = {};
1607
+ const nodeWebCrypto = g.crypto || nodeCrypto.webcrypto;
1471
1608
  if (!window.crypto.getRandomValues) {
1472
- if (g.crypto && typeof g.crypto.getRandomValues === "function") {
1473
- window.crypto.getRandomValues = g.crypto.getRandomValues.bind(g.crypto);
1609
+ if (nodeWebCrypto && typeof nodeWebCrypto.getRandomValues === "function") {
1610
+ window.crypto.getRandomValues = nodeWebCrypto.getRandomValues.bind(nodeWebCrypto);
1474
1611
  } else {
1475
1612
  window.crypto.getRandomValues = (arr) => {
1476
1613
  const buf = nodeCrypto.randomBytes(arr.length);
@@ -1479,6 +1616,22 @@ var Executor = class {
1479
1616
  };
1480
1617
  }
1481
1618
  }
1619
+ if (!window.crypto.subtle && nodeWebCrypto?.subtle) {
1620
+ window.crypto.subtle = nodeWebCrypto.subtle;
1621
+ }
1622
+ if (!window.crypto.randomUUID) {
1623
+ if (typeof nodeWebCrypto?.randomUUID === "function") {
1624
+ window.crypto.randomUUID = nodeWebCrypto.randomUUID.bind(nodeWebCrypto);
1625
+ } else {
1626
+ window.crypto.randomUUID = () => nodeCrypto.randomUUID();
1627
+ }
1628
+ }
1629
+ if (!window.CryptoKey && g.CryptoKey) {
1630
+ window.CryptoKey = g.CryptoKey;
1631
+ }
1632
+ if (!window.SubtleCrypto && g.SubtleCrypto) {
1633
+ window.SubtleCrypto = g.SubtleCrypto;
1634
+ }
1482
1635
  {
1483
1636
  const _g = globalThis;
1484
1637
  if (_g.MessageChannel) {
@@ -1558,6 +1711,81 @@ var Executor = class {
1558
1711
  availHeight: 900
1559
1712
  };
1560
1713
  }
1714
+ const parsePx = (value) => {
1715
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1716
+ if (typeof value === "string") {
1717
+ const parsed = Number.parseFloat(value.replace(/px$/i, ""));
1718
+ if (Number.isFinite(parsed)) return parsed;
1719
+ }
1720
+ return 0;
1721
+ };
1722
+ const fallbackViewportHeight = Math.max(Number(window.innerHeight) || 0, 900);
1723
+ const fallbackViewportWidth = Math.max(Number(window.innerWidth) || 0, 1440);
1724
+ const fallbackDocumentHeight = Math.max(fallbackViewportHeight * 6, 5400);
1725
+ const makeRect = (width, height) => ({
1726
+ x: 0,
1727
+ y: 0,
1728
+ top: 0,
1729
+ left: 0,
1730
+ bottom: height,
1731
+ right: width,
1732
+ width,
1733
+ height,
1734
+ toJSON() {
1735
+ return { x: 0, y: 0, top: 0, left: 0, bottom: height, right: width, width, height };
1736
+ }
1737
+ });
1738
+ const computeElementBox = (el) => {
1739
+ const tag = String(el?.tagName || "").toLowerCase();
1740
+ if (tag === "html" || tag === "body") {
1741
+ return {
1742
+ width: fallbackViewportWidth,
1743
+ height: fallbackDocumentHeight
1744
+ };
1745
+ }
1746
+ const width = parsePx(el?.style?.width) || parsePx(el?.getAttribute?.("width")) || fallbackViewportWidth;
1747
+ const height = parsePx(el?.style?.height) || parsePx(el?.getAttribute?.("height")) || 240;
1748
+ return { width, height };
1749
+ };
1750
+ try {
1751
+ const elementProto = window.Element?.prototype;
1752
+ if (elementProto) {
1753
+ const originalGetBoundingClientRect = elementProto.getBoundingClientRect;
1754
+ elementProto.getBoundingClientRect = function() {
1755
+ try {
1756
+ const rect = originalGetBoundingClientRect?.call(this);
1757
+ if (rect && (rect.width > 0 || rect.height > 0)) return rect;
1758
+ } catch {
1759
+ }
1760
+ const { width, height } = computeElementBox(this);
1761
+ return makeRect(width, height);
1762
+ };
1763
+ }
1764
+ const htmlEl = window.document?.documentElement;
1765
+ const bodyEl = window.document?.body;
1766
+ const defineSize = (target, name, value) => {
1767
+ try {
1768
+ Object.defineProperty(target, name, {
1769
+ configurable: true,
1770
+ get: () => value
1771
+ });
1772
+ } catch {
1773
+ }
1774
+ };
1775
+ if (htmlEl) {
1776
+ defineSize(htmlEl, "clientWidth", fallbackViewportWidth);
1777
+ defineSize(htmlEl, "clientHeight", fallbackViewportHeight);
1778
+ defineSize(htmlEl, "scrollWidth", fallbackViewportWidth);
1779
+ defineSize(htmlEl, "scrollHeight", fallbackDocumentHeight);
1780
+ }
1781
+ if (bodyEl) {
1782
+ defineSize(bodyEl, "clientWidth", fallbackViewportWidth);
1783
+ defineSize(bodyEl, "clientHeight", fallbackViewportHeight);
1784
+ defineSize(bodyEl, "scrollWidth", fallbackViewportWidth);
1785
+ defineSize(bodyEl, "scrollHeight", fallbackDocumentHeight);
1786
+ }
1787
+ } catch {
1788
+ }
1561
1789
  if (!window.visualViewport) {
1562
1790
  window.visualViewport = {
1563
1791
  width: window.innerWidth,
@@ -1620,8 +1848,41 @@ var Executor = class {
1620
1848
  };
1621
1849
  const _setTimeout = window.setTimeout?.bind(window);
1622
1850
  const _setInterval = window.setInterval?.bind(window);
1623
- if (_setTimeout) window.setTimeout = (cb, ms, ...rest) => _setTimeout(wrapCb(cb), ms, ...rest);
1624
- if (_setInterval) window.setInterval = (cb, ms, ...rest) => _setInterval(wrapCb(cb), ms, ...rest);
1851
+ const _clearTimeout = window.clearTimeout?.bind(window);
1852
+ const _clearInterval = window.clearInterval?.bind(window);
1853
+ if (_setTimeout) {
1854
+ window.setTimeout = (cb, ms, ...rest) => {
1855
+ let handle;
1856
+ const wrapped = wrapCb((...args) => {
1857
+ this.activeTimerHandles.delete(handle);
1858
+ return typeof cb === "function" ? cb(...args) : cb;
1859
+ });
1860
+ handle = _setTimeout(wrapped, ms, ...rest);
1861
+ this.activeTimerHandles.add(handle);
1862
+ handle?.unref?.();
1863
+ return handle;
1864
+ };
1865
+ }
1866
+ if (_setInterval) {
1867
+ window.setInterval = (cb, ms, ...rest) => {
1868
+ const handle = _setInterval(wrapCb(cb), ms, ...rest);
1869
+ this.activeTimerHandles.add(handle);
1870
+ handle?.unref?.();
1871
+ return handle;
1872
+ };
1873
+ }
1874
+ if (_clearTimeout) {
1875
+ window.clearTimeout = (handle) => {
1876
+ this.activeTimerHandles.delete(handle);
1877
+ return _clearTimeout(handle);
1878
+ };
1879
+ }
1880
+ if (_clearInterval) {
1881
+ window.clearInterval = (handle) => {
1882
+ this.activeTimerHandles.delete(handle);
1883
+ return _clearInterval(handle);
1884
+ };
1885
+ }
1625
1886
  if (window.queueMicrotask) {
1626
1887
  const _q = window.queueMicrotask.bind(window);
1627
1888
  window.queueMicrotask = (cb) => _q(wrapCb(cb));
@@ -1731,7 +1992,7 @@ var Executor = class {
1731
1992
  try {
1732
1993
  const virtualConsole = new VirtualConsole();
1733
1994
  virtualConsole.on("log", (...args) => log("[JSDOM Log]", ...args));
1734
- virtualConsole.on("error", (...args) => console.error("[JSDOM Error]", ...args));
1995
+ virtualConsole.on("error", (...args) => warn("[JSDOM Error]", ...args));
1735
1996
  virtualConsole.on("warn", (...args) => warn("[JSDOM Warn]", ...args));
1736
1997
  const cookieJar = new CookieJar();
1737
1998
  this.harvestData.cookies.forEach((c) => {
@@ -2137,12 +2398,24 @@ var Executor = class {
2137
2398
  this.timings.quiescence_ms = Date.now() - quiescenceStart;
2138
2399
  const reason = this.matchFound && !this.findAll ? "(early exit on match)" : "";
2139
2400
  log(`[Executor] Quiescence reached in ${Date.now() - quiescenceStart}ms ${reason}`);
2401
+ if (!this.matchFound || this.findAll) {
2402
+ const didScroll = await this.performAutoScroll(window);
2403
+ if (didScroll) {
2404
+ if (this.moduleInFlight.size > 0) {
2405
+ await this.waitForModuleWork(this.moduleWaitMs);
2406
+ }
2407
+ log("[Executor] Waiting for post-scroll quiescence...");
2408
+ const postScrollStart = Date.now();
2409
+ try {
2410
+ await this.waitForQuiescence();
2411
+ } catch (e) {
2412
+ warn("[Executor] Post-scroll quiescence wait failed:", e);
2413
+ }
2414
+ this.timings.quiescence_ms += Date.now() - postScrollStart;
2415
+ }
2416
+ }
2140
2417
  const renderedHtml = this.serializeDocument(window);
2141
2418
  this.windowClosed = true;
2142
- try {
2143
- window.close();
2144
- } catch {
2145
- }
2146
2419
  const result = {
2147
2420
  logs: this.logs,
2148
2421
  matchedRequests: this.earlyMatches,
@@ -2153,6 +2426,7 @@ var Executor = class {
2153
2426
  };
2154
2427
  const shutdownGraceMs = this.clampMs(Number(process.env.PHANTOM_SHUTDOWN_GRACE_MS ?? 50), 10, 5e3);
2155
2428
  await new Promise((r) => setTimeout(r, shutdownGraceMs));
2429
+ this.clearTrackedTimers(window);
2156
2430
  this.unrefNewMessagePorts(initialActiveHandles);
2157
2431
  return result;
2158
2432
  } finally {
@@ -2259,6 +2533,12 @@ var Executor = class {
2259
2533
  };
2260
2534
  window.__phantom = {
2261
2535
  fetch: async (input, opts = {}) => {
2536
+ if (that.windowClosed) {
2537
+ return new (window.Response || global.Response)("", {
2538
+ status: 200,
2539
+ headers: toSafeResponseHeaders({})
2540
+ });
2541
+ }
2262
2542
  const norm = await normalizeFetchInput(input, opts);
2263
2543
  const start = Date.now();
2264
2544
  const headers = {
@@ -2336,6 +2616,7 @@ var Executor = class {
2336
2616
  }
2337
2617
  },
2338
2618
  dynamicImport: async (url) => {
2619
+ if (that.windowClosed) return {};
2339
2620
  const fullUrl = new URL(url, window.location.href).toString();
2340
2621
  const start = Date.now();
2341
2622
  const logEntry = {
@@ -2448,6 +2729,12 @@ var Executor = class {
2448
2729
  this.onloadend?.({ type: "loadend" });
2449
2730
  }
2450
2731
  async send(body) {
2732
+ if (that.windowClosed) {
2733
+ this.readyState = 4;
2734
+ this.status = 0;
2735
+ this.onloadend?.({ type: "loadend" });
2736
+ return;
2737
+ }
2451
2738
  const start = Date.now();
2452
2739
  const headers = {
2453
2740
  "User-Agent": window.navigator.userAgent,