@bytecodealliance/preview3-shim 0.1.0-rc.0
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/LICENSE +220 -0
- package/README.md +14 -0
- package/lib/nodejs/cli.js +127 -0
- package/lib/nodejs/clocks.js +100 -0
- package/lib/nodejs/filesystem/descriptor.js +997 -0
- package/lib/nodejs/filesystem/error.js +92 -0
- package/lib/nodejs/filesystem/utils.js +19 -0
- package/lib/nodejs/filesystem.js +2 -0
- package/lib/nodejs/finalization.js +33 -0
- package/lib/nodejs/future.js +187 -0
- package/lib/nodejs/http/client.js +146 -0
- package/lib/nodejs/http/error.js +71 -0
- package/lib/nodejs/http/fields.js +359 -0
- package/lib/nodejs/http/request.js +589 -0
- package/lib/nodejs/http/response.js +212 -0
- package/lib/nodejs/http/server.js +157 -0
- package/lib/nodejs/http.js +34 -0
- package/lib/nodejs/index.js +8 -0
- package/lib/nodejs/random.js +14 -0
- package/lib/nodejs/sockets/address.js +146 -0
- package/lib/nodejs/sockets/error.js +123 -0
- package/lib/nodejs/sockets/ip-name-lookup.js +93 -0
- package/lib/nodejs/sockets/tcp.js +776 -0
- package/lib/nodejs/sockets/udp.js +516 -0
- package/lib/nodejs/sockets.js +14 -0
- package/lib/nodejs/stream.js +423 -0
- package/lib/nodejs/workers/cli-worker.js +49 -0
- package/lib/nodejs/workers/filesystem-worker.js +119 -0
- package/lib/nodejs/workers/http-worker.js +387 -0
- package/lib/nodejs/workers/resource-worker.js +151 -0
- package/lib/nodejs/workers/tcp-worker.js +283 -0
- package/lib/nodejs/workers/udp-worker.js +194 -0
- package/package.json +57 -0
- package/types/cli.d.ts +12 -0
- package/types/clocks.d.ts +2 -0
- package/types/filesystem.d.ts +2 -0
- package/types/http.d.ts +4 -0
- package/types/index.d.ts +6 -0
- package/types/interfaces/wasi-cli-environment.d.ts +21 -0
- package/types/interfaces/wasi-cli-exit.d.ts +6 -0
- package/types/interfaces/wasi-cli-run.d.ts +5 -0
- package/types/interfaces/wasi-cli-stderr.d.ts +14 -0
- package/types/interfaces/wasi-cli-stdin.d.ts +19 -0
- package/types/interfaces/wasi-cli-stdout.d.ts +14 -0
- package/types/interfaces/wasi-cli-terminal-input.d.ts +8 -0
- package/types/interfaces/wasi-cli-terminal-output.d.ts +8 -0
- package/types/interfaces/wasi-cli-terminal-stderr.d.ts +7 -0
- package/types/interfaces/wasi-cli-terminal-stdin.d.ts +7 -0
- package/types/interfaces/wasi-cli-terminal-stdout.d.ts +7 -0
- package/types/interfaces/wasi-cli-types.d.ts +15 -0
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +33 -0
- package/types/interfaces/wasi-clocks-system-clock.d.ts +38 -0
- package/types/interfaces/wasi-clocks-types.d.ts +5 -0
- package/types/interfaces/wasi-filesystem-preopens.d.ts +6 -0
- package/types/interfaces/wasi-filesystem-types.d.ts +780 -0
- package/types/interfaces/wasi-http-client.d.ts +9 -0
- package/types/interfaces/wasi-http-handler.d.ts +9 -0
- package/types/interfaces/wasi-http-types.d.ts +619 -0
- package/types/interfaces/wasi-random-insecure-seed.d.ts +21 -0
- package/types/interfaces/wasi-random-insecure.d.ts +26 -0
- package/types/interfaces/wasi-random-random.d.ts +31 -0
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +77 -0
- package/types/interfaces/wasi-sockets-types.d.ts +830 -0
- package/types/random.d.ts +3 -0
- package/types/sockets.d.ts +2 -0
- package/types/wasi-cli-command.d.ts +23 -0
- package/types/wasi-http-service.d.ts +14 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Socket, Server } from "node:net";
|
|
2
|
+
import { Readable, Writable } from "stream";
|
|
3
|
+
import { pipeline } from "stream/promises";
|
|
4
|
+
import { once } from "node:events";
|
|
5
|
+
|
|
6
|
+
import { Router } from "../workers/resource-worker.js";
|
|
7
|
+
import { serializeIpAddress, makeIpAddress, ipAddressConflict } from "../sockets/address.js";
|
|
8
|
+
import { SocketError } from "../sockets/error.js";
|
|
9
|
+
|
|
10
|
+
import process from "node:process";
|
|
11
|
+
const { TCP, constants: TCPConstants } = process.binding("tcp_wrap");
|
|
12
|
+
|
|
13
|
+
// Socket instances stored by ID
|
|
14
|
+
const sockets = new Map();
|
|
15
|
+
// Unique IDs for sockets
|
|
16
|
+
let NEXT_SOCKET_ID = 0n;
|
|
17
|
+
|
|
18
|
+
// Handle worker messages
|
|
19
|
+
Router()
|
|
20
|
+
.beforeAll((msg) => {
|
|
21
|
+
if (msg.op !== "tcp-create" && !sockets.has(msg.socketId)) {
|
|
22
|
+
throw new Error("Invalid socket ID");
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.op("tcp-create", handleTcpCreate)
|
|
26
|
+
.op("tcp-bind", handleTcpBind)
|
|
27
|
+
.op("tcp-connect", handleTcpConnect)
|
|
28
|
+
.op("tcp-listen", handleTcpListen)
|
|
29
|
+
.op("tcp-send", handleTcpSend)
|
|
30
|
+
.op("tcp-receive", handleTcpReceive)
|
|
31
|
+
.op("tcp-get-local-address", handleGetLocalAddress)
|
|
32
|
+
.op("tcp-get-remote-address", handleGetRemoteAddress)
|
|
33
|
+
.op("tcp-set-listen-backlog-size", handleTcpSetBacklogSize)
|
|
34
|
+
.op("tcp-set-keep-alive", handleTcpSetKeepAlive)
|
|
35
|
+
.op("tcp-recv-buffer-size", handleRecvBufferSize)
|
|
36
|
+
.op("tcp-send-buffer-size", handleSendBufferSize)
|
|
37
|
+
.op("tcp-dispose", handleTcpDispose);
|
|
38
|
+
|
|
39
|
+
// Create a new TCP socket
|
|
40
|
+
function handleTcpCreate({ family }) {
|
|
41
|
+
const socketId = NEXT_SOCKET_ID++;
|
|
42
|
+
const handle = new TCP(TCPConstants.SOCKET);
|
|
43
|
+
|
|
44
|
+
sockets.set(socketId, {
|
|
45
|
+
handle,
|
|
46
|
+
family,
|
|
47
|
+
tcp: null,
|
|
48
|
+
server: null,
|
|
49
|
+
backlog: 128,
|
|
50
|
+
localAddress: null,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return { socketId };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Bind a socket to local address
|
|
57
|
+
async function handleTcpBind({ socketId, localAddress }) {
|
|
58
|
+
const socket = sockets.get(socketId);
|
|
59
|
+
const address = serializeIpAddress(localAddress);
|
|
60
|
+
const port = localAddress.val.port;
|
|
61
|
+
|
|
62
|
+
const { handle, family } = socket;
|
|
63
|
+
|
|
64
|
+
const hasConflict = [...sockets].some(
|
|
65
|
+
([id, { localAddress: boundAddress }]) =>
|
|
66
|
+
id !== socketId && boundAddress && ipAddressConflict(boundAddress, localAddress),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (hasConflict) {
|
|
70
|
+
const err = new Error("EADDRINUSE");
|
|
71
|
+
err.code = "EADDRINUSE";
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await new Promise((resolve, reject) => {
|
|
76
|
+
let code;
|
|
77
|
+
if (family === "ipv6") {
|
|
78
|
+
code = handle.bind6(address, port, TCPConstants.UV_TCP_IPV6ONLY);
|
|
79
|
+
} else {
|
|
80
|
+
code = handle.bind(address, port);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (code !== 0) {
|
|
84
|
+
reject(SocketError.from(-code));
|
|
85
|
+
} else {
|
|
86
|
+
resolve();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const out = {};
|
|
91
|
+
const code = handle.getsockname(out);
|
|
92
|
+
if (code !== 0) {
|
|
93
|
+
throw SocketError.from(-code);
|
|
94
|
+
}
|
|
95
|
+
socket.localAddress = makeIpAddress(out.family.toLowerCase(), out.address, out.port);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Connect a socket to remote address
|
|
99
|
+
async function handleTcpConnect({ socketId, remoteAddress }) {
|
|
100
|
+
const socket = sockets.get(socketId);
|
|
101
|
+
const host = serializeIpAddress(remoteAddress);
|
|
102
|
+
const port = remoteAddress.val.port;
|
|
103
|
+
|
|
104
|
+
const tcp = (socket.tcp = new Socket({
|
|
105
|
+
handle: socket.handle,
|
|
106
|
+
pauseOnCreate: true,
|
|
107
|
+
allowHalfOpen: true,
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
const onConnect = once(tcp, "connect");
|
|
111
|
+
const onError = once(tcp, "error").then(([err]) => {
|
|
112
|
+
throw err;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// TODO(tandr): Add lookup
|
|
116
|
+
tcp.connect({ port, host });
|
|
117
|
+
|
|
118
|
+
await Promise.race([onConnect, onError]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function handleTcpListen({ socketId, stream }) {
|
|
122
|
+
const writer = stream.getWriter();
|
|
123
|
+
const socket = sockets.get(socketId);
|
|
124
|
+
const { handle, backlog, family } = socket;
|
|
125
|
+
|
|
126
|
+
const server = new Server({
|
|
127
|
+
pauseOnConnect: true,
|
|
128
|
+
allowHalfOpen: true,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const onListening = once(server, "listening");
|
|
132
|
+
const onError = once(server, "error").then(([err]) => {
|
|
133
|
+
throw err;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
server.listen(handle, backlog);
|
|
137
|
+
|
|
138
|
+
await Promise.race([onListening, onError]);
|
|
139
|
+
|
|
140
|
+
const addr = server.address();
|
|
141
|
+
if (addr && typeof addr === "object") {
|
|
142
|
+
socket.localAddress = makeIpAddress(family, addr.address, addr.port);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
server.on("connection", (conn) => {
|
|
146
|
+
const id = NEXT_SOCKET_ID++;
|
|
147
|
+
sockets.set(id, {
|
|
148
|
+
handle: conn._handle,
|
|
149
|
+
family,
|
|
150
|
+
backlog,
|
|
151
|
+
tcp: conn,
|
|
152
|
+
server: null,
|
|
153
|
+
localAddress: makeIpAddress(family, conn.localAddress, conn.localPort),
|
|
154
|
+
});
|
|
155
|
+
writer.write({ family, socketId: id });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
socket.server = server;
|
|
159
|
+
server.on("error", (err) => stream.abort(err));
|
|
160
|
+
server.on("close", () => writer.close());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function handleTcpSend({ socketId, stream }) {
|
|
164
|
+
const socket = sockets.get(socketId);
|
|
165
|
+
const { tcp } = socket;
|
|
166
|
+
const readable = Readable.fromWeb(stream);
|
|
167
|
+
|
|
168
|
+
// TODO(tandr): Should we handle FIN packet?
|
|
169
|
+
await pipeline(readable, tcp);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function handleTcpReceive({ socketId, stream }) {
|
|
173
|
+
const socket = sockets.get(socketId);
|
|
174
|
+
const { tcp } = socket;
|
|
175
|
+
|
|
176
|
+
const writable = Writable.fromWeb(stream);
|
|
177
|
+
await pipeline(tcp, writable);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function handleGetLocalAddress({ socketId }) {
|
|
181
|
+
const socket = sockets.get(socketId);
|
|
182
|
+
const out = {};
|
|
183
|
+
|
|
184
|
+
const code = socket.handle.getsockname(out);
|
|
185
|
+
if (code !== 0) {
|
|
186
|
+
throw new SocketError.from(-code);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return makeIpAddress(out.family.toLowerCase(), out.address, out.port);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function handleGetRemoteAddress({ socketId }) {
|
|
193
|
+
const socket = sockets.get(socketId);
|
|
194
|
+
const out = {};
|
|
195
|
+
|
|
196
|
+
const code = socket.handle.getpeername(out);
|
|
197
|
+
if (code !== 0) {
|
|
198
|
+
throw new SocketError.from(-code);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return makeIpAddress(out.family.toLowerCase(), out.address, out.port);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function handleTcpSetBacklogSize({ socketId, value }) {
|
|
205
|
+
const socket = sockets.get(socketId);
|
|
206
|
+
socket.backlog = Number(value);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function handleTcpSetKeepAlive({ socketId, keepAliveEnabled, keepAliveIdleTime }) {
|
|
210
|
+
const socket = sockets.get(socketId);
|
|
211
|
+
const time = Number(keepAliveIdleTime / 1_000_000_000n);
|
|
212
|
+
|
|
213
|
+
const code = socket.handle.setKeepAlive(keepAliveEnabled, time);
|
|
214
|
+
if (code !== 0) {
|
|
215
|
+
throw mapErrorCode(-code);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function handleRecvBufferSize({ socketId }) {
|
|
220
|
+
const socket = sockets.get(socketId);
|
|
221
|
+
|
|
222
|
+
if (socket.tcp) {
|
|
223
|
+
return BigInt(socket.tcp.getRecvBufferSize());
|
|
224
|
+
} else {
|
|
225
|
+
return await getDefaultReceiveBufferSize();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function handleSendBufferSize({ socketId }) {
|
|
230
|
+
const socket = sockets.get(socketId);
|
|
231
|
+
if (socket.tcp) {
|
|
232
|
+
return BigInt(socket.tcp.getSendBufferSize());
|
|
233
|
+
} else {
|
|
234
|
+
return await getDefaultSendBufferSize();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function handleTcpDispose({ socketId }) {
|
|
239
|
+
const socket = sockets.get(socketId);
|
|
240
|
+
if (!socket) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (socket.server) {
|
|
245
|
+
socket.server.close();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (socket.tcp) {
|
|
249
|
+
socket.tcp.destroy();
|
|
250
|
+
}
|
|
251
|
+
if (socket.handle) {
|
|
252
|
+
socket.handle.close();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
sockets.delete(socketId);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let _recvBufferSize, _sendBufferSize;
|
|
259
|
+
async function getDefaultBufferSizes() {
|
|
260
|
+
var s = new Socket();
|
|
261
|
+
s.bind(0);
|
|
262
|
+
await new Promise((resolve, reject) => {
|
|
263
|
+
s.once("error", reject);
|
|
264
|
+
s.once("listening", resolve);
|
|
265
|
+
});
|
|
266
|
+
_recvBufferSize = BigInt(s.getRecvBufferSize());
|
|
267
|
+
_sendBufferSize = BigInt(s.getSendBufferSize());
|
|
268
|
+
s.close();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function getDefaultSendBufferSize() {
|
|
272
|
+
if (!_sendBufferSize) {
|
|
273
|
+
await getDefaultBufferSizes();
|
|
274
|
+
}
|
|
275
|
+
return _sendBufferSize;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function getDefaultReceiveBufferSize() {
|
|
279
|
+
if (!_recvBufferSize) {
|
|
280
|
+
await getDefaultBufferSizes();
|
|
281
|
+
}
|
|
282
|
+
return _recvBufferSize;
|
|
283
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { once } from "node:events";
|
|
2
|
+
|
|
3
|
+
import { Router } from "../workers/resource-worker.js";
|
|
4
|
+
import {
|
|
5
|
+
serializeIpAddress,
|
|
6
|
+
makeIpAddress,
|
|
7
|
+
ipAddressConflict,
|
|
8
|
+
isLoopbackIpAddress,
|
|
9
|
+
} from "../sockets/address.js";
|
|
10
|
+
|
|
11
|
+
import dgram from "node:dgram";
|
|
12
|
+
|
|
13
|
+
const sockets = new Map();
|
|
14
|
+
// Unique IDs for sockets
|
|
15
|
+
let NEXT_SOCKET_ID = 0n;
|
|
16
|
+
|
|
17
|
+
export function noLookup(ip, _opts, cb) {
|
|
18
|
+
cb(null, ip);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Router()
|
|
22
|
+
.beforeAll((msg) => {
|
|
23
|
+
if (msg.op !== "udp-create" && !sockets.has(msg.socketId)) {
|
|
24
|
+
throw new Error("Invalid socket ID");
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
.op("udp-create", handleCreate)
|
|
28
|
+
.op("udp-bind", handleBind)
|
|
29
|
+
.op("udp-connect", handleConnect)
|
|
30
|
+
.op("udp-disconnect", handleDisconnect)
|
|
31
|
+
.op("udp-send", handleSend)
|
|
32
|
+
.op("udp-receive", handleReceive)
|
|
33
|
+
.op("udp-get-local-address", handleGetLocal)
|
|
34
|
+
.op("udp-set-unicast-hop-limit", handleSetHop)
|
|
35
|
+
.op("udp-recv-buffer-size", handleRecvBuffer)
|
|
36
|
+
.op("udp-send-buffer-size", handleSendBuffer)
|
|
37
|
+
.op("udp-dispose", handleDispose);
|
|
38
|
+
|
|
39
|
+
function handleCreate({ family }) {
|
|
40
|
+
const socketId = NEXT_SOCKET_ID++;
|
|
41
|
+
const socket = {
|
|
42
|
+
family,
|
|
43
|
+
connected: null,
|
|
44
|
+
localAddress: null,
|
|
45
|
+
receiveQueue: [],
|
|
46
|
+
};
|
|
47
|
+
socket.udp = createSocket(socket);
|
|
48
|
+
sockets.set(socketId, socket);
|
|
49
|
+
|
|
50
|
+
return { socketId };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createSocket(socket) {
|
|
54
|
+
const type = socket.family === "ipv6" ? "udp6" : "udp4";
|
|
55
|
+
const ipv6Only = socket.family === "ipv6";
|
|
56
|
+
const udp = dgram.createSocket({
|
|
57
|
+
type,
|
|
58
|
+
ipv6Only,
|
|
59
|
+
reuseAddr: false,
|
|
60
|
+
lookup: noLookup,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
udp.on("message", (msg, rinfo) => socket.receiveQueue.push({ msg, rinfo }));
|
|
64
|
+
udp.on("error", () => {});
|
|
65
|
+
return udp;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function handleBind({ socketId, localAddress }) {
|
|
69
|
+
const socket = sockets.get(socketId);
|
|
70
|
+
const hasConflict = [...sockets].some(
|
|
71
|
+
([id, { localAddress: boundAddress }]) =>
|
|
72
|
+
id !== socketId && boundAddress && ipAddressConflict(boundAddress, localAddress),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (hasConflict) {
|
|
76
|
+
const err = new Error("EADDRINUSE");
|
|
77
|
+
err.code = "EADDRINUSE";
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const addr = serializeIpAddress(localAddress);
|
|
82
|
+
const port = localAddress.val.port;
|
|
83
|
+
|
|
84
|
+
const onListening = once(socket.udp, "listening");
|
|
85
|
+
const onError = once(socket.udp, "error").then(([err]) => {
|
|
86
|
+
throw err;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
socket.udp.bind(port, addr);
|
|
90
|
+
|
|
91
|
+
await Promise.race([onListening, onError]);
|
|
92
|
+
const boundAddr = socket.udp.address();
|
|
93
|
+
socket.localAddress = makeIpAddress(socket.family, boundAddr.address, boundAddr.port);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function handleConnect({ socketId, remoteAddress }) {
|
|
97
|
+
const socket = sockets.get(socketId);
|
|
98
|
+
const preserveLocalAddress =
|
|
99
|
+
socket.connected &&
|
|
100
|
+
socket.localAddress &&
|
|
101
|
+
isLoopbackIpAddress(socket.connected) &&
|
|
102
|
+
isLoopbackIpAddress(remoteAddress);
|
|
103
|
+
|
|
104
|
+
if (socket.connected) {
|
|
105
|
+
const localAddress = preserveLocalAddress ? socket.localAddress : null;
|
|
106
|
+
await new Promise((resolve) => socket.udp.close(resolve));
|
|
107
|
+
socket.udp = createSocket(socket);
|
|
108
|
+
if (localAddress) {
|
|
109
|
+
await handleBind({ socketId, localAddress });
|
|
110
|
+
} else {
|
|
111
|
+
socket.localAddress = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const addr = serializeIpAddress(remoteAddress);
|
|
116
|
+
const port = remoteAddress.val.port;
|
|
117
|
+
|
|
118
|
+
await new Promise((resolve, reject) => {
|
|
119
|
+
socket.udp.connect(port, addr, (err) => (err ? reject(err) : resolve()));
|
|
120
|
+
});
|
|
121
|
+
const boundAddr = socket.udp.address();
|
|
122
|
+
socket.localAddress = makeIpAddress(socket.family, boundAddr.address, boundAddr.port);
|
|
123
|
+
socket.connected = remoteAddress;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function handleDisconnect({ socketId }) {
|
|
127
|
+
const socket = sockets.get(socketId);
|
|
128
|
+
socket.udp.disconnect();
|
|
129
|
+
socket.connected = null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function handleSend({ socketId, data, remoteAddress }) {
|
|
133
|
+
const socket = sockets.get(socketId);
|
|
134
|
+
const isConnected = socket.connected != null;
|
|
135
|
+
|
|
136
|
+
await new Promise((resolve, reject) => {
|
|
137
|
+
if (isConnected) {
|
|
138
|
+
socket.udp.send(data, (err) => (err ? reject(err) : resolve()));
|
|
139
|
+
} else {
|
|
140
|
+
const addr = serializeIpAddress(remoteAddress);
|
|
141
|
+
const port = remoteAddress.val.port;
|
|
142
|
+
|
|
143
|
+
socket.udp.send(data, port, addr, (err) => (err ? reject(err) : resolve()));
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const boundAddr = socket.udp.address();
|
|
148
|
+
socket.localAddress = makeIpAddress(socket.family, boundAddr.address, boundAddr.port);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function handleReceive({ socketId }) {
|
|
152
|
+
const socket = sockets.get(socketId);
|
|
153
|
+
if (socket.receiveQueue.length === 0) {
|
|
154
|
+
await Promise.race([
|
|
155
|
+
once(socket.udp, "message"),
|
|
156
|
+
once(socket.udp, "error").then(([err]) => {
|
|
157
|
+
throw err;
|
|
158
|
+
}),
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { msg, rinfo } = socket.receiveQueue.shift();
|
|
163
|
+
return {
|
|
164
|
+
data: msg,
|
|
165
|
+
remoteAddress: makeIpAddress(socket.family, rinfo.address, rinfo.port),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function handleGetLocal({ socketId }) {
|
|
170
|
+
const socket = sockets.get(socketId);
|
|
171
|
+
const addr = socket.udp.address();
|
|
172
|
+
return makeIpAddress(socket.family, addr.address, addr.port);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function handleSetHop({ socketId, value }) {
|
|
176
|
+
const socket = sockets.get(socketId);
|
|
177
|
+
socket.udp.setTTL(value);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function handleRecvBuffer({ socketId }) {
|
|
181
|
+
const socket = sockets.get(socketId);
|
|
182
|
+
return BigInt(socket.udp.getRecvBufferSize());
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleSendBuffer({ socketId }) {
|
|
186
|
+
const socket = sockets.get(socketId);
|
|
187
|
+
return BigInt(socket.udp.getSendBufferSize());
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function handleDispose({ socketId }) {
|
|
191
|
+
const socket = sockets.get(socketId);
|
|
192
|
+
socket.udp.close();
|
|
193
|
+
sockets.delete(socketId);
|
|
194
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bytecodealliance/preview3-shim",
|
|
3
|
+
"version": "0.1.0-rc.0",
|
|
4
|
+
"description": "WASI Preview3 shim for JS environments",
|
|
5
|
+
"homepage": "https://github.com/bytecodealliance/jco/blob/main/packages/preview3-shim/#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/bytecodealliance/jco/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "(Apache-2.0 WITH LLVM-exception)",
|
|
10
|
+
"author": "Tomasz Andrzejak",
|
|
11
|
+
"contributors": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Tomasz Andrzejak"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "Victor Adossi",
|
|
17
|
+
"email": "vadossi@cosmonic.com"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/bytecodealliance/jco.git"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"types",
|
|
26
|
+
"lib"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"types": "./types/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./types/index.d.ts",
|
|
33
|
+
"node": "./lib/nodejs/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./*": {
|
|
36
|
+
"types": "./types/*.d.ts",
|
|
37
|
+
"node": "./lib/nodejs/*.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@bytecodealliance/preview2-shim": "^0.17.3"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"globals": "^16.3.0",
|
|
45
|
+
"vite": "^7.1.5",
|
|
46
|
+
"vitest": "^3.2.4"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"compile:check": "tsc --noEmit types/index.d.ts",
|
|
50
|
+
"fmt": "oxfmt",
|
|
51
|
+
"fmt:check": "oxfmt --check",
|
|
52
|
+
"lint": "oxlint",
|
|
53
|
+
"lint:fix": "oxlint --fix",
|
|
54
|
+
"test": "vitest --run",
|
|
55
|
+
"bench": "vitest bench --run"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/types/cli.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type * as environment from "./interfaces/wasi-cli-environment.d.ts";
|
|
2
|
+
export type * as exit from "./interfaces/wasi-cli-exit.d.ts";
|
|
3
|
+
export type * as run from "./interfaces/wasi-cli-run.d.ts";
|
|
4
|
+
export type * as stderr from "./interfaces/wasi-cli-stderr.d.ts";
|
|
5
|
+
export type * as stdin from "./interfaces/wasi-cli-stdin.d.ts";
|
|
6
|
+
export type * as stdout from "./interfaces/wasi-cli-stdout.d.ts";
|
|
7
|
+
export type * as terminalInput from "./interfaces/wasi-cli-terminal-input.d.ts";
|
|
8
|
+
export type * as terminalOutput from "./interfaces/wasi-cli-terminal-output.d.ts";
|
|
9
|
+
export type * as terminalStderr from "./interfaces/wasi-cli-terminal-stderr.d.ts";
|
|
10
|
+
export type * as terminalStdin from "./interfaces/wasi-cli-terminal-stdin.d.ts";
|
|
11
|
+
export type * as terminalStdout from "./interfaces/wasi-cli-terminal-stdout.d.ts";
|
|
12
|
+
export type * as types from "./interfaces/wasi-cli-types.d.ts";
|
package/types/http.d.ts
ADDED
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type * as cli from "./cli.d.ts";
|
|
2
|
+
export type * as clocks from "./clocks.d.ts";
|
|
3
|
+
export type * as filesystem from "./filesystem.d.ts";
|
|
4
|
+
export type * as http from "./http.d.ts";
|
|
5
|
+
export type * as random from "./random.d.ts";
|
|
6
|
+
export type * as sockets from "./sockets.d.ts";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** @module Interface wasi:cli/environment@0.3.0-rc-2026-03-15 **/
|
|
2
|
+
/**
|
|
3
|
+
* Get the POSIX-style environment variables.
|
|
4
|
+
*
|
|
5
|
+
* Each environment variable is provided as a pair of string variable names
|
|
6
|
+
* and string value.
|
|
7
|
+
*
|
|
8
|
+
* Morally, these are a value import, but until value imports are available
|
|
9
|
+
* in the component model, this import function should return the same
|
|
10
|
+
* values each time it is called.
|
|
11
|
+
*/
|
|
12
|
+
export function getEnvironment(): Array<[string, string]>;
|
|
13
|
+
/**
|
|
14
|
+
* Get the POSIX-style arguments to the program.
|
|
15
|
+
*/
|
|
16
|
+
export function getArguments(): Array<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Return a path that programs should use as their initial current working
|
|
19
|
+
* directory, interpreting `.` as shorthand for this.
|
|
20
|
+
*/
|
|
21
|
+
export function getInitialCwd(): string | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @module Interface wasi:cli/stderr@0.3.0-rc-2026-03-15 **/
|
|
2
|
+
/**
|
|
3
|
+
* Write the given stream to stderr.
|
|
4
|
+
*
|
|
5
|
+
* If the stream's writable end is dropped this function will either return
|
|
6
|
+
* success once the entire contents of the stream have been written or an
|
|
7
|
+
* error-code representing a failure.
|
|
8
|
+
*
|
|
9
|
+
* Otherwise if there is an error the readable end of the stream will be
|
|
10
|
+
* dropped and this function will return an error-code.
|
|
11
|
+
*/
|
|
12
|
+
export function writeViaStream(data: ReadableStream<number>): Promise<Result<void, ErrorCode>>;
|
|
13
|
+
export type ErrorCode = import('./wasi-cli-types.js').ErrorCode;
|
|
14
|
+
export type Result<T, E> = { tag: 'ok', val: T } | { tag: 'err', val: E };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** @module Interface wasi:cli/stdin@0.3.0-rc-2026-03-15 **/
|
|
2
|
+
/**
|
|
3
|
+
* Return a stream for reading from stdin.
|
|
4
|
+
*
|
|
5
|
+
* This function returns a stream which provides data read from stdin,
|
|
6
|
+
* and a future to signal read results.
|
|
7
|
+
*
|
|
8
|
+
* If the stream's readable end is dropped the future will resolve to success.
|
|
9
|
+
*
|
|
10
|
+
* If the stream's writable end is dropped the future will either resolve to
|
|
11
|
+
* success if stdin was closed by the writer or to an error-code if reading
|
|
12
|
+
* failed for some other reason.
|
|
13
|
+
*
|
|
14
|
+
* Multiple streams may be active at the same time. The behavior of concurrent
|
|
15
|
+
* reads is implementation-specific.
|
|
16
|
+
*/
|
|
17
|
+
export function readViaStream(): [ReadableStream<number>, Promise<Result<void, ErrorCode>>];
|
|
18
|
+
export type ErrorCode = import('./wasi-cli-types.js').ErrorCode;
|
|
19
|
+
export type Result<T, E> = { tag: 'ok', val: T } | { tag: 'err', val: E };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @module Interface wasi:cli/stdout@0.3.0-rc-2026-03-15 **/
|
|
2
|
+
/**
|
|
3
|
+
* Write the given stream to stdout.
|
|
4
|
+
*
|
|
5
|
+
* If the stream's writable end is dropped this function will either return
|
|
6
|
+
* success once the entire contents of the stream have been written or an
|
|
7
|
+
* error-code representing a failure.
|
|
8
|
+
*
|
|
9
|
+
* Otherwise if there is an error the readable end of the stream will be
|
|
10
|
+
* dropped and this function will return an error-code.
|
|
11
|
+
*/
|
|
12
|
+
export function writeViaStream(data: ReadableStream<number>): Promise<Result<void, ErrorCode>>;
|
|
13
|
+
export type ErrorCode = import('./wasi-cli-types.js').ErrorCode;
|
|
14
|
+
export type Result<T, E> = { tag: 'ok', val: T } | { tag: 'err', val: E };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** @module Interface wasi:cli/terminal-stderr@0.3.0-rc-2026-03-15 **/
|
|
2
|
+
/**
|
|
3
|
+
* If stderr is connected to a terminal, return a `terminal-output` handle
|
|
4
|
+
* allowing further interaction with it.
|
|
5
|
+
*/
|
|
6
|
+
export function getTerminalStderr(): TerminalOutput | undefined;
|
|
7
|
+
export type TerminalOutput = import('./wasi-cli-terminal-output.js').TerminalOutput;
|