wasmnet 0.1.1 → 0.1.3
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 +54 -24
- package/dist/wasmnet-client.d.ts +37 -1
- package/dist/wasmnet-client.js +345 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# wasmnet
|
|
2
2
|
|
|
3
|
-
Browser client for [wasmnet](https://github.com/anistark/wasmnet) — a networking proxy that bridges WASI socket APIs to real TCP via WebSocket.
|
|
3
|
+
Browser client for [wasmnet](https://github.com/anistark/wasmnet) — a networking proxy that bridges WASI socket APIs to real TCP/UDP/TLS via WebSocket.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -16,28 +16,28 @@ import { WasmnetClient } from 'wasmnet';
|
|
|
16
16
|
const client = new WasmnetClient('ws://localhost:9000');
|
|
17
17
|
await client.ready();
|
|
18
18
|
|
|
19
|
-
//
|
|
20
|
-
const id = await client.connect('
|
|
19
|
+
// TCP
|
|
20
|
+
const id = await client.connect('example.com', 80);
|
|
21
|
+
client.onData(id, (data) => console.log(new TextDecoder().decode(data)));
|
|
22
|
+
client.send(id, 'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n');
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
// TLS (server handles handshake)
|
|
25
|
+
const tls = await client.connectTls('api.example.com', 443);
|
|
26
|
+
client.onData(tls, (data) => console.log('tls:', data));
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// UDP
|
|
29
|
+
const udp = await client.connectUdp('8.8.8.8', 53);
|
|
30
|
+
client.onDataFrom(udp.id, (data, addr, port) => console.log(data));
|
|
31
|
+
client.send(udp.id, packet);
|
|
32
|
+
client.sendTo(udp.id, '8.8.4.4', 53, packet); // different target
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
// DNS resolve
|
|
35
|
+
const ips = await client.resolve('example.com');
|
|
31
36
|
|
|
32
|
-
// Inbound TCP
|
|
37
|
+
// Inbound TCP
|
|
33
38
|
const listener = await client.bind('0.0.0.0', 3000);
|
|
34
|
-
console.log(`listening on port ${listener.port}`);
|
|
35
|
-
|
|
36
39
|
client.onAccept(listener.id, (connId, remote) => {
|
|
37
|
-
|
|
38
|
-
client.onData(connId, (data) => {
|
|
39
|
-
client.send(connId, data); // echo
|
|
40
|
-
});
|
|
40
|
+
client.onData(connId, (data) => client.send(connId, data));
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
// Cleanup
|
|
@@ -45,20 +45,36 @@ client.close(id);
|
|
|
45
45
|
client.disconnect();
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
### Binary framing
|
|
49
|
+
|
|
50
|
+
Pass `{ binary: true }` to skip JSON + base64 overhead on data frames. Raw bytes are sent directly in WebSocket binary messages.
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
const client = new WasmnetClient('ws://localhost:9000', { binary: true });
|
|
54
|
+
```
|
|
55
|
+
|
|
48
56
|
## API
|
|
49
57
|
|
|
50
|
-
### `new WasmnetClient(url: string)`
|
|
58
|
+
### `new WasmnetClient(url: string, options?: { binary?: boolean })`
|
|
51
59
|
|
|
52
|
-
Creates a client connecting to the wasmnet server
|
|
60
|
+
Creates a client connecting to the wasmnet server. Set `binary: true` for binary framing mode.
|
|
53
61
|
|
|
54
62
|
### `ready(): Promise<void>`
|
|
55
63
|
|
|
56
|
-
Resolves when the WebSocket connection is
|
|
64
|
+
Resolves when the WebSocket connection is open.
|
|
57
65
|
|
|
58
66
|
### `connect(addr: string, port: number): Promise<number>`
|
|
59
67
|
|
|
60
68
|
Opens an outbound TCP connection. Returns the socket ID.
|
|
61
69
|
|
|
70
|
+
### `connectTls(addr: string, port: number): Promise<number>`
|
|
71
|
+
|
|
72
|
+
Opens an outbound TCP connection with TLS. The wasmnet server handles the TLS handshake (using system CA roots). Returns the socket ID. Data sent/received through this socket is plaintext — encryption is handled transparently.
|
|
73
|
+
|
|
74
|
+
### `connectUdp(addr: string, port: number): Promise<{ id: number, port: number }>`
|
|
75
|
+
|
|
76
|
+
Creates a UDP socket connected to the given address. Returns the socket ID and local port. Use `send()` to send to the connected address, or `sendTo()` for arbitrary targets.
|
|
77
|
+
|
|
62
78
|
### `bind(addr: string, port: number): Promise<{ id: number, port: number }>`
|
|
63
79
|
|
|
64
80
|
Binds a TCP listener. Returns the listener ID and actual bound port.
|
|
@@ -69,7 +85,15 @@ Starts accepting connections on a bound listener.
|
|
|
69
85
|
|
|
70
86
|
### `send(id: number, data: string | Uint8Array | ArrayBuffer): void`
|
|
71
87
|
|
|
72
|
-
Sends data on a
|
|
88
|
+
Sends data on a TCP, TLS, or connected UDP socket.
|
|
89
|
+
|
|
90
|
+
### `sendTo(id: number, addr: string, port: number, data: string | Uint8Array | ArrayBuffer): void`
|
|
91
|
+
|
|
92
|
+
Sends a UDP datagram to a specific target address, regardless of the socket's connected address.
|
|
93
|
+
|
|
94
|
+
### `resolve(name: string): Promise<string[]>`
|
|
95
|
+
|
|
96
|
+
Resolves a hostname to an array of IP address strings (both IPv4 and IPv6).
|
|
73
97
|
|
|
74
98
|
### `close(id: number): void`
|
|
75
99
|
|
|
@@ -77,7 +101,11 @@ Closes a socket or listener.
|
|
|
77
101
|
|
|
78
102
|
### `onData(id: number, callback: (data: Uint8Array) => void): void`
|
|
79
103
|
|
|
80
|
-
Registers a data handler. Any buffered
|
|
104
|
+
Registers a data handler for TCP/TLS sockets. Any data buffered before the handler was set is flushed immediately.
|
|
105
|
+
|
|
106
|
+
### `onDataFrom(id: number, callback: (data: Uint8Array, addr: string, port: number) => void): void`
|
|
107
|
+
|
|
108
|
+
Registers a data handler for UDP sockets. Each callback includes the source address and port.
|
|
81
109
|
|
|
82
110
|
### `onClose(id: number, callback: () => void): void`
|
|
83
111
|
|
|
@@ -85,10 +113,12 @@ Registers a close handler.
|
|
|
85
113
|
|
|
86
114
|
### `onAccept(id: number, callback: (connId: number, remote: string) => void): void`
|
|
87
115
|
|
|
88
|
-
Registers an accept handler for a listener.
|
|
116
|
+
Registers an accept handler for a TCP listener. `connId` is the new socket ID for the accepted connection.
|
|
89
117
|
|
|
90
118
|
### `disconnect(): void`
|
|
91
119
|
|
|
92
120
|
Closes the WebSocket connection and all sockets.
|
|
93
121
|
|
|
94
|
-
##
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
package/dist/wasmnet-client.d.ts
CHANGED
|
@@ -3,20 +3,56 @@ export interface ListenResult {
|
|
|
3
3
|
port: number;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
export interface ClientOptions {
|
|
7
|
+
/** Use binary framing instead of JSON (lower overhead for data). */
|
|
8
|
+
binary?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
export type DataCallback = (data: Uint8Array) => void;
|
|
12
|
+
export type DataFromCallback = (
|
|
13
|
+
data: Uint8Array,
|
|
14
|
+
addr: string,
|
|
15
|
+
port: number,
|
|
16
|
+
) => void;
|
|
7
17
|
export type CloseCallback = () => void;
|
|
8
18
|
export type AcceptCallback = (connId: number, remote: string) => void;
|
|
9
19
|
|
|
10
20
|
export class WasmnetClient {
|
|
11
|
-
constructor(url: string);
|
|
21
|
+
constructor(url: string, options?: ClientOptions);
|
|
12
22
|
|
|
13
23
|
ready(): Promise<void>;
|
|
24
|
+
|
|
25
|
+
/** TCP connect to a remote host. */
|
|
14
26
|
connect(addr: string, port: number): Promise<number>;
|
|
27
|
+
|
|
28
|
+
/** TCP connect with TLS (server handles TLS handshake). */
|
|
29
|
+
connectTls(addr: string, port: number): Promise<number>;
|
|
30
|
+
|
|
31
|
+
/** Create a UDP socket connected to a remote host. */
|
|
32
|
+
connectUdp(addr: string, port: number): Promise<ListenResult>;
|
|
33
|
+
|
|
34
|
+
/** Bind a TCP listener. */
|
|
15
35
|
bind(addr: string, port: number): Promise<ListenResult>;
|
|
36
|
+
|
|
16
37
|
listen(id: number, backlog?: number): void;
|
|
38
|
+
|
|
39
|
+
/** Send data on a TCP or connected-UDP socket. */
|
|
17
40
|
send(id: number, data: string | Uint8Array | ArrayBuffer): void;
|
|
41
|
+
|
|
42
|
+
/** Send a UDP datagram to a specific address. */
|
|
43
|
+
sendTo(
|
|
44
|
+
id: number,
|
|
45
|
+
addr: string,
|
|
46
|
+
port: number,
|
|
47
|
+
data: string | Uint8Array | ArrayBuffer,
|
|
48
|
+
): void;
|
|
49
|
+
|
|
50
|
+
/** Resolve a hostname to IP addresses. */
|
|
51
|
+
resolve(name: string): Promise<string[]>;
|
|
52
|
+
|
|
18
53
|
close(id: number): void;
|
|
19
54
|
onData(id: number, callback: DataCallback): void;
|
|
55
|
+
onDataFrom(id: number, callback: DataFromCallback): void;
|
|
20
56
|
onClose(id: number, callback: CloseCallback): void;
|
|
21
57
|
onAccept(id: number, callback: AcceptCallback): void;
|
|
22
58
|
disconnect(): void;
|
package/dist/wasmnet-client.js
CHANGED
|
@@ -1,13 +1,48 @@
|
|
|
1
|
+
const BINARY_HEADER = 9; // 1 byte type + 8 byte id
|
|
2
|
+
|
|
3
|
+
const MSG = {
|
|
4
|
+
// requests
|
|
5
|
+
CONNECT: 0x01,
|
|
6
|
+
BIND: 0x02,
|
|
7
|
+
LISTEN: 0x03,
|
|
8
|
+
SEND: 0x04,
|
|
9
|
+
CLOSE: 0x05,
|
|
10
|
+
CONNECT_UDP: 0x06,
|
|
11
|
+
SEND_TO: 0x07,
|
|
12
|
+
RESOLVE: 0x08,
|
|
13
|
+
CONNECT_TLS: 0x09,
|
|
14
|
+
// events
|
|
15
|
+
CONNECTED: 0x81,
|
|
16
|
+
DATA: 0x82,
|
|
17
|
+
LISTENING: 0x83,
|
|
18
|
+
ACCEPTED: 0x84,
|
|
19
|
+
CLOSED: 0x85,
|
|
20
|
+
ERROR: 0x86,
|
|
21
|
+
DENIED: 0x87,
|
|
22
|
+
DATA_FROM: 0x88,
|
|
23
|
+
RESOLVED: 0x89,
|
|
24
|
+
UDP_BOUND: 0x8a,
|
|
25
|
+
};
|
|
26
|
+
|
|
1
27
|
export class WasmnetClient {
|
|
2
|
-
constructor(url) {
|
|
28
|
+
constructor(url, options = {}) {
|
|
3
29
|
this.ws = new WebSocket(url);
|
|
30
|
+
this.ws.binaryType = "arraybuffer";
|
|
4
31
|
this.sockets = new Map();
|
|
5
32
|
this.nextId = 1;
|
|
33
|
+
this.binary = options.binary === true;
|
|
34
|
+
this._resolves = new Map();
|
|
6
35
|
this._ready = new Promise((resolve, reject) => {
|
|
7
36
|
this.ws.onopen = () => resolve();
|
|
8
37
|
this.ws.onerror = (e) => reject(e);
|
|
9
38
|
});
|
|
10
|
-
this.ws.onmessage = (e) =>
|
|
39
|
+
this.ws.onmessage = (e) => {
|
|
40
|
+
if (e.data instanceof ArrayBuffer) {
|
|
41
|
+
this._handleBinaryEvent(new Uint8Array(e.data));
|
|
42
|
+
} else {
|
|
43
|
+
this._handleEvent(JSON.parse(e.data));
|
|
44
|
+
}
|
|
45
|
+
};
|
|
11
46
|
this.ws.onclose = () => this._handleClose();
|
|
12
47
|
}
|
|
13
48
|
|
|
@@ -18,8 +53,63 @@ export class WasmnetClient {
|
|
|
18
53
|
connect(addr, port) {
|
|
19
54
|
const id = this.nextId++;
|
|
20
55
|
return new Promise((resolve, reject) => {
|
|
21
|
-
this.sockets.set(id, {
|
|
22
|
-
|
|
56
|
+
this.sockets.set(id, {
|
|
57
|
+
resolve,
|
|
58
|
+
reject,
|
|
59
|
+
buffer: [],
|
|
60
|
+
onData: null,
|
|
61
|
+
onClose: null,
|
|
62
|
+
});
|
|
63
|
+
if (this.binary) {
|
|
64
|
+
this._sendBinary(MSG.CONNECT, id, this._addrPortPayload(addr, port));
|
|
65
|
+
} else {
|
|
66
|
+
this._send({ op: "connect", id, addr, port });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
connectTls(addr, port) {
|
|
72
|
+
const id = this.nextId++;
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
this.sockets.set(id, {
|
|
75
|
+
resolve,
|
|
76
|
+
reject,
|
|
77
|
+
buffer: [],
|
|
78
|
+
onData: null,
|
|
79
|
+
onClose: null,
|
|
80
|
+
});
|
|
81
|
+
if (this.binary) {
|
|
82
|
+
this._sendBinary(
|
|
83
|
+
MSG.CONNECT_TLS,
|
|
84
|
+
id,
|
|
85
|
+
this._addrPortPayload(addr, port),
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
this._send({ op: "connect_tls", id, addr, port });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
connectUdp(addr, port) {
|
|
94
|
+
const id = this.nextId++;
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
this.sockets.set(id, {
|
|
97
|
+
resolve,
|
|
98
|
+
reject,
|
|
99
|
+
buffer: [],
|
|
100
|
+
onData: null,
|
|
101
|
+
onDataFrom: null,
|
|
102
|
+
onClose: null,
|
|
103
|
+
});
|
|
104
|
+
if (this.binary) {
|
|
105
|
+
this._sendBinary(
|
|
106
|
+
MSG.CONNECT_UDP,
|
|
107
|
+
id,
|
|
108
|
+
this._addrPortPayload(addr, port),
|
|
109
|
+
);
|
|
110
|
+
} else {
|
|
111
|
+
this._send({ op: "connect_udp", id, addr, port });
|
|
112
|
+
}
|
|
23
113
|
});
|
|
24
114
|
}
|
|
25
115
|
|
|
@@ -27,21 +117,81 @@ export class WasmnetClient {
|
|
|
27
117
|
const id = this.nextId++;
|
|
28
118
|
return new Promise((resolve, reject) => {
|
|
29
119
|
this.sockets.set(id, { resolve, reject, onAccept: null });
|
|
30
|
-
this.
|
|
120
|
+
if (this.binary) {
|
|
121
|
+
this._sendBinary(MSG.BIND, id, this._addrPortPayload(addr, port));
|
|
122
|
+
} else {
|
|
123
|
+
this._send({ op: "bind", id, addr, port });
|
|
124
|
+
}
|
|
31
125
|
});
|
|
32
126
|
}
|
|
33
127
|
|
|
34
128
|
listen(id, backlog = 128) {
|
|
35
|
-
this.
|
|
129
|
+
if (this.binary) {
|
|
130
|
+
const p = new Uint8Array(4);
|
|
131
|
+
new DataView(p.buffer).setUint32(0, backlog);
|
|
132
|
+
this._sendBinary(MSG.LISTEN, id, p);
|
|
133
|
+
} else {
|
|
134
|
+
this._send({ op: "listen", id, backlog });
|
|
135
|
+
}
|
|
36
136
|
}
|
|
37
137
|
|
|
38
138
|
send(id, data) {
|
|
39
|
-
|
|
40
|
-
|
|
139
|
+
if (this.binary) {
|
|
140
|
+
const bytes =
|
|
141
|
+
data instanceof Uint8Array
|
|
142
|
+
? data
|
|
143
|
+
: typeof data === "string"
|
|
144
|
+
? new TextEncoder().encode(data)
|
|
145
|
+
: new Uint8Array(data);
|
|
146
|
+
this._sendBinary(MSG.SEND, id, bytes);
|
|
147
|
+
} else {
|
|
148
|
+
const encoded =
|
|
149
|
+
typeof data === "string" ? btoa(data) : this._encodeBinary(data);
|
|
150
|
+
this._send({ op: "send", id, data: encoded });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
sendTo(id, addr, port, data) {
|
|
155
|
+
if (this.binary) {
|
|
156
|
+
const addrBytes = new TextEncoder().encode(addr);
|
|
157
|
+
const raw =
|
|
158
|
+
data instanceof Uint8Array
|
|
159
|
+
? data
|
|
160
|
+
: typeof data === "string"
|
|
161
|
+
? new TextEncoder().encode(data)
|
|
162
|
+
: new Uint8Array(data);
|
|
163
|
+
const payload = new Uint8Array(4 + addrBytes.length + raw.length);
|
|
164
|
+
const dv = new DataView(payload.buffer);
|
|
165
|
+
dv.setUint16(0, port);
|
|
166
|
+
dv.setUint16(2, addrBytes.length);
|
|
167
|
+
payload.set(addrBytes, 4);
|
|
168
|
+
payload.set(raw, 4 + addrBytes.length);
|
|
169
|
+
this._sendBinary(MSG.SEND_TO, id, payload);
|
|
170
|
+
} else {
|
|
171
|
+
const encoded =
|
|
172
|
+
typeof data === "string" ? btoa(data) : this._encodeBinary(data);
|
|
173
|
+
this._send({ op: "send_to", id, addr, port, data: encoded });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
resolve(name) {
|
|
178
|
+
const id = this.nextId++;
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
this._resolves.set(id, { resolve, reject });
|
|
181
|
+
if (this.binary) {
|
|
182
|
+
this._sendBinary(MSG.RESOLVE, id, new TextEncoder().encode(name));
|
|
183
|
+
} else {
|
|
184
|
+
this._send({ op: "resolve", id, name });
|
|
185
|
+
}
|
|
186
|
+
});
|
|
41
187
|
}
|
|
42
188
|
|
|
43
189
|
close(id) {
|
|
44
|
-
this.
|
|
190
|
+
if (this.binary) {
|
|
191
|
+
this._sendBinary(MSG.CLOSE, id, new Uint8Array(0));
|
|
192
|
+
} else {
|
|
193
|
+
this._send({ op: "close", id });
|
|
194
|
+
}
|
|
45
195
|
this.sockets.delete(id);
|
|
46
196
|
}
|
|
47
197
|
|
|
@@ -55,6 +205,11 @@ export class WasmnetClient {
|
|
|
55
205
|
sock.buffer = [];
|
|
56
206
|
}
|
|
57
207
|
|
|
208
|
+
onDataFrom(id, callback) {
|
|
209
|
+
const sock = this.sockets.get(id);
|
|
210
|
+
if (sock) sock.onDataFrom = callback;
|
|
211
|
+
}
|
|
212
|
+
|
|
58
213
|
onClose(id, callback) {
|
|
59
214
|
const sock = this.sockets.get(id);
|
|
60
215
|
if (sock) sock.onClose = callback;
|
|
@@ -69,6 +224,8 @@ export class WasmnetClient {
|
|
|
69
224
|
this.ws.close();
|
|
70
225
|
}
|
|
71
226
|
|
|
227
|
+
// ── JSON transport ───────────────────────────────────
|
|
228
|
+
|
|
72
229
|
_send(msg) {
|
|
73
230
|
this.ws.send(JSON.stringify(msg));
|
|
74
231
|
}
|
|
@@ -91,7 +248,168 @@ export class WasmnetClient {
|
|
|
91
248
|
return bytes;
|
|
92
249
|
}
|
|
93
250
|
|
|
251
|
+
// ── Binary transport ─────────────────────────────────
|
|
252
|
+
|
|
253
|
+
_sendBinary(type_, id, payload) {
|
|
254
|
+
const frame = new Uint8Array(BINARY_HEADER + payload.length);
|
|
255
|
+
frame[0] = type_;
|
|
256
|
+
const dv = new DataView(frame.buffer);
|
|
257
|
+
dv.setBigUint64(1, BigInt(id));
|
|
258
|
+
frame.set(payload, BINARY_HEADER);
|
|
259
|
+
this.ws.send(frame.buffer);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
_addrPortPayload(addr, port) {
|
|
263
|
+
const addrBytes = new TextEncoder().encode(addr);
|
|
264
|
+
const payload = new Uint8Array(2 + addrBytes.length);
|
|
265
|
+
new DataView(payload.buffer).setUint16(0, port);
|
|
266
|
+
payload.set(addrBytes, 2);
|
|
267
|
+
return payload;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
_handleBinaryEvent(data) {
|
|
271
|
+
if (data.length < BINARY_HEADER) return;
|
|
272
|
+
const type_ = data[0];
|
|
273
|
+
const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
274
|
+
const id = Number(dv.getBigUint64(1));
|
|
275
|
+
const payload = data.subarray(BINARY_HEADER);
|
|
276
|
+
|
|
277
|
+
switch (type_) {
|
|
278
|
+
case MSG.CONNECTED:
|
|
279
|
+
this._dispatch(id, "connected", {});
|
|
280
|
+
break;
|
|
281
|
+
case MSG.DATA:
|
|
282
|
+
this._dispatch(id, "data", { raw: payload });
|
|
283
|
+
break;
|
|
284
|
+
case MSG.LISTENING:
|
|
285
|
+
this._dispatch(id, "listening", {
|
|
286
|
+
port: new DataView(
|
|
287
|
+
payload.buffer,
|
|
288
|
+
payload.byteOffset,
|
|
289
|
+
payload.byteLength,
|
|
290
|
+
).getUint16(0),
|
|
291
|
+
});
|
|
292
|
+
break;
|
|
293
|
+
case MSG.ACCEPTED: {
|
|
294
|
+
const connId = Number(
|
|
295
|
+
new DataView(
|
|
296
|
+
payload.buffer,
|
|
297
|
+
payload.byteOffset,
|
|
298
|
+
payload.byteLength,
|
|
299
|
+
).getBigUint64(0),
|
|
300
|
+
);
|
|
301
|
+
const remote = new TextDecoder().decode(payload.subarray(8));
|
|
302
|
+
this._dispatch(id, "accepted", { conn_id: connId, remote });
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
case MSG.CLOSED:
|
|
306
|
+
this._dispatch(id, "closed", {});
|
|
307
|
+
break;
|
|
308
|
+
case MSG.ERROR:
|
|
309
|
+
this._dispatch(id, "error", {
|
|
310
|
+
msg: new TextDecoder().decode(payload),
|
|
311
|
+
});
|
|
312
|
+
break;
|
|
313
|
+
case MSG.DENIED:
|
|
314
|
+
this._dispatch(id, "denied", {
|
|
315
|
+
msg: new TextDecoder().decode(payload),
|
|
316
|
+
});
|
|
317
|
+
break;
|
|
318
|
+
case MSG.DATA_FROM: {
|
|
319
|
+
const pdv = new DataView(
|
|
320
|
+
payload.buffer,
|
|
321
|
+
payload.byteOffset,
|
|
322
|
+
payload.byteLength,
|
|
323
|
+
);
|
|
324
|
+
const port = pdv.getUint16(0);
|
|
325
|
+
const addrLen = pdv.getUint16(2);
|
|
326
|
+
const addr = new TextDecoder().decode(payload.subarray(4, 4 + addrLen));
|
|
327
|
+
const raw = payload.subarray(4 + addrLen);
|
|
328
|
+
this._dispatch(id, "data_from", { addr, port, raw });
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
case MSG.RESOLVED: {
|
|
332
|
+
const addrs = JSON.parse(new TextDecoder().decode(payload));
|
|
333
|
+
this._dispatchResolve(id, addrs);
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
case MSG.UDP_BOUND:
|
|
337
|
+
this._dispatch(id, "udp_bound", {
|
|
338
|
+
port: new DataView(
|
|
339
|
+
payload.buffer,
|
|
340
|
+
payload.byteOffset,
|
|
341
|
+
payload.byteLength,
|
|
342
|
+
).getUint16(0),
|
|
343
|
+
});
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
_dispatch(id, ev, extra) {
|
|
349
|
+
const sock = this.sockets.get(id);
|
|
350
|
+
if (!sock && ev !== "resolved") return;
|
|
351
|
+
|
|
352
|
+
switch (ev) {
|
|
353
|
+
case "connected":
|
|
354
|
+
sock.resolve?.(id);
|
|
355
|
+
break;
|
|
356
|
+
case "listening":
|
|
357
|
+
sock.resolve?.({ id, port: extra.port });
|
|
358
|
+
break;
|
|
359
|
+
case "udp_bound":
|
|
360
|
+
sock.resolve?.({ id, port: extra.port });
|
|
361
|
+
break;
|
|
362
|
+
case "data": {
|
|
363
|
+
const decoded = extra.raw || this._decodeBinary(extra.b64);
|
|
364
|
+
if (sock.onData) {
|
|
365
|
+
sock.onData(decoded);
|
|
366
|
+
} else {
|
|
367
|
+
sock.buffer.push(decoded);
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
case "data_from":
|
|
372
|
+
sock.onDataFrom?.(extra.raw, extra.addr, extra.port);
|
|
373
|
+
break;
|
|
374
|
+
case "accepted":
|
|
375
|
+
this.sockets.set(extra.conn_id, {
|
|
376
|
+
buffer: [],
|
|
377
|
+
onData: null,
|
|
378
|
+
onClose: null,
|
|
379
|
+
});
|
|
380
|
+
sock.onAccept?.(extra.conn_id, extra.remote);
|
|
381
|
+
break;
|
|
382
|
+
case "closed":
|
|
383
|
+
sock.onClose?.();
|
|
384
|
+
this.sockets.delete(id);
|
|
385
|
+
break;
|
|
386
|
+
case "error":
|
|
387
|
+
sock.reject?.(new Error(extra.msg));
|
|
388
|
+
this.sockets.delete(id);
|
|
389
|
+
break;
|
|
390
|
+
case "denied":
|
|
391
|
+
sock.reject?.(new Error(extra.msg));
|
|
392
|
+
this.sockets.delete(id);
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
_dispatchResolve(id, addrs) {
|
|
398
|
+
const entry = this._resolves.get(id);
|
|
399
|
+
if (entry) {
|
|
400
|
+
entry.resolve(addrs);
|
|
401
|
+
this._resolves.delete(id);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ── JSON event handler ───────────────────────────────
|
|
406
|
+
|
|
94
407
|
_handleEvent(ev) {
|
|
408
|
+
if (ev.ev === "resolved") {
|
|
409
|
+
this._dispatchResolve(ev.id, ev.addrs);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
95
413
|
const sock = this.sockets.get(ev.id);
|
|
96
414
|
if (!sock) return;
|
|
97
415
|
|
|
@@ -102,6 +420,9 @@ export class WasmnetClient {
|
|
|
102
420
|
case "listening":
|
|
103
421
|
sock.resolve({ id: ev.id, port: ev.port });
|
|
104
422
|
break;
|
|
423
|
+
case "udp_bound":
|
|
424
|
+
sock.resolve({ id: ev.id, port: ev.port });
|
|
425
|
+
break;
|
|
105
426
|
case "data": {
|
|
106
427
|
const decoded = this._decodeBinary(ev.data);
|
|
107
428
|
if (sock.onData) {
|
|
@@ -111,8 +432,17 @@ export class WasmnetClient {
|
|
|
111
432
|
}
|
|
112
433
|
break;
|
|
113
434
|
}
|
|
435
|
+
case "data_from": {
|
|
436
|
+
const decoded = this._decodeBinary(ev.data);
|
|
437
|
+
sock.onDataFrom?.(decoded, ev.addr, ev.port);
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
114
440
|
case "accepted":
|
|
115
|
-
this.sockets.set(ev.conn_id, {
|
|
441
|
+
this.sockets.set(ev.conn_id, {
|
|
442
|
+
buffer: [],
|
|
443
|
+
onData: null,
|
|
444
|
+
onClose: null,
|
|
445
|
+
});
|
|
116
446
|
sock.onAccept?.(ev.conn_id, ev.remote);
|
|
117
447
|
break;
|
|
118
448
|
case "closed":
|
|
@@ -131,9 +461,13 @@ export class WasmnetClient {
|
|
|
131
461
|
}
|
|
132
462
|
|
|
133
463
|
_handleClose() {
|
|
134
|
-
for (const [
|
|
464
|
+
for (const [, sock] of this.sockets) {
|
|
135
465
|
sock.onClose?.();
|
|
136
466
|
}
|
|
137
467
|
this.sockets.clear();
|
|
468
|
+
for (const [, entry] of this._resolves) {
|
|
469
|
+
entry.reject?.(new Error("connection closed"));
|
|
470
|
+
}
|
|
471
|
+
this._resolves.clear();
|
|
138
472
|
}
|
|
139
473
|
}
|