@bytecodealliance/preview3-shim 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nodejs/cli.js +2 -1
- package/dist/nodejs/filesystem/descriptor.js +2 -1
- package/dist/nodejs/http/client.js +2 -1
- package/dist/nodejs/http/fields.js +7 -2
- package/dist/nodejs/http/server.js +2 -1
- package/dist/nodejs/sockets/tcp.js +2 -1
- package/dist/nodejs/sockets/udp.js +2 -1
- package/dist/nodejs/workers/cli-worker.bundle.js +88 -0
- package/dist/nodejs/workers/filesystem-worker.bundle.js +236 -0
- package/dist/nodejs/workers/filesystem-worker.js +8 -1
- package/dist/nodejs/workers/http-worker.bundle.js +403 -0
- package/dist/nodejs/workers/resource-worker.d.ts +1 -1
- package/dist/nodejs/workers/resource-worker.js +5 -5
- package/dist/nodejs/workers/tcp-worker.bundle.js +496 -0
- package/dist/nodejs/workers/udp-worker.bundle.js +270 -0
- package/package.json +3 -2
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import { Server, Socket } from "node:net";
|
|
2
|
+
import { Readable } from "stream";
|
|
3
|
+
import { pipeline } from "stream/promises";
|
|
4
|
+
import { once } from "node:events";
|
|
5
|
+
import { isMainThread, parentPort } from "worker_threads";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
//#region dist/nodejs/workers/resource-worker.js
|
|
8
|
+
function Router() {
|
|
9
|
+
const _hooks = {
|
|
10
|
+
beforeAll: null,
|
|
11
|
+
ops: /* @__PURE__ */ new Map()
|
|
12
|
+
};
|
|
13
|
+
if (isMainThread || !parentPort) throw new Error("Router can only be used in worker threads");
|
|
14
|
+
parentPort.on("message", async (msg) => {
|
|
15
|
+
const { _reply, _condvar, ...rest } = msg;
|
|
16
|
+
let result, error, transfer = [];
|
|
17
|
+
try {
|
|
18
|
+
if (_hooks.beforeAll) _hooks.beforeAll(rest);
|
|
19
|
+
const handler = _hooks.ops.get(rest.op);
|
|
20
|
+
if (!handler) throw new Error(`Unknown op ${rest.op}`);
|
|
21
|
+
const outcome = await handler(rest);
|
|
22
|
+
if (outcome && Array.isArray(outcome.transferable)) {
|
|
23
|
+
result = outcome.result;
|
|
24
|
+
transfer = outcome.transferable;
|
|
25
|
+
} else result = outcome;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
error = serializeError(err);
|
|
28
|
+
}
|
|
29
|
+
_reply.postMessage({
|
|
30
|
+
result,
|
|
31
|
+
error
|
|
32
|
+
}, transfer);
|
|
33
|
+
_reply.close();
|
|
34
|
+
if (_condvar) notify(_condvar);
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
beforeAll(fn) {
|
|
38
|
+
_hooks.beforeAll = fn;
|
|
39
|
+
return this;
|
|
40
|
+
},
|
|
41
|
+
op(name, fn) {
|
|
42
|
+
_hooks.ops.set(name, fn);
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function serializeError(err) {
|
|
48
|
+
if (!(err instanceof Error)) return err;
|
|
49
|
+
const serialized = {
|
|
50
|
+
__resourceWorkerError: true,
|
|
51
|
+
name: err.name,
|
|
52
|
+
message: err.message,
|
|
53
|
+
stack: err.stack
|
|
54
|
+
};
|
|
55
|
+
for (const key of Reflect.ownKeys(err)) serialized[key] = err[key];
|
|
56
|
+
if (err.cause !== void 0) serialized.cause = serializeError(err.cause);
|
|
57
|
+
return serialized;
|
|
58
|
+
}
|
|
59
|
+
function notify(condvar) {
|
|
60
|
+
const lock = new Int32Array(condvar);
|
|
61
|
+
Atomics.store(lock, 0, 1);
|
|
62
|
+
Atomics.notify(lock, 0, 1);
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region dist/nodejs/sockets/address.js
|
|
66
|
+
const tupleToIpv6 = (segments) => {
|
|
67
|
+
if (segments.length !== 8) return null;
|
|
68
|
+
return segments.map((segment) => segment.toString(16)).join(":");
|
|
69
|
+
};
|
|
70
|
+
const tupleToIpv4 = (segments) => {
|
|
71
|
+
if (segments.length !== 4) return null;
|
|
72
|
+
return segments.map((segment) => segment.toString(10)).join(".");
|
|
73
|
+
};
|
|
74
|
+
const ipv6ToTuple = (ipv6) => {
|
|
75
|
+
const [lhs, rhs = ""] = ipv6.includes("::") ? ipv6.split("::") : [ipv6];
|
|
76
|
+
const lhsParts = lhs === "" ? [] : lhs.split(":");
|
|
77
|
+
const rhsParts = rhs === "" ? [] : rhs.split(":");
|
|
78
|
+
return [
|
|
79
|
+
...lhsParts,
|
|
80
|
+
...Array(8 - lhsParts.length - rhsParts.length).fill(0),
|
|
81
|
+
...rhsParts
|
|
82
|
+
].map((segment) => {
|
|
83
|
+
const val = parseInt(segment, 16);
|
|
84
|
+
if (val < 0 || val > 65535 || Number.isNaN(val)) throw new Error(`Invalid IPv6 segment: ${segment}`);
|
|
85
|
+
return val;
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
const ipv4ToTuple = (ipv4) => {
|
|
89
|
+
return ipv4.split(".").map((segment) => {
|
|
90
|
+
const val = parseInt(segment, 10);
|
|
91
|
+
if (val < 0 || val > 255 || Number.isNaN(val)) throw new Error(`Invalid IPv4 segment: ${segment}`);
|
|
92
|
+
return val;
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
const isWildcardIpAddress = ({ val: { address } }) => address.every((b) => b === 0);
|
|
96
|
+
const ipAddressConflict = (a, b) => a.tag === b.tag && Number(a.val.port) === Number(b.val.port) && Number(a.val.port) !== 0 && (isWildcardIpAddress(a) || isWildcardIpAddress(b) || a.val.address.every((segment, idx) => segment === b.val.address[idx]));
|
|
97
|
+
/**
|
|
98
|
+
* Serialize a socket‐address to text.
|
|
99
|
+
* @param {{tag:string,val:{address:number[]}}} ipAddr
|
|
100
|
+
* @returns {string|null}
|
|
101
|
+
*/
|
|
102
|
+
const serializeIpAddress = ({ tag, val: { address } }) => {
|
|
103
|
+
switch (tag) {
|
|
104
|
+
case "ipv4": return tupleToIpv4(address);
|
|
105
|
+
case "ipv6": return tupleToIpv6(address);
|
|
106
|
+
default: throw new Error(`Unknown IP tag: ${tag}`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Create a socket‐address object.
|
|
111
|
+
* @param {'ipv4'|'ipv6'} family
|
|
112
|
+
* @param {string} host IP text
|
|
113
|
+
* @param {number|bigint} port
|
|
114
|
+
* @returns {{tag:string,val:Object}}
|
|
115
|
+
*/
|
|
116
|
+
const makeIpAddress = (family, host, port) => {
|
|
117
|
+
let address;
|
|
118
|
+
switch (family) {
|
|
119
|
+
case "ipv4":
|
|
120
|
+
address = ipv4ToTuple(host);
|
|
121
|
+
break;
|
|
122
|
+
case "ipv6":
|
|
123
|
+
address = ipv6ToTuple(host);
|
|
124
|
+
break;
|
|
125
|
+
default: throw new Error(`Unknown IP family: ${family}`);
|
|
126
|
+
}
|
|
127
|
+
const base = {
|
|
128
|
+
address,
|
|
129
|
+
port
|
|
130
|
+
};
|
|
131
|
+
return family === "ipv4" ? {
|
|
132
|
+
tag: "ipv4",
|
|
133
|
+
val: base
|
|
134
|
+
} : {
|
|
135
|
+
tag: "ipv6",
|
|
136
|
+
val: {
|
|
137
|
+
...base,
|
|
138
|
+
flowInfo: 0,
|
|
139
|
+
scopeId: 0
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region dist/nodejs/sockets/error.js
|
|
145
|
+
const ERROR_MAP = {
|
|
146
|
+
EACCES: "access-denied",
|
|
147
|
+
EPERM: "access-denied",
|
|
148
|
+
EOPNOTSUPP: "not-supported",
|
|
149
|
+
ENOPROTOOPT: "not-supported",
|
|
150
|
+
EPFNOSUPPORT: "not-supported",
|
|
151
|
+
EPROTONOSUPPORT: "not-supported",
|
|
152
|
+
ESOCKTNOSUPPORT: "not-supported",
|
|
153
|
+
EINVAL: "invalid-argument",
|
|
154
|
+
EAFNOSUPPORT: "invalid-argument",
|
|
155
|
+
ENOMEM: "out-of-memory",
|
|
156
|
+
ENOBUFS: "out-of-memory",
|
|
157
|
+
ETIMEDOUT: "timeout",
|
|
158
|
+
EADDRINUSE: "address-in-use",
|
|
159
|
+
EADDRNOTAVAIL: "address-not-bindable",
|
|
160
|
+
EHOSTUNREACH: "remote-unreachable",
|
|
161
|
+
EHOSTDOWN: "remote-unreachable",
|
|
162
|
+
ENETUNREACH: "remote-unreachable",
|
|
163
|
+
ENETDOWN: "remote-unreachable",
|
|
164
|
+
ENONET: "remote-unreachable",
|
|
165
|
+
ECONNREFUSED: "connection-refused",
|
|
166
|
+
EPIPE: "connection-broken",
|
|
167
|
+
ECONNRESET: "connection-reset",
|
|
168
|
+
ECONNABORTED: "connection-aborted",
|
|
169
|
+
EMSGSIZE: "datagram-too-large",
|
|
170
|
+
ENOTCONN: "invalid-state",
|
|
171
|
+
EISCONN: "invalid-state",
|
|
172
|
+
EALREADY: "invalid-state",
|
|
173
|
+
EDESTADDRREQ: "invalid-argument"
|
|
174
|
+
};
|
|
175
|
+
const CODE_MAP = {
|
|
176
|
+
4053: "invalid-state",
|
|
177
|
+
4083: "invalid-state",
|
|
178
|
+
ENOTCONN: "invalid-state",
|
|
179
|
+
EBADF: "invalid-state",
|
|
180
|
+
EACCES: "access-denied",
|
|
181
|
+
EPERM: "access-denied",
|
|
182
|
+
ENOTSUP: "not-supported",
|
|
183
|
+
EOPNOTSUPP: "not-supported",
|
|
184
|
+
ENOPROTOOPT: "not-supported",
|
|
185
|
+
EPFNOSUPPORT: "not-supported",
|
|
186
|
+
EPROTONOSUPPORT: "not-supported",
|
|
187
|
+
ESOCKTNOSUPPORT: "not-supported",
|
|
188
|
+
EINVAL: "invalid-argument",
|
|
189
|
+
EAFNOSUPPORT: "invalid-argument",
|
|
190
|
+
ENOMEM: "out-of-memory",
|
|
191
|
+
ENOBUFS: "out-of-memory",
|
|
192
|
+
4090: "address-not-bindable",
|
|
193
|
+
EADDRNOTAVAIL: "address-not-bindable",
|
|
194
|
+
4091: "address-in-use",
|
|
195
|
+
EADDRINUSE: "address-in-use",
|
|
196
|
+
EHOSTUNREACH: "remote-unreachable",
|
|
197
|
+
EHOSTDOWN: "remote-unreachable",
|
|
198
|
+
ENETUNREACH: "remote-unreachable",
|
|
199
|
+
ENETDOWN: "remote-unreachable",
|
|
200
|
+
ENONET: "remote-unreachable",
|
|
201
|
+
ECONNREFUSED: "connection-refused",
|
|
202
|
+
EPIPE: "connection-broken",
|
|
203
|
+
ECONNRESET: "connection-reset",
|
|
204
|
+
ECONNABORTED: "connection-aborted",
|
|
205
|
+
EMSGSIZE: "datagram-too-large"
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Custom error for Socket operations with JCO compatible payload.
|
|
209
|
+
*
|
|
210
|
+
* https://bytecodealliance.github.io/jco/wit-type-representations.html#result-considerations-idiomatic-js-errors-for-host-implementations
|
|
211
|
+
*/
|
|
212
|
+
var SocketError = class SocketError extends Error {
|
|
213
|
+
payload;
|
|
214
|
+
/**
|
|
215
|
+
* @param {string} tag – machine‐readable error tag
|
|
216
|
+
* @param {string} [message] – human‐readable message
|
|
217
|
+
* @param {any} [val] – optional extra data
|
|
218
|
+
*/
|
|
219
|
+
constructor(tag, message = void 0, val = void 0, opts = {}) {
|
|
220
|
+
super(message ?? `Error: ${tag}`, { cause: opts.cause });
|
|
221
|
+
this.name = "SocketError";
|
|
222
|
+
this.payload = val !== void 0 ? {
|
|
223
|
+
tag,
|
|
224
|
+
val
|
|
225
|
+
} : { tag };
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Create or rewrap an error into SocketError
|
|
229
|
+
* @param {any} err – number, string, Error, or SocketError
|
|
230
|
+
*/
|
|
231
|
+
static from(err) {
|
|
232
|
+
if (err instanceof SocketError) return err;
|
|
233
|
+
let tag, message, val;
|
|
234
|
+
if (typeof err === "number") {
|
|
235
|
+
tag = CODE_MAP[err] ?? "other";
|
|
236
|
+
message = `Error code ${err}`;
|
|
237
|
+
if (tag === "other") val = message;
|
|
238
|
+
} else if (typeof err === "string") {
|
|
239
|
+
tag = ERROR_MAP[err] ?? "other";
|
|
240
|
+
if (tag === "other") val = err;
|
|
241
|
+
} else if (err && typeof err.code === "string") {
|
|
242
|
+
tag = ERROR_MAP[err.code] ?? "other";
|
|
243
|
+
message = err.message;
|
|
244
|
+
if (tag === "other") val = err.code;
|
|
245
|
+
} else if (err && typeof err.message === "string" && ERROR_MAP[err.message]) {
|
|
246
|
+
tag = ERROR_MAP[err.message];
|
|
247
|
+
message = err.message;
|
|
248
|
+
} else {
|
|
249
|
+
tag = "other";
|
|
250
|
+
message = err?.message;
|
|
251
|
+
val = message;
|
|
252
|
+
}
|
|
253
|
+
return new SocketError(tag, message, val);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region dist/nodejs/workers/tcp-worker.js
|
|
258
|
+
const { TCP, constants: TCPConstants } = process.binding("tcp_wrap");
|
|
259
|
+
const sockets = /* @__PURE__ */ new Map();
|
|
260
|
+
let NEXT_SOCKET_ID = 0n;
|
|
261
|
+
Router().beforeAll((msg) => {
|
|
262
|
+
if (msg.op !== "tcp-create" && !sockets.has(msg.socketId)) throw new Error("Invalid socket ID");
|
|
263
|
+
}).op("tcp-create", handleTcpCreate).op("tcp-bind", handleTcpBind).op("tcp-connect", handleTcpConnect).op("tcp-listen", handleTcpListen).op("tcp-send", handleTcpSend).op("tcp-receive", handleTcpReceive).op("tcp-get-local-address", handleGetLocalAddress).op("tcp-get-remote-address", handleGetRemoteAddress).op("tcp-set-listen-backlog-size", handleTcpSetBacklogSize).op("tcp-set-keep-alive", handleTcpSetKeepAlive).op("tcp-recv-buffer-size", handleRecvBufferSize).op("tcp-send-buffer-size", handleSendBufferSize).op("tcp-dispose", handleTcpDispose);
|
|
264
|
+
function handleTcpCreate({ family }) {
|
|
265
|
+
const socketId = NEXT_SOCKET_ID++;
|
|
266
|
+
const handle = new TCP(TCPConstants.SOCKET);
|
|
267
|
+
sockets.set(socketId, {
|
|
268
|
+
handle,
|
|
269
|
+
family,
|
|
270
|
+
tcp: null,
|
|
271
|
+
server: null,
|
|
272
|
+
backlog: 128,
|
|
273
|
+
localAddress: null,
|
|
274
|
+
disposed: false,
|
|
275
|
+
activeStreams: 0
|
|
276
|
+
});
|
|
277
|
+
return { socketId };
|
|
278
|
+
}
|
|
279
|
+
async function handleTcpBind({ socketId, localAddress }) {
|
|
280
|
+
const socket = sockets.get(socketId);
|
|
281
|
+
const address = serializeIpAddress(localAddress);
|
|
282
|
+
const port = localAddress.val.port;
|
|
283
|
+
const { handle, family } = socket;
|
|
284
|
+
if ([...sockets].some(([id, { localAddress: boundAddress }]) => id !== socketId && boundAddress && ipAddressConflict(boundAddress, localAddress))) {
|
|
285
|
+
const err = /* @__PURE__ */ new Error("EADDRINUSE");
|
|
286
|
+
err.code = "EADDRINUSE";
|
|
287
|
+
throw err;
|
|
288
|
+
}
|
|
289
|
+
await new Promise((resolve, reject) => {
|
|
290
|
+
let code;
|
|
291
|
+
if (family === "ipv6") code = handle.bind6(address, port, TCPConstants.UV_TCP_IPV6ONLY);
|
|
292
|
+
else code = handle.bind(address, port);
|
|
293
|
+
if (code !== 0) reject(SocketError.from(-code));
|
|
294
|
+
else resolve();
|
|
295
|
+
});
|
|
296
|
+
const out = {};
|
|
297
|
+
const code = handle.getsockname(out);
|
|
298
|
+
if (code !== 0) throw SocketError.from(-code);
|
|
299
|
+
socket.localAddress = makeIpAddress(out.family.toLowerCase(), out.address, out.port);
|
|
300
|
+
}
|
|
301
|
+
async function handleTcpConnect({ socketId, remoteAddress }) {
|
|
302
|
+
const socket = sockets.get(socketId);
|
|
303
|
+
const host = serializeIpAddress(remoteAddress);
|
|
304
|
+
const port = remoteAddress.val.port;
|
|
305
|
+
const tcp = socket.tcp = new Socket({
|
|
306
|
+
handle: socket.handle,
|
|
307
|
+
pauseOnCreate: true,
|
|
308
|
+
allowHalfOpen: true
|
|
309
|
+
});
|
|
310
|
+
const onConnect = once(tcp, "connect");
|
|
311
|
+
const onError = once(tcp, "error").then(([err]) => {
|
|
312
|
+
throw err;
|
|
313
|
+
});
|
|
314
|
+
tcp.connect({
|
|
315
|
+
port,
|
|
316
|
+
host
|
|
317
|
+
});
|
|
318
|
+
await Promise.race([onConnect, onError]);
|
|
319
|
+
}
|
|
320
|
+
async function handleTcpListen({ socketId, stream }) {
|
|
321
|
+
const writer = stream.getWriter();
|
|
322
|
+
const socket = sockets.get(socketId);
|
|
323
|
+
const { handle, backlog, family } = socket;
|
|
324
|
+
const server = new Server({
|
|
325
|
+
pauseOnConnect: true,
|
|
326
|
+
allowHalfOpen: true
|
|
327
|
+
});
|
|
328
|
+
const onListening = once(server, "listening");
|
|
329
|
+
const onError = once(server, "error").then(([err]) => {
|
|
330
|
+
throw err;
|
|
331
|
+
});
|
|
332
|
+
server.listen(handle, backlog);
|
|
333
|
+
await Promise.race([onListening, onError]);
|
|
334
|
+
const addr = server.address();
|
|
335
|
+
if (addr && typeof addr === "object") socket.localAddress = makeIpAddress(family, addr.address, addr.port);
|
|
336
|
+
server.on("connection", (conn) => {
|
|
337
|
+
conn.allowHalfOpen = true;
|
|
338
|
+
const id = NEXT_SOCKET_ID++;
|
|
339
|
+
sockets.set(id, {
|
|
340
|
+
handle: conn._handle,
|
|
341
|
+
family,
|
|
342
|
+
backlog,
|
|
343
|
+
tcp: conn,
|
|
344
|
+
server: null,
|
|
345
|
+
localAddress: makeIpAddress(family, conn.localAddress, conn.localPort),
|
|
346
|
+
disposed: false,
|
|
347
|
+
activeStreams: 0
|
|
348
|
+
});
|
|
349
|
+
writer.write({
|
|
350
|
+
family,
|
|
351
|
+
socketId: id
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
socket.server = server;
|
|
355
|
+
server.on("error", (err) => stream.abort(err));
|
|
356
|
+
server.on("close", () => writer.close());
|
|
357
|
+
}
|
|
358
|
+
async function handleTcpSend({ socketId, stream }) {
|
|
359
|
+
const socket = sockets.get(socketId);
|
|
360
|
+
socket.activeStreams++;
|
|
361
|
+
const { tcp } = socket;
|
|
362
|
+
const readable = Readable.fromWeb(stream);
|
|
363
|
+
try {
|
|
364
|
+
await pipeline(readable, tcp);
|
|
365
|
+
} finally {
|
|
366
|
+
socket.activeStreams--;
|
|
367
|
+
cleanupDisposedSocket(socketId, socket);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async function handleTcpReceive({ socketId, stream }) {
|
|
371
|
+
const socket = sockets.get(socketId);
|
|
372
|
+
const writer = stream.getWriter();
|
|
373
|
+
socket.activeStreams++;
|
|
374
|
+
const { tcp } = socket;
|
|
375
|
+
try {
|
|
376
|
+
await new Promise((resolve, reject) => {
|
|
377
|
+
let settled = false;
|
|
378
|
+
let pending = Promise.resolve();
|
|
379
|
+
const cleanup = () => {
|
|
380
|
+
tcp.off("data", onData);
|
|
381
|
+
tcp.off("end", onEnd);
|
|
382
|
+
tcp.off("error", onError);
|
|
383
|
+
};
|
|
384
|
+
const settle = (err = null) => {
|
|
385
|
+
if (settled) return;
|
|
386
|
+
settled = true;
|
|
387
|
+
cleanup();
|
|
388
|
+
if (err) reject(err);
|
|
389
|
+
else resolve();
|
|
390
|
+
};
|
|
391
|
+
const onData = (chunk) => {
|
|
392
|
+
tcp.pause();
|
|
393
|
+
pending = pending.then(async () => {
|
|
394
|
+
try {
|
|
395
|
+
await writer.write(chunk);
|
|
396
|
+
} catch {
|
|
397
|
+
settle();
|
|
398
|
+
return;
|
|
399
|
+
} finally {
|
|
400
|
+
tcp.resume();
|
|
401
|
+
}
|
|
402
|
+
}, settle);
|
|
403
|
+
};
|
|
404
|
+
const onEnd = () => {
|
|
405
|
+
pending = pending.then(async () => {
|
|
406
|
+
try {
|
|
407
|
+
await writer.close();
|
|
408
|
+
} catch {}
|
|
409
|
+
settle();
|
|
410
|
+
}, settle);
|
|
411
|
+
};
|
|
412
|
+
const onError = (err) => {
|
|
413
|
+
pending = pending.finally(() => settle(err));
|
|
414
|
+
};
|
|
415
|
+
writer.closed.then(() => settle(), () => settle());
|
|
416
|
+
tcp.on("data", onData);
|
|
417
|
+
tcp.once("end", onEnd);
|
|
418
|
+
tcp.once("error", onError);
|
|
419
|
+
tcp.resume();
|
|
420
|
+
});
|
|
421
|
+
} finally {
|
|
422
|
+
writer.releaseLock();
|
|
423
|
+
socket.activeStreams--;
|
|
424
|
+
cleanupDisposedSocket(socketId, socket);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function cleanupDisposedSocket(socketId, socket) {
|
|
428
|
+
if (!socket.disposed || socket.activeStreams > 0) return;
|
|
429
|
+
if (socket.server) socket.server.close();
|
|
430
|
+
if (socket.tcp) socket.tcp.destroy();
|
|
431
|
+
if (socket.handle) socket.handle.close();
|
|
432
|
+
sockets.delete(socketId);
|
|
433
|
+
}
|
|
434
|
+
async function handleGetLocalAddress({ socketId }) {
|
|
435
|
+
const socket = sockets.get(socketId);
|
|
436
|
+
const out = {};
|
|
437
|
+
const code = socket.handle.getsockname(out);
|
|
438
|
+
if (code !== 0) throw SocketError.from(-code);
|
|
439
|
+
return makeIpAddress(out.family.toLowerCase(), out.address, out.port);
|
|
440
|
+
}
|
|
441
|
+
async function handleGetRemoteAddress({ socketId }) {
|
|
442
|
+
const socket = sockets.get(socketId);
|
|
443
|
+
const out = {};
|
|
444
|
+
const code = socket.handle.getpeername(out);
|
|
445
|
+
if (code !== 0) throw SocketError.from(-code);
|
|
446
|
+
return makeIpAddress(out.family.toLowerCase(), out.address, out.port);
|
|
447
|
+
}
|
|
448
|
+
function handleTcpSetBacklogSize({ socketId, value }) {
|
|
449
|
+
const socket = sockets.get(socketId);
|
|
450
|
+
socket.backlog = Number(value);
|
|
451
|
+
}
|
|
452
|
+
async function handleTcpSetKeepAlive({ socketId, keepAliveEnabled, keepAliveIdleTime }) {
|
|
453
|
+
const socket = sockets.get(socketId);
|
|
454
|
+
const time = Number(keepAliveIdleTime / 1000000000n);
|
|
455
|
+
const code = socket.handle.setKeepAlive(keepAliveEnabled, time);
|
|
456
|
+
if (code !== 0) throw SocketError.from(-code);
|
|
457
|
+
}
|
|
458
|
+
async function handleRecvBufferSize({ socketId }) {
|
|
459
|
+
const socket = sockets.get(socketId);
|
|
460
|
+
if (socket.tcp) return BigInt(socket.tcp.getRecvBufferSize());
|
|
461
|
+
else return await getDefaultReceiveBufferSize();
|
|
462
|
+
}
|
|
463
|
+
async function handleSendBufferSize({ socketId }) {
|
|
464
|
+
const socket = sockets.get(socketId);
|
|
465
|
+
if (socket.tcp) return BigInt(socket.tcp.getSendBufferSize());
|
|
466
|
+
else return await getDefaultSendBufferSize();
|
|
467
|
+
}
|
|
468
|
+
function handleTcpDispose({ socketId }) {
|
|
469
|
+
const socket = sockets.get(socketId);
|
|
470
|
+
if (!socket) return;
|
|
471
|
+
socket.disposed = true;
|
|
472
|
+
cleanupDisposedSocket(socketId, socket);
|
|
473
|
+
}
|
|
474
|
+
let _recvBufferSize;
|
|
475
|
+
let _sendBufferSize;
|
|
476
|
+
async function getDefaultBufferSizes() {
|
|
477
|
+
var s = new Socket();
|
|
478
|
+
s.bind(0);
|
|
479
|
+
await new Promise((resolve, reject) => {
|
|
480
|
+
s.once("error", reject);
|
|
481
|
+
s.once("listening", resolve);
|
|
482
|
+
});
|
|
483
|
+
_recvBufferSize = BigInt(s.getRecvBufferSize());
|
|
484
|
+
_sendBufferSize = BigInt(s.getSendBufferSize());
|
|
485
|
+
s.close();
|
|
486
|
+
}
|
|
487
|
+
async function getDefaultSendBufferSize() {
|
|
488
|
+
if (!_sendBufferSize) await getDefaultBufferSizes();
|
|
489
|
+
return _sendBufferSize;
|
|
490
|
+
}
|
|
491
|
+
async function getDefaultReceiveBufferSize() {
|
|
492
|
+
if (!_recvBufferSize) await getDefaultBufferSizes();
|
|
493
|
+
return _recvBufferSize;
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
export { getDefaultReceiveBufferSize, getDefaultSendBufferSize };
|