visaljs 0.0.8 → 0.0.9

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 (39) hide show
  1. package/README.md +6 -3
  2. package/THIRD_PARTY_LICENSES +1 -1
  3. package/cli.js +55 -4
  4. package/node/prelude/_http_agent.js +5 -0
  5. package/node/prelude/_http_server.js +7 -0
  6. package/node/prelude/dns.js +7 -1
  7. package/node/prelude/events.js +7 -3
  8. package/node/prelude/http.js +685 -279
  9. package/node/prelude/https.js +14 -3
  10. package/node/prelude/internal/events_extras.js +2 -5
  11. package/node/prelude/internal/features.js +17 -0
  12. package/node/prelude/internal/http_agent.js +88 -2
  13. package/node/prelude/internal/http_symbols.js +3 -0
  14. package/node/prelude/internal/net_bound.js +123 -0
  15. package/node/prelude/internal/net_onread.js +73 -0
  16. package/node/prelude/internal/net_tos.js +54 -0
  17. package/node/prelude/internal/perf_observe.js +7 -1
  18. package/node/prelude/internal/tls/wrap.js +2 -1
  19. package/node/prelude/module.js +42 -18
  20. package/node/prelude/net.js +195 -48
  21. package/node/prelude/perf_hooks.js +4 -0
  22. package/node/src/host_http.c +151 -21
  23. package/node/src/host_net.c +75 -9
  24. package/package.json +1 -1
  25. package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
  26. package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
  27. package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
  28. package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
  29. package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
  30. package/runtime/include/js_atoms.h +4 -0
  31. package/runtime/include/js_web.h +3 -0
  32. package/runtime/include/jscontext.h +8 -0
  33. package/runtime/src/atoms.c +37 -3
  34. package/runtime/src/events.c +19 -0
  35. package/runtime/src/gc.c +4 -4
  36. package/runtime/src/internal.h +4 -4
  37. package/runtime/src/regexp.c +569 -12
  38. package/runtime/src/shape.c +68 -1
  39. 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
+ 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
 
@@ -81,6 +84,9 @@ function validatePort(port, name = "Port", allowZero = true) {
81
84
  return +port;
82
85
  }
83
86
 
87
+ /* for internal/net_bound.js (BoundSocket) */
88
+ features.net = { errnoException, exceptionWithHostPort, validatePort };
89
+
84
90
  const toNumber = (x) => ((x = Number(x)) >= 0 ? x : false);
85
91
  const isPipeName = (s) => typeof s === "string" && toNumber(s) === false;
86
92
 
