visaljs 0.0.8 → 0.0.10

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.
Files changed (54) hide show
  1. package/README.md +6 -3
  2. package/THIRD_PARTY_LICENSES +1 -1
  3. package/cli.js +2782 -770
  4. package/node/include/js_node.h +1 -0
  5. package/node/prelude/_http_agent.js +5 -0
  6. package/node/prelude/_http_server.js +7 -0
  7. package/node/prelude/dns.js +7 -1
  8. package/node/prelude/events.js +7 -3
  9. package/node/prelude/http.js +693 -285
  10. package/node/prelude/https.js +14 -3
  11. package/node/prelude/internal/events_extras.js +2 -5
  12. package/node/prelude/internal/features.js +17 -0
  13. package/node/prelude/internal/http_agent.js +88 -2
  14. package/node/prelude/internal/http_chars.ts +22 -0
  15. package/node/prelude/internal/http_symbols.js +3 -0
  16. package/node/prelude/internal/net_bound.js +123 -0
  17. package/node/prelude/internal/net_onread.js +73 -0
  18. package/node/prelude/internal/net_tos.js +54 -0
  19. package/node/prelude/internal/perf_observe.js +7 -1
  20. package/node/prelude/internal/tls/wrap.js +2 -1
  21. package/node/prelude/module.js +42 -18
  22. package/node/prelude/net.js +212 -61
  23. package/node/prelude/perf_hooks.js +4 -0
  24. package/node/src/host_http.c +151 -21
  25. package/node/src/host_net.c +75 -9
  26. package/node/src/host_sys.c +9 -0
  27. package/package.json +1 -1
  28. package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
  29. package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
  30. package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
  31. package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
  32. package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
  33. package/runtime/include/js_atoms.h +4 -0
  34. package/runtime/include/js_buffer.h +185 -0
  35. package/runtime/include/js_builtins.h +53 -0
  36. package/runtime/include/js_exception.h +3 -0
  37. package/runtime/include/js_function.h +1 -1
  38. package/runtime/include/js_object.h +21 -3
  39. package/runtime/include/js_ops.h +27 -0
  40. package/runtime/include/js_runtime.h +1 -0
  41. package/runtime/include/js_web.h +3 -3
  42. package/runtime/include/jscontext.h +15 -0
  43. package/runtime/src/atoms.c +37 -3
  44. package/runtime/src/builtins.c +17 -4
  45. package/runtime/src/events.c +19 -0
  46. package/runtime/src/function.c +37 -10
  47. package/runtime/src/gc.c +7 -5
  48. package/runtime/src/internal.h +4 -4
  49. package/runtime/src/iterator.c +5 -1
  50. package/runtime/src/object.c +42 -6
  51. package/runtime/src/regexp.c +569 -12
  52. package/runtime/src/shape.c +68 -1
  53. package/runtime/src/typedarray.c +23 -0
  54. package/runtime/src/weak.c +95 -24
@@ -9,15 +9,18 @@
9
9
  // Ported from Node.js (Copyright Node.js contributors, MIT): node/third_party/nodejs/LICENSE.
10
10
  import {
11
11
  errnoMessage, errnoName, netAddress, netBind, netCallback, netChmod, netClose, netConnect, netGuessHandle, netKeepAlive, netListen,
12
- netNoDelay, netOpen, netOpenFd, netReadStart, netReadStop, netRef, netReset, netShutdown, netWrite, timerUnref,
12
+ loopNow, netFd, netNoDelay, netOpen, netOpenFd, netReadStart, netReadStop, netRef, netReset, netShutdown, netWrite, timerUnref,
13
13
  } from "visaljs:host";
14
- import { lookup } from "./dns.js";
14
+ import { ADDRCONFIG, lookup } from "./dns.js";
15
15
  import { EventEmitter } from "./events.js";
16
16
  import { bindContext } from "./internal/async_context.js";
17
- import { codeError, invalidArgType, invalidArgValue, notSupported, outOfRange, validateFunction, validateString } from "./internal/errors.js";
17
+ import { codeError, invalidArgType, invalidArgValue, outOfRange, validateFunction, validateString } from "./internal/errors.js";
18
18
  import { isIP, isIPv4, isIPv6 } from "./internal/net.js";
19
19
  import { BlockList, SocketAddress } from "./internal/socketaddress.js";
20
+ import { AbortError } from "./internal/streams/errors.js";
21
+ import { validateInt32 } from "./internal/streams/validators.js";
20
22
  import { Duplex } from "./stream.js";
23
+ import { callableClass, feature, features, lazyClass, lazyFunction } from "./internal/features.js";
21
24
 
