// The browser's own async APIs run outside the event loop's bookkeeping, so a guest // whose only pending work is a fetch(), a blob.text() or a WebCrypto call would see the // loop go idle and exit before the answer, where Node keeps running (an undici socket, // a libuv job). Each wrapper holds the loop until its promise settles. // // Not wrapped: reads from a ReadableStream. A guest's own stream that never enqueues // must not keep the process alive, and Node doesn't either; fetch's body methods are // wrapped instead. import type { Loop } from "./loop"; const BODY = ["arrayBuffer", "blob", "bytes", "formData", "json", "text"]; const SUBTLE = [ "encrypt", "decrypt", "sign", "verify", "digest", "generateKey", "deriveKey", "deriveBits", "importKey", "exportKey", "wrapKey", "unwrapKey", ]; const WASM = ["compile", "instantiate", "compileStreaming", "instantiateStreaming"]; export function holdNativePromises(loop: Loop): void { const held = (p: unknown): unknown => { if (!(p instanceof Promise)) return p; loop.hold(); return p.finally(() => loop.release()); }; const wrap = (target: object | undefined, name: string): void => { // A worker's globals (fetch) live on WorkerGlobalScope.prototype, not on globalThis. let owner = target; while (owner && !Object.hasOwn(owner, name)) owner = Object.getPrototypeOf(owner) as object | undefined; const desc = owner && Object.getOwnPropertyDescriptor(owner, name); const original = desc?.value as ((...args: unknown[]) => unknown) | undefined; if (typeof original !== "function") return; const wrapped = { [name](this: unknown, ...args: unknown[]): unknown { return held(original.apply(this, args)); }, }[name]!; Object.defineProperty(wrapped, "length", { value: original.length }); Object.defineProperty(owner, name, { ...desc, value: wrapped }); }; wrap(globalThis, "fetch"); for (const name of BODY) { wrap(globalThis.Response?.prototype, name); wrap(globalThis.Request?.prototype, name); } for (const name of ["arrayBuffer", "bytes", "text"]) wrap(globalThis.Blob?.prototype, name); for (const name of SUBTLE) wrap(globalThis.SubtleCrypto?.prototype, name); for (const name of WASM) wrap(globalThis.WebAssembly, name); }