@grabbit-labs/dynafetch 0.2.2 → 0.2.4

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
  }
@@ -814,6 +815,34 @@ function shouldSkipDynamicScriptUrl(url, pageUrl, policy) {
814
815
  }
815
816
 
816
817
  // ../../src/phantom/execute.ts
818
+ var esbuildModulePromise = null;
819
+ var esbuildRefCount = 0;
820
+ async function acquireEsbuildModule() {
821
+ if (!esbuildModulePromise) {
822
+ esbuildModulePromise = import("esbuild");
823
+ }
824
+ esbuildRefCount++;
825
+ try {
826
+ return await esbuildModulePromise;
827
+ } catch (error2) {
828
+ esbuildRefCount = Math.max(0, esbuildRefCount - 1);
829
+ if (esbuildRefCount === 0) esbuildModulePromise = null;
830
+ throw error2;
831
+ }
832
+ }
833
+ function releaseEsbuildModule(esbuildMod) {
834
+ if (!esbuildMod) return;
835
+ esbuildRefCount = Math.max(0, esbuildRefCount - 1);
836
+ if (esbuildRefCount > 0) return;
837
+ const stopFn = esbuildMod?.stop || esbuildMod?.default?.stop;
838
+ if (typeof stopFn === "function") {
839
+ try {
840
+ stopFn.call(esbuildMod?.default ?? esbuildMod);
841
+ } catch {
842
+ }
843
+ }
844
+ esbuildModulePromise = null;
845
+ }
817
846
  var Executor = class {
818
847
  constructor(harvestData, options = null) {
819
848
  this.logs = [];
@@ -845,6 +874,10 @@ var Executor = class {
845
874
  this.moduleInFlight = /* @__PURE__ */ new Map();
846
875
  // entryUrl -> promise
847
876
  this.windowClosed = false;
877
+ this.esbuildModule = null;
878
+ this.originalGlobalMessageChannel = void 0;
879
+ this.originalGlobalMessagePort = void 0;
880
+ this.activeTimerHandles = /* @__PURE__ */ new Set();
848
881
  // Simple telemetry counters (useful for debugging).
849
882
  this.telemetry_stubbed = 0;
850
883
  this.telemetry_proxy = 0;
@@ -936,6 +969,33 @@ var Executor = class {
936
969
  if (!warning) return;
937
970
  this.warnings.add(warning);
938
971
  }
972
+ unrefNewMessagePorts(initialHandles) {
973
+ for (const handle of process._getActiveHandles()) {
974
+ if (initialHandles.has(handle)) continue;
975
+ if (handle?.constructor?.name !== "MessagePort") continue;
976
+ try {
977
+ handle.unref?.();
978
+ } catch {
979
+ }
980
+ }
981
+ }
982
+ clearTrackedTimers(window) {
983
+ for (const handle of this.activeTimerHandles) {
984
+ try {
985
+ window.clearTimeout?.(handle);
986
+ } catch {
987
+ }
988
+ try {
989
+ window.clearInterval?.(handle);
990
+ } catch {
991
+ }
992
+ try {
993
+ handle?.unref?.();
994
+ } catch {
995
+ }
996
+ }
997
+ this.activeTimerHandles.clear();
998
+ }
939
999
  applyDefaults(quiescence, moduleWaitMsOverride) {
940
1000
  const hardMaxCap = this.clampMs(Number(process.env.PHANTOM_QUIESCENCE_MAX_CAP_MS ?? 8e3), 500, 6e4);
941
1001
  const minWaitMs = this.clampMs(quiescence?.minWaitMs ?? 75, 0, 1e4);
@@ -1213,7 +1273,8 @@ var Executor = class {
1213
1273
  if (!this.windowClosed) window.eval(cached);
1214
1274
  return;
1215
1275
  }
1216
- const esbuildMod = await import("esbuild");
1276
+ const esbuildMod = this.esbuildModule ?? await acquireEsbuildModule();
1277
+ this.esbuildModule = esbuildMod;
1217
1278
  const buildFn = esbuildMod?.build || esbuildMod?.default?.build;
1218
1279
  if (typeof buildFn !== "function") {
1219
1280
  throw new Error("esbuild.build not available (esbuild import failed)");
@@ -1372,14 +1433,66 @@ var Executor = class {
1372
1433
  };
1373
1434
  }
1374
1435
  const g = globalThis;
1375
- if (!window.Headers && g.Headers) window.Headers = g.Headers;
1376
- if (!window.Request && g.Request) window.Request = g.Request;
1377
- if (!window.Response && g.Response) window.Response = g.Response;
1378
- if (!window.AbortController && g.AbortController) window.AbortController = g.AbortController;
1379
- if (!window.AbortSignal && g.AbortSignal) window.AbortSignal = g.AbortSignal;
1436
+ if (g.Headers) window.Headers = g.Headers;
1437
+ if (g.Response) window.Response = g.Response;
1438
+ if (g.AbortController) window.AbortController = g.AbortController;
1439
+ if (g.AbortSignal) window.AbortSignal = g.AbortSignal;
1380
1440
  if (!window.TextEncoder && g.TextEncoder) window.TextEncoder = g.TextEncoder;
1381
1441
  if (!window.TextDecoder && g.TextDecoder) window.TextDecoder = g.TextDecoder;
1382
1442
  if (!window.structuredClone && g.structuredClone) window.structuredClone = g.structuredClone.bind(g);
1443
+ const normalizeUrlForRequest = (value) => {
1444
+ if (typeof value !== "string") return value;
1445
+ try {
1446
+ return new URL(value, window.location.href).toString();
1447
+ } catch {
1448
+ return value;
1449
+ }
1450
+ };
1451
+ const normalizeAbortSignalForRequest = (signal) => {
1452
+ if (!signal || !g.AbortController || !g.AbortSignal) return signal;
1453
+ if (signal instanceof g.AbortSignal) return signal;
1454
+ if (typeof signal.aborted !== "boolean") return void 0;
1455
+ const controller = new g.AbortController();
1456
+ if (signal.aborted) {
1457
+ try {
1458
+ controller.abort(signal.reason);
1459
+ } catch {
1460
+ controller.abort();
1461
+ }
1462
+ return controller.signal;
1463
+ }
1464
+ if (typeof signal.addEventListener === "function") {
1465
+ try {
1466
+ signal.addEventListener("abort", () => {
1467
+ try {
1468
+ controller.abort(signal.reason);
1469
+ } catch {
1470
+ controller.abort();
1471
+ }
1472
+ }, { once: true });
1473
+ } catch {
1474
+ }
1475
+ }
1476
+ return controller.signal;
1477
+ };
1478
+ if (g.Request) {
1479
+ const NativeRequest = g.Request;
1480
+ const RequestShim = class Request extends NativeRequest {
1481
+ constructor(input, init) {
1482
+ const normalizedInput = typeof input === "string" ? normalizeUrlForRequest(input) : input instanceof URL ? normalizeUrlForRequest(input.toString()) : input;
1483
+ const normalizedInit = init && typeof init === "object" ? {
1484
+ ...init,
1485
+ signal: normalizeAbortSignalForRequest(init.signal)
1486
+ } : init;
1487
+ super(normalizedInput, normalizedInit);
1488
+ }
1489
+ };
1490
+ try {
1491
+ Object.setPrototypeOf(RequestShim, NativeRequest);
1492
+ } catch {
1493
+ }
1494
+ window.Request = RequestShim;
1495
+ }
1383
1496
  const makeStorage = () => {
1384
1497
  const store = /* @__PURE__ */ new Map();
1385
1498
  return {
@@ -1439,8 +1552,27 @@ var Executor = class {
1439
1552
  }
1440
1553
  {
1441
1554
  const _g = globalThis;
1442
- if (!window.MessageChannel && _g.MessageChannel) window.MessageChannel = _g.MessageChannel;
1443
- if (!window.MessagePort && _g.MessagePort) window.MessagePort = _g.MessagePort;
1555
+ if (_g.MessageChannel) {
1556
+ if (this.originalGlobalMessageChannel === void 0) {
1557
+ this.originalGlobalMessageChannel = _g.MessageChannel;
1558
+ }
1559
+ const NativeMessageChannel = _g.MessageChannel;
1560
+ const UnrefMessageChannel = class MessageChannel extends NativeMessageChannel {
1561
+ constructor() {
1562
+ super();
1563
+ this.port1?.unref?.();
1564
+ this.port2?.unref?.();
1565
+ }
1566
+ };
1567
+ window.MessageChannel = UnrefMessageChannel;
1568
+ _g.MessageChannel = UnrefMessageChannel;
1569
+ }
1570
+ if (_g.MessagePort) {
1571
+ if (this.originalGlobalMessagePort === void 0) {
1572
+ this.originalGlobalMessagePort = _g.MessagePort;
1573
+ }
1574
+ window.MessagePort = _g.MessagePort;
1575
+ }
1444
1576
  }
1445
1577
  if (!window.requestIdleCallback) {
1446
1578
  window.requestIdleCallback = (cb) => window.setTimeout(() => cb({
@@ -1559,8 +1691,41 @@ var Executor = class {
1559
1691
  };
1560
1692
  const _setTimeout = window.setTimeout?.bind(window);
1561
1693
  const _setInterval = window.setInterval?.bind(window);
1562
- if (_setTimeout) window.setTimeout = (cb, ms, ...rest) => _setTimeout(wrapCb(cb), ms, ...rest);
1563
- if (_setInterval) window.setInterval = (cb, ms, ...rest) => _setInterval(wrapCb(cb), ms, ...rest);
1694
+ const _clearTimeout = window.clearTimeout?.bind(window);
1695
+ const _clearInterval = window.clearInterval?.bind(window);
1696
+ if (_setTimeout) {
1697
+ window.setTimeout = (cb, ms, ...rest) => {
1698
+ let handle;
1699
+ const wrapped = wrapCb((...args) => {
1700
+ this.activeTimerHandles.delete(handle);
1701
+ return typeof cb === "function" ? cb(...args) : cb;
1702
+ });
1703
+ handle = _setTimeout(wrapped, ms, ...rest);
1704
+ this.activeTimerHandles.add(handle);
1705
+ handle?.unref?.();
1706
+ return handle;
1707
+ };
1708
+ }
1709
+ if (_setInterval) {
1710
+ window.setInterval = (cb, ms, ...rest) => {
1711
+ const handle = _setInterval(wrapCb(cb), ms, ...rest);
1712
+ this.activeTimerHandles.add(handle);
1713
+ handle?.unref?.();
1714
+ return handle;
1715
+ };
1716
+ }
1717
+ if (_clearTimeout) {
1718
+ window.clearTimeout = (handle) => {
1719
+ this.activeTimerHandles.delete(handle);
1720
+ return _clearTimeout(handle);
1721
+ };
1722
+ }
1723
+ if (_clearInterval) {
1724
+ window.clearInterval = (handle) => {
1725
+ this.activeTimerHandles.delete(handle);
1726
+ return _clearInterval(handle);
1727
+ };
1728
+ }
1564
1729
  if (window.queueMicrotask) {
1565
1730
  const _q = window.queueMicrotask.bind(window);
1566
1731
  window.queueMicrotask = (cb) => _q(wrapCb(cb));
@@ -1664,12 +1829,13 @@ var Executor = class {
1664
1829
  async execute() {
1665
1830
  const onNodeUncaught = (err) => this.recordExecutionError(err, "uncaughtException");
1666
1831
  const onNodeUnhandled = (reason) => this.recordExecutionError(reason, "unhandledRejection");
1832
+ const initialActiveHandles = new Set(process._getActiveHandles());
1667
1833
  process.on("uncaughtException", onNodeUncaught);
1668
1834
  process.on("unhandledRejection", onNodeUnhandled);
1669
1835
  try {
1670
1836
  const virtualConsole = new VirtualConsole();
1671
1837
  virtualConsole.on("log", (...args) => log("[JSDOM Log]", ...args));
1672
- virtualConsole.on("error", (...args) => console.error("[JSDOM Error]", ...args));
1838
+ virtualConsole.on("error", (...args) => warn("[JSDOM Error]", ...args));
1673
1839
  virtualConsole.on("warn", (...args) => warn("[JSDOM Warn]", ...args));
1674
1840
  const cookieJar = new CookieJar();
1675
1841
  this.harvestData.cookies.forEach((c) => {
@@ -2077,10 +2243,6 @@ var Executor = class {
2077
2243
  log(`[Executor] Quiescence reached in ${Date.now() - quiescenceStart}ms ${reason}`);
2078
2244
  const renderedHtml = this.serializeDocument(window);
2079
2245
  this.windowClosed = true;
2080
- try {
2081
- window.close();
2082
- } catch {
2083
- }
2084
2246
  const result = {
2085
2247
  logs: this.logs,
2086
2248
  matchedRequests: this.earlyMatches,
@@ -2091,8 +2253,21 @@ var Executor = class {
2091
2253
  };
2092
2254
  const shutdownGraceMs = this.clampMs(Number(process.env.PHANTOM_SHUTDOWN_GRACE_MS ?? 50), 10, 5e3);
2093
2255
  await new Promise((r) => setTimeout(r, shutdownGraceMs));
2256
+ this.clearTrackedTimers(window);
2257
+ this.unrefNewMessagePorts(initialActiveHandles);
2094
2258
  return result;
2095
2259
  } finally {
2260
+ const g = globalThis;
2261
+ if (this.originalGlobalMessageChannel !== void 0) {
2262
+ g.MessageChannel = this.originalGlobalMessageChannel;
2263
+ this.originalGlobalMessageChannel = void 0;
2264
+ }
2265
+ if (this.originalGlobalMessagePort !== void 0) {
2266
+ g.MessagePort = this.originalGlobalMessagePort;
2267
+ this.originalGlobalMessagePort = void 0;
2268
+ }
2269
+ releaseEsbuildModule(this.esbuildModule);
2270
+ this.esbuildModule = null;
2096
2271
  process.off("uncaughtException", onNodeUncaught);
2097
2272
  process.off("unhandledRejection", onNodeUnhandled);
2098
2273
  }
@@ -2185,6 +2360,12 @@ var Executor = class {
2185
2360
  };
2186
2361
  window.__phantom = {
2187
2362
  fetch: async (input, opts = {}) => {
2363
+ if (that.windowClosed) {
2364
+ return new (window.Response || global.Response)("", {
2365
+ status: 200,
2366
+ headers: toSafeResponseHeaders({})
2367
+ });
2368
+ }
2188
2369
  const norm = await normalizeFetchInput(input, opts);
2189
2370
  const start = Date.now();
2190
2371
  const headers = {
@@ -2262,6 +2443,7 @@ var Executor = class {
2262
2443
  }
2263
2444
  },
2264
2445
  dynamicImport: async (url) => {
2446
+ if (that.windowClosed) return {};
2265
2447
  const fullUrl = new URL(url, window.location.href).toString();
2266
2448
  const start = Date.now();
2267
2449
  const logEntry = {
@@ -2374,6 +2556,12 @@ var Executor = class {
2374
2556
  this.onloadend?.({ type: "loadend" });
2375
2557
  }
2376
2558
  async send(body) {
2559
+ if (that.windowClosed) {
2560
+ this.readyState = 4;
2561
+ this.status = 0;
2562
+ this.onloadend?.({ type: "loadend" });
2563
+ return;
2564
+ }
2377
2565
  const start = Date.now();
2378
2566
  const headers = {
2379
2567
  "User-Agent": window.navigator.userAgent,