@bytecodealliance/preview3-shim 0.1.2 → 0.2.1
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.d.ts +51 -0
- package/dist/nodejs/cli.js +106 -0
- package/dist/nodejs/clocks.d.ts +55 -0
- package/dist/nodejs/clocks.js +83 -0
- package/dist/nodejs/filesystem/descriptor.d.ts +443 -0
- package/dist/nodejs/filesystem/descriptor.js +1010 -0
- package/dist/nodejs/filesystem/error.d.ts +61 -0
- package/dist/nodejs/filesystem/error.js +86 -0
- package/dist/nodejs/filesystem/utils.d.ts +8 -0
- package/dist/nodejs/filesystem/utils.js +25 -0
- package/dist/nodejs/filesystem.js +2 -0
- package/dist/nodejs/finalization.d.ts +2 -0
- package/{lib → dist}/nodejs/finalization.js +15 -21
- package/dist/nodejs/future.d.ts +98 -0
- package/dist/nodejs/future.js +176 -0
- package/dist/nodejs/http/client.d.ts +22 -0
- package/dist/nodejs/http/client.js +138 -0
- package/dist/nodejs/http/error.d.ts +18 -0
- package/dist/nodejs/http/error.js +62 -0
- package/dist/nodejs/http/fields.d.ts +144 -0
- package/dist/nodejs/http/fields.js +332 -0
- package/dist/nodejs/http/request.d.ts +256 -0
- package/dist/nodejs/http/request.js +606 -0
- package/dist/nodejs/http/response.d.ts +82 -0
- package/dist/nodejs/http/response.js +202 -0
- package/dist/nodejs/http/server.d.ts +13 -0
- package/dist/nodejs/http/server.js +127 -0
- package/dist/nodejs/http.d.ts +19 -0
- package/{lib → dist}/nodejs/http.js +13 -18
- package/dist/nodejs/index.js +8 -0
- package/dist/nodejs/random.d.ts +5 -0
- package/{lib → dist}/nodejs/random.js +2 -8
- package/dist/nodejs/sockets/address.d.ts +78 -0
- package/dist/nodejs/sockets/address.js +120 -0
- package/dist/nodejs/sockets/error.d.ts +83 -0
- package/dist/nodejs/sockets/error.js +120 -0
- package/dist/nodejs/sockets/ip-name-lookup.d.ts +18 -0
- package/dist/nodejs/sockets/ip-name-lookup.js +84 -0
- package/dist/nodejs/sockets/tcp.d.ts +333 -0
- package/dist/nodejs/sockets/tcp.js +723 -0
- package/dist/nodejs/sockets/udp.d.ts +224 -0
- package/dist/nodejs/sockets/udp.js +475 -0
- package/dist/nodejs/sockets.d.ts +11 -0
- package/{lib → dist}/nodejs/sockets.js +2 -5
- package/dist/nodejs/stream.d.ts +161 -0
- package/dist/nodejs/stream.js +388 -0
- package/dist/nodejs/workers/cli-worker.d.ts +1 -0
- package/dist/nodejs/workers/cli-worker.js +41 -0
- package/dist/nodejs/workers/filesystem-worker.d.ts +1 -0
- package/dist/nodejs/workers/filesystem-worker.js +109 -0
- package/dist/nodejs/workers/http-worker.d.ts +1 -0
- package/dist/nodejs/workers/http-worker.js +355 -0
- package/dist/nodejs/workers/resource-worker.d.ts +13 -0
- package/dist/nodejs/workers/resource-worker.js +152 -0
- package/dist/nodejs/workers/tcp-worker.d.ts +2 -0
- package/dist/nodejs/workers/tcp-worker.js +318 -0
- package/dist/nodejs/workers/udp-worker.d.ts +1 -0
- package/dist/nodejs/workers/udp-worker.js +156 -0
- package/package.json +20 -12
- package/types/interfaces/wasi-cli-environment.d.ts +3 -3
- package/types/interfaces/wasi-cli-exit.d.ts +12 -1
- package/types/interfaces/wasi-cli-run.d.ts +1 -1
- package/types/interfaces/wasi-cli-stderr.d.ts +3 -3
- package/types/interfaces/wasi-cli-stdin.d.ts +5 -5
- package/types/interfaces/wasi-cli-stdout.d.ts +3 -3
- package/types/interfaces/wasi-cli-terminal-input.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-output.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
- package/types/interfaces/wasi-cli-types.d.ts +5 -5
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +3 -3
- package/types/interfaces/wasi-clocks-system-clock.d.ts +6 -6
- package/types/interfaces/wasi-clocks-types.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-types.d.ts +74 -74
- package/types/interfaces/wasi-http-client.d.ts +1 -1
- package/types/interfaces/wasi-http-handler.d.ts +1 -1
- package/types/interfaces/wasi-http-types.d.ts +34 -34
- package/types/interfaces/wasi-random-insecure-seed.d.ts +5 -5
- package/types/interfaces/wasi-random-insecure.d.ts +5 -5
- package/types/interfaces/wasi-random-random.d.ts +5 -5
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +11 -11
- package/types/interfaces/wasi-sockets-types.d.ts +121 -121
- package/types/wasi-cli-command.d.ts +23 -23
- package/types/wasi-http-service.d.ts +14 -14
- package/lib/nodejs/cli.js +0 -127
- package/lib/nodejs/clocks.js +0 -98
- package/lib/nodejs/filesystem/descriptor.js +0 -1093
- package/lib/nodejs/filesystem/error.js +0 -92
- package/lib/nodejs/filesystem/utils.js +0 -19
- package/lib/nodejs/future.js +0 -190
- package/lib/nodejs/http/client.js +0 -161
- package/lib/nodejs/http/error.js +0 -71
- package/lib/nodejs/http/fields.js +0 -373
- package/lib/nodejs/http/request.js +0 -691
- package/lib/nodejs/http/response.js +0 -227
- package/lib/nodejs/http/server.js +0 -157
- package/lib/nodejs/sockets/address.js +0 -146
- package/lib/nodejs/sockets/error.js +0 -123
- package/lib/nodejs/sockets/ip-name-lookup.js +0 -93
- package/lib/nodejs/sockets/tcp.js +0 -801
- package/lib/nodejs/sockets/udp.js +0 -516
- package/lib/nodejs/stream.js +0 -437
- package/lib/nodejs/workers/cli-worker.js +0 -49
- package/lib/nodejs/workers/filesystem-worker.js +0 -119
- package/lib/nodejs/workers/http-worker.js +0 -417
- package/lib/nodejs/workers/resource-worker.js +0 -195
- package/lib/nodejs/workers/tcp-worker.js +0 -370
- package/lib/nodejs/workers/udp-worker.js +0 -194
- /package/{lib/nodejs/filesystem.js → dist/nodejs/filesystem.d.ts} +0 -0
- /package/{lib/nodejs/index.js → dist/nodejs/index.d.ts} +0 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { Socket, Server } from "node:net";
|
|
2
|
+
import { Readable } from "stream";
|
|
3
|
+
import { pipeline } from "stream/promises";
|
|
4
|
+
import { once } from "node:events";
|
|
5
|
+
import { Router } from "../workers/resource-worker.js";
|
|
6
|
+
import { serializeIpAddress, makeIpAddress, ipAddressConflict } from "../sockets/address.js";
|
|
7
|
+
import { SocketError } from "../sockets/error.js";
|
|
8
|
+
import process from "node:process";
|
|
9
|
+
const { TCP, constants: TCPConstants } = process.binding("tcp_wrap");
|
|
10
|
+
// Socket instances stored by ID
|
|
11
|
+
const sockets = new Map();
|
|
12
|
+
// Unique IDs for sockets
|
|
13
|
+
let NEXT_SOCKET_ID = 0n;
|
|
14
|
+
// Handle worker messages
|
|
15
|
+
Router()
|
|
16
|
+
.beforeAll((msg) => {
|
|
17
|
+
if (msg.op !== "tcp-create" && !sockets.has(msg.socketId)) {
|
|
18
|
+
throw new Error("Invalid socket ID");
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
.op("tcp-create", handleTcpCreate)
|
|
22
|
+
.op("tcp-bind", handleTcpBind)
|
|
23
|
+
.op("tcp-connect", handleTcpConnect)
|
|
24
|
+
.op("tcp-listen", handleTcpListen)
|
|
25
|
+
.op("tcp-send", handleTcpSend)
|
|
26
|
+
.op("tcp-receive", handleTcpReceive)
|
|
27
|
+
.op("tcp-get-local-address", handleGetLocalAddress)
|
|
28
|
+
.op("tcp-get-remote-address", handleGetRemoteAddress)
|
|
29
|
+
.op("tcp-set-listen-backlog-size", handleTcpSetBacklogSize)
|
|
30
|
+
.op("tcp-set-keep-alive", handleTcpSetKeepAlive)
|
|
31
|
+
.op("tcp-recv-buffer-size", handleRecvBufferSize)
|
|
32
|
+
.op("tcp-send-buffer-size", handleSendBufferSize)
|
|
33
|
+
.op("tcp-dispose", handleTcpDispose);
|
|
34
|
+
// Create a new TCP socket
|
|
35
|
+
function handleTcpCreate({ family }) {
|
|
36
|
+
const socketId = NEXT_SOCKET_ID++;
|
|
37
|
+
const handle = new TCP(TCPConstants.SOCKET);
|
|
38
|
+
sockets.set(socketId, {
|
|
39
|
+
handle,
|
|
40
|
+
family,
|
|
41
|
+
tcp: null,
|
|
42
|
+
server: null,
|
|
43
|
+
backlog: 128,
|
|
44
|
+
localAddress: null,
|
|
45
|
+
disposed: false,
|
|
46
|
+
activeStreams: 0,
|
|
47
|
+
});
|
|
48
|
+
return { socketId };
|
|
49
|
+
}
|
|
50
|
+
// Bind a socket to local address
|
|
51
|
+
async function handleTcpBind({ socketId, localAddress }) {
|
|
52
|
+
const socket = sockets.get(socketId);
|
|
53
|
+
const address = serializeIpAddress(localAddress);
|
|
54
|
+
const port = localAddress.val.port;
|
|
55
|
+
const { handle, family } = socket;
|
|
56
|
+
const hasConflict = [...sockets].some(([id, { localAddress: boundAddress }]) => id !== socketId && boundAddress && ipAddressConflict(boundAddress, localAddress));
|
|
57
|
+
if (hasConflict) {
|
|
58
|
+
const err = new Error("EADDRINUSE");
|
|
59
|
+
err.code = "EADDRINUSE";
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
await new Promise((resolve, reject) => {
|
|
63
|
+
let code;
|
|
64
|
+
if (family === "ipv6") {
|
|
65
|
+
code = handle.bind6(address, port, TCPConstants.UV_TCP_IPV6ONLY);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
code = handle.bind(address, port);
|
|
69
|
+
}
|
|
70
|
+
if (code !== 0) {
|
|
71
|
+
reject(SocketError.from(-code));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
resolve();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const out = {};
|
|
78
|
+
const code = handle.getsockname(out);
|
|
79
|
+
if (code !== 0) {
|
|
80
|
+
throw SocketError.from(-code);
|
|
81
|
+
}
|
|
82
|
+
socket.localAddress = makeIpAddress(out.family.toLowerCase(), out.address, out.port);
|
|
83
|
+
}
|
|
84
|
+
// Connect a socket to remote address
|
|
85
|
+
async function handleTcpConnect({ socketId, remoteAddress }) {
|
|
86
|
+
const socket = sockets.get(socketId);
|
|
87
|
+
const host = serializeIpAddress(remoteAddress);
|
|
88
|
+
const port = remoteAddress.val.port;
|
|
89
|
+
const tcp = (socket.tcp = new Socket({
|
|
90
|
+
handle: socket.handle,
|
|
91
|
+
pauseOnCreate: true,
|
|
92
|
+
allowHalfOpen: true,
|
|
93
|
+
}));
|
|
94
|
+
const onConnect = once(tcp, "connect");
|
|
95
|
+
const onError = once(tcp, "error").then(([err]) => {
|
|
96
|
+
throw err;
|
|
97
|
+
});
|
|
98
|
+
// TODO(tandr): Add lookup
|
|
99
|
+
tcp.connect({ port, host });
|
|
100
|
+
await Promise.race([onConnect, onError]);
|
|
101
|
+
}
|
|
102
|
+
async function handleTcpListen({ socketId, stream }) {
|
|
103
|
+
const writer = stream.getWriter();
|
|
104
|
+
const socket = sockets.get(socketId);
|
|
105
|
+
const { handle, backlog, family } = socket;
|
|
106
|
+
const server = new Server({
|
|
107
|
+
pauseOnConnect: true,
|
|
108
|
+
allowHalfOpen: true,
|
|
109
|
+
});
|
|
110
|
+
const onListening = once(server, "listening");
|
|
111
|
+
const onError = once(server, "error").then(([err]) => {
|
|
112
|
+
throw err;
|
|
113
|
+
});
|
|
114
|
+
server.listen(handle, backlog);
|
|
115
|
+
await Promise.race([onListening, onError]);
|
|
116
|
+
const addr = server.address();
|
|
117
|
+
if (addr && typeof addr === "object") {
|
|
118
|
+
socket.localAddress = makeIpAddress(family, addr.address, addr.port);
|
|
119
|
+
}
|
|
120
|
+
server.on("connection", (conn) => {
|
|
121
|
+
conn.allowHalfOpen = true;
|
|
122
|
+
const id = NEXT_SOCKET_ID++;
|
|
123
|
+
sockets.set(id, {
|
|
124
|
+
handle: conn._handle,
|
|
125
|
+
family,
|
|
126
|
+
backlog,
|
|
127
|
+
tcp: conn,
|
|
128
|
+
server: null,
|
|
129
|
+
localAddress: makeIpAddress(family, conn.localAddress, conn.localPort),
|
|
130
|
+
disposed: false,
|
|
131
|
+
activeStreams: 0,
|
|
132
|
+
});
|
|
133
|
+
writer.write({ family, socketId: id });
|
|
134
|
+
});
|
|
135
|
+
socket.server = server;
|
|
136
|
+
server.on("error", (err) => stream.abort(err));
|
|
137
|
+
server.on("close", () => writer.close());
|
|
138
|
+
}
|
|
139
|
+
async function handleTcpSend({ socketId, stream }) {
|
|
140
|
+
const socket = sockets.get(socketId);
|
|
141
|
+
socket.activeStreams++;
|
|
142
|
+
const { tcp } = socket;
|
|
143
|
+
const readable = Readable.fromWeb(stream);
|
|
144
|
+
try {
|
|
145
|
+
await pipeline(readable, tcp);
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
socket.activeStreams--;
|
|
149
|
+
cleanupDisposedSocket(socketId, socket);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function handleTcpReceive({ socketId, stream }) {
|
|
153
|
+
const socket = sockets.get(socketId);
|
|
154
|
+
const writer = stream.getWriter();
|
|
155
|
+
socket.activeStreams++;
|
|
156
|
+
const { tcp } = socket;
|
|
157
|
+
try {
|
|
158
|
+
await new Promise((resolve, reject) => {
|
|
159
|
+
let settled = false;
|
|
160
|
+
let pending = Promise.resolve();
|
|
161
|
+
const cleanup = () => {
|
|
162
|
+
tcp.off("data", onData);
|
|
163
|
+
tcp.off("end", onEnd);
|
|
164
|
+
tcp.off("error", onError);
|
|
165
|
+
};
|
|
166
|
+
const settle = (err = null) => {
|
|
167
|
+
if (settled) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
settled = true;
|
|
171
|
+
cleanup();
|
|
172
|
+
if (err) {
|
|
173
|
+
reject(err);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
resolve();
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const onData = (chunk) => {
|
|
180
|
+
tcp.pause();
|
|
181
|
+
pending = pending.then(async () => {
|
|
182
|
+
try {
|
|
183
|
+
await writer.write(chunk);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
settle();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
// Always undo the pause, even if the guest dropped or cancelled the
|
|
191
|
+
// receive stream. Otherwise the send path can remain backpressured
|
|
192
|
+
// forever.
|
|
193
|
+
tcp.resume();
|
|
194
|
+
}
|
|
195
|
+
}, settle);
|
|
196
|
+
};
|
|
197
|
+
const onEnd = () => {
|
|
198
|
+
pending = pending.then(async () => {
|
|
199
|
+
try {
|
|
200
|
+
await writer.close();
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// The guest can drop the receive stream before remote EOF.
|
|
204
|
+
}
|
|
205
|
+
settle();
|
|
206
|
+
}, settle);
|
|
207
|
+
};
|
|
208
|
+
const onError = (err) => {
|
|
209
|
+
pending = pending.finally(() => settle(err));
|
|
210
|
+
};
|
|
211
|
+
writer.closed.then(() => settle(), () => settle());
|
|
212
|
+
tcp.on("data", onData);
|
|
213
|
+
tcp.once("end", onEnd);
|
|
214
|
+
tcp.once("error", onError);
|
|
215
|
+
tcp.resume();
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
writer.releaseLock();
|
|
220
|
+
socket.activeStreams--;
|
|
221
|
+
cleanupDisposedSocket(socketId, socket);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function cleanupDisposedSocket(socketId, socket) {
|
|
225
|
+
if (!socket.disposed || socket.activeStreams > 0) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (socket.server) {
|
|
229
|
+
socket.server.close();
|
|
230
|
+
}
|
|
231
|
+
if (socket.tcp) {
|
|
232
|
+
socket.tcp.destroy();
|
|
233
|
+
}
|
|
234
|
+
if (socket.handle) {
|
|
235
|
+
socket.handle.close();
|
|
236
|
+
}
|
|
237
|
+
sockets.delete(socketId);
|
|
238
|
+
}
|
|
239
|
+
async function handleGetLocalAddress({ socketId }) {
|
|
240
|
+
const socket = sockets.get(socketId);
|
|
241
|
+
const out = {};
|
|
242
|
+
const code = socket.handle.getsockname(out);
|
|
243
|
+
if (code !== 0) {
|
|
244
|
+
throw SocketError.from(-code);
|
|
245
|
+
}
|
|
246
|
+
return makeIpAddress(out.family.toLowerCase(), out.address, out.port);
|
|
247
|
+
}
|
|
248
|
+
async function handleGetRemoteAddress({ socketId }) {
|
|
249
|
+
const socket = sockets.get(socketId);
|
|
250
|
+
const out = {};
|
|
251
|
+
const code = socket.handle.getpeername(out);
|
|
252
|
+
if (code !== 0) {
|
|
253
|
+
throw SocketError.from(-code);
|
|
254
|
+
}
|
|
255
|
+
return makeIpAddress(out.family.toLowerCase(), out.address, out.port);
|
|
256
|
+
}
|
|
257
|
+
function handleTcpSetBacklogSize({ socketId, value }) {
|
|
258
|
+
const socket = sockets.get(socketId);
|
|
259
|
+
socket.backlog = Number(value);
|
|
260
|
+
}
|
|
261
|
+
async function handleTcpSetKeepAlive({ socketId, keepAliveEnabled, keepAliveIdleTime }) {
|
|
262
|
+
const socket = sockets.get(socketId);
|
|
263
|
+
const time = Number(keepAliveIdleTime / 1000000000n);
|
|
264
|
+
const code = socket.handle.setKeepAlive(keepAliveEnabled, time);
|
|
265
|
+
if (code !== 0) {
|
|
266
|
+
throw SocketError.from(-code);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function handleRecvBufferSize({ socketId }) {
|
|
270
|
+
const socket = sockets.get(socketId);
|
|
271
|
+
if (socket.tcp) {
|
|
272
|
+
return BigInt(socket.tcp.getRecvBufferSize());
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
return await getDefaultReceiveBufferSize();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async function handleSendBufferSize({ socketId }) {
|
|
279
|
+
const socket = sockets.get(socketId);
|
|
280
|
+
if (socket.tcp) {
|
|
281
|
+
return BigInt(socket.tcp.getSendBufferSize());
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
return await getDefaultSendBufferSize();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function handleTcpDispose({ socketId }) {
|
|
288
|
+
const socket = sockets.get(socketId);
|
|
289
|
+
if (!socket) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
socket.disposed = true;
|
|
293
|
+
cleanupDisposedSocket(socketId, socket);
|
|
294
|
+
}
|
|
295
|
+
let _recvBufferSize, _sendBufferSize;
|
|
296
|
+
async function getDefaultBufferSizes() {
|
|
297
|
+
var s = new Socket();
|
|
298
|
+
s.bind(0);
|
|
299
|
+
await new Promise((resolve, reject) => {
|
|
300
|
+
s.once("error", reject);
|
|
301
|
+
s.once("listening", resolve);
|
|
302
|
+
});
|
|
303
|
+
_recvBufferSize = BigInt(s.getRecvBufferSize());
|
|
304
|
+
_sendBufferSize = BigInt(s.getSendBufferSize());
|
|
305
|
+
s.close();
|
|
306
|
+
}
|
|
307
|
+
export async function getDefaultSendBufferSize() {
|
|
308
|
+
if (!_sendBufferSize) {
|
|
309
|
+
await getDefaultBufferSizes();
|
|
310
|
+
}
|
|
311
|
+
return _sendBufferSize;
|
|
312
|
+
}
|
|
313
|
+
export async function getDefaultReceiveBufferSize() {
|
|
314
|
+
if (!_recvBufferSize) {
|
|
315
|
+
await getDefaultBufferSizes();
|
|
316
|
+
}
|
|
317
|
+
return _recvBufferSize;
|
|
318
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function noLookup(ip: any, _opts: any, cb: any): void;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { once } from "node:events";
|
|
2
|
+
import { Router } from "../workers/resource-worker.js";
|
|
3
|
+
import { serializeIpAddress, makeIpAddress, ipAddressConflict, isLoopbackIpAddress, } from "../sockets/address.js";
|
|
4
|
+
import dgram from "node:dgram";
|
|
5
|
+
const sockets = new Map();
|
|
6
|
+
// Unique IDs for sockets
|
|
7
|
+
let NEXT_SOCKET_ID = 0n;
|
|
8
|
+
export function noLookup(ip, _opts, cb) {
|
|
9
|
+
cb(null, ip);
|
|
10
|
+
}
|
|
11
|
+
Router()
|
|
12
|
+
.beforeAll((msg) => {
|
|
13
|
+
if (msg.op !== "udp-create" && !sockets.has(msg.socketId)) {
|
|
14
|
+
throw new Error("Invalid socket ID");
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
.op("udp-create", handleCreate)
|
|
18
|
+
.op("udp-bind", handleBind)
|
|
19
|
+
.op("udp-connect", handleConnect)
|
|
20
|
+
.op("udp-disconnect", handleDisconnect)
|
|
21
|
+
.op("udp-send", handleSend)
|
|
22
|
+
.op("udp-receive", handleReceive)
|
|
23
|
+
.op("udp-get-local-address", handleGetLocal)
|
|
24
|
+
.op("udp-set-unicast-hop-limit", handleSetHop)
|
|
25
|
+
.op("udp-recv-buffer-size", handleRecvBuffer)
|
|
26
|
+
.op("udp-send-buffer-size", handleSendBuffer)
|
|
27
|
+
.op("udp-dispose", handleDispose);
|
|
28
|
+
function handleCreate({ family }) {
|
|
29
|
+
const socketId = NEXT_SOCKET_ID++;
|
|
30
|
+
const socket = {
|
|
31
|
+
family,
|
|
32
|
+
connected: null,
|
|
33
|
+
localAddress: null,
|
|
34
|
+
receiveQueue: [],
|
|
35
|
+
};
|
|
36
|
+
socket.udp = createSocket(socket);
|
|
37
|
+
sockets.set(socketId, socket);
|
|
38
|
+
return { socketId };
|
|
39
|
+
}
|
|
40
|
+
function createSocket(socket) {
|
|
41
|
+
const type = socket.family === "ipv6" ? "udp6" : "udp4";
|
|
42
|
+
const ipv6Only = socket.family === "ipv6";
|
|
43
|
+
const udp = dgram.createSocket({
|
|
44
|
+
type,
|
|
45
|
+
ipv6Only,
|
|
46
|
+
reuseAddr: false,
|
|
47
|
+
lookup: noLookup,
|
|
48
|
+
});
|
|
49
|
+
udp.on("message", (msg, rinfo) => socket.receiveQueue.push({ msg, rinfo }));
|
|
50
|
+
udp.on("error", () => { });
|
|
51
|
+
return udp;
|
|
52
|
+
}
|
|
53
|
+
async function handleBind({ socketId, localAddress }) {
|
|
54
|
+
const socket = sockets.get(socketId);
|
|
55
|
+
const hasConflict = [...sockets].some(([id, { localAddress: boundAddress }]) => id !== socketId && boundAddress && ipAddressConflict(boundAddress, localAddress));
|
|
56
|
+
if (hasConflict) {
|
|
57
|
+
const err = new Error("EADDRINUSE");
|
|
58
|
+
err.code = "EADDRINUSE";
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
const addr = serializeIpAddress(localAddress);
|
|
62
|
+
const port = localAddress.val.port;
|
|
63
|
+
const onListening = once(socket.udp, "listening");
|
|
64
|
+
const onError = once(socket.udp, "error").then(([err]) => {
|
|
65
|
+
throw err;
|
|
66
|
+
});
|
|
67
|
+
socket.udp.bind(port, addr);
|
|
68
|
+
await Promise.race([onListening, onError]);
|
|
69
|
+
const boundAddr = socket.udp.address();
|
|
70
|
+
socket.localAddress = makeIpAddress(socket.family, boundAddr.address, boundAddr.port);
|
|
71
|
+
}
|
|
72
|
+
async function handleConnect({ socketId, remoteAddress }) {
|
|
73
|
+
const socket = sockets.get(socketId);
|
|
74
|
+
const preserveLocalAddress = socket.connected &&
|
|
75
|
+
socket.localAddress &&
|
|
76
|
+
isLoopbackIpAddress(socket.connected) &&
|
|
77
|
+
isLoopbackIpAddress(remoteAddress);
|
|
78
|
+
if (socket.connected) {
|
|
79
|
+
const localAddress = preserveLocalAddress ? socket.localAddress : null;
|
|
80
|
+
await new Promise((resolve) => socket.udp.close(resolve));
|
|
81
|
+
socket.udp = createSocket(socket);
|
|
82
|
+
if (localAddress) {
|
|
83
|
+
await handleBind({ socketId, localAddress });
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
socket.localAddress = null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const addr = serializeIpAddress(remoteAddress);
|
|
90
|
+
const port = remoteAddress.val.port;
|
|
91
|
+
await new Promise((resolve, reject) => {
|
|
92
|
+
socket.udp.connect(port, addr, (err) => (err ? reject(err) : resolve()));
|
|
93
|
+
});
|
|
94
|
+
const boundAddr = socket.udp.address();
|
|
95
|
+
socket.localAddress = makeIpAddress(socket.family, boundAddr.address, boundAddr.port);
|
|
96
|
+
socket.connected = remoteAddress;
|
|
97
|
+
}
|
|
98
|
+
function handleDisconnect({ socketId }) {
|
|
99
|
+
const socket = sockets.get(socketId);
|
|
100
|
+
socket.udp.disconnect();
|
|
101
|
+
socket.connected = null;
|
|
102
|
+
}
|
|
103
|
+
async function handleSend({ socketId, data, remoteAddress }) {
|
|
104
|
+
const socket = sockets.get(socketId);
|
|
105
|
+
const isConnected = socket.connected != null;
|
|
106
|
+
await new Promise((resolve, reject) => {
|
|
107
|
+
if (isConnected) {
|
|
108
|
+
socket.udp.send(data, (err) => (err ? reject(err) : resolve()));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const addr = serializeIpAddress(remoteAddress);
|
|
112
|
+
const port = remoteAddress.val.port;
|
|
113
|
+
socket.udp.send(data, port, addr, (err) => (err ? reject(err) : resolve()));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
const boundAddr = socket.udp.address();
|
|
117
|
+
socket.localAddress = makeIpAddress(socket.family, boundAddr.address, boundAddr.port);
|
|
118
|
+
}
|
|
119
|
+
async function handleReceive({ socketId }) {
|
|
120
|
+
const socket = sockets.get(socketId);
|
|
121
|
+
if (socket.receiveQueue.length === 0) {
|
|
122
|
+
await Promise.race([
|
|
123
|
+
once(socket.udp, "message"),
|
|
124
|
+
once(socket.udp, "error").then(([err]) => {
|
|
125
|
+
throw err;
|
|
126
|
+
}),
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
const { msg, rinfo } = socket.receiveQueue.shift();
|
|
130
|
+
return {
|
|
131
|
+
data: msg,
|
|
132
|
+
remoteAddress: makeIpAddress(socket.family, rinfo.address, rinfo.port),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function handleGetLocal({ socketId }) {
|
|
136
|
+
const socket = sockets.get(socketId);
|
|
137
|
+
const addr = socket.udp.address();
|
|
138
|
+
return makeIpAddress(socket.family, addr.address, addr.port);
|
|
139
|
+
}
|
|
140
|
+
function handleSetHop({ socketId, value }) {
|
|
141
|
+
const socket = sockets.get(socketId);
|
|
142
|
+
socket.udp.setTTL(value);
|
|
143
|
+
}
|
|
144
|
+
function handleRecvBuffer({ socketId }) {
|
|
145
|
+
const socket = sockets.get(socketId);
|
|
146
|
+
return BigInt(socket.udp.getRecvBufferSize());
|
|
147
|
+
}
|
|
148
|
+
function handleSendBuffer({ socketId }) {
|
|
149
|
+
const socket = sockets.get(socketId);
|
|
150
|
+
return BigInt(socket.udp.getSendBufferSize());
|
|
151
|
+
}
|
|
152
|
+
function handleDispose({ socketId }) {
|
|
153
|
+
const socket = sockets.get(socketId);
|
|
154
|
+
socket.udp.close();
|
|
155
|
+
sockets.delete(socketId);
|
|
156
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytecodealliance/preview3-shim",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "WASI Preview3 shim for JS environments",
|
|
5
5
|
"homepage": "https://github.com/bytecodealliance/jco/blob/main/packages/preview3-shim/#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -23,35 +23,43 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"types",
|
|
26
|
-
"
|
|
26
|
+
"dist"
|
|
27
27
|
],
|
|
28
28
|
"type": "module",
|
|
29
|
-
"types": "./types/index.d.ts",
|
|
30
29
|
"exports": {
|
|
31
30
|
".": {
|
|
32
|
-
"types": "./
|
|
33
|
-
"node": "./
|
|
31
|
+
"types": "./dist/nodejs/index.d.ts",
|
|
32
|
+
"node": "./dist/nodejs/index.js"
|
|
34
33
|
},
|
|
35
34
|
"./*": {
|
|
36
|
-
"types": "./
|
|
37
|
-
"node": "./
|
|
35
|
+
"types": "./dist/nodejs/*.d.ts",
|
|
36
|
+
"node": "./dist/nodejs/*.js"
|
|
37
|
+
},
|
|
38
|
+
"./interfaces/*": {
|
|
39
|
+
"types": "./types/interfaces/*.d.ts"
|
|
38
40
|
}
|
|
39
41
|
},
|
|
40
42
|
"dependencies": {
|
|
41
|
-
"@bytecodealliance/preview2-shim": "^0.
|
|
43
|
+
"@bytecodealliance/preview2-shim": "^0.19.0"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
46
|
+
"@types/node": "^24.12.4",
|
|
47
|
+
"globals": "^17.6.0",
|
|
48
|
+
"typescript": "^6.0.3",
|
|
49
|
+
"vite": "^8.0.16",
|
|
50
|
+
"vitest": "^4.0.8"
|
|
47
51
|
},
|
|
48
52
|
"scripts": {
|
|
49
|
-
"
|
|
53
|
+
"build": "tsc --project tsconfig.build.json",
|
|
54
|
+
"build:watch": "tsc --watch",
|
|
55
|
+
"types:check": "tsc --noEmit",
|
|
50
56
|
"fmt": "oxfmt",
|
|
51
57
|
"fmt:check": "oxfmt --check",
|
|
52
58
|
"lint": "oxlint",
|
|
53
59
|
"lint:fix": "oxlint --fix",
|
|
60
|
+
"pretest": "pnpm run build",
|
|
54
61
|
"test": "vitest --run",
|
|
62
|
+
"prebench": "pnpm run build",
|
|
55
63
|
"bench": "vitest bench --run"
|
|
56
64
|
}
|
|
57
65
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/** @module Interface wasi:cli/environment@0.3.0
|
|
1
|
+
/** @module Interface wasi:cli/environment@0.3.0 **/
|
|
2
2
|
/**
|
|
3
3
|
* Get the POSIX-style environment variables.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Each environment variable is provided as a pair of string variable names
|
|
6
6
|
* and string value.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* Morally, these are a value import, but until value imports are available
|
|
9
9
|
* in the component model, this import function should return the same
|
|
10
10
|
* values each time it is called.
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
/** @module Interface wasi:cli/exit@0.3.0
|
|
1
|
+
/** @module Interface wasi:cli/exit@0.3.0 **/
|
|
2
2
|
/**
|
|
3
3
|
* Exit the current instance and any linked instances.
|
|
4
4
|
*/
|
|
5
5
|
export function exit(status: Result<void, void>): void;
|
|
6
|
+
/**
|
|
7
|
+
* Exit the current instance and any linked instances, reporting the
|
|
8
|
+
* specified status code to the host.
|
|
9
|
+
*
|
|
10
|
+
* The meaning of the code depends on the context, with 0 usually meaning
|
|
11
|
+
* "success", and other values indicating various types of failure.
|
|
12
|
+
*
|
|
13
|
+
* This function does not return; the effect is analogous to a trap, but
|
|
14
|
+
* without the connotation that something bad has happened.
|
|
15
|
+
*/
|
|
16
|
+
export function exitWithCode(statusCode: number): void;
|
|
6
17
|
export type Result<T, E> = { tag: 'ok', val: T } | { tag: 'err', val: E };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/** @module Interface wasi:cli/stderr@0.3.0
|
|
1
|
+
/** @module Interface wasi:cli/stderr@0.3.0 **/
|
|
2
2
|
/**
|
|
3
3
|
* Write the given stream to stderr.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* If the stream's writable end is dropped this function will either return
|
|
6
6
|
* success once the entire contents of the stream have been written or an
|
|
7
7
|
* error-code representing a failure.
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* Otherwise if there is an error the readable end of the stream will be
|
|
10
10
|
* dropped and this function will return an error-code.
|
|
11
11
|
*/
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
/** @module Interface wasi:cli/stdin@0.3.0
|
|
1
|
+
/** @module Interface wasi:cli/stdin@0.3.0 **/
|
|
2
2
|
/**
|
|
3
3
|
* Return a stream for reading from stdin.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* This function returns a stream which provides data read from stdin,
|
|
6
6
|
* and a future to signal read results.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* If the stream's readable end is dropped the future will resolve to success.
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
10
|
* If the stream's writable end is dropped the future will either resolve to
|
|
11
11
|
* success if stdin was closed by the writer or to an error-code if reading
|
|
12
12
|
* failed for some other reason.
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* Multiple streams may be active at the same time. The behavior of concurrent
|
|
15
15
|
* reads is implementation-specific.
|
|
16
16
|
*/
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/** @module Interface wasi:cli/stdout@0.3.0
|
|
1
|
+
/** @module Interface wasi:cli/stdout@0.3.0 **/
|
|
2
2
|
/**
|
|
3
3
|
* Write the given stream to stdout.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* If the stream's writable end is dropped this function will either return
|
|
6
6
|
* success once the entire contents of the stream have been written or an
|
|
7
7
|
* error-code representing a failure.
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* Otherwise if there is an error the readable end of the stream will be
|
|
10
10
|
* dropped and this function will return an error-code.
|
|
11
11
|
*/
|