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.
- package/README.md +6 -3
- package/THIRD_PARTY_LICENSES +1 -1
- package/cli.js +55 -4
- package/node/prelude/_http_agent.js +5 -0
- package/node/prelude/_http_server.js +7 -0
- package/node/prelude/dns.js +7 -1
- package/node/prelude/events.js +7 -3
- package/node/prelude/http.js +685 -279
- package/node/prelude/https.js +14 -3
- package/node/prelude/internal/events_extras.js +2 -5
- package/node/prelude/internal/features.js +17 -0
- package/node/prelude/internal/http_agent.js +88 -2
- package/node/prelude/internal/http_symbols.js +3 -0
- package/node/prelude/internal/net_bound.js +123 -0
- package/node/prelude/internal/net_onread.js +73 -0
- package/node/prelude/internal/net_tos.js +54 -0
- package/node/prelude/internal/perf_observe.js +7 -1
- package/node/prelude/internal/tls/wrap.js +2 -1
- package/node/prelude/module.js +42 -18
- package/node/prelude/net.js +195 -48
- package/node/prelude/perf_hooks.js +4 -0
- package/node/src/host_http.c +151 -21
- package/node/src/host_net.c +75 -9
- package/package.json +1 -1
- package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
- package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
- package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
- package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
- package/runtime/include/js_atoms.h +4 -0
- package/runtime/include/js_web.h +3 -0
- package/runtime/include/jscontext.h +8 -0
- package/runtime/src/atoms.c +37 -3
- package/runtime/src/events.c +19 -0
- package/runtime/src/gc.c +4 -4
- package/runtime/src/internal.h +4 -4
- package/runtime/src/regexp.c +569 -12
- package/runtime/src/shape.c +68 -1
- package/runtime/src/weak.c +95 -24
package/node/prelude/https.js
CHANGED
|
@@ -7,10 +7,11 @@ import { Agent as HttpAgent, ClientRequest, Server as HttpServer } from "./http.
|
|
|
7
7
|
import { invalidArgType } from "./internal/errors.js";
|
|
8
8
|
import { Server as TlsServer, connect as tlsConnect } from "./tls.js";
|
|
9
9
|
import { urlToHttpOptions } from "./url.js";
|
|
10
|
+
import { callableClass } from "./internal/features.js";
|
|
10
11
|
|
|
11
12
|
/* ---- Server ------------------------------------------------------------------------------------- */
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
class ServerClass extends TlsServer {
|
|
14
15
|
constructor(options, requestListener) {
|
|
15
16
|
if (typeof options === "function") {
|
|
16
17
|
requestListener = options;
|
|
@@ -28,6 +29,7 @@ export class Server extends TlsServer {
|
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
}
|
|
32
|
+
export const Server = callableClass(ServerClass, "Server"); /* Server(…) without new, as Node's */
|
|
31
33
|
|
|
32
34
|
/* http.Server's methods: its requests, timeouts and connections */
|
|
33
35
|
for (const key of Object.getOwnPropertyNames(HttpServer.prototype)) {
|
|
@@ -123,7 +125,7 @@ Agent.prototype._evictSession = function _evictSession(key) {
|
|
|
123
125
|
delete this._sessionCache.map[key];
|
|
124
126
|
};
|
|
125
127
|
|
|
126
|
-
export
|
|
128
|
+
export let globalAgent = new Agent({ keepAlive: true, scheduling: "lifo", timeout: 5000 });
|
|
127
129
|
|
|
128
130
|
/* ---- request and get ---------------------------------------------------------------------------- */
|
|
129
131
|
|
|
@@ -145,4 +147,13 @@ export function get(input, options, cb) {
|
|
|
145
147
|
return req;
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
export default {
|
|
150
|
+
export default {
|
|
151
|
+
Agent, Server, createServer, get, request,
|
|
152
|
+
/* an accessor, as Node's: a program may replace the agent requests use by default */
|
|
153
|
+
get globalAgent() {
|
|
154
|
+
return globalAgent;
|
|
155
|
+
},
|
|
156
|
+
set globalAgent(value) {
|
|
157
|
+
globalAgent = value;
|
|
158
|
+
},
|
|
159
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// node:events' functions on emitters and EventTargets (getEventListeners, the max-listener
|
|
2
2
|
// counts, addAbortListener) and EventEmitterAsyncResource. Linked into programs that name them
|
|
3
3
|
// (internal/features.js).
|
|
4
|
+
import { eventTargetListeners } from "visaljs:host";
|
|
4
5
|
import { AsyncResource } from "../async_hooks.js";
|
|
5
6
|
import { EventEmitter, EventEmitterAsyncResource as EventEmitterAsyncResourceWrapper } from "../events.js";
|
|
6
7
|
import { invalidArgType } from "./errors.js";
|
|
@@ -18,11 +19,7 @@ function validateSignal(signal, name) {
|
|
|
18
19
|
/** An emitter's listeners for the event, or an EventTarget's (as its listeners() would). */
|
|
19
20
|
function getEventListeners(emitterOrTarget, type) {
|
|
20
21
|
if (typeof emitterOrTarget?.listeners === "function") return emitterOrTarget.listeners(type);
|
|
21
|
-
if (isEventTarget(emitterOrTarget))
|
|
22
|
-
const e = new Error("getEventListeners of an EventTarget is not supported by visaljs yet");
|
|
23
|
-
e.code = "ERR_VISALJS_UNSUPPORTED";
|
|
24
|
-
throw e;
|
|
25
|
-
}
|
|
22
|
+
if (isEventTarget(emitterOrTarget)) return eventTargetListeners(emitterOrTarget, type) ?? [];
|
|
26
23
|
throw invalidArgType("emitter", "an instance of EventEmitter or EventTarget", emitterOrTarget);
|
|
27
24
|
}
|
|
28
25
|
|
|
@@ -53,3 +53,20 @@ export function provideClass(Wrapper, Class) {
|
|
|
53
53
|
Object.defineProperty(Class.prototype, "constructor", { value: Wrapper, writable: true, configurable: true });
|
|
54
54
|
Object.setPrototypeOf(Wrapper, Object.getPrototypeOf(Class));
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/** A class as Node's constructor functions are: callable without new (`http.Server(…)`, as
|
|
58
|
+
* `new http.Server(…)`), and subclassed and tested with instanceof as the class. */
|
|
59
|
+
export function callableClass(Class, name = Class.name) {
|
|
60
|
+
const Callable = function (...args) {
|
|
61
|
+
return Reflect.construct(Class, args, new.target ?? Callable);
|
|
62
|
+
};
|
|
63
|
+
Object.defineProperty(Callable, "name", { value: name });
|
|
64
|
+
Object.defineProperty(Callable, "length", { value: Class.length });
|
|
65
|
+
Object.setPrototypeOf(Callable, Object.getPrototypeOf(Class));
|
|
66
|
+
for (const key of Reflect.ownKeys(Class)) {
|
|
67
|
+
if (key !== "length" && key !== "name" && key !== "prototype") Object.defineProperty(Callable, key, Object.getOwnPropertyDescriptor(Class, key));
|
|
68
|
+
}
|
|
69
|
+
Callable.prototype = Class.prototype;
|
|
70
|
+
Object.defineProperty(Class.prototype, "constructor", { value: Callable, writable: true, configurable: true });
|
|
71
|
+
return Callable;
|
|
72
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// response is done goes to freeSockets (unref'd, so it doesn't keep the program running) for
|
|
4
4
|
// the next request to the same origin, until the agent's timeout or the server's Keep-Alive hint.
|
|
5
5
|
// Ported from Node.js (Copyright Node.js contributors, MIT): node/third_party/nodejs/LICENSE.
|
|
6
|
+
import { httpGuard } from "visaljs:host";
|
|
6
7
|
import { EventEmitter } from "../events.js";
|
|
7
8
|
import { createConnection } from "../net.js";
|
|
8
9
|
import { invalidArgType, invalidArgValue, outOfRange } from "./errors.js";
|
|
@@ -13,6 +14,73 @@ function freeSocketErrorListener() {
|
|
|
13
14
|
this.emit("agentRemove");
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/* Node's free socket data guard: bytes a socket reads while it waits in the pool (no request on it)
|
|
18
|
+
* would be taken for the next request's response; the socket is destroyed instead. The bytes are
|
|
19
|
+
* caught in C (httpGuard), before they reach the socket's stream; attaching the next request's
|
|
20
|
+
* parser takes the guard off. */
|
|
21
|
+
const guardedSockets = new Map(); /* the guarded sockets, by their handle's id */
|
|
22
|
+
|
|
23
|
+
function onGuardedData(id) {
|
|
24
|
+
const socket = guardedSockets.get(id);
|
|
25
|
+
guardedSockets.delete(id);
|
|
26
|
+
socket?.destroy();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Node's maybeEnableKeylog: the sockets' keylog events become the agent's */
|
|
30
|
+
function maybeEnableKeylog(eventName) {
|
|
31
|
+
if (eventName !== "keylog") return;
|
|
32
|
+
this.removeListener("newListener", maybeEnableKeylog);
|
|
33
|
+
const emit = this.emit.bind(this);
|
|
34
|
+
this._onKeylog = function onkeylog(keylog) {
|
|
35
|
+
emit("keylog", keylog, this); /* this: the socket */
|
|
36
|
+
};
|
|
37
|
+
for (const set of [this.freeSockets, this.sockets]) {
|
|
38
|
+
for (const sockets of Object.values(set)) for (const socket of sockets) socket.on("keylog", this._onKeylog);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function installFreeSocketDataGuard(socket) {
|
|
43
|
+
/* a TLS socket's reads are records (session tickets, alerts) before they're data: its own */
|
|
44
|
+
if (socket.encrypted) return;
|
|
45
|
+
if (socket.readableLength > 0) {
|
|
46
|
+
socket.destroy();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (socket.connecting) {
|
|
50
|
+
if (!socket._freeGuardConnect) { /* once: a socket may come back to the pool before it connects */
|
|
51
|
+
socket._freeGuardConnect = () => {
|
|
52
|
+
socket._freeGuardConnect = undefined;
|
|
53
|
+
installFreeSocketDataGuard(socket);
|
|
54
|
+
};
|
|
55
|
+
socket.once("connect", socket._freeGuardConnect);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const id = socket._handle;
|
|
60
|
+
if (typeof id === "number" && httpGuard(id, onGuardedData)) {
|
|
61
|
+
guardedSockets.set(id, socket);
|
|
62
|
+
socket._freeGuardId = id;
|
|
63
|
+
/* one listener while it's guarded, taken off when it's reused (a kept-alive socket comes back
|
|
64
|
+
* to the pool after each request) */
|
|
65
|
+
socket._freeGuardClose = () => removeFreeSocketDataGuard(socket);
|
|
66
|
+
socket.once("close", socket._freeGuardClose);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function removeFreeSocketDataGuard(socket) {
|
|
71
|
+
if (socket._freeGuardConnect) {
|
|
72
|
+
socket.removeListener("connect", socket._freeGuardConnect);
|
|
73
|
+
socket._freeGuardConnect = undefined;
|
|
74
|
+
}
|
|
75
|
+
if (socket._freeGuardClose) {
|
|
76
|
+
socket.removeListener("close", socket._freeGuardClose);
|
|
77
|
+
socket._freeGuardClose = undefined;
|
|
78
|
+
}
|
|
79
|
+
const id = socket._freeGuardId;
|
|
80
|
+
if (id !== undefined && guardedSockets.get(id) === socket) guardedSockets.delete(id);
|
|
81
|
+
socket._freeGuardId = undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
16
84
|
export function Agent(options) {
|
|
17
85
|
if (!(this instanceof Agent)) return new Agent(options);
|
|
18
86
|
EventEmitter.call(this);
|
|
@@ -29,6 +97,9 @@ export function Agent(options) {
|
|
|
29
97
|
this.maxSockets = this.options.maxSockets || Agent.defaultMaxSockets;
|
|
30
98
|
this.maxFreeSockets = this.options.maxFreeSockets || 256;
|
|
31
99
|
this.scheduling = this.options.scheduling || "lifo";
|
|
100
|
+
/* what the server's Keep-Alive timeout is cut by, so the socket isn't reused as the server closes it */
|
|
101
|
+
const buffer = this.options.agentKeepAliveTimeoutBuffer;
|
|
102
|
+
this.agentKeepAliveTimeoutBuffer = typeof buffer === "number" && buffer >= 0 && Number.isFinite(buffer) ? buffer : 1000;
|
|
32
103
|
this.maxTotalSockets = this.options.maxTotalSockets;
|
|
33
104
|
this.totalSocketCount = 0;
|
|
34
105
|
if (this.scheduling !== "fifo" && this.scheduling !== "lifo") throw invalidArgValue("scheduling", "must be one of: 'fifo', 'lifo'", this.scheduling);
|
|
@@ -39,6 +110,9 @@ export function Agent(options) {
|
|
|
39
110
|
this.maxTotalSockets = Infinity;
|
|
40
111
|
}
|
|
41
112
|
|
|
113
|
+
/* keylog events only once someone listens: then every socket's, the existing ones' too */
|
|
114
|
+
this.on("newListener", maybeEnableKeylog);
|
|
115
|
+
|
|
42
116
|
/* a socket whose request is done: the next queued request's, kept alive, or destroyed */
|
|
43
117
|
this.on("free", (socket, options) => {
|
|
44
118
|
const name = this.getName(options);
|
|
@@ -70,6 +144,7 @@ export function Agent(options) {
|
|
|
70
144
|
socket._httpMessage = null;
|
|
71
145
|
this.removeSocket(socket, options);
|
|
72
146
|
socket.once("error", freeSocketErrorListener);
|
|
147
|
+
installFreeSocketDataGuard(socket);
|
|
73
148
|
freeSockets.push(socket);
|
|
74
149
|
});
|
|
75
150
|
}
|
|
@@ -131,7 +206,12 @@ Agent.prototype.addRequest = function addRequest(req, options, port, localAddres
|
|
|
131
206
|
}
|
|
132
207
|
const freeLen = freeSockets ? freeSockets.length : 0;
|
|
133
208
|
const sockLen = freeLen + this.sockets[name].length;
|
|
209
|
+
/* a reused socket takes the request's highWaterMark, as Node's */
|
|
210
|
+
if (socket && options.highWaterMark != null && socket.writableHighWaterMark !== options.highWaterMark) {
|
|
211
|
+
socket._writableState.highWaterMark = options.highWaterMark;
|
|
212
|
+
}
|
|
134
213
|
if (socket) {
|
|
214
|
+
removeFreeSocketDataGuard(socket);
|
|
135
215
|
this.reuseSocket(socket, req);
|
|
136
216
|
setRequestSocket(this, req, socket);
|
|
137
217
|
this.sockets[name].push(socket);
|
|
@@ -163,6 +243,7 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) {
|
|
|
163
243
|
this.sockets[name].push(s);
|
|
164
244
|
this.totalSocketCount++;
|
|
165
245
|
installListeners(this, s, options);
|
|
246
|
+
if (this._onKeylog) s.on("keylog", this._onKeylog);
|
|
166
247
|
cb(null, s);
|
|
167
248
|
};
|
|
168
249
|
if (this.keepAlive) {
|
|
@@ -244,7 +325,7 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
|
|
|
244
325
|
const hint = socket._httpMessage?.res?.headers["keep-alive"];
|
|
245
326
|
const seconds = hint ? /^timeout=(\d+)/.exec(hint)?.[1] : undefined;
|
|
246
327
|
if (seconds) {
|
|
247
|
-
let serverHintTimeout = parseInt(seconds, 10) * 1000 -
|
|
328
|
+
let serverHintTimeout = parseInt(seconds, 10) * 1000 - this.agentKeepAliveTimeoutBuffer;
|
|
248
329
|
serverHintTimeout = serverHintTimeout > 0 ? serverHintTimeout : 0;
|
|
249
330
|
if (serverHintTimeout === 0) canKeepSocketAlive = false;
|
|
250
331
|
else if (serverHintTimeout < agentTimeout) agentTimeout = serverHintTimeout;
|
|
@@ -272,4 +353,9 @@ function setRequestSocket(agent, req, socket) {
|
|
|
272
353
|
socket.setTimeout(req.timeout);
|
|
273
354
|
}
|
|
274
355
|
|
|
275
|
-
export
|
|
356
|
+
export let globalAgent = new Agent({ keepAlive: true, scheduling: "lifo", timeout: 5000 });
|
|
357
|
+
|
|
358
|
+
/* http.globalAgent's setter: the agent requests use by default from then on, as Node's */
|
|
359
|
+
export function setGlobalAgent(agent) {
|
|
360
|
+
globalAgent = agent;
|
|
361
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// net.BoundSocket, as Node's: a TCP or pipe handle bound at once (its errors thrown by the
|
|
2
|
+
// constructor), neither listening nor connecting until one Server (server.listen(bound)) or Socket
|
|
3
|
+
// (new net.Socket({ handle: bound })) adopts it, which then owns it. No DNS: a host is an IP.
|
|
4
|
+
// Linked into programs that name BoundSocket (internal/features.js); net.js adopts the handle
|
|
5
|
+
// through features.adoptBoundSocket.
|
|
6
|
+
import { netAddress, netBind, netClose, netFd, netOpen } from "visaljs:host";
|
|
7
|
+
import { BoundSocket as Wrapper } from "../net.js";
|
|
8
|
+
import { codeError, invalidArgType, invalidArgValue, validateString } from "./errors.js";
|
|
9
|
+
import { features, provideClass } from "./features.js";
|
|
10
|
+
import { isIP } from "./net.js";
|
|
11
|
+
|
|
12
|
+
const kConsume = Symbol("kBoundSocketConsume");
|
|
13
|
+
|
|
14
|
+
const adopted = () => codeError(Error, "ERR_SOCKET_HANDLE_ADOPTED", "The bound socket has already been adopted by a server or socket");
|
|
15
|
+
|
|
16
|
+
function validateBoolean(value, name) {
|
|
17
|
+
if (typeof value !== "boolean") throw invalidArgType(name, "of type boolean", value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class BoundSocket {
|
|
21
|
+
#handle = null; /* the host handle's id, until adopted or closed */
|
|
22
|
+
#address = {};
|
|
23
|
+
#path;
|
|
24
|
+
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) throw invalidArgType("options", "of type object", options);
|
|
27
|
+
if (options.path !== undefined) {
|
|
28
|
+
this.#bindPipe(options);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const { exceptionWithHostPort, validatePort } = features.net;
|
|
32
|
+
const port = validatePort(options.port ?? 0, "options.port");
|
|
33
|
+
const ipv6Only = options.ipv6Only ?? false;
|
|
34
|
+
validateBoolean(ipv6Only, "options.ipv6Only");
|
|
35
|
+
const reusePort = options.reusePort ?? false;
|
|
36
|
+
validateBoolean(reusePort, "options.reusePort");
|
|
37
|
+
let { host } = options;
|
|
38
|
+
if (host === undefined || host === null) {
|
|
39
|
+
host = ipv6Only ? "::" : "0.0.0.0";
|
|
40
|
+
} else {
|
|
41
|
+
validateString(host, "options.host");
|
|
42
|
+
if (isIP(host) === 0) {
|
|
43
|
+
throw invalidArgValue("options.host", "must be a numeric IP address; net.BoundSocket does not perform DNS resolution", host);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const id = netOpen(false);
|
|
47
|
+
let err = netBind(id, host, port, (ipv6Only ? 1 : 0) | (reusePort ? 2 : 0));
|
|
48
|
+
/* libuv keeps a bind's EADDRINUSE for the next getsockname, listen or connect: asked now, so it
|
|
49
|
+
* throws here, as Node's */
|
|
50
|
+
if (err === 0) {
|
|
51
|
+
const a = netAddress(id, false);
|
|
52
|
+
if (typeof a === "number") err = a;
|
|
53
|
+
else this.#address = { address: a[0], family: a[1], port: a[2] };
|
|
54
|
+
}
|
|
55
|
+
if (err) {
|
|
56
|
+
netClose(id);
|
|
57
|
+
throw exceptionWithHostPort(err, "bind", host, port);
|
|
58
|
+
}
|
|
59
|
+
this.#handle = id;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* a Unix domain socket's path (or a Windows named pipe's); one starting with "\0" is in Linux's
|
|
63
|
+
* abstract namespace */
|
|
64
|
+
#bindPipe(options) {
|
|
65
|
+
const { path, host, port, ipv6Only, reusePort } = options;
|
|
66
|
+
if (host !== undefined || port !== undefined || ipv6Only !== undefined || reusePort !== undefined) {
|
|
67
|
+
throw invalidArgValue("options", "path is mutually exclusive with host, port, ipv6Only, and reusePort", options);
|
|
68
|
+
}
|
|
69
|
+
validateString(path, "options.path");
|
|
70
|
+
if (path[0] === "\0" && process.platform !== "linux") {
|
|
71
|
+
throw invalidArgValue("options.path", "abstract socket paths are only supported on Linux", path);
|
|
72
|
+
}
|
|
73
|
+
const id = netOpen(true);
|
|
74
|
+
const err = netBind(id, path);
|
|
75
|
+
if (err) {
|
|
76
|
+
netClose(id);
|
|
77
|
+
throw features.net.errnoException(err, "bind");
|
|
78
|
+
}
|
|
79
|
+
this.#handle = id;
|
|
80
|
+
this.#path = path;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* { address, family, port }, or a pipe's path */
|
|
84
|
+
address() {
|
|
85
|
+
if (this.#handle === null) throw adopted();
|
|
86
|
+
return this.#path ?? this.#address;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* the descriptor, or -1 where sockets have none (Windows) */
|
|
90
|
+
fd() {
|
|
91
|
+
if (this.#handle === null) throw adopted();
|
|
92
|
+
const fd = netFd(this.#handle);
|
|
93
|
+
return fd < 0 ? -1 : fd;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
close() {
|
|
97
|
+
if (this.#handle === null) throw adopted();
|
|
98
|
+
netClose(this.#handle);
|
|
99
|
+
this.#handle = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
[Symbol.dispose]() {
|
|
103
|
+
if (this.#handle !== null) {
|
|
104
|
+
netClose(this.#handle);
|
|
105
|
+
this.#handle = null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get isPipe() {
|
|
110
|
+
return this.#path !== undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
[kConsume]() {
|
|
114
|
+
if (this.#handle === null) throw adopted();
|
|
115
|
+
const id = this.#handle;
|
|
116
|
+
this.#handle = null;
|
|
117
|
+
return { id, path: this.#path };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* the handle of a BoundSocket being adopted ({ id, path }), or null for anything else */
|
|
122
|
+
features.adoptBoundSocket = (value) => (value instanceof BoundSocket ? value[kConsume]() : null);
|
|
123
|
+
provideClass(Wrapper, BoundSocket);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// net.Socket's onread option, as Node's: the bytes read go into the user's buffer, a buffer's worth
|
|
2
|
+
// at a time, and to its callback instead of 'data'. The callback returning false, or pausing,
|
|
3
|
+
// stops reading, as a read into that buffer would: the bytes not yet given wait for resume().
|
|
4
|
+
// Linked into programs that name onread (internal/features.js).
|
|
5
|
+
import { netReadStart, netReadStop } from "visaljs:host";
|
|
6
|
+
import { features } from "./features.js";
|
|
7
|
+
|
|
8
|
+
function deliver(socket, bytes) {
|
|
9
|
+
for (let at = 0; at < bytes.length;) {
|
|
10
|
+
const { buffer, callback } = socket._onread;
|
|
11
|
+
const buf = typeof buffer === "function" ? buffer() : buffer;
|
|
12
|
+
const n = Math.min(buf.length, bytes.length - at);
|
|
13
|
+
buf.set(bytes.subarray(at, at + n));
|
|
14
|
+
at += n;
|
|
15
|
+
const more = callback.call(socket, n, buf) !== false && socket._reading;
|
|
16
|
+
if (!more) {
|
|
17
|
+
if (at < bytes.length) socket._onreadRest = bytes.subarray(at);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
features.onread = {
|
|
25
|
+
/* bytes read: false stops reading */
|
|
26
|
+
deliver,
|
|
27
|
+
|
|
28
|
+
/* a TLS socket's decrypted bytes: all of them, false when a callback asks to stop (its reads
|
|
29
|
+
* resume through its own stream, not the bytes kept here) */
|
|
30
|
+
deliverAll(socket, bytes) {
|
|
31
|
+
let more = true;
|
|
32
|
+
for (let at = 0; at < bytes.length;) {
|
|
33
|
+
const { buffer, callback } = socket._onread;
|
|
34
|
+
const buf = typeof buffer === "function" ? buffer() : buffer;
|
|
35
|
+
const n = Math.min(buf.length, bytes.length - at);
|
|
36
|
+
buf.set(bytes.subarray(at, at + n));
|
|
37
|
+
at += n;
|
|
38
|
+
if (callback.call(socket, n, buf) === false) more = false;
|
|
39
|
+
}
|
|
40
|
+
return more;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/* libuv takes a buffer for the read that finds EOF too: a buffer function is asked for one */
|
|
44
|
+
eof(socket) {
|
|
45
|
+
if (typeof socket._onread.buffer === "function") socket._onread.buffer();
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/* Node's: pausing stops reading from the handle, resuming starts it (a TLS socket reads through
|
|
49
|
+
* its own stream) */
|
|
50
|
+
pause(socket) {
|
|
51
|
+
if (socket.encrypted || socket.connecting || socket._handle === null || !socket._reading) return;
|
|
52
|
+
socket._reading = false;
|
|
53
|
+
if (!socket.destroyed) {
|
|
54
|
+
const err = netReadStop(socket._handle);
|
|
55
|
+
if (err) socket.destroy(features.net.errnoException(err, "read"));
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
resume(socket) {
|
|
60
|
+
if (!socket.encrypted && !socket.connecting && socket._handle !== null && !socket._reading) socket._startReading();
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/* reading again: the bytes left from the last read first, as the next read */
|
|
64
|
+
rest(socket) {
|
|
65
|
+
const rest = socket._onreadRest;
|
|
66
|
+
socket._onreadRest = null;
|
|
67
|
+
process.nextTick(() => {
|
|
68
|
+
if (socket.destroyed || socket._handle === null) return;
|
|
69
|
+
if (deliver(socket, rest)) netReadStart(socket._handle);
|
|
70
|
+
else socket._reading = false;
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// net.Socket's type of service, as Node's: setTypeOfService, getTypeOfService and the
|
|
2
|
+
// typeOfService option (IP_TOS, or IPV6_TCLASS on an IPv6 socket; on Windows, best effort). Linked
|
|
3
|
+
// into programs that name it (internal/features.js).
|
|
4
|
+
import { errnoName, netTypeOfService } from "visaljs:host";
|
|
5
|
+
import { invalidArgType } from "./errors.js";
|
|
6
|
+
import { features } from "./features.js";
|
|
7
|
+
import { validateInt32 } from "./streams/validators.js";
|
|
8
|
+
|
|
9
|
+
const isWindows = process.platform === "win32";
|
|
10
|
+
const UV_EBADF = -9;
|
|
11
|
+
|
|
12
|
+
/* Node's handle exists from connect() on, its descriptor only once connect(2) is under way:
|
|
13
|
+
* between the two, the option can't be set or read */
|
|
14
|
+
const noDescriptor = (socket) => socket._handle === null && socket.connecting;
|
|
15
|
+
|
|
16
|
+
features.validateTypeOfService = (tos) => validateInt32(tos, "options.typeOfService", 0, 255);
|
|
17
|
+
|
|
18
|
+
/* kept until there's a handle */
|
|
19
|
+
features.setTypeOfService = function (tos) {
|
|
20
|
+
if (Number.isNaN(tos)) throw invalidArgType("tos", "of type number", tos);
|
|
21
|
+
validateInt32(tos, "tos", 0, 255);
|
|
22
|
+
if (noDescriptor(this) && !this._isPipe && tos !== this._tos) {
|
|
23
|
+
this._tos = tos;
|
|
24
|
+
if (!isWindows) throw features.net.errnoException(UV_EBADF, "setTypeOfService");
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
if (this._handle === null || this._isPipe) {
|
|
28
|
+
this._tos = tos;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
if (tos !== this._tos) {
|
|
32
|
+
this._tos = tos;
|
|
33
|
+
const err = netTypeOfService(this._handle, tos);
|
|
34
|
+
if (err && !isWindows) throw features.net.errnoException(err, "setTypeOfService");
|
|
35
|
+
}
|
|
36
|
+
return this;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
features.getTypeOfService = function () {
|
|
40
|
+
if (noDescriptor(this) && !this._isPipe && !isWindows) throw features.net.errnoException(UV_EBADF, "getTypeOfService");
|
|
41
|
+
if (this._handle === null || this._isPipe) return this._tos !== undefined ? this._tos : 0;
|
|
42
|
+
const res = netTypeOfService(this._handle, -1);
|
|
43
|
+
if (res < 0) {
|
|
44
|
+
if (isWindows) return this._tos !== undefined ? this._tos : 0;
|
|
45
|
+
throw features.net.errnoException(res, "getTypeOfService");
|
|
46
|
+
}
|
|
47
|
+
return res;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/* the type of service given before the connection, set once it's made */
|
|
51
|
+
features.applyTypeOfService = (socket) => {
|
|
52
|
+
const err = netTypeOfService(socket._handle, socket._tos);
|
|
53
|
+
if (err && errnoName(err) !== "EBADF" && !isWindows) socket.emit("error", features.net.errnoException(err, "setTypeOfService"));
|
|
54
|
+
};
|
|
@@ -109,7 +109,13 @@ class PerformanceResourceTiming extends PerformanceEntry {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/*
|
|
112
|
+
/* whether an observer takes entries of this type: net.js times a connect only then */
|
|
113
|
+
features.perfObserving = (type) => {
|
|
114
|
+
for (const observer of observers) if (observer._observes(type)) return true;
|
|
115
|
+
return false;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/* perf_hooks.js calls this with each mark and measure, net.js with a connect's entry */
|
|
113
119
|
features.perfObserve = (entry) => {
|
|
114
120
|
for (const observer of observers) if (observer._observes(entry.entryType)) observer._enqueue(entry);
|
|
115
121
|
};
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
tlsRootCertificates, tlsRun, tlsSession, tlsSetNoRenegotiation, tlsShutdown, tlsVerifyError,
|
|
13
13
|
} from "visaljs:host";
|
|
14
14
|
import { codeError, invalidArgType, outOfRange, validateFunction, validateString } from "../errors.js";
|
|
15
|
+
import { features } from "../features.js";
|
|
15
16
|
import { isIP } from "../net.js";
|
|
16
17
|
import { Server as NetServer, Socket } from "../../net.js";
|
|
17
18
|
import { createSecureContext, SecureContext, sslError, tlsFailure, VERSION_NAMES, verifyError } from "./common.js";
|
|
@@ -343,7 +344,7 @@ export class TLSSocket extends Socket {
|
|
|
343
344
|
_pushPlain(bytes) {
|
|
344
345
|
this._unrefTimer();
|
|
345
346
|
let more;
|
|
346
|
-
if (this._onread) more =
|
|
347
|
+
if (this._onread) more = features.onread.deliverAll(this, bytes);
|
|
347
348
|
else more = this.push(bytes);
|
|
348
349
|
if (!more && this._secureEstablished) this._stopReading();
|
|
349
350
|
}
|
package/node/prelude/module.js
CHANGED
|
@@ -53,6 +53,9 @@ for (const ext of [".js", ".json", ".node"]) {
|
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/* Module._cache: every require's cache, as Node's */
|
|
57
|
+
const cache = { __proto__: null };
|
|
58
|
+
|
|
56
59
|
export function createRequire(filename) {
|
|
57
60
|
if (typeof filename !== "string" && !(typeof URL === "function" && filename instanceof URL)) {
|
|
58
61
|
throw withCode(new TypeError("The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received " +
|
|
@@ -62,38 +65,55 @@ export function createRequire(filename) {
|
|
|
62
65
|
const require = (id) => {
|
|
63
66
|
const load = globalThis[REQUIRE_TABLE]?.get(id);
|
|
64
67
|
if (load !== undefined) return load();
|
|
65
|
-
if (typeof id === "string" && id.endsWith(".json") &&
|
|
68
|
+
if (typeof id === "string" && id.endsWith(".json") && (isRelative(id) || isAbsolute(id))) return requireJSON(require.cache, id, base);
|
|
66
69
|
throw notFound(id);
|
|
67
70
|
};
|
|
68
71
|
require.resolve = (id) => {
|
|
69
72
|
throw notFound(id);
|
|
70
73
|
};
|
|
71
74
|
require.resolve.paths = () => null;
|
|
72
|
-
require.cache =
|
|
75
|
+
require.cache = cache;
|
|
73
76
|
require.main = undefined;
|
|
74
77
|
require.extensions = extensions;
|
|
75
78
|
return require;
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
/* paths as node:path's default (which this module can't import) has them: on Windows, a root of
|
|
82
|
+
* a drive (C:), a share (\\server\share) or none, and either separator */
|
|
83
|
+
const onWindows = () => globalThis.process.platform === "win32";
|
|
84
|
+
const isAbsolute = (path) => path.startsWith("/") || (onWindows() && /^(?:\\|[A-Za-z]:[\\/])/.test(path));
|
|
85
|
+
const isRelative = (path) => /^\.\.?\//.test(path) || (onWindows() && /^\.\.?\\/.test(path));
|
|
86
|
+
const endsWithSep = (path) => path.endsWith("/") || (onWindows() && path.endsWith("\\"));
|
|
87
|
+
|
|
78
88
|
function pathOf(filename) {
|
|
79
89
|
const s = typeof filename === "string" ? filename : filename.href;
|
|
80
|
-
|
|
90
|
+
if (!s.startsWith("file:")) return s;
|
|
91
|
+
const url = new URL(s);
|
|
92
|
+
const path = decodeURIComponent(url.pathname);
|
|
93
|
+
if (!onWindows()) return path;
|
|
94
|
+
const local = path.replace(/^\/([A-Za-z]:)/, "$1").replaceAll("/", "\\");
|
|
95
|
+
return url.hostname ? `\\\\${url.hostname}${local}` : local;
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
/* an absolute path without its . and .. segments (node:path's normalize
|
|
98
|
+
/* an absolute path without its . and .. segments (node:path's normalize); a root's parent is itself.
|
|
99
|
+
* On Windows, \path is on the working directory's drive. */
|
|
100
|
+
const windowsRoot = (path) => /^(?:[A-Za-z]:|[\\/]{2}[^\\/]+[\\/][^\\/]+)/.exec(path)?.[0];
|
|
84
101
|
function normalize(path) {
|
|
102
|
+
const windows = onWindows();
|
|
103
|
+
const given = windows ? windowsRoot(path) ?? "" : "";
|
|
104
|
+
const root = windows && !given ? windowsRoot(globalThis.process.cwd()) ?? "" : given;
|
|
85
105
|
const out = [];
|
|
86
|
-
for (const part of path.split("/")) {
|
|
106
|
+
for (const part of path.slice(given.length).split(windows ? /[\\/]/ : "/")) {
|
|
87
107
|
if (part === "..") out.pop();
|
|
88
108
|
else if (part !== "." && part !== "") out.push(part);
|
|
89
109
|
}
|
|
90
|
-
return "/" + out.join("/");
|
|
110
|
+
return windows ? `${root.replaceAll("/", "\\")}\\${out.join("\\")}` : "/" + out.join("/");
|
|
91
111
|
}
|
|
92
112
|
|
|
93
113
|
/** A JSON file, by a path absolute or relative to the requiring file's directory: parsed once,
|
|
94
114
|
* then from require.cache, as Node's loader does. */
|
|
95
115
|
function requireJSON(cache, id, from) {
|
|
96
|
-
const path = normalize(id
|
|
116
|
+
const path = normalize(isAbsolute(id) ? id : `${from}/../${id}`);
|
|
97
117
|
if (path in cache) return cache[path].exports;
|
|
98
118
|
const text = fsReadFile(path, false);
|
|
99
119
|
if (typeof text !== "string") throw notFound(id);
|
|
@@ -182,6 +202,9 @@ const isDir = (path) => {
|
|
|
182
202
|
};
|
|
183
203
|
const exists = (path) => typeof fsStat(path, true) !== "number";
|
|
184
204
|
const dirOf = (path) => path.slice(0, Math.max(path.lastIndexOf("/"), 0)) || "/";
|
|
205
|
+
const parentOf = (path) => normalize(`${path}/..`);
|
|
206
|
+
/* on Windows, Node gives the closest package.json as a namespaced path: \\?\C:\… or \\?\UNC\server\… */
|
|
207
|
+
const namespaced = (path) => !onWindows() ? path : path.startsWith("\\\\") ? `\\\\?\\UNC\\${path.slice(2)}` : `\\\\?\\${path}`;
|
|
185
208
|
|
|
186
209
|
/** findPackageJSON(specifier[, base]): the package.json closest to what the specifier resolves to
|
|
187
210
|
* (a path or file: URL, relative to base; or a package, from base's node_modules), or undefined. */
|
|
@@ -192,26 +215,27 @@ export function findPackageJSON(specifier, base) {
|
|
|
192
215
|
"Invalid relative URL or base scheme is not hierarchical."), "ERR_UNSUPPORTED_RESOLVE_REQUEST");
|
|
193
216
|
const basePath = baseText === undefined ? undefined : baseText.startsWith("file:") ? pathOf(baseText) : baseText;
|
|
194
217
|
let target;
|
|
195
|
-
if (spec.startsWith("file:")) target = pathOf(spec);
|
|
218
|
+
if (spec.startsWith("file:")) target = normalize(pathOf(spec));
|
|
219
|
+
/* a specifier is a URL: C:\… has the scheme c: */
|
|
220
|
+
else if (onWindows() && isAbsolute(spec)) throw withCode(new TypeError("The URL must be of scheme file"), "ERR_INVALID_URL_SCHEME");
|
|
196
221
|
else if (spec.startsWith("/")) target = normalize(spec);
|
|
197
222
|
else if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
198
223
|
if (basePath === undefined) throw unresolved();
|
|
199
|
-
|
|
200
|
-
target = normalize(`${from}/${spec}`);
|
|
224
|
+
target = normalize(endsWithSep(baseText) ? `${basePath}/${spec}` : `${basePath}/../${spec}`);
|
|
201
225
|
} else {
|
|
202
226
|
/* a package: its directory under the closest node_modules that has it */
|
|
203
227
|
if (basePath === undefined) throw unresolved();
|
|
204
228
|
const name = spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
|
|
205
|
-
for (let dir = baseText
|
|
206
|
-
const candidate = `${dir
|
|
229
|
+
for (let dir = endsWithSep(baseText) ? normalize(basePath) : parentOf(basePath); ; dir = parentOf(dir)) {
|
|
230
|
+
const candidate = normalize(`${dir}/node_modules/${name}/package.json`);
|
|
207
231
|
if (exists(candidate)) return candidate;
|
|
208
|
-
if (dir ===
|
|
232
|
+
if (parentOf(dir) === dir) throw withCode(new Error(`Cannot find package '${name}' imported from ${basePath}`), "ERR_MODULE_NOT_FOUND");
|
|
209
233
|
}
|
|
210
234
|
}
|
|
211
|
-
for (let dir = isDir(target) ? target :
|
|
212
|
-
const candidate = `${dir
|
|
213
|
-
if (exists(candidate)) return candidate;
|
|
214
|
-
if (dir ===
|
|
235
|
+
for (let dir = isDir(target) ? target : parentOf(target); ; dir = parentOf(dir)) {
|
|
236
|
+
const candidate = normalize(`${dir}/package.json`);
|
|
237
|
+
if (exists(candidate)) return namespaced(candidate);
|
|
238
|
+
if (parentOf(dir) === dir) return undefined;
|
|
215
239
|
}
|
|
216
240
|
}
|
|
217
241
|
|
|
@@ -239,7 +263,7 @@ const ModuleWithHelpers = /* @__PURE__ */ Object.assign(Module, {
|
|
|
239
263
|
enableCompileCache, getCompileCacheDir, flushCompileCache, getSourceMapsSupport, setSourceMapsSupport, findSourceMap, SourceMap,
|
|
240
264
|
findPackageJSON, globalPaths, Module, wrapper,
|
|
241
265
|
wrap: (script) => wrapper[0] + script + wrapper[1],
|
|
242
|
-
_cache:
|
|
266
|
+
_cache: cache,
|
|
243
267
|
_extensions: extensions,
|
|
244
268
|
/* a built-in resolves to itself; a compiled program has no files to resolve to */
|
|
245
269
|
_resolveFilename(request) {
|