@vibecook/truffle 0.4.8 → 0.5.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/dist/create-mesh-node.d.ts +49 -4
- package/dist/create-mesh-node.d.ts.map +1 -1
- package/dist/create-mesh-node.js +22 -4
- package/dist/create-mesh-node.js.map +1 -1
- package/dist/dgram.d.ts +106 -0
- package/dist/dgram.d.ts.map +1 -0
- package/dist/dgram.js +248 -0
- package/dist/dgram.js.map +1 -0
- package/dist/http.d.ts +57 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +116 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/net.d.ts +111 -0
- package/dist/net.d.ts.map +1 -0
- package/dist/net.js +271 -0
- package/dist/net.js.map +1 -0
- package/dist/quic.d.ts +88 -0
- package/dist/quic.d.ts.map +1 -0
- package/dist/quic.js +150 -0
- package/dist/quic.js.map +1 -0
- package/dist/ws.d.ts +72 -0
- package/dist/ws.d.ts.map +1 -0
- package/dist/ws.js +152 -0
- package/dist/ws.js.map +1 -0
- package/package.json +19 -8
- package/scripts/postinstall.cjs +29 -8
- package/sidecar-checksums.json +20 -8
|
@@ -1,4 +1,44 @@
|
|
|
1
1
|
import { NapiNode, type NapiPeerEvent } from '@vibecook/truffle-native';
|
|
2
|
+
import { type TruffleNet } from './net.js';
|
|
3
|
+
import { type TruffleHttp } from './http.js';
|
|
4
|
+
import { type TruffleQuic } from './quic.js';
|
|
5
|
+
import { type TruffleDgram } from './dgram.js';
|
|
6
|
+
import { type TruffleWs } from './ws.js';
|
|
7
|
+
/**
|
|
8
|
+
* A started mesh node: the full native `NapiNode` API plus protocol
|
|
9
|
+
* namespaces (RFC 021). Namespaces are attached to the same underlying
|
|
10
|
+
* instance, so every `NapiNode` method is available directly and new
|
|
11
|
+
* native methods surface automatically.
|
|
12
|
+
*/
|
|
13
|
+
export type MeshNode = NapiNode & {
|
|
14
|
+
/** node:net-shaped raw TCP API over the mesh (RFC 021). */
|
|
15
|
+
net: TruffleNet;
|
|
16
|
+
/**
|
|
17
|
+
* node:http interop over the mesh (RFC 021): a MeshAgent for outbound
|
|
18
|
+
* requests to peers plus `request`/`get`/`fetchText` sugar. Inbound HTTP
|
|
19
|
+
* is served by feeding `mesh.net` connections into an `http.Server` via
|
|
20
|
+
* `httpServer.emit('connection', socket)`.
|
|
21
|
+
*/
|
|
22
|
+
http: TruffleHttp;
|
|
23
|
+
/**
|
|
24
|
+
* QUIC over the mesh (RFC 021): multiplexed bidirectional byte streams
|
|
25
|
+
* with no head-of-line blocking, async-iterable connections/streams.
|
|
26
|
+
*/
|
|
27
|
+
quic: TruffleQuic;
|
|
28
|
+
/**
|
|
29
|
+
* node:dgram-shaped raw UDP API over the mesh (RFC 021): datagram sockets
|
|
30
|
+
* with `'message'` events and preserved datagram boundaries.
|
|
31
|
+
*/
|
|
32
|
+
dgram: TruffleDgram;
|
|
33
|
+
/**
|
|
34
|
+
* WebSocket over the mesh (RFC 021): thin wrappers over the `ws` package —
|
|
35
|
+
* `ws.connect(peer, port)` for a client, `ws.createServer({ port })` for a
|
|
36
|
+
* server. Requires the optional `ws` peer dependency to be installed.
|
|
37
|
+
*/
|
|
38
|
+
ws: TruffleWs;
|
|
39
|
+
/** The underlying native handle (escape hatch; the same object). */
|
|
40
|
+
native: NapiNode;
|
|
41
|
+
};
|
|
2
42
|
/**
|
|
3
43
|
* Options for {@link createMeshNode}. RFC 017 shape — `appId` is required,
|
|
4
44
|
* `deviceName` / `deviceId` are optional overrides that the underlying
|
|
@@ -62,15 +102,20 @@ export interface CreateMeshNodeOptions {
|
|
|
62
102
|
*
|
|
63
103
|
* @example
|
|
64
104
|
* ```ts
|
|
65
|
-
* const
|
|
105
|
+
* const mesh = await createMeshNode({
|
|
66
106
|
* appId: 'playground',
|
|
67
107
|
* deviceName: 'alice-mbp',
|
|
68
108
|
* onPeerChange: (event) => console.log('Peer event:', event),
|
|
69
109
|
* });
|
|
70
110
|
*
|
|
71
|
-
* const peers = await
|
|
72
|
-
* await
|
|
111
|
+
* const peers = await mesh.getPeers();
|
|
112
|
+
* await mesh.send(peers[0].deviceId, 'chat', Buffer.from(JSON.stringify({ text: 'hello' })));
|
|
113
|
+
*
|
|
114
|
+
* // Raw TCP over the mesh, node:net-style (RFC 021):
|
|
115
|
+
* const server = mesh.net.createServer((socket) => socket.pipe(socket));
|
|
116
|
+
* server.listen(8080);
|
|
117
|
+
* const client = mesh.net.connect({ host: 'other-machine', port: 8080 });
|
|
73
118
|
* ```
|
|
74
119
|
*/
|
|
75
|
-
export declare function createMeshNode(options: CreateMeshNodeOptions): Promise<
|
|
120
|
+
export declare function createMeshNode(options: CreateMeshNodeOptions): Promise<MeshNode>;
|
|
76
121
|
//# sourceMappingURL=create-mesh-node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-mesh-node.d.ts","sourceRoot":"","sources":["../src/create-mesh-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"create-mesh-node.d.ts","sourceRoot":"","sources":["../src/create-mesh-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAG5D;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG;IAChC,2DAA2D;IAC3D,GAAG,EAAE,UAAU,CAAC;IAChB;;;;;OAKG;IACH,IAAI,EAAE,WAAW,CAAC;IAClB;;;OAGG;IACH,IAAI,EAAE,WAAW,CAAC;IAClB;;;OAGG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB;;;;OAIG;IACH,EAAE,EAAE,SAAS,CAAC;IACd,oEAAoE;IACpE,MAAM,EAAE,QAAQ,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,qEAAqE;IACrE,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AAsBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoFtF"}
|
package/dist/create-mesh-node.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { NapiNode } from '@vibecook/truffle-native';
|
|
3
|
+
import { createNetNamespace } from './net.js';
|
|
4
|
+
import { createHttpNamespace } from './http.js';
|
|
5
|
+
import { createQuicNamespace } from './quic.js';
|
|
6
|
+
import { createDgramNamespace } from './dgram.js';
|
|
7
|
+
import { createWsNamespace } from './ws.js';
|
|
3
8
|
import { resolveSidecarPath } from './sidecar.js';
|
|
4
9
|
/**
|
|
5
10
|
* Open a URL in the system's default browser.
|
|
@@ -29,14 +34,19 @@ function defaultOpenUrl(url) {
|
|
|
29
34
|
*
|
|
30
35
|
* @example
|
|
31
36
|
* ```ts
|
|
32
|
-
* const
|
|
37
|
+
* const mesh = await createMeshNode({
|
|
33
38
|
* appId: 'playground',
|
|
34
39
|
* deviceName: 'alice-mbp',
|
|
35
40
|
* onPeerChange: (event) => console.log('Peer event:', event),
|
|
36
41
|
* });
|
|
37
42
|
*
|
|
38
|
-
* const peers = await
|
|
39
|
-
* await
|
|
43
|
+
* const peers = await mesh.getPeers();
|
|
44
|
+
* await mesh.send(peers[0].deviceId, 'chat', Buffer.from(JSON.stringify({ text: 'hello' })));
|
|
45
|
+
*
|
|
46
|
+
* // Raw TCP over the mesh, node:net-style (RFC 021):
|
|
47
|
+
* const server = mesh.net.createServer((socket) => socket.pipe(socket));
|
|
48
|
+
* server.listen(8080);
|
|
49
|
+
* const client = mesh.net.connect({ host: 'other-machine', port: 8080 });
|
|
40
50
|
* ```
|
|
41
51
|
*/
|
|
42
52
|
export async function createMeshNode(options) {
|
|
@@ -92,6 +102,14 @@ export async function createMeshNode(options) {
|
|
|
92
102
|
if (onPeerChange) {
|
|
93
103
|
node.onPeerChange(onPeerChange);
|
|
94
104
|
}
|
|
95
|
-
|
|
105
|
+
// Attach the protocol namespaces (RFC 021) to the native instance.
|
|
106
|
+
const mesh = node;
|
|
107
|
+
mesh.net = createNetNamespace(node);
|
|
108
|
+
mesh.http = createHttpNamespace(mesh.net);
|
|
109
|
+
mesh.quic = createQuicNamespace(node);
|
|
110
|
+
mesh.dgram = createDgramNamespace(node);
|
|
111
|
+
mesh.ws = createWsNamespace(mesh.net);
|
|
112
|
+
mesh.native = node;
|
|
113
|
+
return mesh;
|
|
96
114
|
}
|
|
97
115
|
//# sourceMappingURL=create-mesh-node.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-mesh-node.js","sourceRoot":"","sources":["../src/create-mesh-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAA2C,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"create-mesh-node.js","sourceRoot":"","sources":["../src/create-mesh-node.ts"],"names":[],"mappings":"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"}
|
package/dist/dgram.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { NapiNode } from '@vibecook/truffle-native';
|
|
3
|
+
/**
|
|
4
|
+
* Sender metadata delivered with each `'message'` event, mirroring
|
|
5
|
+
* node:dgram's `RemoteInfo` and extending it with mesh peer identity.
|
|
6
|
+
*
|
|
7
|
+
* `address`/`port` are the datagram's WireGuard-authenticated tailnet
|
|
8
|
+
* source (`100.x` IPv4). `peerId`/`peerName` are best-effort enrichments
|
|
9
|
+
* resolved from the local netmap — they may be `undefined` for a sender
|
|
10
|
+
* that isn't a known peer yet, and message delivery never blocks on them.
|
|
11
|
+
*/
|
|
12
|
+
export interface TruffleRemoteInfo {
|
|
13
|
+
/** Sender's tailnet IP (`100.x.x.x`). */
|
|
14
|
+
address: string;
|
|
15
|
+
/** Sender's source port. */
|
|
16
|
+
port: number;
|
|
17
|
+
/** Always `'IPv4'`: datagram addressing is IPv4-only in v1 (RFC 021 §3). */
|
|
18
|
+
family: 'IPv4';
|
|
19
|
+
/** Datagram payload length in bytes. */
|
|
20
|
+
size: number;
|
|
21
|
+
/** Sender's stable device id, when the source IP maps to a known peer. */
|
|
22
|
+
peerId?: string;
|
|
23
|
+
/** Sender's human-readable device name, when known. */
|
|
24
|
+
peerName?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A UDP socket over the mesh, mimicking `dgram.Socket`.
|
|
28
|
+
*
|
|
29
|
+
* Events:
|
|
30
|
+
* - `'listening'` — after {@link bind} resolves; `this.port` is set.
|
|
31
|
+
* - `'message'` — `(msg: Buffer, rinfo: TruffleRemoteInfo)` per datagram.
|
|
32
|
+
* - `'error'` — a terminal receive error, or a {@link send} failure with no
|
|
33
|
+
* callback (node:dgram contract).
|
|
34
|
+
* - `'close'` — the socket has shut down (via {@link close} or a closed
|
|
35
|
+
* relay); fires exactly once.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* const sock = mesh.dgram.createSocket();
|
|
39
|
+
* sock.on('message', (msg, rinfo) => {
|
|
40
|
+
* console.log(`${rinfo.peerName ?? rinfo.address}: ${msg}`);
|
|
41
|
+
* });
|
|
42
|
+
* await sock.bind(8081);
|
|
43
|
+
* sock.send(Buffer.from('ping'), 8081, 'other-machine');
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare class TruffleDgramSocket extends EventEmitter {
|
|
47
|
+
#private;
|
|
48
|
+
/**
|
|
49
|
+
* The mesh port this socket is bound to; `0` until {@link bind} resolves.
|
|
50
|
+
* Note it reflects the *requested* tsnet port — an ephemeral client
|
|
51
|
+
* socket bound with port `0` keeps `0` here (its real relay port isn't
|
|
52
|
+
* surfaced), so read `port` only after `'listening'` and only when you
|
|
53
|
+
* bound an explicit port.
|
|
54
|
+
*/
|
|
55
|
+
port: number;
|
|
56
|
+
constructor(node: NapiNode);
|
|
57
|
+
/**
|
|
58
|
+
* Bind the socket to `port` (default `0` = ephemeral relay port) and start
|
|
59
|
+
* receiving. Resolves with the socket once bound; `'listening'` fires at
|
|
60
|
+
* the same point, and `callback` (if given) is invoked as a one-shot
|
|
61
|
+
* `'listening'` listener — so both `await sock.bind(port)` and
|
|
62
|
+
* `sock.bind(port, () => …)` work.
|
|
63
|
+
*
|
|
64
|
+
* Deviates from node:dgram, which returns the socket synchronously and
|
|
65
|
+
* signals readiness only via the event; here bind is genuinely async
|
|
66
|
+
* (it round-trips to the sidecar), so the promise is the primary channel.
|
|
67
|
+
* Failure semantics follow the calling style: promise style (`await
|
|
68
|
+
* sock.bind(p)`) **rejects**; callback style (`sock.bind(p, cb)`,
|
|
69
|
+
* typically fire-and-forget) emits **`'error'`** like node:dgram and
|
|
70
|
+
* never leaves an unhandled rejection.
|
|
71
|
+
*/
|
|
72
|
+
bind(port?: number, callback?: () => void): Promise<this>;
|
|
73
|
+
/**
|
|
74
|
+
* Send a datagram to `address:port`. `address` accepts any peer reference
|
|
75
|
+
* (device id or ≥4-char prefix, device name, Tailscale hostname, or `100.x`
|
|
76
|
+
* IP), matching node:dgram's `(msg, port, address, callback?)` arg order.
|
|
77
|
+
*
|
|
78
|
+
* Must be called after {@link bind}: unlike node:dgram this does **not**
|
|
79
|
+
* auto-bind (that would silently pick an ephemeral port), so sending before
|
|
80
|
+
* bind throws synchronously. Async send failures go to `callback` if given,
|
|
81
|
+
* otherwise the `'error'` event (node:dgram contract).
|
|
82
|
+
*/
|
|
83
|
+
send(msg: Buffer | string | Uint8Array, port: number, address: string, callback?: (error: Error | null) => void): void;
|
|
84
|
+
/**
|
|
85
|
+
* Close the socket. In-flight `recv()` resolves `null`, ending the receive
|
|
86
|
+
* pump and emitting `'close'` once. Idempotent; `callback` (if given) runs
|
|
87
|
+
* on `'close'`, even if the socket has already closed.
|
|
88
|
+
*/
|
|
89
|
+
close(callback?: () => void): void;
|
|
90
|
+
/**
|
|
91
|
+
* node:dgram `address()` compat. Mesh sockets have no local IP, so
|
|
92
|
+
* `address` is empty; `port` is the bound port (see the {@link port} note).
|
|
93
|
+
*/
|
|
94
|
+
address(): {
|
|
95
|
+
address: string;
|
|
96
|
+
family: 'IPv4';
|
|
97
|
+
port: number;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** node:dgram-shaped namespace bound to a mesh node. */
|
|
101
|
+
export interface TruffleDgram {
|
|
102
|
+
/** Create an unbound UDP socket, like `dgram.createSocket('udp4')`. */
|
|
103
|
+
createSocket(): TruffleDgramSocket;
|
|
104
|
+
}
|
|
105
|
+
export declare function createDgramNamespace(node: NapiNode): TruffleDgram;
|
|
106
|
+
//# sourceMappingURL=dgram.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dgram.d.ts","sourceRoot":"","sources":["../src/dgram.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAgB,QAAQ,EAAiB,MAAM,0BAA0B,CAAC;AAUtF;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;;IAYlD;;;;;;OAMG;IACH,IAAI,SAAK;gBAEG,IAAI,EAAE,QAAQ;IAK1B;;;;;;;;;;;;;;OAcG;IACH,IAAI,CAAC,IAAI,SAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCpD;;;;;;;;;OASG;IACH,IAAI,CACF,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,EACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GACvC,IAAI;IAaP;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI;IAkBlC;;;OAGG;IACH,OAAO,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAwE7D;AAED,wDAAwD;AACxD,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,YAAY,IAAI,kBAAkB,CAAC;CACpC;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAIjE"}
|
package/dist/dgram.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// node:dgram-shaped API over truffle's raw UDP transport (RFC 021 Phase 3).
|
|
2
|
+
//
|
|
3
|
+
// TruffleDgramSocket mirrors `dgram.Socket` closely enough that existing
|
|
4
|
+
// node:dgram code ports by swapping the module: `createSocket()`, `bind()`,
|
|
5
|
+
// `send()`, `close()`, and the `'listening'` / `'message'` / `'error'` /
|
|
6
|
+
// `'close'` events all behave as a Node developer expects.
|
|
7
|
+
//
|
|
8
|
+
// Two deliberate deviations from node:dgram, both documented at their call
|
|
9
|
+
// sites: (1) `bind()` returns a Promise<this> (node:dgram returns the socket
|
|
10
|
+
// synchronously and signals readiness only via `'listening'`); the event
|
|
11
|
+
// still fires, so callback-style code keeps working. (2) `send()` before
|
|
12
|
+
// `bind()` throws instead of auto-binding — auto-bind would silently pick an
|
|
13
|
+
// ephemeral relay port, and explicitness beats a surprise here.
|
|
14
|
+
//
|
|
15
|
+
// Datagram semantics are preserved by the receive pump: it awaits one
|
|
16
|
+
// `recv()` at a time, so a slow `'message'` handler backpressures the read
|
|
17
|
+
// loop and the relay/OS buffer drops the overflow — correct UDP behaviour
|
|
18
|
+
// with bounded memory, never an unbounded in-process queue.
|
|
19
|
+
import { EventEmitter } from 'node:events';
|
|
20
|
+
/**
|
|
21
|
+
* Minimum spacing between `getPeers()` calls made to enrich datagram
|
|
22
|
+
* `rinfo` with peer identity. A cache miss from an unknown sender triggers
|
|
23
|
+
* at most one refresh per window, so a burst of datagrams from unmapped IPs
|
|
24
|
+
* can't turn into a `getPeers()` storm.
|
|
25
|
+
*/
|
|
26
|
+
const PEER_CACHE_TTL_MS = 5_000;
|
|
27
|
+
/**
|
|
28
|
+
* A UDP socket over the mesh, mimicking `dgram.Socket`.
|
|
29
|
+
*
|
|
30
|
+
* Events:
|
|
31
|
+
* - `'listening'` — after {@link bind} resolves; `this.port` is set.
|
|
32
|
+
* - `'message'` — `(msg: Buffer, rinfo: TruffleRemoteInfo)` per datagram.
|
|
33
|
+
* - `'error'` — a terminal receive error, or a {@link send} failure with no
|
|
34
|
+
* callback (node:dgram contract).
|
|
35
|
+
* - `'close'` — the socket has shut down (via {@link close} or a closed
|
|
36
|
+
* relay); fires exactly once.
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* const sock = mesh.dgram.createSocket();
|
|
40
|
+
* sock.on('message', (msg, rinfo) => {
|
|
41
|
+
* console.log(`${rinfo.peerName ?? rinfo.address}: ${msg}`);
|
|
42
|
+
* });
|
|
43
|
+
* await sock.bind(8081);
|
|
44
|
+
* sock.send(Buffer.from('ping'), 8081, 'other-machine');
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export class TruffleDgramSocket extends EventEmitter {
|
|
48
|
+
#node;
|
|
49
|
+
#native = null;
|
|
50
|
+
#binding = false;
|
|
51
|
+
#closed = false;
|
|
52
|
+
#closeEmitted = false;
|
|
53
|
+
// Lazy ip→peer map for rinfo enrichment, rebuilt wholesale on refresh so
|
|
54
|
+
// departed peers don't linger. Refreshes are rate-limited (see below).
|
|
55
|
+
#peerCache = new Map();
|
|
56
|
+
#peerCacheRefreshedAt = 0;
|
|
57
|
+
/**
|
|
58
|
+
* The mesh port this socket is bound to; `0` until {@link bind} resolves.
|
|
59
|
+
* Note it reflects the *requested* tsnet port — an ephemeral client
|
|
60
|
+
* socket bound with port `0` keeps `0` here (its real relay port isn't
|
|
61
|
+
* surfaced), so read `port` only after `'listening'` and only when you
|
|
62
|
+
* bound an explicit port.
|
|
63
|
+
*/
|
|
64
|
+
port = 0;
|
|
65
|
+
constructor(node) {
|
|
66
|
+
super();
|
|
67
|
+
this.#node = node;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Bind the socket to `port` (default `0` = ephemeral relay port) and start
|
|
71
|
+
* receiving. Resolves with the socket once bound; `'listening'` fires at
|
|
72
|
+
* the same point, and `callback` (if given) is invoked as a one-shot
|
|
73
|
+
* `'listening'` listener — so both `await sock.bind(port)` and
|
|
74
|
+
* `sock.bind(port, () => …)` work.
|
|
75
|
+
*
|
|
76
|
+
* Deviates from node:dgram, which returns the socket synchronously and
|
|
77
|
+
* signals readiness only via the event; here bind is genuinely async
|
|
78
|
+
* (it round-trips to the sidecar), so the promise is the primary channel.
|
|
79
|
+
* Failure semantics follow the calling style: promise style (`await
|
|
80
|
+
* sock.bind(p)`) **rejects**; callback style (`sock.bind(p, cb)`,
|
|
81
|
+
* typically fire-and-forget) emits **`'error'`** like node:dgram and
|
|
82
|
+
* never leaves an unhandled rejection.
|
|
83
|
+
*/
|
|
84
|
+
bind(port = 0, callback) {
|
|
85
|
+
const attempt = this.#bind(port);
|
|
86
|
+
if (!callback)
|
|
87
|
+
return attempt;
|
|
88
|
+
this.once('listening', callback);
|
|
89
|
+
return attempt.catch((err) => {
|
|
90
|
+
this.removeListener('listening', callback);
|
|
91
|
+
this.emit('error', err);
|
|
92
|
+
return this;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async #bind(port) {
|
|
96
|
+
if (this.#closed)
|
|
97
|
+
throw new Error('dgram: socket is closed');
|
|
98
|
+
if (this.#native || this.#binding)
|
|
99
|
+
throw new Error('dgram: bind() already called');
|
|
100
|
+
this.#binding = true;
|
|
101
|
+
let native;
|
|
102
|
+
try {
|
|
103
|
+
native = await this.#node.bindUdp(port);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
this.#binding = false;
|
|
107
|
+
}
|
|
108
|
+
// close() may have raced in while bindUdp was in flight; honour it and
|
|
109
|
+
// don't start the pump or announce 'listening'.
|
|
110
|
+
if (this.#closed) {
|
|
111
|
+
native.close();
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
this.#native = native;
|
|
115
|
+
this.port = native.port();
|
|
116
|
+
this.emit('listening');
|
|
117
|
+
void this.#recvPump(native);
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Send a datagram to `address:port`. `address` accepts any peer reference
|
|
122
|
+
* (device id or ≥4-char prefix, device name, Tailscale hostname, or `100.x`
|
|
123
|
+
* IP), matching node:dgram's `(msg, port, address, callback?)` arg order.
|
|
124
|
+
*
|
|
125
|
+
* Must be called after {@link bind}: unlike node:dgram this does **not**
|
|
126
|
+
* auto-bind (that would silently pick an ephemeral port), so sending before
|
|
127
|
+
* bind throws synchronously. Async send failures go to `callback` if given,
|
|
128
|
+
* otherwise the `'error'` event (node:dgram contract).
|
|
129
|
+
*/
|
|
130
|
+
send(msg, port, address, callback) {
|
|
131
|
+
const native = this.#native;
|
|
132
|
+
if (!native) {
|
|
133
|
+
throw new Error('dgram: call bind() before send() (auto-bind is not supported)');
|
|
134
|
+
}
|
|
135
|
+
const data = typeof msg === 'string' ? Buffer.from(msg) : Buffer.isBuffer(msg) ? msg : Buffer.from(msg);
|
|
136
|
+
native.send(data, address, port).then(() => callback?.(null), (err) => this.#failSend(err, callback));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Close the socket. In-flight `recv()` resolves `null`, ending the receive
|
|
140
|
+
* pump and emitting `'close'` once. Idempotent; `callback` (if given) runs
|
|
141
|
+
* on `'close'`, even if the socket has already closed.
|
|
142
|
+
*/
|
|
143
|
+
close(callback) {
|
|
144
|
+
if (callback) {
|
|
145
|
+
if (this.#closeEmitted)
|
|
146
|
+
queueMicrotask(callback);
|
|
147
|
+
else
|
|
148
|
+
this.once('close', callback);
|
|
149
|
+
}
|
|
150
|
+
if (this.#closed)
|
|
151
|
+
return;
|
|
152
|
+
this.#closed = true;
|
|
153
|
+
const native = this.#native;
|
|
154
|
+
if (native) {
|
|
155
|
+
// recv() resolves null → the pump exits and emits 'close'.
|
|
156
|
+
native.close();
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Never bound (or bind still in flight): no pump to unwind.
|
|
160
|
+
this.#emitClose();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* node:dgram `address()` compat. Mesh sockets have no local IP, so
|
|
165
|
+
* `address` is empty; `port` is the bound port (see the {@link port} note).
|
|
166
|
+
*/
|
|
167
|
+
address() {
|
|
168
|
+
return { address: '', family: 'IPv4', port: this.port };
|
|
169
|
+
}
|
|
170
|
+
// ─── internals ─────────────────────────────────────────────────────────
|
|
171
|
+
async #recvPump(native) {
|
|
172
|
+
try {
|
|
173
|
+
for (;;) {
|
|
174
|
+
const datagram = await native.recv();
|
|
175
|
+
if (datagram === null)
|
|
176
|
+
break; // socket closed
|
|
177
|
+
// Enrichment may await one getPeers() on a cold-cache miss (rate-
|
|
178
|
+
// limited); it's the only place the pump awaits beyond recv itself.
|
|
179
|
+
const rinfo = await this.#rinfoFor(datagram);
|
|
180
|
+
this.emit('message', datagram.data, rinfo);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
// A recv error on this socket is terminal — surface it and stop (unless
|
|
185
|
+
// we're already tearing down, where the null-on-close path handles it).
|
|
186
|
+
if (!this.#closed)
|
|
187
|
+
this.emit('error', err);
|
|
188
|
+
}
|
|
189
|
+
this.#emitClose();
|
|
190
|
+
}
|
|
191
|
+
async #rinfoFor(datagram) {
|
|
192
|
+
let peer = this.#peerCache.get(datagram.address);
|
|
193
|
+
if (!peer) {
|
|
194
|
+
await this.#maybeRefreshPeerCache();
|
|
195
|
+
peer = this.#peerCache.get(datagram.address);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
address: datagram.address,
|
|
199
|
+
port: datagram.port,
|
|
200
|
+
family: 'IPv4',
|
|
201
|
+
size: datagram.data.length,
|
|
202
|
+
peerId: peer?.peerId,
|
|
203
|
+
peerName: peer?.peerName,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Rebuild the ip→peer cache from `getPeers()`, at most once per
|
|
208
|
+
* {@link PEER_CACHE_TTL_MS}. Best-effort: a failed lookup leaves the prior
|
|
209
|
+
* cache in place and never propagates, so enrichment never blocks or breaks
|
|
210
|
+
* message delivery. The pump is sequential, so no in-flight guard is needed.
|
|
211
|
+
*/
|
|
212
|
+
async #maybeRefreshPeerCache() {
|
|
213
|
+
const now = Date.now();
|
|
214
|
+
if (now - this.#peerCacheRefreshedAt < PEER_CACHE_TTL_MS)
|
|
215
|
+
return;
|
|
216
|
+
this.#peerCacheRefreshedAt = now; // rate-limit even if the call fails
|
|
217
|
+
try {
|
|
218
|
+
const peers = await this.#node.getPeers();
|
|
219
|
+
const next = new Map();
|
|
220
|
+
for (const peer of peers) {
|
|
221
|
+
if (peer.ip)
|
|
222
|
+
next.set(peer.ip, { peerId: peer.deviceId, peerName: peer.deviceName });
|
|
223
|
+
}
|
|
224
|
+
this.#peerCache = next;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Enrichment is best-effort; keep the prior cache.
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
#failSend(err, callback) {
|
|
231
|
+
if (callback)
|
|
232
|
+
callback(err);
|
|
233
|
+
else
|
|
234
|
+
this.emit('error', err);
|
|
235
|
+
}
|
|
236
|
+
#emitClose() {
|
|
237
|
+
if (this.#closeEmitted)
|
|
238
|
+
return;
|
|
239
|
+
this.#closeEmitted = true;
|
|
240
|
+
this.emit('close');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
export function createDgramNamespace(node) {
|
|
244
|
+
return {
|
|
245
|
+
createSocket: () => new TruffleDgramSocket(node),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=dgram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dgram.js","sourceRoot":"","sources":["../src/dgram.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import type { Duplex } from 'node:stream';
|
|
3
|
+
import type { TruffleNet } from './net.js';
|
|
4
|
+
/**
|
|
5
|
+
* An `http.Agent` that dials its connections over the mesh instead of the
|
|
6
|
+
* host network. Pass it as `{ agent }` to any `node:http` request and the
|
|
7
|
+
* request travels to the peer named by the URL hostname (or `options.host`).
|
|
8
|
+
*
|
|
9
|
+
* Connection reuse behaves exactly like the stock agent. The default
|
|
10
|
+
* `keepAlive: false` opens a fresh mesh connection per request and closes it
|
|
11
|
+
* on response end; `new MeshAgent(net, { keepAlive: true })` pools and reuses
|
|
12
|
+
* idle sockets like any http.Agent (but see the idle-reaping caveat on
|
|
13
|
+
* `TruffleSocket.setTimeout` — quiet pooled sockets aren't reaped on a timer).
|
|
14
|
+
*/
|
|
15
|
+
export declare class MeshAgent extends http.Agent {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(net: TruffleNet, opts?: http.AgentOptions);
|
|
18
|
+
/**
|
|
19
|
+
* Called by node:http to obtain a socket for a request. Returns a mesh
|
|
20
|
+
* TruffleSocket (a `stream.Duplex`) synchronously — it connects in the
|
|
21
|
+
* background and emits `'connect'` later, which is exactly the net.Socket
|
|
22
|
+
* contract http expects. The callback form is also honoured for robustness
|
|
23
|
+
* (node uses whichever settles first, once).
|
|
24
|
+
*/
|
|
25
|
+
createConnection(options: http.ClientRequestArgs, callback?: (err: Error | null, stream: Duplex) => void): Duplex;
|
|
26
|
+
}
|
|
27
|
+
/** node:http-shaped namespace bound to a mesh node's `net` namespace. */
|
|
28
|
+
export interface TruffleHttp {
|
|
29
|
+
/**
|
|
30
|
+
* Shared MeshAgent instance. Pass as `{ agent: mesh.http.agent }` to a raw
|
|
31
|
+
* `node:http` / `node:https`-less request to route it over the mesh.
|
|
32
|
+
*/
|
|
33
|
+
agent: MeshAgent;
|
|
34
|
+
/** The MeshAgent class, for constructing an agent with custom options. */
|
|
35
|
+
Agent: typeof MeshAgent;
|
|
36
|
+
/**
|
|
37
|
+
* `http.request` with the mesh agent injected. URL form:
|
|
38
|
+
* `http://<peer>:<port>/path` (Tailscale IPs work as hostnames). Device
|
|
39
|
+
* names containing spaces aren't valid URL hostnames — use the options-
|
|
40
|
+
* object form, which takes any peer reference via `host`:
|
|
41
|
+
* `mesh.http.request({ host: 'living room pi', port: 8080, path: '/x' })`.
|
|
42
|
+
* A user-supplied `agent` (including `agent: false`) wins over the default.
|
|
43
|
+
*/
|
|
44
|
+
request(options: http.RequestOptions | string | URL, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
|
|
45
|
+
request(url: string | URL, options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
|
|
46
|
+
/** As {@link request}, but calls `req.end()` for you (http.get shape). */
|
|
47
|
+
get(options: http.RequestOptions | string | URL, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
|
|
48
|
+
get(url: string | URL, options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
|
|
49
|
+
/** GET a path on a peer and buffer the response body as UTF-8 text. */
|
|
50
|
+
fetchText(peer: string, port: number, path?: string): Promise<{
|
|
51
|
+
status: number;
|
|
52
|
+
headers: http.IncomingHttpHeaders;
|
|
53
|
+
body: string;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
56
|
+
export declare function createHttpNamespace(net: TruffleNet): TruffleHttp;
|
|
57
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAmBA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,qBAAa,SAAU,SAAQ,IAAI,CAAC,KAAK;;gBAG3B,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,YAAY;IAKrD;;;;;;OAMG;IACM,gBAAgB,CACvB,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAC/B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GACrD,MAAM;CAiBV;AAgBD,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB,0EAA0E;IAC1E,KAAK,EAAE,OAAO,SAAS,CAAC;IACxB;;;;;;;OAOG;IACH,OAAO,CACL,OAAO,EAAE,IAAI,CAAC,cAAc,GAAG,MAAM,GAAG,GAAG,EAC3C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,KAAK,IAAI,GAC7C,IAAI,CAAC,aAAa,CAAC;IACtB,OAAO,CACL,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,EAAE,IAAI,CAAC,cAAc,EAC5B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,KAAK,IAAI,GAC7C,IAAI,CAAC,aAAa,CAAC;IACtB,0EAA0E;IAC1E,GAAG,CACD,OAAO,EAAE,IAAI,CAAC,cAAc,GAAG,MAAM,GAAG,GAAG,EAC3C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,KAAK,IAAI,GAC7C,IAAI,CAAC,aAAa,CAAC;IACtB,GAAG,CACD,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,EAAE,IAAI,CAAC,cAAc,EAC5B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,KAAK,IAAI,GAC7C,IAAI,CAAC,aAAa,CAAC;IACtB,uEAAuE;IACvE,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjF;AAeD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,CAoFhE"}
|