@volter/tabnode 0.5.47 → 0.5.49

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/index.mjs CHANGED
@@ -5132,7 +5132,7 @@ function parse$1(urlString, parseQueryString = false, slashesDenoteHost = false)
5132
5132
  hostname: null,
5133
5133
  hash: __hash,
5134
5134
  search: __search,
5135
- query: parseQueryString ? Object.fromEntries(new URLSearchParams(__search || "")) : __search ? __search.slice(1) : null,
5135
+ query: parseQueryString ? Object.fromEntries(new URLSearchParams$1(__search || "")) : __search ? __search.slice(1) : null,
5136
5136
  pathname: __pathname || null,
5137
5137
  path: __pathname + (__search || "") || null,
5138
5138
  href: urlString
@@ -5206,7 +5206,7 @@ function format$1(urlObject) {
5206
5206
  if (typeof urlObject.query === "string") {
5207
5207
  result += "?" + urlObject.query;
5208
5208
  } else {
5209
- const params = new URLSearchParams();
5209
+ const params = new URLSearchParams$1();
5210
5210
  for (const [key, value] of Object.entries(urlObject.query)) {
5211
5211
  if (Array.isArray(value)) {
5212
5212
  for (const v of value) {
@@ -5235,7 +5235,7 @@ function resolve$2(from, to) {
5235
5235
  }
5236
5236
  }
5237
5237
  const URL$1 = globalThis.URL;
5238
- const URLSearchParams = globalThis.URLSearchParams;
5238
+ const URLSearchParams$1 = globalThis.URLSearchParams;
5239
5239
  function fileURLToPath(url2) {
5240
5240
  const urlObj = typeof url2 === "string" ? new globalThis.URL(url2) : url2;
5241
5241
  if (urlObj.protocol !== "file:") {
@@ -5258,7 +5258,7 @@ const url$1 = {
5258
5258
  format: format$1,
5259
5259
  resolve: resolve$2,
5260
5260
  URL: URL$1,
5261
- URLSearchParams,
5261
+ URLSearchParams: URLSearchParams$1,
5262
5262
  URLPattern: URLPattern$1,
5263
5263
  fileURLToPath,
5264
5264
  pathToFileURL
@@ -5267,7 +5267,7 @@ const urlShim = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
5267
5267
  __proto__: null,
5268
5268
  URL: URL$1,
5269
5269
  URLPattern: URLPattern$1,
5270
- URLSearchParams,
5270
+ URLSearchParams: URLSearchParams$1,
5271
5271
  default: url$1,
5272
5272
  fileURLToPath,
5273
5273
  format: format$1,
@@ -32424,6 +32424,68 @@ function guestPromise(owner) {
32424
32424
  }
32425
32425
  return constructor;
32426
32426
  }
32427
+ const kIsDisturbed = Symbol.for("nodejs.stream.disturbed");
32428
+ function isAsyncIterableBody(body) {
32429
+ if (body === null || typeof body !== "object") return false;
32430
+ if (typeof ReadableStream === "function" && body instanceof ReadableStream) return false;
32431
+ if (typeof Blob === "function" && body instanceof Blob) return false;
32432
+ if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return false;
32433
+ if (typeof FormData === "function" && body instanceof FormData) return false;
32434
+ if (body instanceof URLSearchParams) return false;
32435
+ return typeof body[Symbol.asyncIterator] === "function";
32436
+ }
32437
+ function refuseDisturbed(body) {
32438
+ const stream = body;
32439
+ const disturbed = stream[kIsDisturbed] !== void 0 ? Boolean(stream[kIsDisturbed]) : Boolean(stream.readableDidRead || stream.readableAborted);
32440
+ if (disturbed || stream.locked === true) throw new TypeError("Response body object should not be disturbed or locked");
32441
+ }
32442
+ function streamFrom(iterable) {
32443
+ let iterator;
32444
+ return new ReadableStream({
32445
+ async start() {
32446
+ iterator = iterable[Symbol.asyncIterator]();
32447
+ },
32448
+ async pull(controller) {
32449
+ for (; ; ) {
32450
+ const { done, value } = await iterator.next();
32451
+ if (done) {
32452
+ controller.close();
32453
+ return;
32454
+ }
32455
+ const bytes2 = Buffer$9.isBuffer(value) ? value : Buffer$9.from(value);
32456
+ if (bytes2.byteLength) {
32457
+ controller.enqueue(new Uint8Array(bytes2));
32458
+ return;
32459
+ }
32460
+ }
32461
+ },
32462
+ async cancel() {
32463
+ var _a2;
32464
+ await ((_a2 = iterator == null ? void 0 : iterator.return) == null ? void 0 : _a2.call(iterator));
32465
+ }
32466
+ }, { highWaterMark: 0 });
32467
+ }
32468
+ const REQUEST_INIT_MEMBERS = ["method", "headers", "referrer", "referrerPolicy", "mode", "credentials", "cache", "redirect", "integrity", "keepalive", "signal", "window", "duplex", "priority"];
32469
+ function withNodeRequestBody(init) {
32470
+ if (!init) return init;
32471
+ const body = init.body;
32472
+ if (!isAsyncIterableBody(body)) return init;
32473
+ refuseDisturbed(body);
32474
+ const copy2 = {};
32475
+ for (const member of Object.keys(init)) if (member !== "body") copy2[member] = init[member];
32476
+ for (const member of REQUEST_INIT_MEMBERS) {
32477
+ if (member in copy2) continue;
32478
+ const value = init[member];
32479
+ if (value !== void 0) copy2[member] = value;
32480
+ }
32481
+ copy2.body = streamFrom(body);
32482
+ return copy2;
32483
+ }
32484
+ function nodeResponseBody(body) {
32485
+ if (!isAsyncIterableBody(body)) return body;
32486
+ refuseDisturbed(body);
32487
+ return streamFrom(body);
32488
+ }
32427
32489
  let transport;
32428
32490
  const staticRequestBodies = /* @__PURE__ */ new WeakSet();
32429
32491
  function rememberRequestBodySource(request, input, init) {
@@ -32478,14 +32540,19 @@ function activity(owner, cancel) {
32478
32540
  function guestFetch(process2, fallback) {
32479
32541
  return function fetch2(input, init) {
32480
32542
  const adapter = transport;
32481
- if (!adapter) return fallback.call(globalThis, input, init);
32482
- const owner = __tokenForProcess(process2);
32543
+ const owner = adapter ? __tokenForProcess(process2) : null;
32483
32544
  const ended = () => {
32484
32545
  var _a2;
32485
32546
  return owner !== null && ((_a2 = __runFor(owner)) == null ? void 0 : _a2.process) !== process2;
32486
32547
  };
32487
32548
  const stopped2 = () => new DOMException("The requesting process has ended.", "AbortError");
32488
- if (ended()) return Promise.reject(stopped2());
32549
+ if (adapter && ended()) return Promise.reject(stopped2());
32550
+ try {
32551
+ init = withNodeRequestBody(init);
32552
+ } catch (error) {
32553
+ return Promise.reject(error);
32554
+ }
32555
+ if (!adapter) return fallback.call(globalThis, input, init);
32489
32556
  const context2 = { begin(cancel) {
32490
32557
  if (ended()) throw stopped2();
32491
32558
  return activity(owner, cancel);
@@ -32494,6 +32561,124 @@ function guestFetch(process2, fallback) {
32494
32561
  return owner === null ? dispatch() : enterRun(owner, dispatch);
32495
32562
  };
32496
32563
  }
32564
+ const INSTALLED = Symbol.for("tabnode.response.keepsSetCookie");
32565
+ function withCookies(own, given) {
32566
+ const headers = new Headers(own);
32567
+ if (!given) return headers;
32568
+ const cookies = [];
32569
+ if (typeof given.getSetCookie === "function") cookies.push(...given.getSetCookie());
32570
+ else given.forEach((value, name) => {
32571
+ if (name === "set-cookie") cookies.push(value);
32572
+ });
32573
+ if (cookies.length) {
32574
+ headers.delete("set-cookie");
32575
+ for (const cookie of cookies) headers.append("set-cookie", cookie);
32576
+ }
32577
+ const cookie2 = given.get("set-cookie2");
32578
+ if (cookie2 !== null) headers.set("set-cookie2", cookie2);
32579
+ return headers;
32580
+ }
32581
+ function givenHeaders(init) {
32582
+ const headers = init == null ? void 0 : init.headers;
32583
+ return headers === void 0 || headers === null ? void 0 : new Headers(headers);
32584
+ }
32585
+ function withHeaders(init, headers) {
32586
+ const status = init == null ? void 0 : init.status;
32587
+ const statusText = init == null ? void 0 : init.statusText;
32588
+ return {
32589
+ headers,
32590
+ ...status === void 0 ? {} : { status },
32591
+ ...statusText === void 0 ? {} : { statusText }
32592
+ };
32593
+ }
32594
+ function installNodeResponse(target) {
32595
+ var _a2;
32596
+ const Native = target.Response;
32597
+ if (typeof Native !== "function" || Native[INSTALLED]) return;
32598
+ const proto = Native.prototype;
32599
+ let nativeHeaders;
32600
+ for (let at2 = proto; at2 && !nativeHeaders; at2 = Object.getPrototypeOf(at2)) nativeHeaders = (_a2 = Object.getOwnPropertyDescriptor(at2, "headers")) == null ? void 0 : _a2.get;
32601
+ const nativeFormData = proto.formData;
32602
+ const nativeBlob = proto.blob;
32603
+ const nativeArrayBuffer = proto.arrayBuffer;
32604
+ const nativeClone = proto.clone;
32605
+ if (!nativeHeaders) return;
32606
+ const kept = /* @__PURE__ */ new WeakMap();
32607
+ const NodeResponse = function Response(body, init) {
32608
+ if (!new.target) throw new TypeError("Class constructor Response cannot be invoked without 'new'");
32609
+ const given = givenHeaders(init);
32610
+ const response = Reflect.construct(Native, [nodeResponseBody(body), given ? withHeaders(init, given) : init], new.target === NodeResponse ? Native : new.target);
32611
+ kept.set(response, withCookies(nativeHeaders.call(response), given));
32612
+ return response;
32613
+ };
32614
+ NodeResponse.prototype = proto;
32615
+ Object.defineProperty(NodeResponse, "length", { value: 0, configurable: true });
32616
+ Object.defineProperty(NodeResponse, INSTALLED, { value: true });
32617
+ Object.setPrototypeOf(NodeResponse, Object.getPrototypeOf(Native));
32618
+ Object.defineProperty(NodeResponse, "json", {
32619
+ value: function json(data2, init) {
32620
+ const given = givenHeaders(init);
32621
+ const response = Native.json(data2, given ? withHeaders(init, given) : init);
32622
+ kept.set(response, withCookies(nativeHeaders.call(response), given));
32623
+ return response;
32624
+ },
32625
+ writable: true,
32626
+ configurable: true
32627
+ });
32628
+ Object.defineProperty(NodeResponse, "redirect", { value: function redirect(url2, status) {
32629
+ return Native.redirect(url2, status);
32630
+ }, writable: true, configurable: true });
32631
+ Object.defineProperty(NodeResponse, "error", { value: function error() {
32632
+ return Native.error();
32633
+ }, writable: true, configurable: true });
32634
+ defineOnHost(proto, "headers", {
32635
+ get() {
32636
+ return kept.get(this) ?? nativeHeaders.call(this);
32637
+ },
32638
+ enumerable: true,
32639
+ configurable: true
32640
+ });
32641
+ defineOnHost(proto, "constructor", { value: NodeResponse, writable: true, enumerable: false, configurable: true });
32642
+ defineOnHost(proto, "clone", {
32643
+ value: function clone() {
32644
+ const copy2 = nativeClone.call(this);
32645
+ const headers = kept.get(this);
32646
+ if (headers) kept.set(copy2, new Headers(headers));
32647
+ return copy2;
32648
+ },
32649
+ writable: true,
32650
+ enumerable: true,
32651
+ configurable: true
32652
+ });
32653
+ const changedType = (response) => {
32654
+ const headers = kept.get(response);
32655
+ if (!headers) return void 0;
32656
+ const current3 = headers.get("content-type");
32657
+ return current3 === nativeHeaders.call(response).get("content-type") ? void 0 : current3;
32658
+ };
32659
+ defineOnHost(proto, "formData", {
32660
+ value: async function formData() {
32661
+ const type2 = changedType(this);
32662
+ if (type2 === void 0) return nativeFormData.call(this);
32663
+ const bytes2 = await nativeArrayBuffer.call(this);
32664
+ return nativeFormData.call(Reflect.construct(Native, [bytes2, type2 === null ? {} : { headers: { "content-type": type2 } }]));
32665
+ },
32666
+ writable: true,
32667
+ enumerable: true,
32668
+ configurable: true
32669
+ });
32670
+ defineOnHost(proto, "blob", {
32671
+ value: async function blob() {
32672
+ const type2 = changedType(this);
32673
+ if (type2 === void 0) return nativeBlob.call(this);
32674
+ return new Blob([await nativeArrayBuffer.call(this)], type2 === null ? {} : { type: type2 });
32675
+ },
32676
+ writable: true,
32677
+ enumerable: true,
32678
+ configurable: true
32679
+ });
32680
+ takeFromHost(target, "Response", NodeResponse);
32681
+ }
32497
32682
  function simpleHash(str) {
32498
32683
  let hash2 = 0;
32499
32684
  for (let i2 = 0; i2 < str.length; i2++) {
@@ -33692,6 +33877,63 @@ function installShellJobs(bash, host2) {
33692
33877
  if (shell.jobs.size === 0) shells$1.delete(id);
33693
33878
  return { stdout: held.stdout, stderr: held.stderr + stderr, exitCode: status };
33694
33879
  }));
33880
+ for (const shell of ["sh", "bash"]) {
33881
+ bash.registerCommand(defineCommand(shell, async (args, ctx) => {
33882
+ var _a2;
33883
+ const exec = ctx.exec;
33884
+ if (!exec) return { stdout: "", stderr: `bash: ${shell}: needs a shell that can run a script
33885
+ `, exitCode: 1 };
33886
+ let script;
33887
+ let zero2;
33888
+ let positional;
33889
+ let sourced;
33890
+ let at2 = 0;
33891
+ while (at2 < args.length && /^[-+][a-bd-zA-Z]+$/u.test(args[at2])) at2++;
33892
+ args = args.slice(at2);
33893
+ if (args[0] === "-c" && args.length >= 2) {
33894
+ script = args[1];
33895
+ zero2 = args[2] ?? shell;
33896
+ positional = args.slice(3);
33897
+ } else if (args.length === 0) {
33898
+ if (!((_a2 = ctx.stdin) == null ? void 0 : _a2.trim())) return { stdout: "", stderr: "", exitCode: 0 };
33899
+ script = ctx.stdin;
33900
+ zero2 = shell;
33901
+ positional = [];
33902
+ } else {
33903
+ const path2 = ctx.fs.resolvePath(ctx.cwd, args[0]);
33904
+ try {
33905
+ script = String(await ctx.fs.readFile(path2));
33906
+ } catch {
33907
+ return { stdout: "", stderr: `${shell}: ${args[0]}: No such file or directory
33908
+ `, exitCode: 127 };
33909
+ }
33910
+ zero2 = args[0];
33911
+ positional = args.slice(1);
33912
+ sourced = path2;
33913
+ }
33914
+ if (script.startsWith("#!")) {
33915
+ const newline = script.indexOf("\n");
33916
+ if (newline !== -1) script = script.slice(newline + 1);
33917
+ }
33918
+ const env = { ...ctx.exportedEnv ?? {}, "0": zero2, "#": String(positional.length), "@": positional.join(" "), "*": positional.join(" ") };
33919
+ positional.forEach((word, index2) => {
33920
+ env[String(index2 + 1)] = word;
33921
+ });
33922
+ const token = ctx.env.get(PROCESS_TOKEN_ENV);
33923
+ if (token) env[PROCESS_TOKEN_ENV] = token;
33924
+ if (sourced !== void 0) script = `. ${sourced.replace(/'/gu, "'\\''").replace(/^(.*)$/su, "'$1'")}`;
33925
+ const result = await exec(script, { env, cwd: ctx.cwd, stdin: ctx.stdin, signal: ctx.signal });
33926
+ return { stdout: result.stdout ?? "", stderr: result.stderr ?? "", exitCode: result.exitCode ?? 0 };
33927
+ }));
33928
+ }
33929
+ bash.registerCommand(defineCommand("exec", async (args, ctx) => {
33930
+ if (args.length === 0) return { stdout: "", stderr: "", exitCode: 0 };
33931
+ if (!ctx.exec) return { stdout: "", stderr: "bash: exec: needs a shell that can run a line\n", exitCode: 1 };
33932
+ const line = args.map((word) => /^[A-Za-z0-9_./:=@%+,-]+$/u.test(word) ? word : `'${word.replace(/'/gu, "'\\''")}'`).join(" ");
33933
+ const env = Object.fromEntries(ctx.env);
33934
+ const result = await ctx.exec(line, { env, cwd: ctx.cwd, replaceEnv: true });
33935
+ return { stdout: result.stdout ?? "", stderr: result.stderr ?? "", exitCode: result.exitCode ?? 0 };
33936
+ }));
33695
33937
  bash.registerCommand(defineCommand("kill", async (args) => {
33696
33938
  let signal = "SIGTERM";
33697
33939
  let index2 = 0;
@@ -33892,6 +34134,18 @@ function registerRunStreams(token, streams2) {
33892
34134
  }
33893
34135
  function releaseRunStreams(token) {
33894
34136
  _runStreams.delete(token);
34137
+ shellRuns.delete(token);
34138
+ hostedNodes.delete(token);
34139
+ }
34140
+ const shellRuns = /* @__PURE__ */ new Set();
34141
+ const hostedNodes = /* @__PURE__ */ new Set();
34142
+ function nestedNodeToken(parentToken, args, cwd) {
34143
+ var _a2;
34144
+ const token = `child-${__nextChildRun++}`;
34145
+ setRunPid(token, mintPid(), ((_a2 = runPid(parentToken)) == null ? void 0 : _a2.pid) ?? 0, { argv: ["node", ...args], ...cwd ? { cwd } : {} });
34146
+ const parentStreams = runStreamsFor(parentToken);
34147
+ if (parentStreams) registerRunStreams(token, parentStreams);
34148
+ return token;
33895
34149
  }
33896
34150
  function runStreamsFor(token) {
33897
34151
  return _runStreams.get(token);
@@ -33983,19 +34237,26 @@ function initChildProcess(vfs2) {
33983
34237
  if (!tree2) {
33984
34238
  return { stdout: "", stderr: "VFS not initialized\n", exitCode: 1 };
33985
34239
  }
33986
- const runToken = runTokenOf(ctx);
34240
+ const parentToken = runTokenOf(ctx);
34241
+ const nested = parentToken !== null && (shellRuns.has(parentToken) || hostedNodes.has(parentToken));
34242
+ const runToken = nested ? nestedNodeToken(parentToken, args, ctx.cwd) : parentToken;
33987
34243
  const streams2 = runToken === null ? void 0 : runStreamsFor(runToken);
33988
34244
  const processHost = nodeProcessHostFor(runToken);
33989
34245
  if (processHost && runToken !== null) {
33990
- return runHostedNode(processHost, {
33991
- token: runToken,
33992
- argv: [...args],
33993
- cwd: ctx.cwd,
33994
- filesystem: tree2,
33995
- env: guestEnvironmentOf(ctx),
33996
- ...typeof ctx.stdin === "string" ? { stdin: ctx.stdin } : {},
33997
- ...streams2 ? { streams: streams2 } : {}
33998
- });
34246
+ if (!nested) hostedNodes.add(runToken);
34247
+ try {
34248
+ return await runHostedNode(processHost, {
34249
+ token: runToken,
34250
+ argv: [...args],
34251
+ cwd: ctx.cwd,
34252
+ filesystem: tree2,
34253
+ env: guestEnvironmentOf(ctx),
34254
+ ...typeof ctx.stdin === "string" ? { stdin: ctx.stdin } : {},
34255
+ ...streams2 ? { streams: streams2 } : {}
34256
+ });
34257
+ } finally {
34258
+ if (nested) releaseRunStreams(runToken);
34259
+ }
33999
34260
  }
34000
34261
  const execArgv = [];
34001
34262
  let first = 0;
@@ -34901,6 +35162,7 @@ function existsInTree(path2) {
34901
35162
  return false;
34902
35163
  }
34903
35164
  }
35165
+ const SHELL_PROGRAMS = /* @__PURE__ */ new Set(["sh", "bash", "dash", "/bin/sh", "/bin/bash", "/usr/bin/sh", "/usr/bin/bash"]);
34904
35166
  function engineProgramFor(file, cwd) {
34905
35167
  const name = __substrateProgramName(file);
34906
35168
  if (!name.includes("/")) return name;
@@ -34947,6 +35209,7 @@ function startChildRun(request) {
34947
35209
  const controller = new AbortController();
34948
35210
  const pendingStdin = [];
34949
35211
  const admittedNode = nodeProcessHostInstalled() && engineProgramFor(request.file, request.cwd) === "node";
35212
+ if (!admittedNode) shellRuns.add(token);
34950
35213
  const hostTerminal = request.terminal !== void 0 && hostExecutor() !== null && !admittedNode;
34951
35214
  let wakeInput;
34952
35215
  let liveInput = false;
@@ -35045,7 +35308,8 @@ function startChildRun(request) {
35045
35308
  const begin = () => {
35046
35309
  if (started2 || finished) return;
35047
35310
  started2 = true;
35048
- const engineFirst = !hostTerminal && programExists(request.file, request.cwd, request.env);
35311
+ const shellChild = SHELL_PROGRAMS.has(engineProgramFor(request.file, request.cwd)) && hostExecutor() !== null;
35312
+ const engineFirst = !hostTerminal && !shellChild && programExists(request.file, request.cwd, request.env);
35049
35313
  liveInput = !hostTerminal && !admittedNode && request.stdinIsPipe && !engineFirst;
35050
35314
  if (liveInput) {
35051
35315
  pendingStdin.unshift(...initialStdin);
@@ -40103,6 +40367,7 @@ forGuestRealm(() => {
40103
40367
  const kept = /* @__PURE__ */ new WeakMap();
40104
40368
  class NodeRequest extends Native {
40105
40369
  constructor(input, init) {
40370
+ init = withNodeRequestBody(init);
40106
40371
  super(input, init);
40107
40372
  rememberRequestBodySource(this, input, init);
40108
40373
  const asked = new Headers((init == null ? void 0 : init.headers) ?? (input instanceof Native ? input.headers : void 0));
@@ -40126,6 +40391,9 @@ forGuestRealm(() => {
40126
40391
  takeFromHost(globalThis, "Request", NodeRequest);
40127
40392
  }
40128
40393
  });
40394
+ forGuestRealm(() => {
40395
+ installNodeResponse(globalThis);
40396
+ });
40129
40397
  forGuestRealm(() => {
40130
40398
  const kIsClosedPromise = Symbol.for("nodejs.webstream.isClosedPromise");
40131
40399
  const Native = globalThis.ReadableStream;
@@ -40428,7 +40696,7 @@ class WorkerRuntime {
40428
40696
  this.worker = new Worker(
40429
40697
  new URL(
40430
40698
  /* @vite-ignore */
40431
- "/assets/runtime-worker-4iI8R3l-.js",
40699
+ "/assets/runtime-worker-C1IV4qnr.js",
40432
40700
  import.meta.url
40433
40701
  ),
40434
40702
  { type: "module" }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The init with an async-iterable body read as a stream, or the init itself.
3
+ * The body is read once; the replacement is built member by member (an init
4
+ * may carry its members on getters of its prototype, as a `Request` passed as
5
+ * an init does), with the init's own other members as they are.
6
+ */
7
+ export declare function withNodeRequestBody<T extends RequestInit | undefined>(init: T): T;
8
+ /** A response body as undici takes it: an async iterable read as a stream, anything else as given. */
9
+ export declare function nodeResponseBody(body: unknown): unknown;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Install the corrected constructor on `target` (the realm's global object),
3
+ * over the `Response` it has. Every property it changes is remembered for the
4
+ * host to have back (`restoreHostGlobals`); a realm that has it already is left.
5
+ */
6
+ export declare function installNodeResponse(target: {
7
+ Response?: typeof Response;
8
+ }): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/tabnode",
3
- "version": "0.5.47",
3
+ "version": "0.5.49",
4
4
  "description": "Node's own library, in a browser tab.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,4 +1,5 @@
1
1
  /** Host Fetch I/O uses the same process-owned liveness registry as sockets. */
2
+ import { withNodeRequestBody } from './node-body';
2
3
  import type { Process } from './shims/process';
3
4
  import { __tokenForProcess, __runFor, enterRun, type ProcessToken } from './process-tokens';
4
5
  import { registerHandle, __adoptHandle, refHandle, unrefHandle, releaseHandle } from './node-lib/binding/handles';
@@ -83,11 +84,14 @@ function activity(owner: ProcessToken | null, cancel: () => void): FetchActivity
83
84
  export function guestFetch(process: Process, fallback: typeof globalThis.fetch): typeof globalThis.fetch {
84
85
  return function fetch(input, init) {
85
86
  const adapter = transport;
86
- if (!adapter) return fallback.call(globalThis, input, init);
87
- const owner = __tokenForProcess(process);
87
+ const owner = adapter ? __tokenForProcess(process) : null;
88
88
  const ended = () => owner !== null && __runFor(owner)?.process !== process;
89
89
  const stopped = () => new DOMException('The requesting process has ended.', 'AbortError');
90
- if (ended()) return Promise.reject(stopped());
90
+ if (adapter && ended()) return Promise.reject(stopped());
91
+ // A stream body is read as undici reads it, whichever door the fetch
92
+ // leaves by; a body undici refuses rejects the fetch, as Node's does.
93
+ try { init = withNodeRequestBody(init); } catch (error) { return Promise.reject(error); }
94
+ if (!adapter) return fallback.call(globalThis, input, init);
91
95
  const context: FetchTransportContext = { begin(cancel) {
92
96
  if (ended()) throw stopped();
93
97
  return activity(owner, cancel);
@@ -0,0 +1,88 @@
1
+ /**
2
+ * A body as Node's fetch (undici's `extractBody`) accepts it: besides what a
3
+ * browser takes, any async iterable (a Node `Readable`, an `http.IncomingMessage`,
4
+ * an async generator) is read as a stream of its chunks, each a `Buffer`,
5
+ * a typed array or a string (UTF-8). A browser's `Request` and `Response` take
6
+ * such an object for a string: `new Request(url, { method: 'POST', body:
7
+ * incomingMessage, duplex: 'half' })` has the body "[object Object]". Next's
8
+ * App Router builds every route handler's request exactly that way (its
9
+ * request adapter passes the Node request as the body, "handled by undici"),
10
+ * so every POST a route handler read was that string, and NextAuth's
11
+ * credentials callback found no CSRF token in it.
12
+ *
13
+ * Only such a body is changed; every other init is handed on untouched.
14
+ */
15
+ import { Buffer } from './node-lib/buffer-module';
16
+
17
+ /** The mark Node's streams carry once read (`internal/streams/utils`, `kIsDisturbed`). */
18
+ const kIsDisturbed = Symbol.for('nodejs.stream.disturbed');
19
+
20
+ function isAsyncIterableBody(body: unknown): body is AsyncIterable<unknown> {
21
+ if (body === null || typeof body !== 'object') return false;
22
+ if (typeof ReadableStream === 'function' && body instanceof ReadableStream) return false;
23
+ if (typeof Blob === 'function' && body instanceof Blob) return false;
24
+ if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return false;
25
+ if (typeof FormData === 'function' && body instanceof FormData) return false;
26
+ if (body instanceof URLSearchParams) return false;
27
+ return typeof (body as { [Symbol.asyncIterator]?: unknown })[Symbol.asyncIterator] === 'function';
28
+ }
29
+
30
+ /** Node's `isDisturbed`, and undici's refusal of a body already read or locked. */
31
+ function refuseDisturbed(body: object): void {
32
+ const stream = body as { locked?: unknown; readableDidRead?: unknown; readableAborted?: unknown; [kIsDisturbed]?: unknown };
33
+ const disturbed = stream[kIsDisturbed] !== undefined ? Boolean(stream[kIsDisturbed]) : Boolean(stream.readableDidRead || stream.readableAborted);
34
+ if (disturbed || stream.locked === true) throw new TypeError('Response body object should not be disturbed or locked');
35
+ }
36
+
37
+ /**
38
+ * undici's `ReadableStreamFrom`: the iterator made when the stream starts,
39
+ * each chunk `Buffer.from`'d as Node's is (a chunk `Buffer.from` refuses
40
+ * errors the stream), an empty chunk passed over for the next, and the
41
+ * iterator returned, with no argument, on cancel.
42
+ */
43
+ function streamFrom(iterable: AsyncIterable<unknown>): ReadableStream<Uint8Array> {
44
+ let iterator: AsyncIterator<unknown> | undefined;
45
+ return new ReadableStream<Uint8Array>({
46
+ async start() { iterator = iterable[Symbol.asyncIterator](); },
47
+ async pull(controller) {
48
+ for (;;) {
49
+ const { done, value } = await iterator!.next();
50
+ if (done) { controller.close(); return; }
51
+ const bytes = (Buffer.isBuffer(value) ? value : (Buffer.from as (chunk: unknown) => Uint8Array)(value)) as Uint8Array;
52
+ if (bytes.byteLength) { controller.enqueue(new Uint8Array(bytes)); return; }
53
+ }
54
+ },
55
+ async cancel() { await iterator?.return?.(); },
56
+ }, { highWaterMark: 0 });
57
+ }
58
+
59
+ const REQUEST_INIT_MEMBERS = ['method', 'headers', 'referrer', 'referrerPolicy', 'mode', 'credentials', 'cache', 'redirect', 'integrity', 'keepalive', 'signal', 'window', 'duplex', 'priority'] as const;
60
+
61
+ /**
62
+ * The init with an async-iterable body read as a stream, or the init itself.
63
+ * The body is read once; the replacement is built member by member (an init
64
+ * may carry its members on getters of its prototype, as a `Request` passed as
65
+ * an init does), with the init's own other members as they are.
66
+ */
67
+ export function withNodeRequestBody<T extends RequestInit | undefined>(init: T): T {
68
+ if (!init) return init;
69
+ const body = (init as RequestInit).body as unknown;
70
+ if (!isAsyncIterableBody(body)) return init;
71
+ refuseDisturbed(body);
72
+ const copy: Record<string, unknown> = {};
73
+ for (const member of Object.keys(init)) if (member !== 'body') copy[member] = (init as Record<string, unknown>)[member];
74
+ for (const member of REQUEST_INIT_MEMBERS) {
75
+ if (member in copy) continue;
76
+ const value = (init as Record<string, unknown>)[member];
77
+ if (value !== undefined) copy[member] = value;
78
+ }
79
+ copy.body = streamFrom(body);
80
+ return copy as T;
81
+ }
82
+
83
+ /** A response body as undici takes it: an async iterable read as a stream, anything else as given. */
84
+ export function nodeResponseBody(body: unknown): unknown {
85
+ if (!isAsyncIterableBody(body)) return body;
86
+ refuseDisturbed(body);
87
+ return streamFrom(body);
88
+ }