@vibecook/truffle 0.4.7 → 0.4.9
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 +50 -5
- package/dist/create-mesh-node.d.ts.map +1 -1
- package/dist/create-mesh-node.js +57 -20
- 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/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +49 -3
- package/dist/sidecar.js.map +1 -1
- 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 +17 -6
- package/scripts/postinstall.cjs +152 -23
- package/sidecar-checksums.json +10 -0
package/dist/quic.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Duplex } from 'node:stream';
|
|
2
|
+
import type { NapiNode, NapiQuicConnection, NapiQuicListener, NapiQuicStream } from '@vibecook/truffle-native';
|
|
3
|
+
/**
|
|
4
|
+
* A bidirectional byte stream on a QUIC connection, as a `stream.Duplex`.
|
|
5
|
+
*
|
|
6
|
+
* Behaves like an independent TCP connection (ordered, reliable,
|
|
7
|
+
* flow-controlled) without head-of-line blocking against sibling streams.
|
|
8
|
+
* `end()` half-closes (the peer sees clean EOF); `destroy()` also stops
|
|
9
|
+
* the read side.
|
|
10
|
+
*/
|
|
11
|
+
export declare class TruffleQuicStream extends Duplex {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(native: NapiQuicStream);
|
|
14
|
+
_read(size: number): void;
|
|
15
|
+
_write(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
|
|
16
|
+
_final(callback: (error?: Error | null) => void): void;
|
|
17
|
+
_destroy(error: Error | null, callback: (error: Error | null) => void): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A QUIC connection to a peer. Open streams with {@link openStream};
|
|
21
|
+
* iterate peer-opened streams with `for await (const stream of conn)`.
|
|
22
|
+
*
|
|
23
|
+
* Streams are lazy: the peer's iterator does not yield a stream until the
|
|
24
|
+
* opener writes its first bytes. Keep the connection object alive while
|
|
25
|
+
* its streams are in use — closing it closes all of them.
|
|
26
|
+
*/
|
|
27
|
+
export declare class TruffleQuicConnection implements AsyncIterable<TruffleQuicStream> {
|
|
28
|
+
#private;
|
|
29
|
+
/** The peer's tailnet address (`100.x.x.x:port`). */
|
|
30
|
+
readonly remoteAddress: string;
|
|
31
|
+
/** The peer's stable device id when known. */
|
|
32
|
+
readonly remotePeerId?: string;
|
|
33
|
+
constructor(native: NapiQuicConnection);
|
|
34
|
+
/** Open a new bidirectional byte stream on this connection. */
|
|
35
|
+
openStream(): Promise<TruffleQuicStream>;
|
|
36
|
+
/**
|
|
37
|
+
* Accept the next stream the peer opens; `null` once the connection has
|
|
38
|
+
* closed. Prefer iteration: `for await (const stream of conn.streams())`.
|
|
39
|
+
*/
|
|
40
|
+
acceptStream(): Promise<TruffleQuicStream | null>;
|
|
41
|
+
/** Async iterator over peer-opened streams, ending when the connection closes. */
|
|
42
|
+
streams(): AsyncIterableIterator<TruffleQuicStream>;
|
|
43
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<TruffleQuicStream>;
|
|
44
|
+
/** Close the connection and all its streams. */
|
|
45
|
+
close(): void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A QUIC server on the mesh. Iterate incoming connections:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* const server = await mesh.quic.listen(4433);
|
|
52
|
+
* for await (const conn of server) {
|
|
53
|
+
* for await (const stream of conn.streams()) {
|
|
54
|
+
* stream.pipe(stream); // echo
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare class TruffleQuicServer implements AsyncIterable<TruffleQuicConnection> {
|
|
60
|
+
#private;
|
|
61
|
+
/** The port this server is bound to. */
|
|
62
|
+
readonly port: number;
|
|
63
|
+
constructor(native: NapiQuicListener);
|
|
64
|
+
/** Accept the next incoming connection; `null` once the server is closed. */
|
|
65
|
+
accept(): Promise<TruffleQuicConnection | null>;
|
|
66
|
+
/** Alias of async iteration, mirroring the RFC's `sessions()` sketch. */
|
|
67
|
+
connections(): AsyncIterableIterator<TruffleQuicConnection>;
|
|
68
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<TruffleQuicConnection>;
|
|
69
|
+
/** Close the server and every connection accepted from it. */
|
|
70
|
+
close(): void;
|
|
71
|
+
}
|
|
72
|
+
/** QUIC namespace bound to a mesh node. */
|
|
73
|
+
export interface TruffleQuic {
|
|
74
|
+
/**
|
|
75
|
+
* Open a QUIC connection to a peer. `host` accepts a device id (or
|
|
76
|
+
* unique ≥4-char prefix), device name, Tailscale hostname, or IP. Only
|
|
77
|
+
* same-app peers can complete the handshake (ALPN scoping).
|
|
78
|
+
*/
|
|
79
|
+
connect(host: string, port: number): Promise<TruffleQuicConnection>;
|
|
80
|
+
/**
|
|
81
|
+
* Listen for QUIC connections. Ports 443/9417 are reserved; port 0 is
|
|
82
|
+
* not supported over the tsnet relay — choose an explicit port
|
|
83
|
+
* (suggested default: 9420+).
|
|
84
|
+
*/
|
|
85
|
+
listen(port: number): Promise<TruffleQuicServer>;
|
|
86
|
+
}
|
|
87
|
+
export declare function createQuicNamespace(node: NapiNode): TruffleQuic;
|
|
88
|
+
//# sourceMappingURL=quic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quic.d.ts","sourceRoot":"","sources":["../src/quic.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACf,MAAM,0BAA0B,CAAC;AAElC;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,MAAM;;gBAI/B,MAAM,EAAE,cAAc;IAKzB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAezB,MAAM,CACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GACvC,IAAI;IAIE,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;IAItD,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;CAItF;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAsB,YAAW,aAAa,CAAC,iBAAiB,CAAC;;IAG5E,qDAAqD;IACrD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAEnB,MAAM,EAAE,kBAAkB;IAMtC,+DAA+D;IACzD,UAAU,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAI9C;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAKvD,kFAAkF;IAClF,OAAO,IAAI,qBAAqB,CAAC,iBAAiB,CAAC;IAI5C,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,iBAAiB,CAAC;IAQzE,gDAAgD;IAChD,KAAK,IAAI,IAAI;CAGd;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,YAAW,aAAa,CAAC,qBAAqB,CAAC;;IAG5E,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,MAAM,EAAE,gBAAgB;IAKpC,6EAA6E;IACvE,MAAM,IAAI,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IAKrD,yEAAyE;IACzE,WAAW,IAAI,qBAAqB,CAAC,qBAAqB,CAAC;IAIpD,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,qBAAqB,CAAC;IAQ7E,8DAA8D;IAC9D,KAAK,IAAI,IAAI;CAGd;AAED,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpE;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAClD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,WAAW,CAS/D"}
|
package/dist/quic.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// mesh.quic — modern QUIC API over the mesh (RFC 021 Phase 3).
|
|
2
|
+
//
|
|
3
|
+
// Shape follows the RFC (and iroh's precedent): connect a peer → a
|
|
4
|
+
// connection carrying many concurrent bidirectional byte streams with no
|
|
5
|
+
// head-of-line blocking between them. Connections and streams are async
|
|
6
|
+
// iterable; each stream is a real `stream.Duplex` (same pull-model
|
|
7
|
+
// backpressure as `mesh.net` sockets).
|
|
8
|
+
import { Duplex } from 'node:stream';
|
|
9
|
+
/**
|
|
10
|
+
* A bidirectional byte stream on a QUIC connection, as a `stream.Duplex`.
|
|
11
|
+
*
|
|
12
|
+
* Behaves like an independent TCP connection (ordered, reliable,
|
|
13
|
+
* flow-controlled) without head-of-line blocking against sibling streams.
|
|
14
|
+
* `end()` half-closes (the peer sees clean EOF); `destroy()` also stops
|
|
15
|
+
* the read side.
|
|
16
|
+
*/
|
|
17
|
+
export class TruffleQuicStream extends Duplex {
|
|
18
|
+
#native;
|
|
19
|
+
#reading = false;
|
|
20
|
+
constructor(native) {
|
|
21
|
+
super({ allowHalfOpen: true });
|
|
22
|
+
this.#native = native;
|
|
23
|
+
}
|
|
24
|
+
_read(size) {
|
|
25
|
+
if (this.#reading)
|
|
26
|
+
return;
|
|
27
|
+
this.#reading = true;
|
|
28
|
+
this.#native
|
|
29
|
+
.read(size > 0 ? size : undefined)
|
|
30
|
+
.then((chunk) => {
|
|
31
|
+
this.#reading = false;
|
|
32
|
+
this.push(chunk);
|
|
33
|
+
})
|
|
34
|
+
.catch((err) => {
|
|
35
|
+
this.#reading = false;
|
|
36
|
+
this.destroy(err);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
_write(chunk, _encoding, callback) {
|
|
40
|
+
this.#native.write(chunk).then(() => callback(null), callback);
|
|
41
|
+
}
|
|
42
|
+
_final(callback) {
|
|
43
|
+
this.#native.finish().then(() => callback(null), callback);
|
|
44
|
+
}
|
|
45
|
+
_destroy(error, callback) {
|
|
46
|
+
const finish = () => callback(error);
|
|
47
|
+
this.#native.close().then(finish, finish);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A QUIC connection to a peer. Open streams with {@link openStream};
|
|
52
|
+
* iterate peer-opened streams with `for await (const stream of conn)`.
|
|
53
|
+
*
|
|
54
|
+
* Streams are lazy: the peer's iterator does not yield a stream until the
|
|
55
|
+
* opener writes its first bytes. Keep the connection object alive while
|
|
56
|
+
* its streams are in use — closing it closes all of them.
|
|
57
|
+
*/
|
|
58
|
+
export class TruffleQuicConnection {
|
|
59
|
+
#native;
|
|
60
|
+
/** The peer's tailnet address (`100.x.x.x:port`). */
|
|
61
|
+
remoteAddress;
|
|
62
|
+
/** The peer's stable device id when known. */
|
|
63
|
+
remotePeerId;
|
|
64
|
+
constructor(native) {
|
|
65
|
+
this.#native = native;
|
|
66
|
+
this.remoteAddress = native.remoteAddress();
|
|
67
|
+
this.remotePeerId = native.remotePeerId() ?? undefined;
|
|
68
|
+
}
|
|
69
|
+
/** Open a new bidirectional byte stream on this connection. */
|
|
70
|
+
async openStream() {
|
|
71
|
+
return new TruffleQuicStream(await this.#native.openStream());
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Accept the next stream the peer opens; `null` once the connection has
|
|
75
|
+
* closed. Prefer iteration: `for await (const stream of conn.streams())`.
|
|
76
|
+
*/
|
|
77
|
+
async acceptStream() {
|
|
78
|
+
const native = await this.#native.acceptStream();
|
|
79
|
+
return native === null ? null : new TruffleQuicStream(native);
|
|
80
|
+
}
|
|
81
|
+
/** Async iterator over peer-opened streams, ending when the connection closes. */
|
|
82
|
+
streams() {
|
|
83
|
+
return this[Symbol.asyncIterator]();
|
|
84
|
+
}
|
|
85
|
+
async *[Symbol.asyncIterator]() {
|
|
86
|
+
for (;;) {
|
|
87
|
+
const stream = await this.acceptStream();
|
|
88
|
+
if (stream === null)
|
|
89
|
+
return;
|
|
90
|
+
yield stream;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Close the connection and all its streams. */
|
|
94
|
+
close() {
|
|
95
|
+
this.#native.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A QUIC server on the mesh. Iterate incoming connections:
|
|
100
|
+
*
|
|
101
|
+
* ```ts
|
|
102
|
+
* const server = await mesh.quic.listen(4433);
|
|
103
|
+
* for await (const conn of server) {
|
|
104
|
+
* for await (const stream of conn.streams()) {
|
|
105
|
+
* stream.pipe(stream); // echo
|
|
106
|
+
* }
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export class TruffleQuicServer {
|
|
111
|
+
#native;
|
|
112
|
+
/** The port this server is bound to. */
|
|
113
|
+
port;
|
|
114
|
+
constructor(native) {
|
|
115
|
+
this.#native = native;
|
|
116
|
+
this.port = native.port();
|
|
117
|
+
}
|
|
118
|
+
/** Accept the next incoming connection; `null` once the server is closed. */
|
|
119
|
+
async accept() {
|
|
120
|
+
const native = await this.#native.accept();
|
|
121
|
+
return native === null ? null : new TruffleQuicConnection(native);
|
|
122
|
+
}
|
|
123
|
+
/** Alias of async iteration, mirroring the RFC's `sessions()` sketch. */
|
|
124
|
+
connections() {
|
|
125
|
+
return this[Symbol.asyncIterator]();
|
|
126
|
+
}
|
|
127
|
+
async *[Symbol.asyncIterator]() {
|
|
128
|
+
for (;;) {
|
|
129
|
+
const conn = await this.accept();
|
|
130
|
+
if (conn === null)
|
|
131
|
+
return;
|
|
132
|
+
yield conn;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Close the server and every connection accepted from it. */
|
|
136
|
+
close() {
|
|
137
|
+
this.#native.close();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function createQuicNamespace(node) {
|
|
141
|
+
return {
|
|
142
|
+
async connect(host, port) {
|
|
143
|
+
return new TruffleQuicConnection(await node.connectQuic(host, port));
|
|
144
|
+
},
|
|
145
|
+
async listen(port) {
|
|
146
|
+
return new TruffleQuicServer(await node.listenQuic(port));
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=quic.js.map
|
package/dist/quic.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quic.js","sourceRoot":"","sources":["../src/quic.ts"],"names":[],"mappings":"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"}
|
package/dist/sidecar.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAyDH;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CA+B3C"}
|
package/dist/sidecar.js
CHANGED
|
@@ -8,11 +8,56 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Follows the esbuild pattern for distributing platform-specific binaries.
|
|
10
10
|
*/
|
|
11
|
-
import { existsSync } from 'node:fs';
|
|
11
|
+
import { closeSync, existsSync, openSync, readSync } from 'node:fs';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
const require = createRequire(import.meta.url);
|
|
16
|
+
/**
|
|
17
|
+
* Best-effort check that a local binary's architecture matches this process.
|
|
18
|
+
*
|
|
19
|
+
* The `bin/` fallback is populated by postinstall for the *current* platform,
|
|
20
|
+
* but a stray/leftover binary of the wrong arch would otherwise be returned and
|
|
21
|
+
* fail later with a cryptic `ENOEXEC`. We sniff the executable's magic bytes and
|
|
22
|
+
* only reject on a *positive* mismatch; when we can't tell, we allow it.
|
|
23
|
+
*/
|
|
24
|
+
function binaryMatchesPlatform(path) {
|
|
25
|
+
let fd;
|
|
26
|
+
try {
|
|
27
|
+
fd = openSync(path, 'r');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const buf = Buffer.alloc(20);
|
|
34
|
+
if (readSync(fd, buf, 0, 20, 0) < 20)
|
|
35
|
+
return true; // too small to judge
|
|
36
|
+
// Mach-O 64-bit (macOS): LE magic 0xFEEDFACF, cputype at offset 4.
|
|
37
|
+
if (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) {
|
|
38
|
+
const cpuType = buf.readUInt32LE(4);
|
|
39
|
+
if (process.arch === 'arm64')
|
|
40
|
+
return cpuType === 0x0100000c; // CPU_TYPE_ARM64
|
|
41
|
+
if (process.arch === 'x64')
|
|
42
|
+
return cpuType === 0x01000007; // CPU_TYPE_X86_64
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// ELF (Linux): magic 0x7F 'E' 'L' 'F', e_machine at offset 18 (LE).
|
|
46
|
+
if (buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46) {
|
|
47
|
+
const machine = buf.readUInt16LE(18);
|
|
48
|
+
if (process.arch === 'x64')
|
|
49
|
+
return machine === 0x3e; // EM_X86_64
|
|
50
|
+
if (process.arch === 'arm64')
|
|
51
|
+
return machine === 0xb7; // EM_AARCH64
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
// PE (Windows) / unknown format — don't block.
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
closeSync(fd);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
16
61
|
const PLATFORM_PACKAGES = {
|
|
17
62
|
'darwin-arm64': '@vibecook/truffle-sidecar-darwin-arm64',
|
|
18
63
|
'darwin-x64': '@vibecook/truffle-sidecar-darwin-x64',
|
|
@@ -46,10 +91,11 @@ export function resolveSidecarPath() {
|
|
|
46
91
|
// Package not installed — fall through
|
|
47
92
|
}
|
|
48
93
|
}
|
|
49
|
-
// 2. Try postinstall-downloaded binary (fallback)
|
|
94
|
+
// 2. Try postinstall-downloaded binary (fallback), but only if its arch
|
|
95
|
+
// matches this process — a wrong-arch leftover would fail with ENOEXEC.
|
|
50
96
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
51
97
|
const localBin = join(__dirname, '..', 'bin', binName);
|
|
52
|
-
if (existsSync(localBin))
|
|
98
|
+
if (existsSync(localBin) && binaryMatchesPlatform(localBin))
|
|
53
99
|
return localBin;
|
|
54
100
|
// 3. Nothing found
|
|
55
101
|
const supported = Object.keys(PLATFORM_PACKAGES).join(', ');
|
package/dist/sidecar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar.js","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"sidecar.js","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"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"}
|
package/dist/ws.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { TruffleNet } from './net.js';
|
|
2
|
+
import type WebSocket from 'ws';
|
|
3
|
+
import type { WebSocketServer as WsServerInstance, ServerOptions as WsServerOptions, ClientOptions as WsClientOptions } from 'ws';
|
|
4
|
+
/** The subset of the `ws` module mesh.ws uses; see {@link WsLoader}. */
|
|
5
|
+
interface WsExports {
|
|
6
|
+
/** The `ws` client constructor. */
|
|
7
|
+
WebSocket: new (address: string, options?: WsClientOptions) => WebSocket;
|
|
8
|
+
/** The `ws` server constructor. */
|
|
9
|
+
WebSocketServer: new (options?: WsServerOptions) => WsServerInstance;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Loads the `ws` module. Defaults to a real `import('ws')`; the seam exists so
|
|
13
|
+
* tests can inject a loader (including one that rejects, to exercise the
|
|
14
|
+
* missing-dependency path) without touching the filesystem.
|
|
15
|
+
*/
|
|
16
|
+
export type WsLoader = () => Promise<WsExports>;
|
|
17
|
+
/**
|
|
18
|
+
* A `ws` {@link WebSocket.Server} bound to a mesh listener.
|
|
19
|
+
*
|
|
20
|
+
* Everything about it is the ordinary `ws` server API — most importantly the
|
|
21
|
+
* `'connection'` event still fires with `(ws, request)` — with two additions:
|
|
22
|
+
* `port` (resolved, so an ephemeral `0` request surfaces the real port) and a
|
|
23
|
+
* `close()` that also tears down the underlying mesh listener.
|
|
24
|
+
*/
|
|
25
|
+
export interface TruffleWsServer extends WsServerInstance {
|
|
26
|
+
/** The mesh port this server is bound to (resolved when `0` was requested). */
|
|
27
|
+
readonly port: number;
|
|
28
|
+
}
|
|
29
|
+
/** Options for {@link TruffleWs.createServer}. */
|
|
30
|
+
export interface TruffleWsServerOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Mesh port to listen on. `0` binds an ephemeral port (read it back from
|
|
33
|
+
* `server.port`). Ports 443 and 9417 are reserved by the mesh and rejected.
|
|
34
|
+
*/
|
|
35
|
+
port: number;
|
|
36
|
+
/**
|
|
37
|
+
* Optional path filter, like `ws`'s own `path` option: only upgrade requests
|
|
38
|
+
* whose URL path matches are accepted; others get a `400`-style socket close.
|
|
39
|
+
*/
|
|
40
|
+
path?: string;
|
|
41
|
+
}
|
|
42
|
+
/** WebSocket-over-mesh namespace bound to a mesh node's `net` namespace. */
|
|
43
|
+
export interface TruffleWs {
|
|
44
|
+
/**
|
|
45
|
+
* Open a WebSocket to a peer, returning a connecting `ws` `WebSocket` (await
|
|
46
|
+
* its `'open'` event as usual). `host` accepts any peer reference — device
|
|
47
|
+
* id (or ≥4-char prefix), device name, Tailscale hostname, or `100.x` IP —
|
|
48
|
+
* including names with spaces and case-sensitive ids that aren't valid URL
|
|
49
|
+
* hostnames (they're pinned past the URL, see the module docs). `path`
|
|
50
|
+
* defaults to `/`; `options` are forwarded to the `ws` `WebSocket`
|
|
51
|
+
* constructor (a caller-supplied `agent` is overridden — the mesh agent is
|
|
52
|
+
* how it reaches the peer).
|
|
53
|
+
*/
|
|
54
|
+
connect(host: string, port: number, path?: string, options?: WsClientOptions): Promise<WebSocket>;
|
|
55
|
+
/**
|
|
56
|
+
* Start a WebSocket server on the mesh. Resolves once it's listening; the
|
|
57
|
+
* returned server's `'connection'` event delivers `(ws, request)` like any
|
|
58
|
+
* `ws` server. Close it (and release the mesh port) with `server.close()`.
|
|
59
|
+
*/
|
|
60
|
+
createServer(options: TruffleWsServerOptions): Promise<TruffleWsServer>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the `mesh.ws` namespace over a node's `net` namespace.
|
|
64
|
+
*
|
|
65
|
+
* @param net The node's {@link TruffleNet} (mesh TCP), used for the client
|
|
66
|
+
* agent and the server's listener.
|
|
67
|
+
* @param load How to obtain the `ws` module; defaults to `import('ws')`.
|
|
68
|
+
* Injectable for tests.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createWsNamespace(net: TruffleNet, load?: WsLoader): TruffleWs;
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=ws.d.ts.map
|
package/dist/ws.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;AAChC,OAAO,KAAK,EACV,eAAe,IAAI,gBAAgB,EACnC,aAAa,IAAI,eAAe,EAChC,aAAa,IAAI,eAAe,EACjC,MAAM,IAAI,CAAC;AAsBZ,wEAAwE;AACxE,UAAU,SAAS;IACjB,mCAAmC;IACnC,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,SAAS,CAAC;IACzE,mCAAmC;IACnC,eAAe,EAAE,KAAK,OAAO,CAAC,EAAE,eAAe,KAAK,gBAAgB,CAAC;CACtE;AAED;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;AAyBhD;;;;;;;GAOG;AACH,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB;;;;;;;;;OASG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAClG;;;;OAIG;IACH,YAAY,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACzE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,GAAE,QAAmB,GAAG,SAAS,CA8FvF"}
|
package/dist/ws.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// WebSocket over the mesh via the `ws` package (RFC 021 Phase 4, D3).
|
|
2
|
+
//
|
|
3
|
+
// mesh.ws ships zero new Rust: it runs the battle-tested `ws` npm package over
|
|
4
|
+
// mesh TCP. The client drives `ws`'s HTTP upgrade through a MeshAgent (whose
|
|
5
|
+
// `createConnection` returns a mesh `TruffleSocket`); the server feeds mesh
|
|
6
|
+
// connections into a stock `http.Server` and lets a `noServer` WebSocketServer
|
|
7
|
+
// complete the upgrade — exactly the seams `ws` documents for custom
|
|
8
|
+
// transports. `websocket.rs` stays the internal envelope pipe on 9417; this is
|
|
9
|
+
// a separate, user-facing raw-WebSocket surface.
|
|
10
|
+
//
|
|
11
|
+
// `ws` is an OPTIONAL peer dependency: apps that never touch mesh.ws don't need
|
|
12
|
+
// it installed. Nothing here imports `ws` at module load — the namespace holds
|
|
13
|
+
// lazy async functions that `await import('ws')` on first use and throw a
|
|
14
|
+
// clear, actionable error when it's missing.
|
|
15
|
+
import http from 'node:http';
|
|
16
|
+
import { MeshAgent } from './http.js';
|
|
17
|
+
/**
|
|
18
|
+
* Hostname placed in the `ws://` URL when the caller's peer reference can't be
|
|
19
|
+
* a URL hostname (device names with spaces, case-sensitive device ids, …).
|
|
20
|
+
* It only ever sets the HTTP `Host` header — correctness never depends on it,
|
|
21
|
+
* because the agent dials the real peer reference verbatim (see {@link PIN_KEY}).
|
|
22
|
+
* `.invalid` is reserved by RFC 2606 and never resolves, so it reads as the
|
|
23
|
+
* placeholder it is.
|
|
24
|
+
*/
|
|
25
|
+
const PLACEHOLDER_HOST = 'truffle.invalid';
|
|
26
|
+
/**
|
|
27
|
+
* Request-option key carrying the true peer reference past `ws`'s URL
|
|
28
|
+
* rewriting. `ws` overwrites `options.host` with the URL's hostname before the
|
|
29
|
+
* agent sees it, so a non-URL-safe peer reference can't ride `options.host`.
|
|
30
|
+
* It CAN ride an unknown key: `ws` spreads user options into the request and
|
|
31
|
+
* `http` forwards unknown keys to the agent's `createConnection` untouched
|
|
32
|
+
* (verified empirically). {@link MeshWsAgent} reads it back.
|
|
33
|
+
*/
|
|
34
|
+
const PIN_KEY = 'truffleWsPeer';
|
|
35
|
+
const importWs = () => import('ws');
|
|
36
|
+
/**
|
|
37
|
+
* A {@link MeshAgent} that honours a pinned peer reference ({@link PIN_KEY}).
|
|
38
|
+
*
|
|
39
|
+
* For a URL-safe peer reference the pin equals the URL hostname and this is a
|
|
40
|
+
* no-op; for a non-URL-safe one the URL carries a placeholder and the real
|
|
41
|
+
* reference arrives only via the pin, so we rewrite `host`/`hostname` before
|
|
42
|
+
* delegating to MeshAgent — which then dials the exact peer over mesh TCP.
|
|
43
|
+
*/
|
|
44
|
+
class MeshWsAgent extends MeshAgent {
|
|
45
|
+
createConnection(options, callback) {
|
|
46
|
+
const pinned = options[PIN_KEY];
|
|
47
|
+
if (typeof pinned === 'string' && pinned.length > 0) {
|
|
48
|
+
return super.createConnection({ ...options, host: pinned, hostname: pinned }, callback);
|
|
49
|
+
}
|
|
50
|
+
return super.createConnection(options, callback);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the `mesh.ws` namespace over a node's `net` namespace.
|
|
55
|
+
*
|
|
56
|
+
* @param net The node's {@link TruffleNet} (mesh TCP), used for the client
|
|
57
|
+
* agent and the server's listener.
|
|
58
|
+
* @param load How to obtain the `ws` module; defaults to `import('ws')`.
|
|
59
|
+
* Injectable for tests.
|
|
60
|
+
*/
|
|
61
|
+
export function createWsNamespace(net, load = importWs) {
|
|
62
|
+
const agent = new MeshWsAgent(net);
|
|
63
|
+
async function loadWs() {
|
|
64
|
+
try {
|
|
65
|
+
return await load();
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
throw new Error("mesh.ws requires the 'ws' package, which is an optional peer dependency — " +
|
|
69
|
+
'install it with `npm install ws` (or `pnpm add ws`).', { cause: err });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function connect(host, port, path = '/', options = {}) {
|
|
73
|
+
const { WebSocket: WebSocketImpl } = await loadWs();
|
|
74
|
+
const requestPath = path.startsWith('/') ? path : `/${path}`;
|
|
75
|
+
// The URL hostname only sets the HTTP Host header; the agent always dials
|
|
76
|
+
// the pinned peer. Embed `host` when it round-trips as a URL hostname
|
|
77
|
+
// unchanged (a plain IP or lowercase device name), so the Host header is
|
|
78
|
+
// natural; otherwise a placeholder (a name with spaces, or a device id
|
|
79
|
+
// whose case a URL would fold — either would dial the wrong peer).
|
|
80
|
+
const urlHost = urlHostnameFor(host, port) ?? PLACEHOLDER_HOST;
|
|
81
|
+
const url = `ws://${urlHost}:${port}${requestPath}`;
|
|
82
|
+
// `agent` last: the mesh agent is non-negotiable. PIN_KEY is an unknown key
|
|
83
|
+
// ws/http pass through to the agent (see PIN_KEY); cast past the typed
|
|
84
|
+
// ClientOptions to attach it.
|
|
85
|
+
const wsOptions = { ...options, agent, [PIN_KEY]: host };
|
|
86
|
+
return new WebSocketImpl(url, wsOptions);
|
|
87
|
+
}
|
|
88
|
+
async function createServer(options) {
|
|
89
|
+
const { WebSocketServer } = await loadWs();
|
|
90
|
+
// A throwaway http.Server does the HTTP/WebSocket upgrade parsing; a
|
|
91
|
+
// noServer WebSocketServer completes the handshake. We never `.listen()`
|
|
92
|
+
// this http server on a host port — mesh connections are fed in by hand.
|
|
93
|
+
const httpServer = http.createServer();
|
|
94
|
+
const wss = new WebSocketServer({ noServer: true, path: options.path });
|
|
95
|
+
httpServer.on('upgrade', (req, socket, head) => {
|
|
96
|
+
// `ws`'s own path check: true when no `path` was set, else on a match.
|
|
97
|
+
Promise.resolve(wss.shouldHandle(req)).then((ok) => {
|
|
98
|
+
if (!ok) {
|
|
99
|
+
socket.destroy();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
wss.handleUpgrade(req, socket, head, (client, request) => {
|
|
103
|
+
wss.emit('connection', client, request);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// Each mesh TCP connection becomes an http.Server 'connection' — exactly
|
|
108
|
+
// what a real listening socket would emit — so http parses it and, on an
|
|
109
|
+
// Upgrade request, fires the 'upgrade' handler above.
|
|
110
|
+
const meshServer = net.createServer((sock) => httpServer.emit('connection', sock));
|
|
111
|
+
await new Promise((resolve, reject) => {
|
|
112
|
+
const onError = (err) => reject(err);
|
|
113
|
+
meshServer.once('error', onError);
|
|
114
|
+
meshServer.listen(options.port, () => {
|
|
115
|
+
meshServer.removeListener('error', onError);
|
|
116
|
+
resolve();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
const server = wss;
|
|
120
|
+
Object.defineProperty(server, 'port', {
|
|
121
|
+
value: meshServer.port ?? options.port,
|
|
122
|
+
enumerable: true,
|
|
123
|
+
});
|
|
124
|
+
// close() also tears down the mesh listener and any live client sockets;
|
|
125
|
+
// the http.Server holds no listening socket, so there's nothing to close
|
|
126
|
+
// there. Keep ws's void-returning signature so the type stays a ws Server.
|
|
127
|
+
const closeWs = wss.close.bind(wss);
|
|
128
|
+
server.close = (callback) => {
|
|
129
|
+
for (const client of wss.clients)
|
|
130
|
+
client.close();
|
|
131
|
+
meshServer.close();
|
|
132
|
+
closeWs(callback);
|
|
133
|
+
};
|
|
134
|
+
return server;
|
|
135
|
+
}
|
|
136
|
+
return { connect, createServer };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Returns `host` when it is a URL hostname that round-trips unchanged (so it's
|
|
140
|
+
* safe to embed in a `ws://` URL and use for the Host header), else `null`.
|
|
141
|
+
* The equality check rejects anything a WHATWG URL would normalise — notably
|
|
142
|
+
* uppercase device ids, which URL parsing lowercases.
|
|
143
|
+
*/
|
|
144
|
+
function urlHostnameFor(host, port) {
|
|
145
|
+
try {
|
|
146
|
+
return new URL(`ws://${host}:${port}`).hostname === host ? host : null;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=ws.js.map
|
package/dist/ws.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.js","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,+EAA+E;AAC/E,qEAAqE;AACrE,+EAA+E;AAC/E,iDAAiD;AACjD,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,0EAA0E;AAC1E,6CAA6C;AAE7C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAWtC;;;;;;;GAOG;AACH,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG,eAAe,CAAC;AAiBhC,MAAM,QAAQ,GAAa,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAuB,CAAC;AAEpE;;;;;;;GAOG;AACH,MAAM,WAAY,SAAQ,SAAS;IACxB,gBAAgB,CACvB,OAA+B,EAC/B,QAAsD;QAEtD,MAAM,MAAM,GAAI,OAAmC,CAAC,OAAO,CAAC,CAAC;QAC7D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpD,OAAO,KAAK,CAAC,gBAAgB,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC1F,CAAC;QACD,OAAO,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;CACF;AAkDD;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAe,EAAE,OAAiB,QAAQ;IAC1E,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC;IAEnC,KAAK,UAAU,MAAM;QACnB,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,4EAA4E;gBAC1E,sDAAsD,EACxD,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,UAAU,OAAO,CACpB,IAAY,EACZ,IAAY,EACZ,IAAI,GAAG,GAAG,EACV,UAA2B,EAAE;QAE7B,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;QACpD,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAE7D,0EAA0E;QAC1E,sEAAsE;QACtE,yEAAyE;QACzE,uEAAuE;QACvE,mEAAmE;QACnE,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,gBAAgB,CAAC;QAC/D,MAAM,GAAG,GAAG,QAAQ,OAAO,IAAI,IAAI,GAAG,WAAW,EAAE,CAAC;QAEpD,4EAA4E;QAC5E,uEAAuE;QACvE,8BAA8B;QAC9B,MAAM,SAAS,GAA4B,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC;QAClF,OAAO,IAAI,aAAa,CAAC,GAAG,EAAE,SAA4B,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,UAAU,YAAY,CAAC,OAA+B;QACzD,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;QAE3C,qEAAqE;QACrE,yEAAyE;QACzE,yEAAyE;QACzE,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAExE,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;YAC7C,uEAAuE;YACvE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;gBACjD,IAAI,CAAC,EAAE,EAAE,CAAC;oBACR,MAAM,CAAC,OAAO,EAAE,CAAC;oBACjB,OAAO;gBACT,CAAC;gBACD,GAAG,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,MAAiB,EAAE,OAA6B,EAAE,EAAE;oBACxF,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;gBAC1C,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,yEAAyE;QACzE,yEAAyE;QACzE,sDAAsD;QACtD,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC;QACnF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,MAAM,OAAO,GAAG,CAAC,GAAU,EAAQ,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClD,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAClC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE;gBACnC,UAAU,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC5C,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,GAAsB,CAAC;QACtC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE;YACpC,KAAK,EAAE,UAAU,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI;YACtC,UAAU,EAAE,IAAI;SACjB,CAAC,CAAC;QAEH,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,GAAG,CAAC,QAAgC,EAAQ,EAAE;YACxD,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO;gBAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YACjD,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpB,CAAC,CAAC;QAEF,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,IAAY,EAAE,IAAY;IAChD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibecook/truffle",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Mesh networking for local-first apps, built on Tailscale",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "James Yong",
|
|
@@ -40,26 +40,37 @@
|
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"dist",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
43
|
+
"scripts",
|
|
44
|
+
"sidecar-checksums.json"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@vibecook/truffle-native": "0.4.
|
|
47
|
+
"@vibecook/truffle-native": "0.4.9"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"ws": ">=8"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"ws": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
48
56
|
},
|
|
49
57
|
"optionalDependencies": {
|
|
50
|
-
"@vibecook/truffle-sidecar-darwin-arm64": "^0.
|
|
58
|
+
"@vibecook/truffle-sidecar-darwin-arm64": "^0.4.6",
|
|
51
59
|
"@vibecook/truffle-sidecar-darwin-x64": "^0.4.6",
|
|
52
60
|
"@vibecook/truffle-sidecar-linux-x64": "^0.4.5",
|
|
53
61
|
"@vibecook/truffle-sidecar-linux-arm64": "^0.4.5",
|
|
54
62
|
"@vibecook/truffle-sidecar-win32-x64": "^0.4.5"
|
|
55
63
|
},
|
|
56
64
|
"devDependencies": {
|
|
57
|
-
"
|
|
65
|
+
"@types/ws": "^8.5.13",
|
|
66
|
+
"typescript": "^5.7.0",
|
|
67
|
+
"ws": "^8.18.0"
|
|
58
68
|
},
|
|
59
69
|
"scripts": {
|
|
60
70
|
"build": "tsc",
|
|
61
71
|
"clean": "rm -rf dist",
|
|
62
72
|
"typecheck": "tsc --noEmit",
|
|
73
|
+
"test": "node --test \"test/**/*.test.cjs\"",
|
|
63
74
|
"postinstall": "node scripts/postinstall.cjs"
|
|
64
75
|
}
|
|
65
76
|
}
|