22
25
  export { BlockList, isIP, isIPv4, isIPv6, SocketAddress };
23
26
 
@@ -27,12 +30,6 @@ const UV_EOF = -4095;
27
30
  const UV_EINVAL = -22;
28
31
  const TIMEOUT_MAX = 2 ** 31 - 1;
29
32
 
30
- /* a monotonic clock in ms */
31
- const now = () => {
32
- const [seconds, nanoseconds] = process.hrtime();
33
- return seconds * 1e3 + nanoseconds / 1e6;
34
- };
35
-
36
33
  /* ---- Node's error shapes ------------------------------------------------------------------- */
37
34
 
38
35
  /** `read ECONNRESET` */
@@ -81,6 +78,9 @@ function validatePort(port, name = "Port", allowZero = true) {
81
78
  return +port;
82
79
  }
83
80
 
81
+ /* for internal/net_bound.js (BoundSocket) */
82
+ features.net = { errnoException, exceptionWithHostPort, validatePort };
83
+
84
84
  const toNumber = (x) => ((x = Number(x)) >= 0 ? x : false);
85
85
  const isPipeName = (s) => typeof s === "string" && toNumber(s) === false;
86
86
 
@@ -120,6 +120,7 @@ export function setDefaultAutoSelectFamilyAttemptTimeout(value) {
120
120
 
121
121
  /* a descriptor as a handle: Node's createHandle */
122
122
  function handleForFd(fd, cb) {
123
+ validateInt32(fd, "fd", 0);
123
124
  const type = netGuessHandle(fd);
124
125
  if (type !== "PIPE" && type !== "TCP") throw codeError(TypeError, "ERR_INVALID_FD_TYPE", "Unsupported fd type: " + type);
125
126
  return netOpen(type === "PIPE", bindContext(cb));
@@ -129,6 +130,14 @@ function validateBlockList(value, name) {
129
130
  if (value !== undefined && !BlockList.isBlockList(value)) throw invalidArgType(name, "an net.BlockList", value);
130
131
  }
131
132
 
133
+ /* Node's ERR_INVALID_ADDRESS_FAMILY, with the host and port */
134
+ function addressFamilyError(addressType, host, port) {
135
+ const err = codeError(RangeError, "ERR_INVALID_ADDRESS_FAMILY", "Invalid address family: " + addressType + " " + host + ":" + port);
136
+ err.host = host;
137
+ err.port = port;
138
+ return err;
139
+ }
140
+
132
141
  const ipBlocked = (ip) => codeError(Error, "ERR_IP_BLOCKED", "IP(" + ip + ") is blocked by net.BlockList");
133
142
 
134
143
  /* ---- Socket -------------------------------------------------------------------------------- */
@@ -148,13 +157,30 @@ function writeAfterFIN(chunk, encoding, cb) {
148
157
  return false;
149
158
  }
150
159
 
151
- export class Socket extends Duplex {
160
+ class SocketClass extends Duplex {
152
161
  constructor(options) {
153
162
  if (typeof options === "number") options = { fd: options };
154
163
  else options = { ...options };
164
+ if (options.objectMode) throw invalidArgValue("options.objectMode", "is not supported", options.objectMode);
165
+ if (options.readableObjectMode || options.writableObjectMode) {
166
+ const name = options.readableObjectMode ? "readableObjectMode" : "writableObjectMode";
167
+ throw invalidArgValue("options." + name, "is not supported", options[name]);
168
+ }
155
169
  /* Node's: not half-open by default, 'close' emitted by the socket itself, strings written as given */
156
170
  const allowHalfOpen = Boolean(options.allowHalfOpen);
157
- super({ ...options, allowHalfOpen, emitClose: false, autoDestroy: true, decodeStrings: false });
171
+ super({ ...options, signal: undefined, allowHalfOpen, emitClose: false, autoDestroy: true, decodeStrings: false });
172
+ /* the signal option, as Node's socket has it: aborted, the socket is destroyed with an AbortError
173
+ * (one already aborted: on the next tick, once connect() has begun) */
174
+ if (options.signal !== undefined) {
175
+ const signal = options.signal;
176
+ if (signal === null || typeof signal !== "object" || !("aborted" in signal)) throw invalidArgType("options.signal", "an instance of AbortSignal", signal);
177
+ const onAbort = () => this.destroy(new AbortError(undefined, { cause: signal.reason }));
178
+ if (signal.aborted) process.nextTick(onAbort);
179
+ else {
180
+ signal.addEventListener("abort", onAbort, { once: true });
181
+ this.once("close", () => signal.removeEventListener("abort", onAbort));
182
+ }
183
+ }
158
184
  this.connecting = false;
159
185
  this._hadError = false;
160
186
  this._handle = null; /* the host handle's id while open */
@@ -178,9 +204,16 @@ export class Socket extends Duplex {
178
204
  this._keepAlive = options.keepAlive === true ? [true, ~~((options.keepAliveInitialDelay ?? 0) / 1000)] : null;
179
205
  this._unref = false;
180
206
  this._timer = null; /* the timeout: an unref'd timer, re-armed for the idle time left when it fires */
181
- this._timeoutMs = 0;
207
+ this._timerDue = 0; /* when _timer fires (loopNow's clock) */
208
+ this._timeoutMs = 0; /* 0: off; a pending _timer then finds nothing to do */
182
209
  this._lastActive = 0;
183
210
  this._onread = null;
211
+ if (options.typeOfService !== undefined) feature("validateTypeOfService")(options.typeOfService);
212
+ this._tos = options.typeOfService; /* internal/net_tos.js's: the type of service, set at connect when given before it */
213
+ this._boundSource = false; /* the handle is an adopted BoundSocket's */
214
+ this._boundPath = undefined; /* an adopted pipe BoundSocket's path, its local address */
215
+ this._perfConnect = null; /* a TCP connect's start, while a PerformanceObserver takes 'net' entries */ /* set at connect when given before it */
216
+ this._onreadRest = null; /* internal/net_onread.js's: the bytes read but not yet given when reading stopped */
184
217
  this.timeout = undefined;
185
218
  this.autoSelectFamilyAttemptedAddresses = undefined;
186
219
  validateBlockList(options.blockList, "options.blockList");
@@ -188,10 +221,28 @@ export class Socket extends Duplex {
188
221
  const onread = options.onread;
189
222
  if (onread !== null && typeof onread === "object" && (typeof onread.buffer === "function" || onread.buffer instanceof Uint8Array) &&
190
223
  typeof onread.callback === "function") {
224
+ feature("onread"); /* internal/net_onread.js's */
191
225
  this._onread = onread;
192
226
  }
193
227
  this.on("end", onReadableStreamEnd);
194
- if (options.handle !== undefined) {
228
+ /* an adopted BoundSocket's handle: bound, not yet connected, so not read until it is */
229
+ const bound = options.handle !== undefined ? features.adoptBoundSocket?.(options.handle) : null;
230
+ if (bound) {
231
+ const { id, path } = bound;
232
+ this._handle = id;
233
+ this._boundSource = true;
234
+ this._isPipe = path !== undefined;
235
+ netCallback(id, bindContext((event, value) => this._onEvent(event, value, id)));
236
+ if (path !== undefined) {
237
+ this._boundPath = path;
238
+ /* a pipe's bind leaves its stream unopened: opened on its own descriptor, as Node's */
239
+ const fd = netFd(id);
240
+ if (fd >= 0) {
241
+ const err = netOpenFd(id, fd);
242
+ if (err) throw errnoException(err, "open");
243
+ }
244
+ }
245
+ } else if (options.handle !== undefined) {
195
246
  const id = options.handle;
196
247
  this._isPipe = !!options._isPipe;
197
248
  this._attach(id);
@@ -209,7 +260,7 @@ export class Socket extends Duplex {
209
260
  }
210
261
  this._attach(id);
211
262
  }
212
- if (this._handle !== null && options.readable !== false) {
263
+ if (this._handle !== null && !bound && options.readable !== false) {
213
264
  if (options.pauseOnCreate) this._readableState.flowing = false;
214
265
  else if (!options.manualStart) this.read(0);
215
266
  }
@@ -257,7 +308,7 @@ export class Socket extends Duplex {
257
308
  this._bytesRead += value.byteLength;
258
309
  this._unrefTimer();
259
310
  let more;
260
- if (this._onread) more = this._deliver(value);
311
+ if (this._onread) more = features.onread.deliver(this, value);
261
312
  else more = this.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
262
313
  if (!more) {
263
314
  this._reading = false;
@@ -273,22 +324,31 @@ export class Socket extends Duplex {
273
324
  this.destroy(errnoException(value, "read"));
274
325
  return;
275
326
  }
327
+ if (this._onread) features.onread.eof(this);
276
328
  this.push(null);
277
329
  this.read(0);
278
330
  }
279
331
 
280
- /* onread: the bytes in the user's buffer, a buffer's worth at a time; false stops reading */
281
- _deliver(bytes) {
282
- let more = true;
283
- for (let at = 0; at < bytes.length;) {
284
- const { buffer, callback } = this._onread;
285
- const buf = typeof buffer === "function" ? buffer() : buffer;
286
- const n = Math.min(buf.length, bytes.length - at);
287
- buf.set(bytes.subarray(at, at + n));
288
- at += n;
289
- if (callback.call(this, n, buf) === false) more = false;
332
+ /* with onread, pausing stops reading from the handle and resuming starts it, as Node's */
333
+ pause() {
334
+ if (this._onread) features.onread.pause(this);
335
+ return super.pause();
336
+ }
337
+
338
+ resume() {
339
+ if (this._onread) features.onread.resume(this);
340
+ return super.resume();
341
+ }
342
+
343
+ /* reading from the handle again (with onread, the bytes left from the last read first) */
344
+ _startReading() {
345
+ this._reading = true;
346
+ if (this._onreadRest) {
347
+ features.onread.rest(this);
348
+ return;
290
349
  }
291
- return more;
350
+ const err = netReadStart(this._handle);
351
+ if (err) this.destroy(errnoException(err, "read"));
292
352
  }
293
353
 
294
354
  get pending() {
@@ -304,25 +364,36 @@ export class Socket extends Duplex {
304
364
  }
305
365
 
306
366
  get bufferSize() {
307
- return this._handle !== null ? this.writableLength : undefined;
367
+ return this._handle !== null || this.connecting ? this.writableLength : undefined;
368
+ }
369
+
370
+ get _connecting() {
371
+ return this.connecting;
308
372
  }
309
373
 
310
374
  get bytesRead() {
311
375
  return this._bytesRead;
312
376
  }
313
377
 
378
+ /* Node's: what was sent, and what's still buffered (undefined on the prototype) */
314
379
  get bytesWritten() {
380
+ const writableBuffer = this.writableBuffer;
381
+ if (!writableBuffer) return undefined;
315
382
  let bytes = this._bytesDispatched;
316
- const state = this._writableState;
317
- for (const el of state.buffered) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
383
+ for (const el of writableBuffer) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
318
384
  const data = this._pendingData;
319
- if (data) bytes += typeof data === "string" ? Buffer.byteLength(data, this._pendingEncoding) : data.length;
385
+ if (Array.isArray(data)) {
386
+ for (const el of data) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
387
+ } else if (data) {
388
+ bytes += typeof data === "string" ? Buffer.byteLength(data, this._pendingEncoding) : data.length;
389
+ }
320
390
  return bytes;
321
391
  }
322
392
 
323
- /* a pipe's ends have no address object: Node's Pipe has no getpeername/getsockname */
393
+ /* a pipe's ends have no address object: Node's Pipe has no getpeername/getsockname;
394
+ * once closed, the address read while connected */
324
395
  _getpeername() {
325
- if (this._handle === null || this._isPipe) return {};
396
+ if (this._handle === null || this._isPipe || this.connecting) return this._peername || {};
326
397
  if (!this._peername) {
327
398
  const a = netAddress(this._handle, true);
328
399
  if (typeof a === "number") return {};
@@ -332,7 +403,8 @@ export class Socket extends Duplex {
332
403
  }
333
404
 
334
405
  _getsockname() {
335
- if (this._handle === null || this._isPipe) return {};
406
+ if (this._boundPath !== undefined) return { address: this._boundPath };
407
+ if (this._handle === null || this._isPipe) return this._sockname || {};
336
408
  if (!this._sockname) {
337
409
  const a = netAddress(this._handle, false);
338
410
  if (typeof a === "number") return {};
@@ -391,16 +463,19 @@ export class Socket extends Duplex {
391
463
  if (typeof msecs !== "number") throw invalidArgType("msecs", "of type number", msecs);
392
464
  if (!(msecs >= 0 && msecs !== Infinity)) throw outOfRange("msecs", "a non-negative finite number", msecs);
393
465
  if (msecs > TIMEOUT_MAX) msecs = TIMEOUT_MAX;
394
- this._clearTimeout();
466
+ /* the pending timer stays when it fires no later than the new deadline: _onTimer re-arms it
467
+ * for the rest, so a keep-alive socket, off for each request and on after it, sets no timer */
395
468
  if (msecs === 0) {
469
+ this._timeoutMs = 0;
396
470
  if (callback !== undefined) {
397
471
  validateFunction(callback, "callback");
398
472
  this.removeListener("timeout", callback);
399
473
  }
400
474
  } else {
401
475
  this._timeoutMs = msecs;
402
- this._lastActive = now();
403
- this._armTimeout(msecs);
476
+ this._lastActive = loopNow();
477
+ if (this._timer !== null && this._timerDue > this._lastActive + msecs) this._stopTimer();
478
+ if (this._timer === null) this._armTimeout(msecs);
404
479
  if (callback !== undefined) {
405
480
  validateFunction(callback, "callback");
406
481
  this.once("timeout", callback);
@@ -410,26 +485,32 @@ export class Socket extends Duplex {
410
485
  }
411
486
 
412
487
  _armTimeout(ms) {
488
+ this._timerDue = loopNow() + ms;
413
489
  this._timer = timerUnref(() => this._onTimer(), ms);
414
490
  }
415
491
 
416
492
  _onTimer() {
417
493
  this._timer = null;
418
- const idle = now() - this._lastActive;
494
+ if (this._timeoutMs === 0) return;
495
+ const idle = loopNow() - this._lastActive;
419
496
  if (idle < this._timeoutMs - 1) this._armTimeout(this._timeoutMs - idle);
420
497
  else this.emit("timeout");
421
498
  }
422
499
 
423
- _clearTimeout() {
500
+ _stopTimer() {
424
501
  if (this._timer !== null) clearTimeout(this._timer);
425
502
  this._timer = null;
503
+ }
504
+
505
+ _clearTimeout() {
506
+ this._stopTimer();
426
507
  this._timeoutMs = 0;
427
508
  }
428
509
 
429
510
  /* activity: the timeout counts from now (Node's timer.refresh(), which re-arms a fired one) */
430
511
  _unrefTimer() {
431
512
  if (this._timeoutMs) {
432
- this._lastActive = now();
513
+ this._lastActive = loopNow();
433
514
  if (this._timer === null) this._armTimeout(this._timeoutMs);
434
515
  }
435
516
  }
@@ -448,11 +529,7 @@ export class Socket extends Duplex {
448
529
 
449
530
  _read(n) {
450
531
  if (this.connecting || this._handle === null) this.once("connect", () => this._read(n));
451
- else if (!this._reading) {
452
- this._reading = true;
453
- const err = netReadStart(this._handle);
454
- if (err) this.destroy(errnoException(err, "read"));
455
- }
532
+ else if (!this._reading) this._startReading();
456
533
  }
457
534
 
458
535
  /* Node's _writeGeneric: a write before the connection waits for it; a write the kernel takes at
@@ -498,7 +575,7 @@ export class Socket extends Duplex {
498
575
 
499
576
  /* end(): shut the write side down once the writes are out */
500
577
  _final(cb) {
501
- if (this.pending) {
578
+ if (this.connecting) {
502
579
  this.once("connect", () => this._final(cb));
503
580
  return;
504
581
  }
@@ -551,7 +628,7 @@ export class Socket extends Duplex {
551
628
 
552
629
  /* close with a TCP RST instead of a FIN */
553
630
  resetAndDestroy() {
554
- if (this._handle !== null) {
631
+ if (this._handle !== null || this.connecting) {
555
632
  if (this._isPipe) throw codeError(TypeError, "ERR_INVALID_HANDLE_TYPE", "This handle type cannot be sent");
556
633
  if (this.connecting) this.once("connect", () => this._reset());
557
634
  else this._reset();
@@ -574,12 +651,27 @@ export class Socket extends Duplex {
574
651
  connect(...args) {
575
652
  const [options, cb] = args[0] && args[0][normalizedArgs] ? args[0] : normalizeArgs(args);
576
653
  if (this.write !== Socket.prototype.write) this.write = Socket.prototype.write;
577
- if (this.destroyed) throw notSupported("reconnecting a destroyed socket");
654
+ if (options.port === undefined && options.path == null) {
655
+ throw codeError(TypeError, "ERR_MISSING_ARGS", 'The "options" or "port" or "path" argument must be specified');
656
+ }
657
+ /* a closed socket connects again, as Node's: its streams undestroyed, its counts the new handle's */
658
+ if (this.destroyed) {
659
+ this._handle = null;
660
+ this._peername = null;
661
+ this._sockname = null;
662
+ this._bytesRead = 0;
663
+ this._bytesDispatched = 0;
664
+ this._hadError = false;
665
+ this._perfConnect = null;
666
+ this._undestroy();
667
+ }
578
668
  const { path } = options;
579
669
  if (cb !== null) this.once("connect", cb);
580
670
  this._unrefTimer();
581
671
  this.connecting = true;
582
- if (path) {
672
+ /* an adopted handle is a pipe or not whatever the options say; once it's gone, the path does */
673
+ const pipe = this._boundSource && this._handle !== null ? this._isPipe : !!path;
674
+ if (pipe) {
583
675
  validateString(path, "options.path");
584
676
  this._isPipe = true;
585
677
  this._internalConnect(path);
@@ -594,23 +686,37 @@ export class Socket extends Duplex {
594
686
  const host = options.host || "localhost";
595
687
  let { port, autoSelectFamilyAttemptTimeout: attemptTimeout, autoSelectFamily: selectFamily } = options;
596
688
  if (localAddress && !isIP(localAddress)) throw codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + localAddress);
597
- if (localPort !== undefined && typeof localPort !== "number") throw invalidArgType("options.localPort", "of type number", localPort);
689
+ if (localPort && typeof localPort !== "number") throw invalidArgType("options.localPort", "of type number", localPort);
598
690
  if (typeof host !== "string") throw invalidArgType("options.host", "of type string", host);
691
+ if (host.includes("\0")) throw invalidArgValue("options.host", "must be a string without null bytes", host);
692
+ /* an adopted BoundSocket owns the local end and the address family */
693
+ if (this._boundSource) {
694
+ if (localAddress !== undefined || localPort !== undefined) {
695
+ throw invalidArgValue("options", "localAddress and localPort cannot be used with an adopted bound socket", options);
696
+ }
697
+ selectFamily = false;
698
+ }
599
699
  if (typeof port !== "undefined") {
600
700
  if (typeof port !== "number" && typeof port !== "string") throw invalidArgType("options.port", "of type number or string", port);
601
701
  port = validatePort(port);
602
702
  }
603
703
  port |= 0;
604
- if (selectFamily !== undefined && typeof selectFamily !== "boolean") throw invalidArgType("options.autoSelectFamily", "of type boolean", selectFamily);
704
+ if (selectFamily != null && typeof selectFamily !== "boolean") throw invalidArgType("options.autoSelectFamily", "of type boolean", selectFamily);
605
705
  selectFamily ??= autoSelectFamily;
606
- if (attemptTimeout !== undefined) {
607
- if (typeof attemptTimeout !== "number") throw invalidArgType("options.autoSelectFamilyAttemptTimeout", "of type number", attemptTimeout);
706
+ if (attemptTimeout != null) {
707
+ validateInt32(attemptTimeout, "options.autoSelectFamilyAttemptTimeout", 1);
608
708
  if (attemptTimeout < 10) attemptTimeout = 10;
609
709
  } else {
610
710
  attemptTimeout = autoSelectFamilyAttemptTimeout;
611
711
  }
612
712
  this._host = host;
613
713
  const addressType = isIP(host);
714
+ /* an adopted BoundSocket connects in this tick, so its local address is known at once (a
715
+ * failure still comes as an 'error' event) */
716
+ if (addressType && this._boundSource) {
717
+ this._internalConnect(host, port, addressType, localAddress, localPort);
718
+ return;
719
+ }
614
720
  if (addressType) {
615
721
  process.nextTick(() => {
616
722
  if (this.connecting) this._internalConnect(host, port, addressType, localAddress, localPort);
@@ -620,17 +726,19 @@ export class Socket extends Duplex {
620
726
  let family = options.family ?? 0;
621
727
  if (family === "IPv4") family = 4;
622
728
  else if (family === "IPv6") family = 6;
623
- const dnsopts = { family, all: selectFamily && family === 0 };
624
729
  const lookupFn = options.lookup ?? lookup;
625
730
  if (typeof lookupFn !== "function") throw invalidArgType("options.lookup", "of type function", lookupFn);
731
+ const dnsopts = { family, hints: options.hints || 0, all: false };
732
+ if (process.platform !== "win32" && family !== 4 && family !== 6 && dnsopts.hints === 0) dnsopts.hints = ADDRCONFIG;
733
+ dnsopts.all = family !== 4 && family !== 6 && !localAddress && selectFamily;
626
734
  if (!dnsopts.all) {
627
735
  lookupFn(host, dnsopts, (err, ip, addressType) => {
628
736
  this.emit("lookup", err, ip, addressType, host);
629
737
  if (!this.connecting) return;
630
738
  if (err) process.nextTick(() => this.destroy(err));
631
- else if (!isIP(ip)) process.nextTick(() => this.destroy(codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + ip)));
739
+ else if (typeof ip !== "string" || !isIP(ip)) process.nextTick(() => this.destroy(codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + ip)));
632
740
  else if (addressType !== 4 && addressType !== 6) {
633
- process.nextTick(() => this.destroy(codeError(TypeError, "ERR_INVALID_ADDRESS_FAMILY", "Invalid address family: " + addressType + " " + host + ":" + port)));
741
+ process.nextTick(() => this.destroy(addressFamilyError(addressType, host, options.port)));
634
742
  } else {
635
743
  this._unrefTimer();
636
744
  this._internalConnect(ip, port, addressType, localAddress, localPort);
@@ -661,7 +769,7 @@ export class Socket extends Duplex {
661
769
  const first = addresses[0];
662
770
  const error = !first || !isIP(first.address)
663
771
  ? codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + first?.address)
664
- : codeError(TypeError, "ERR_INVALID_ADDRESS_FAMILY", "Invalid address family: " + first.family + " " + host + ":" + port);
772
+ : addressFamilyError(first.family, host, options.port);
665
773
  process.nextTick(() => this.destroy(error));
666
774
  return;
667
775
  }
@@ -687,14 +795,23 @@ export class Socket extends Duplex {
687
795
  /* a new handle bound to the local address, if any, and connecting: 0 or the failure as Node reports it */
688
796
  _open(address, port, addressType, localAddress, localPort, onConnect) {
689
797
  let id;
690
- id = netOpen(this._isPipe, bindContext((event, value) => {
798
+ const cb = bindContext((event, value) => {
691
799
  if (event === NET_CONNECT) {
692
800
  if (this._handle === id) onConnect(value, id);
693
801
  } else {
694
802
  this._onEvent(event, value, id);
695
803
  }
696
- }));
697
- this._handle = id;
804
+ });
805
+ if (this._boundSource && this._handle !== null) { /* an adopted BoundSocket's, already bound */
806
+ id = this._handle;
807
+ netCallback(id, cb);
808
+ } else {
809
+ id = netOpen(this._isPipe, cb);
810
+ this._handle = id;
811
+ }
812
+ if (!this._isPipe && this._perfConnect === null && features.perfObserving?.("net")) {
813
+ this._perfConnect = { start: features.perfNow(), host: address, port };
814
+ }
698
815
  if (!this._isPipe && (localAddress || localPort)) {
699
816
  localAddress ||= addressType === 4 ? "0.0.0.0" : "::";
700
817
  const err = netBind(id, localAddress, localPort | 0, 0);
@@ -780,17 +897,27 @@ export class Socket extends Duplex {
780
897
  }
781
898
  this._attach(this._handle);
782
899
  this._unrefTimer();
900
+ if (this._perfConnect !== null) {
901
+ const { start, host, port } = this._perfConnect;
902
+ this._perfConnect = null;
903
+ features.perfObserve(features.perfEntry("connect", "net", start, features.perfNow() - start, { host, port }));
904
+ }
905
+ if (this._tos !== undefined && !this._isPipe) features.applyTypeOfService(this);
783
906
  this.emit("connect");
784
907
  this.emit("ready");
785
908
  if (this.readable && !this.isPaused()) this.read(0);
786
909
  }
787
910
  }
911
+ /* internal/net_tos.js's, linked when the program names them */
912
+ SocketClass.prototype.setTypeOfService = lazyFunction("setTypeOfService", 1);
913
+ SocketClass.prototype.getTypeOfService = lazyFunction("getTypeOfService");
914
+ export const Socket = callableClass(SocketClass, "Socket"); /* Socket(…) without new, as Node's */
788
915
 
789
916
  export const Stream = Socket;
790
917
 
791
918
  /* ---- Server -------------------------------------------------------------------------------- */
792
919
 
793
- export class Server extends EventEmitter {
920
+ class ServerClass extends EventEmitter {
794
921
  constructor(options, connectionListener) {
795
922
  super();
796
923
  if (typeof options === "function") {
@@ -803,6 +930,7 @@ export class Server extends EventEmitter {
803
930
  }
804
931
  if (typeof connectionListener === "function") this.on("connection", connectionListener);
805
932
  this._connections = 0;
933
+ this._listeningId = 1; /* a listen or close since a lookup began makes the lookup's result stale */
806
934
  this._handle = null;
807
935
  this._isPipe = false;
808
936
  this._pipeName = undefined;
@@ -827,7 +955,16 @@ export class Server extends EventEmitter {
827
955
  if (typeof options.callback === "function") cb = options.callback;
828
956
  if (this._handle !== null) throw codeError(Error, "ERR_SERVER_ALREADY_LISTEN", "Listen method has been called more than once without closing.");
829
957
  if (cb !== null) this.once("listening", cb);
958
+ this._listeningId++;
830
959
  const backlogFromArgs = toNumber(args.length > 1 && args[1]) || toNumber(args.length > 2 && args[2]);
960
+ /* listen(boundSocket) or listen({ handle: boundSocket }): the server takes its handle */
961
+ const bound = features.adoptBoundSocket?.(options) ?? features.adoptBoundSocket?.(options.handle);
962
+ if (bound) {
963
+ if (bound.path !== undefined) this._pipeName = bound.path;
964
+ this._isPipe = bound.path !== undefined;
965
+ this._setupListen(null, -1, -1, backlogFromArgs, undefined, 0, bound.id);
966
+ return this;
967
+ }
831
968
  if (options._handle !== undefined && options._handle !== null) options = options._handle;
832
969
  else if (options.handle !== undefined && options.handle !== null) options = options.handle;
833
970
  const flags = (options.ipv6Only ? 1 : 0) | (options.reusePort ? 2 : 0);
@@ -880,7 +1017,9 @@ export class Server extends EventEmitter {
880
1017
  }
881
1018
 
882
1019
  _lookupAndListen(port, address, backlog, flags) {
1020
+ const listeningId = this._listeningId;
883
1021
  lookup(address, (err, ip, addressType) => {
1022
+ if (listeningId !== this._listeningId) return;
884
1023
  if (err) this.emit("error", err);
885
1024
  else this._setupListen(ip, port, ip ? addressType : 4, backlog, undefined, flags);
886
1025
  });
@@ -970,6 +1109,7 @@ export class Server extends EventEmitter {
970
1109
  process.nextTick(() => this.emit("error", ex));
971
1110
  return;
972
1111
  }
1112
+ this._connectionKey = addressType + ":" + address + ":" + port;
973
1113
  if (this._unref) netRef(id, false);
974
1114
  process.nextTick(() => {
975
1115
  if (this._handle !== null) this.emit("listening");
@@ -1024,6 +1164,7 @@ export class Server extends EventEmitter {
1024
1164
 
1025
1165
  /* close([cb]): stop accepting; 'close' once the open connections end too */
1026
1166
  close(cb) {
1167
+ this._listeningId++;
1027
1168
  if (typeof cb === "function") {
1028
1169
  if (this._handle === null) this.once("close", () => cb(codeError(Error, "ERR_SERVER_NOT_RUNNING", "Server is not running.")));
1029
1170
  else this.once("close", cb);
@@ -1036,6 +1177,12 @@ export class Server extends EventEmitter {
1036
1177
  return this;
1037
1178
  }
1038
1179
 
1180
+ /* captureRejections: a 'connection' listener's rejection destroys the socket, as Node's */
1181
+ [Symbol.for("nodejs.rejection")](err, event, sock) {
1182
+ if (event === "connection") sock.destroy(err);
1183
+ else this.emit("error", err);
1184
+ }
1185
+
1039
1186
  _emitCloseIfDrained() {
1040
1187
  if (this._handle !== null || this._connections) return;
1041
1188
  process.nextTick(() => this.emit("close"));
@@ -1063,6 +1210,7 @@ export class Server extends EventEmitter {
1063
1210
  return new Promise((resolve, reject) => this.close((err) => (err ? reject(err) : resolve())));
1064
1211
  }
1065
1212
  }
1213
+ export const Server = callableClass(ServerClass, "Server"); /* Server(…) without new, as Node's */
1066
1214
 
1067
1215
  export function createServer(options, connectionListener) {
1068
1216
  return new Server(options, connectionListener);
@@ -1079,7 +1227,10 @@ export function connect(...args) {
1079
1227
 
1080
1228
  export const createConnection = connect;
1081
1229
 
1230
+ /* internal/net_bound.js's, linked when the program names it */
1231
+ export const BoundSocket = lazyClass("BoundSocket");
1232
+
1082
1233
  export default {
1083
- BlockList, SocketAddress, isIP, isIPv4, isIPv6, Socket, Stream, Server, connect, createConnection, createServer,
1234
+ BlockList, BoundSocket, SocketAddress, isIP, isIPv4, isIPv6, Socket, Stream, Server, connect, createConnection, createServer,
1084
1235
  getDefaultAutoSelectFamily, setDefaultAutoSelectFamily, getDefaultAutoSelectFamilyAttemptTimeout, setDefaultAutoSelectFamilyAttemptTimeout,
1085
1236
  };
@@ -15,6 +15,10 @@ const now = () => hrMs() - origin;
15
15
 
16
16
  const kConstruct = Symbol("construct");
17
17
 
18
+ /* for internal/perf_observe.js's users: the clock, and an entry of another module's (net's 'connect') */
19
+ features.perfNow = now;
20
+ features.perfEntry = (name, entryType, startTime, duration, detail) => new PerformanceEntry(kConstruct, name, entryType, startTime, duration, detail);
21
+
18
22
  export class PerformanceEntry {
19
23
  constructor(key, name, entryType, startTime, duration, detail) {
20
24
  if (key !== kConstruct) throw codeError(TypeError, "ERR_ILLEGAL_CONSTRUCTOR", "Illegal constructor");