wasmnet 0.1.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/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # wasmnet
2
+
3
+ Browser client for [wasmnet](https://github.com/anistark/wasmnet) — a networking proxy that bridges WASI socket APIs to real TCP via WebSocket.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install wasmnet
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```javascript
14
+ import { WasmnetClient } from 'wasmnet';
15
+
16
+ const client = new WasmnetClient('ws://localhost:9000');
17
+ await client.ready();
18
+
19
+ // Outbound TCP connection
20
+ const id = await client.connect('api.example.com', 443);
21
+
22
+ client.onData(id, (data) => {
23
+ console.log('received:', new TextDecoder().decode(data));
24
+ });
25
+
26
+ client.onClose(id, () => {
27
+ console.log('connection closed');
28
+ });
29
+
30
+ client.send(id, 'GET / HTTP/1.1\r\nHost: api.example.com\r\n\r\n');
31
+
32
+ // Inbound TCP (bind a port)
33
+ const listener = await client.bind('0.0.0.0', 3000);
34
+ console.log(`listening on port ${listener.port}`);
35
+
36
+ client.onAccept(listener.id, (connId, remote) => {
37
+ console.log(`accepted connection from ${remote}`);
38
+ client.onData(connId, (data) => {
39
+ client.send(connId, data); // echo
40
+ });
41
+ });
42
+
43
+ // Cleanup
44
+ client.close(id);
45
+ client.disconnect();
46
+ ```
47
+
48
+ ## API
49
+
50
+ ### `new WasmnetClient(url: string)`
51
+
52
+ Creates a client connecting to the wasmnet server at the given WebSocket URL.
53
+
54
+ ### `ready(): Promise<void>`
55
+
56
+ Resolves when the WebSocket connection is established.
57
+
58
+ ### `connect(addr: string, port: number): Promise<number>`
59
+
60
+ Opens an outbound TCP connection. Returns the socket ID.
61
+
62
+ ### `bind(addr: string, port: number): Promise<{ id: number, port: number }>`
63
+
64
+ Binds a TCP listener. Returns the listener ID and actual bound port.
65
+
66
+ ### `listen(id: number, backlog?: number): void`
67
+
68
+ Starts accepting connections on a bound listener.
69
+
70
+ ### `send(id: number, data: string | Uint8Array | ArrayBuffer): void`
71
+
72
+ Sends data on a socket. Strings are UTF-8 encoded, binary data is sent as-is.
73
+
74
+ ### `close(id: number): void`
75
+
76
+ Closes a socket or listener.
77
+
78
+ ### `onData(id: number, callback: (data: Uint8Array) => void): void`
79
+
80
+ Registers a data handler. Any buffered data is flushed immediately.
81
+
82
+ ### `onClose(id: number, callback: () => void): void`
83
+
84
+ Registers a close handler.
85
+
86
+ ### `onAccept(id: number, callback: (connId: number, remote: string) => void): void`
87
+
88
+ Registers an accept handler for a listener.
89
+
90
+ ### `disconnect(): void`
91
+
92
+ Closes the WebSocket connection and all sockets.
93
+
94
+ ## MIT License
@@ -0,0 +1,23 @@
1
+ export interface ListenResult {
2
+ id: number;
3
+ port: number;
4
+ }
5
+
6
+ export type DataCallback = (data: Uint8Array) => void;
7
+ export type CloseCallback = () => void;
8
+ export type AcceptCallback = (connId: number, remote: string) => void;
9
+
10
+ export class WasmnetClient {
11
+ constructor(url: string);
12
+
13
+ ready(): Promise<void>;
14
+ connect(addr: string, port: number): Promise<number>;
15
+ bind(addr: string, port: number): Promise<ListenResult>;
16
+ listen(id: number, backlog?: number): void;
17
+ send(id: number, data: string | Uint8Array | ArrayBuffer): void;
18
+ close(id: number): void;
19
+ onData(id: number, callback: DataCallback): void;
20
+ onClose(id: number, callback: CloseCallback): void;
21
+ onAccept(id: number, callback: AcceptCallback): void;
22
+ disconnect(): void;
23
+ }
@@ -0,0 +1,139 @@
1
+ export class WasmnetClient {
2
+ constructor(url) {
3
+ this.ws = new WebSocket(url);
4
+ this.sockets = new Map();
5
+ this.nextId = 1;
6
+ this._ready = new Promise((resolve, reject) => {
7
+ this.ws.onopen = () => resolve();
8
+ this.ws.onerror = (e) => reject(e);
9
+ });
10
+ this.ws.onmessage = (e) => this._handleEvent(JSON.parse(e.data));
11
+ this.ws.onclose = () => this._handleClose();
12
+ }
13
+
14
+ async ready() {
15
+ return this._ready;
16
+ }
17
+
18
+ connect(addr, port) {
19
+ const id = this.nextId++;
20
+ return new Promise((resolve, reject) => {
21
+ this.sockets.set(id, { resolve, reject, buffer: [], onData: null, onClose: null });
22
+ this._send({ op: "connect", id, addr, port });
23
+ });
24
+ }
25
+
26
+ bind(addr, port) {
27
+ const id = this.nextId++;
28
+ return new Promise((resolve, reject) => {
29
+ this.sockets.set(id, { resolve, reject, onAccept: null });
30
+ this._send({ op: "bind", id, addr, port });
31
+ });
32
+ }
33
+
34
+ listen(id, backlog = 128) {
35
+ this._send({ op: "listen", id, backlog });
36
+ }
37
+
38
+ send(id, data) {
39
+ const encoded = typeof data === "string" ? btoa(data) : this._encodeBinary(data);
40
+ this._send({ op: "send", id, data: encoded });
41
+ }
42
+
43
+ close(id) {
44
+ this._send({ op: "close", id });
45
+ this.sockets.delete(id);
46
+ }
47
+
48
+ onData(id, callback) {
49
+ const sock = this.sockets.get(id);
50
+ if (!sock) return;
51
+ sock.onData = callback;
52
+ for (const buffered of sock.buffer) {
53
+ callback(buffered);
54
+ }
55
+ sock.buffer = [];
56
+ }
57
+
58
+ onClose(id, callback) {
59
+ const sock = this.sockets.get(id);
60
+ if (sock) sock.onClose = callback;
61
+ }
62
+
63
+ onAccept(id, callback) {
64
+ const sock = this.sockets.get(id);
65
+ if (sock) sock.onAccept = callback;
66
+ }
67
+
68
+ disconnect() {
69
+ this.ws.close();
70
+ }
71
+
72
+ _send(msg) {
73
+ this.ws.send(JSON.stringify(msg));
74
+ }
75
+
76
+ _encodeBinary(data) {
77
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
78
+ let binary = "";
79
+ for (let i = 0; i < bytes.length; i++) {
80
+ binary += String.fromCharCode(bytes[i]);
81
+ }
82
+ return btoa(binary);
83
+ }
84
+
85
+ _decodeBinary(b64) {
86
+ const binary = atob(b64);
87
+ const bytes = new Uint8Array(binary.length);
88
+ for (let i = 0; i < binary.length; i++) {
89
+ bytes[i] = binary.charCodeAt(i);
90
+ }
91
+ return bytes;
92
+ }
93
+
94
+ _handleEvent(ev) {
95
+ const sock = this.sockets.get(ev.id);
96
+ if (!sock) return;
97
+
98
+ switch (ev.ev) {
99
+ case "connected":
100
+ sock.resolve(ev.id);
101
+ break;
102
+ case "listening":
103
+ sock.resolve({ id: ev.id, port: ev.port });
104
+ break;
105
+ case "data": {
106
+ const decoded = this._decodeBinary(ev.data);
107
+ if (sock.onData) {
108
+ sock.onData(decoded);
109
+ } else {
110
+ sock.buffer.push(decoded);
111
+ }
112
+ break;
113
+ }
114
+ case "accepted":
115
+ this.sockets.set(ev.conn_id, { buffer: [], onData: null, onClose: null });
116
+ sock.onAccept?.(ev.conn_id, ev.remote);
117
+ break;
118
+ case "closed":
119
+ sock.onClose?.();
120
+ this.sockets.delete(ev.id);
121
+ break;
122
+ case "error":
123
+ sock.reject?.(new Error(ev.msg));
124
+ this.sockets.delete(ev.id);
125
+ break;
126
+ case "denied":
127
+ sock.reject?.(new Error(ev.msg));
128
+ this.sockets.delete(ev.id);
129
+ break;
130
+ }
131
+ }
132
+
133
+ _handleClose() {
134
+ for (const [id, sock] of this.sockets) {
135
+ sock.onClose?.();
136
+ }
137
+ this.sockets.clear();
138
+ }
139
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "wasmnet",
3
+ "version": "0.1.0",
4
+ "description": "Browser client for wasmnet — networking proxy for WASM",
5
+ "type": "module",
6
+ "main": "dist/wasmnet-client.js",
7
+ "module": "dist/wasmnet-client.js",
8
+ "types": "dist/wasmnet-client.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/wasmnet-client.js",
12
+ "types": "./dist/wasmnet-client.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "mkdir -p dist && cp wasmnet-client.js dist/ && cp wasmnet-client.d.ts dist/",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "wasm",
24
+ "wasi",
25
+ "websocket",
26
+ "networking",
27
+ "proxy",
28
+ "tcp"
29
+ ],
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/anistark/wasmnet",
34
+ "directory": "client"
35
+ },
36
+ "homepage": "https://github.com/anistark/wasmnet#readme"
37
+ }