@@ -120,6 +126,7 @@ export function setDefaultAutoSelectFamilyAttemptTimeout(value) {
120
126
 
121
127
  /* a descriptor as a handle: Node's createHandle */
122
128
  function handleForFd(fd, cb) {
129
+ validateInt32(fd, "fd", 0);
123
130
  const type = netGuessHandle(fd);
124
131
  if (type !== "PIPE" && type !== "TCP") throw codeError(TypeError, "ERR_INVALID_FD_TYPE", "Unsupported fd type: " + type);
125
132
  return netOpen(type === "PIPE", bindContext(cb));
@@ -129,6 +136,14 @@ function validateBlockList(value, name) {
129
136
  if (value !== undefined && !BlockList.isBlockList(value)) throw invalidArgType(name, "an net.BlockList", value);
130
137
  }
131
138
 
139
+ /* Node's ERR_INVALID_ADDRESS_FAMILY, with the host and port */
140
+ function addressFamilyError(addressType, host, port) {
141
+ const err = codeError(RangeError, "ERR_INVALID_ADDRESS_FAMILY", "Invalid address family: " + addressType + " " + host + ":" + port);
142
+ err.host = host;
143
+ err.port = port;
144
+ return err;
145
+ }
146
+
132
147
  const ipBlocked = (ip) => codeError(Error, "ERR_IP_BLOCKED", "IP(" + ip + ") is blocked by net.BlockList");
133
148
 
134
149
  /* ---- Socket -------------------------------------------------------------------------------- */
@@ -148,13 +163,30 @@ function writeAfterFIN(chunk, encoding, cb) {
148
163
  return false;
149
164
  }
150
165
 
151
- export class Socket extends Duplex {
166
+ class SocketClass extends Duplex {
152
167
  constructor(options) {
153
168
  if (typeof options === "number") options = { fd: options };
154
169
  else options = { ...options };
170
+ if (options.objectMode) throw invalidArgValue("options.objectMode", "is not supported", options.objectMode);
171
+ if (options.readableObjectMode || options.writableObjectMode) {
172
+ const name = options.readableObjectMode ? "readableObjectMode" : "writableObjectMode";
173
+ throw invalidArgValue("options." + name, "is not supported", options[name]);
174
+ }
155
175
  /* Node's: not half-open by default, 'close' emitted by the socket itself, strings written as given */
156
176
  const allowHalfOpen = Boolean(options.allowHalfOpen);
157
- super({ ...options, allowHalfOpen, emitClose: false, autoDestroy: true, decodeStrings: false });
177
+ super({ ...options, signal: undefined, allowHalfOpen, emitClose: false, autoDestroy: true, decodeStrings: false });
178
+ /* the signal option, as Node's socket has it: aborted, the socket is destroyed with an AbortError
179
+ * (one already aborted: on the next tick, once connect() has begun) */
180
+ if (options.signal !== undefined) {
181
+ const signal = options.signal;
182
+ if (signal === null || typeof signal !== "object" || !("aborted" in signal)) throw invalidArgType("options.signal", "an instance of AbortSignal", signal);
183
+ const onAbort = () => this.destroy(new AbortError(undefined, { cause: signal.reason }));
184
+ if (signal.aborted) process.nextTick(onAbort);
185
+ else {
186
+ signal.addEventListener("abort", onAbort, { once: true });
187
+ this.once("close", () => signal.removeEventListener("abort", onAbort));
188
+ }
189
+ }
158
190
  this.connecting = false;
159
191
  this._hadError = false;
160
192
  this._handle = null; /* the host handle's id while open */
@@ -181,6 +213,12 @@ export class Socket extends Duplex {
181
213
  this._timeoutMs = 0;
182
214
  this._lastActive = 0;
183
215
  this._onread = null;
216
+ if (options.typeOfService !== undefined) feature("validateTypeOfService")(options.typeOfService);
217
+ this._tos = options.typeOfService; /* internal/net_tos.js's: the type of service, set at connect when given before it */
218
+ this._boundSource = false; /* the handle is an adopted BoundSocket's */
219
+ this._boundPath = undefined; /* an adopted pipe BoundSocket's path, its local address */
220
+ this._perfConnect = null; /* a TCP connect's start, while a PerformanceObserver takes 'net' entries */ /* set at connect when given before it */
221
+ this._onreadRest = null; /* internal/net_onread.js's: the bytes read but not yet given when reading stopped */
184
222
  this.timeout = undefined;
185
223
  this.autoSelectFamilyAttemptedAddresses = undefined;
186
224
  validateBlockList(options.blockList, "options.blockList");
@@ -188,10 +226,28 @@ export class Socket extends Duplex {
188
226
  const onread = options.onread;
189
227
  if (onread !== null && typeof onread === "object" && (typeof onread.buffer === "function" || onread.buffer instanceof Uint8Array) &&
190
228
  typeof onread.callback === "function") {
229
+ feature("onread"); /* internal/net_onread.js's */
191
230
  this._onread = onread;
192
231
  }
193
232
  this.on("end", onReadableStreamEnd);
194
- if (options.handle !== undefined) {
233
+ /* an adopted BoundSocket's handle: bound, not yet connected, so not read until it is */
234
+ const bound = options.handle !== undefined ? features.adoptBoundSocket?.(options.handle) : null;
235
+ if (bound) {
236
+ const { id, path } = bound;
237
+ this._handle = id;
238
+ this._boundSource = true;
239
+ this._isPipe = path !== undefined;
240
+ netCallback(id, bindContext((event, value) => this._onEvent(event, value, id)));
241
+ if (path !== undefined) {
242
+ this._boundPath = path;
243
+ /* a pipe's bind leaves its stream unopened: opened on its own descriptor, as Node's */
244
+ const fd = netFd(id);
245
+ if (fd >= 0) {
246
+ const err = netOpenFd(id, fd);
247
+ if (err) throw errnoException(err, "open");
248
+ }
249
+ }
250
+ } else if (options.handle !== undefined) {
195
251
  const id = options.handle;
196
252
  this._isPipe = !!options._isPipe;
197
253
  this._attach(id);
@@ -209,7 +265,7 @@ export class Socket extends Duplex {
209
265
  }
210
266
  this._attach(id);
211
267
  }
212
- if (this._handle !== null && options.readable !== false) {
268
+ if (this._handle !== null && !bound && options.readable !== false) {
213
269
  if (options.pauseOnCreate) this._readableState.flowing = false;
214
270
  else if (!options.manualStart) this.read(0);
215
271
  }
@@ -257,7 +313,7 @@ export class Socket extends Duplex {
257
313
  this._bytesRead += value.byteLength;
258
314
  this._unrefTimer();
259
315
  let more;
260
- if (this._onread) more = this._deliver(value);
316
+ if (this._onread) more = features.onread.deliver(this, value);
261
317
  else more = this.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
262
318
  if (!more) {
263
319
  this._reading = false;
@@ -273,22 +329,31 @@ export class Socket extends Duplex {
273
329
  this.destroy(errnoException(value, "read"));
274
330
  return;
275
331
  }
332
+ if (this._onread) features.onread.eof(this);
276
333
  this.push(null);
277
334
  this.read(0);
278
335
  }
279
336
 
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;
337
+ /* with onread, pausing stops reading from the handle and resuming starts it, as Node's */
338
+ pause() {
339
+ if (this._onread) features.onread.pause(this);
340
+ return super.pause();
341
+ }
342
+
343
+ resume() {
344
+ if (this._onread) features.onread.resume(this);
345
+ return super.resume();
346
+ }
347
+
348
+ /* reading from the handle again (with onread, the bytes left from the last read first) */
349
+ _startReading() {
350
+ this._reading = true;
351
+ if (this._onreadRest) {
352
+ features.onread.rest(this);
353
+ return;
290
354
  }
291
- return more;
355
+ const err = netReadStart(this._handle);
356
+ if (err) this.destroy(errnoException(err, "read"));
292
357
  }
293
358
 
294
359
  get pending() {
@@ -304,25 +369,36 @@ export class Socket extends Duplex {
304
369
  }
305
370
 
306
371
  get bufferSize() {
307
- return this._handle !== null ? this.writableLength : undefined;
372
+ return this._handle !== null || this.connecting ? this.writableLength : undefined;
373
+ }
374
+
375
+ get _connecting() {
376
+ return this.connecting;
308
377
  }
309
378
 
310
379
  get bytesRead() {
311
380
  return this._bytesRead;
312
381
  }
313
382
 
383
+ /* Node's: what was sent, and what's still buffered (undefined on the prototype) */
314
384
  get bytesWritten() {
385
+ const writableBuffer = this.writableBuffer;
386
+ if (!writableBuffer) return undefined;
315
387
  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;
388
+ for (const el of writableBuffer) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
318
389
  const data = this._pendingData;
319
- if (data) bytes += typeof data === "string" ? Buffer.byteLength(data, this._pendingEncoding) : data.length;
390
+ if (Array.isArray(data)) {
391
+ for (const el of data) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
392
+ } else if (data) {
393
+ bytes += typeof data === "string" ? Buffer.byteLength(data, this._pendingEncoding) : data.length;
394
+ }
320
395
  return bytes;
321
396
  }
322
397
 
323
- /* a pipe's ends have no address object: Node's Pipe has no getpeername/getsockname */
398
+ /* a pipe's ends have no address object: Node's Pipe has no getpeername/getsockname;
399
+ * once closed, the address read while connected */
324
400
  _getpeername() {
325
- if (this._handle === null || this._isPipe) return {};
401
+ if (this._handle === null || this._isPipe || this.connecting) return this._peername || {};
326
402
  if (!this._peername) {
327
403
  const a = netAddress(this._handle, true);
328
404
  if (typeof a === "number") return {};
@@ -332,7 +408,8 @@ export class Socket extends Duplex {
332
408
  }
333
409
 
334
410
  _getsockname() {
335
- if (this._handle === null || this._isPipe) return {};
411
+ if (this._boundPath !== undefined) return { address: this._boundPath };
412
+ if (this._handle === null || this._isPipe) return this._sockname || {};
336
413
  if (!this._sockname) {
337
414
  const a = netAddress(this._handle, false);
338
415
  if (typeof a === "number") return {};
@@ -448,11 +525,7 @@ export class Socket extends Duplex {
448
525
 
449
526
  _read(n) {
450
527
  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
- }
528
+ else if (!this._reading) this._startReading();
456
529
  }
457
530
 
458
531
  /* Node's _writeGeneric: a write before the connection waits for it; a write the kernel takes at
@@ -498,7 +571,7 @@ export class Socket extends Duplex {
498
571
 
499
572
  /* end(): shut the write side down once the writes are out */
500
573
  _final(cb) {
501
- if (this.pending) {
574
+ if (this.connecting) {
502
575
  this.once("connect", () => this._final(cb));
503
576
  return;
504
577
  }
@@ -551,7 +624,7 @@ export class Socket extends Duplex {
551
624
 
552
625
  /* close with a TCP RST instead of a FIN */
553
626
  resetAndDestroy() {
554
- if (this._handle !== null) {
627
+ if (this._handle !== null || this.connecting) {
555
628
  if (this._isPipe) throw codeError(TypeError, "ERR_INVALID_HANDLE_TYPE", "This handle type cannot be sent");
556
629
  if (this.connecting) this.once("connect", () => this._reset());
557
630
  else this._reset();
@@ -574,12 +647,27 @@ export class Socket extends Duplex {
574
647
  connect(...args) {
575
648
  const [options, cb] = args[0] && args[0][normalizedArgs] ? args[0] : normalizeArgs(args);
576
649
  if (this.write !== Socket.prototype.write) this.write = Socket.prototype.write;
577
- if (this.destroyed) throw notSupported("reconnecting a destroyed socket");
650
+ if (options.port === undefined && options.path == null) {
651
+ throw codeError(TypeError, "ERR_MISSING_ARGS", 'The "options" or "port" or "path" argument must be specified');
652
+ }
653
+ /* a closed socket connects again, as Node's: its streams undestroyed, its counts the new handle's */
654
+ if (this.destroyed) {
655
+ this._handle = null;
656
+ this._peername = null;
657
+ this._sockname = null;
658
+ this._bytesRead = 0;
659
+ this._bytesDispatched = 0;
660
+ this._hadError = false;
661
+ this._perfConnect = null;
662
+ this._undestroy();
663
+ }
578
664
  const { path } = options;
579
665
  if (cb !== null) this.once("connect", cb);
580
666
  this._unrefTimer();
581
667
  this.connecting = true;
582
- if (path) {
668
+ /* an adopted handle is a pipe or not whatever the options say; once it's gone, the path does */
669
+ const pipe = this._boundSource && this._handle !== null ? this._isPipe : !!path;
670
+ if (pipe) {
583
671
  validateString(path, "options.path");
584
672
  this._isPipe = true;
585
673
  this._internalConnect(path);
@@ -594,23 +682,37 @@ export class Socket extends Duplex {
594
682
  const host = options.host || "localhost";
595
683
  let { port, autoSelectFamilyAttemptTimeout: attemptTimeout, autoSelectFamily: selectFamily } = options;
596
684
  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);
685
+ if (localPort && typeof localPort !== "number") throw invalidArgType("options.localPort", "of type number", localPort);
598
686
  if (typeof host !== "string") throw invalidArgType("options.host", "of type string", host);
687
+ if (host.includes("\0")) throw invalidArgValue("options.host", "must be a string without null bytes", host);
688
+ /* an adopted BoundSocket owns the local end and the address family */
689
+ if (this._boundSource) {
690
+ if (localAddress !== undefined || localPort !== undefined) {
691
+ throw invalidArgValue("options", "localAddress and localPort cannot be used with an adopted bound socket", options);
692
+ }
693
+ selectFamily = false;
694
+ }
599
695
  if (typeof port !== "undefined") {
600
696
  if (typeof port !== "number" && typeof port !== "string") throw invalidArgType("options.port", "of type number or string", port);
601
697
  port = validatePort(port);
602
698
  }
603
699
  port |= 0;
604
- if (selectFamily !== undefined && typeof selectFamily !== "boolean") throw invalidArgType("options.autoSelectFamily", "of type boolean", selectFamily);
700
+ if (selectFamily != null && typeof selectFamily !== "boolean") throw invalidArgType("options.autoSelectFamily", "of type boolean", selectFamily);
605
701
  selectFamily ??= autoSelectFamily;
606
- if (attemptTimeout !== undefined) {
607
- if (typeof attemptTimeout !== "number") throw invalidArgType("options.autoSelectFamilyAttemptTimeout", "of type number", attemptTimeout);
702
+ if (attemptTimeout != null) {
703
+ validateInt32(attemptTimeout, "options.autoSelectFamilyAttemptTimeout", 1);
608
704
  if (attemptTimeout < 10) attemptTimeout = 10;
609
705
  } else {
610
706
  attemptTimeout = autoSelectFamilyAttemptTimeout;
611
707
  }
612
708
  this._host = host;
613
709
  const addressType = isIP(host);
710
+ /* an adopted BoundSocket connects in this tick, so its local address is known at once (a
711
+ * failure still comes as an 'error' event) */
712
+ if (addressType && this._boundSource) {
713
+ this._internalConnect(host, port, addressType, localAddress, localPort);
714
+ return;
715
+ }
614
716
  if (addressType) {
615
717
  process.nextTick(() => {
616
718
  if (this.connecting) this._internalConnect(host, port, addressType, localAddress, localPort);
@@ -620,17 +722,19 @@ export class Socket extends Duplex {
620
722
  let family = options.family ?? 0;
621
723
  if (family === "IPv4") family = 4;
622
724
  else if (family === "IPv6") family = 6;
623
- const dnsopts = { family, all: selectFamily && family === 0 };
624
725
  const lookupFn = options.lookup ?? lookup;
625
726
  if (typeof lookupFn !== "function") throw invalidArgType("options.lookup", "of type function", lookupFn);
727
+ const dnsopts = { family, hints: options.hints || 0, all: false };
728
+ if (process.platform !== "win32" && family !== 4 && family !== 6 && dnsopts.hints === 0) dnsopts.hints = ADDRCONFIG;
729
+ dnsopts.all = family !== 4 && family !== 6 && !localAddress && selectFamily;
626
730
  if (!dnsopts.all) {
627
731
  lookupFn(host, dnsopts, (err, ip, addressType) => {
628
732
  this.emit("lookup", err, ip, addressType, host);
629
733
  if (!this.connecting) return;
630
734
  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)));
735
+ else if (typeof ip !== "string" || !isIP(ip)) process.nextTick(() => this.destroy(codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + ip)));
632
736
  else if (addressType !== 4 && addressType !== 6) {
633
- process.nextTick(() => this.destroy(codeError(TypeError, "ERR_INVALID_ADDRESS_FAMILY", "Invalid address family: " + addressType + " " + host + ":" + port)));
737
+ process.nextTick(() => this.destroy(addressFamilyError(addressType, host, options.port)));
634
738
  } else {
635
739
  this._unrefTimer();
636
740
  this._internalConnect(ip, port, addressType, localAddress, localPort);
@@ -661,7 +765,7 @@ export class Socket extends Duplex {
661
765
  const first = addresses[0];
662
766
  const error = !first || !isIP(first.address)
663
767
  ? 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);
768
+ : addressFamilyError(first.family, host, options.port);
665
769
  process.nextTick(() => this.destroy(error));
666
770
  return;
667
771
  }
@@ -687,14 +791,23 @@ export class Socket extends Duplex {
687
791
  /* a new handle bound to the local address, if any, and connecting: 0 or the failure as Node reports it */
688
792
  _open(address, port, addressType, localAddress, localPort, onConnect) {
689
793
  let id;
690
- id = netOpen(this._isPipe, bindContext((event, value) => {
794
+ const cb = bindContext((event, value) => {
691
795
  if (event === NET_CONNECT) {
692
796
  if (this._handle === id) onConnect(value, id);
693
797
  } else {
694
798
  this._onEvent(event, value, id);
695
799
  }
696
- }));
697
- this._handle = id;
800
+ });
801
+ if (this._boundSource && this._handle !== null) { /* an adopted BoundSocket's, already bound */
802
+ id = this._handle;
803
+ netCallback(id, cb);
804
+ } else {
805
+ id = netOpen(this._isPipe, cb);
806
+ this._handle = id;
807
+ }
808
+ if (!this._isPipe && this._perfConnect === null && features.perfObserving?.("net")) {
809
+ this._perfConnect = { start: features.perfNow(), host: address, port };
810
+ }
698
811
  if (!this._isPipe && (localAddress || localPort)) {
699
812
  localAddress ||= addressType === 4 ? "0.0.0.0" : "::";
700
813
  const err = netBind(id, localAddress, localPort | 0, 0);
@@ -780,17 +893,27 @@ export class Socket extends Duplex {
780
893
  }
781
894
  this._attach(this._handle);
782
895
  this._unrefTimer();
896
+ if (this._perfConnect !== null) {
897
+ const { start, host, port } = this._perfConnect;
898
+ this._perfConnect = null;
899
+ features.perfObserve(features.perfEntry("connect", "net", start, features.perfNow() - start, { host, port }));
900
+ }
901
+ if (this._tos !== undefined && !this._isPipe) features.applyTypeOfService(this);
783
902
  this.emit("connect");
784
903
  this.emit("ready");
785
904
  if (this.readable && !this.isPaused()) this.read(0);
786
905
  }
787
906
  }
907
+ /* internal/net_tos.js's, linked when the program names them */
908
+ SocketClass.prototype.setTypeOfService = lazyFunction("setTypeOfService", 1);
909
+ SocketClass.prototype.getTypeOfService = lazyFunction("getTypeOfService");
910
+ export const Socket = callableClass(SocketClass, "Socket"); /* Socket(…) without new, as Node's */
788
911
 
789
912
  export const Stream = Socket;
790
913
 
791
914
  /* ---- Server -------------------------------------------------------------------------------- */
792
915
 
793
- export class Server extends EventEmitter {
916
+ class ServerClass extends EventEmitter {
794
917
  constructor(options, connectionListener) {
795
918
  super();
796
919
  if (typeof options === "function") {
@@ -803,6 +926,7 @@ export class Server extends EventEmitter {
803
926
  }
804
927
  if (typeof connectionListener === "function") this.on("connection", connectionListener);
805
928
  this._connections = 0;
929
+ this._listeningId = 1; /* a listen or close since a lookup began makes the lookup's result stale */
806
930
  this._handle = null;
807
931
  this._isPipe = false;
808
932
  this._pipeName = undefined;
@@ -827,7 +951,16 @@ export class Server extends EventEmitter {
827
951
  if (typeof options.callback === "function") cb = options.callback;
828
952
  if (this._handle !== null) throw codeError(Error, "ERR_SERVER_ALREADY_LISTEN", "Listen method has been called more than once without closing.");
829
953
  if (cb !== null) this.once("listening", cb);
954
+ this._listeningId++;
830
955
  const backlogFromArgs = toNumber(args.length > 1 && args[1]) || toNumber(args.length > 2 && args[2]);
956
+ /* listen(boundSocket) or listen({ handle: boundSocket }): the server takes its handle */
957
+ const bound = features.adoptBoundSocket?.(options) ?? features.adoptBoundSocket?.(options.handle);
958
+ if (bound) {
959
+ if (bound.path !== undefined) this._pipeName = bound.path;
960
+ this._isPipe = bound.path !== undefined;
961
+ this._setupListen(null, -1, -1, backlogFromArgs, undefined, 0, bound.id);
962
+ return this;
963
+ }
831
964
  if (options._handle !== undefined && options._handle !== null) options = options._handle;
832
965
  else if (options.handle !== undefined && options.handle !== null) options = options.handle;
833
966
  const flags = (options.ipv6Only ? 1 : 0) | (options.reusePort ? 2 : 0);
@@ -880,7 +1013,9 @@ export class Server extends EventEmitter {
880
1013
  }
881
1014
 
882
1015
  _lookupAndListen(port, address, backlog, flags) {
1016
+ const listeningId = this._listeningId;
883
1017
  lookup(address, (err, ip, addressType) => {
1018
+ if (listeningId !== this._listeningId) return;
884
1019
  if (err) this.emit("error", err);
885
1020
  else this._setupListen(ip, port, ip ? addressType : 4, backlog, undefined, flags);
886
1021
  });
@@ -970,6 +1105,7 @@ export class Server extends EventEmitter {
970
1105
  process.nextTick(() => this.emit("error", ex));
971
1106
  return;
972
1107
  }
1108
+ this._connectionKey = addressType + ":" + address + ":" + port;
973
1109
  if (this._unref) netRef(id, false);
974
1110
  process.nextTick(() => {
975
1111
  if (this._handle !== null) this.emit("listening");
@@ -1024,6 +1160,7 @@ export class Server extends EventEmitter {
1024
1160
 
1025
1161
  /* close([cb]): stop accepting; 'close' once the open connections end too */
1026
1162
  close(cb) {
1163
+ this._listeningId++;
1027
1164
  if (typeof cb === "function") {
1028
1165
  if (this._handle === null) this.once("close", () => cb(codeError(Error, "ERR_SERVER_NOT_RUNNING", "Server is not running.")));
1029
1166
  else this.once("close", cb);
@@ -1036,6 +1173,12 @@ export class Server extends EventEmitter {
1036
1173
  return this;
1037
1174
  }
1038
1175
 
1176
+ /* captureRejections: a 'connection' listener's rejection destroys the socket, as Node's */
1177
+ [Symbol.for("nodejs.rejection")](err, event, sock) {
1178
+ if (event === "connection") sock.destroy(err);
1179
+ else this.emit("error", err);
1180
+ }
1181
+
1039
1182
  _emitCloseIfDrained() {
1040
1183
  if (this._handle !== null || this._connections) return;
1041
1184
  process.nextTick(() => this.emit("close"));
@@ -1063,6 +1206,7 @@ export class Server extends EventEmitter {
1063
1206
  return new Promise((resolve, reject) => this.close((err) => (err ? reject(err) : resolve())));
1064
1207
  }
1065
1208
  }
1209
+ export const Server = callableClass(ServerClass, "Server"); /* Server(…) without new, as Node's */
1066
1210
 
1067
1211
  export function createServer(options, connectionListener) {
1068
1212
  return new Server(options, connectionListener);
@@ -1079,7 +1223,10 @@ export function connect(...args) {
1079
1223
 
1080
1224
  export const createConnection = connect;
1081
1225
 
1226
+ /* internal/net_bound.js's, linked when the program names it */
1227
+ export const BoundSocket = lazyClass("BoundSocket");
1228
+
1082
1229
  export default {
1083
- BlockList, SocketAddress, isIP, isIPv4, isIPv6, Socket, Stream, Server, connect, createConnection, createServer,
1230
+ BlockList, BoundSocket, SocketAddress, isIP, isIPv4, isIPv6, Socket, Stream, Server, connect, createConnection, createServer,
1084
1231
  getDefaultAutoSelectFamily, setDefaultAutoSelectFamily, getDefaultAutoSelectFamilyAttemptTimeout, setDefaultAutoSelectFamilyAttemptTimeout,
1085
1232
  };
@@ -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");