hbeam 0.1.0-alpha.1

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,15 @@
1
+ # hbeam
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.5. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
package/dist/cli.mjs ADDED
@@ -0,0 +1,485 @@
1
+ #!/usr/bin/env node
2
+ import mri from "mri";
3
+ import queueTick from "queue-tick";
4
+ import { Duplex } from "streamx";
5
+ import * as b4a from "b4a";
6
+ import DHT from "hyperdht";
7
+ import b32 from "hi-base32";
8
+ import sodium from "sodium-universal";
9
+ import { bold, bold as bold$1, cyan, dim, dim as dim$1, gray, red } from "colorette";
10
+
11
+ //#region src/lib/encoding.ts
12
+ /** Encode a buffer as a lowercase base32 string without padding. */
13
+ function toBase32(buf) {
14
+ return b32.encode(buf).replace(/=/g, "").toLowerCase();
15
+ }
16
+ /** Decode a base32 string back into a raw Buffer. */
17
+ function fromBase32(str) {
18
+ return b4a.from(b32.decode.asBytes(str.toUpperCase()));
19
+ }
20
+ /** Generate cryptographically secure random bytes. */
21
+ function randomBytes(length) {
22
+ const buffer = b4a.alloc(length);
23
+ sodium.randombytes_buf(buffer);
24
+ return buffer;
25
+ }
26
+
27
+ //#endregion
28
+ //#region src/lib/dht.ts
29
+ /** Derive a Noise keypair from a base32-encoded passphrase. */
30
+ function deriveKeyPair(passphrase) {
31
+ return DHT.keyPair(fromBase32(passphrase));
32
+ }
33
+ /** Create an ephemeral HyperDHT node that is destroyed with the beam. */
34
+ function createNode() {
35
+ return new DHT({ ephemeral: true });
36
+ }
37
+ /** Wait for an encrypted socket to complete its Noise handshake. */
38
+ function awaitOpen(socket) {
39
+ return new Promise((resolve, reject) => {
40
+ socket.once("open", resolve);
41
+ socket.once("close", reject);
42
+ socket.once("error", reject);
43
+ });
44
+ }
45
+ /** Create a firewall that rejects any connection not matching the keypair. */
46
+ function createFirewall(keyPair) {
47
+ return (remotePublicKey) => !b4a.equals(remotePublicKey, keyPair.publicKey);
48
+ }
49
+
50
+ //#endregion
51
+ //#region src/beam.ts
52
+ /** Number of random bytes used to generate a passphrase seed. */
53
+ const KEY_SEED_BYTES = 32;
54
+ /**
55
+ * A 1-to-1 end-to-end encrypted duplex stream powered by HyperDHT.
56
+ *
57
+ * Creates an encrypted tunnel between two peers using a shared passphrase.
58
+ * If no passphrase is provided, one is generated and the beam listens for
59
+ * an incoming connection (server mode). When a passphrase is provided, the
60
+ * beam connects to the listening peer (client mode).
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const server = new Beam()
65
+ * console.log(server.key) // Share this with the other side
66
+ *
67
+ * const client = new Beam(server.key)
68
+ * ```
69
+ */
70
+ var Beam = class extends Duplex {
71
+ /** Base32-encoded passphrase for peer discovery and key derivation. */
72
+ key;
73
+ /** Whether this beam is announcing (server) or connecting (client). */
74
+ announce;
75
+ node;
76
+ server = void 0;
77
+ inbound = void 0;
78
+ outbound = void 0;
79
+ openCallback = void 0;
80
+ readCallback = void 0;
81
+ drainCallback = void 0;
82
+ constructor(keyOrOptions, options) {
83
+ super();
84
+ let key = void 0;
85
+ let opts = {};
86
+ if (typeof keyOrOptions === "string") {
87
+ key = keyOrOptions;
88
+ opts = options ?? {};
89
+ } else opts = keyOrOptions ?? {};
90
+ let shouldAnnounce = opts.announce ?? false;
91
+ if (!key) {
92
+ key = toBase32(randomBytes(KEY_SEED_BYTES));
93
+ shouldAnnounce = true;
94
+ }
95
+ this.key = key;
96
+ this.announce = shouldAnnounce;
97
+ this.node = opts.dht ?? void 0;
98
+ }
99
+ /** Whether a peer connection has been established. */
100
+ get connected() {
101
+ return this.outbound !== void 0;
102
+ }
103
+ async _open(cb) {
104
+ this.openCallback = cb;
105
+ const keyPair = deriveKeyPair(this.key);
106
+ this.node ??= createNode();
107
+ if (this.announce) await this.listenAsServer(keyPair);
108
+ else await this.connectAsClient(keyPair);
109
+ }
110
+ _read(cb) {
111
+ this.readCallback = cb;
112
+ this.inbound?.resume();
113
+ }
114
+ _write(data, cb) {
115
+ if (this.outbound.write(data) !== false) {
116
+ cb();
117
+ return;
118
+ }
119
+ this.drainCallback = cb;
120
+ }
121
+ _final(cb) {
122
+ const done = () => {
123
+ this.outbound.removeListener("finish", done);
124
+ this.outbound.removeListener("error", done);
125
+ cb();
126
+ };
127
+ this.outbound.end();
128
+ this.outbound.on("finish", done);
129
+ this.outbound.on("error", done);
130
+ }
131
+ _predestroy() {
132
+ this.inbound?.destroy();
133
+ this.outbound?.destroy();
134
+ const error = /* @__PURE__ */ new Error("Destroyed");
135
+ this.resolveOpen(error);
136
+ this.resolveRead(error);
137
+ this.resolveDrain(error);
138
+ }
139
+ async _destroy(cb) {
140
+ if (!this.node) {
141
+ cb();
142
+ return;
143
+ }
144
+ if (this.server) await this.server.close().catch(() => {});
145
+ await this.node.destroy().catch(() => {});
146
+ cb();
147
+ }
148
+ async listenAsServer(keyPair) {
149
+ this.server = this.node.createServer({ firewall: createFirewall(keyPair) });
150
+ this.server.on("connection", (socket) => this.handleConnection(socket));
151
+ try {
152
+ await this.server.listen(keyPair);
153
+ } catch (error) {
154
+ this.resolveOpen(error);
155
+ return;
156
+ }
157
+ this.emitRemoteAddress();
158
+ }
159
+ async connectAsClient(keyPair) {
160
+ const socket = this.node.connect(keyPair.publicKey, { keyPair });
161
+ try {
162
+ await awaitOpen(socket);
163
+ } catch (error) {
164
+ this.resolveOpen(error);
165
+ return;
166
+ }
167
+ this.emitRemoteAddress();
168
+ this.handleConnection(socket);
169
+ }
170
+ handleConnection(socket) {
171
+ socket.on("data", (data) => {
172
+ if (!this.inbound) {
173
+ this.inbound = socket;
174
+ this.inbound.on("error", (err) => this.destroy(err));
175
+ this.inbound.on("end", () => this.pushEndOfStream());
176
+ }
177
+ if (socket !== this.inbound) return;
178
+ if (this.pushData(data) === false) socket.pause();
179
+ });
180
+ socket.on("end", () => {
181
+ if (this.inbound) return;
182
+ this.pushEndOfStream();
183
+ });
184
+ if (!this.outbound) {
185
+ this.outbound = socket;
186
+ this.outbound.on("error", (err) => this.destroy(err));
187
+ this.outbound.on("drain", () => this.resolveDrain());
188
+ this.emit("connected");
189
+ this.resolveOpen();
190
+ }
191
+ }
192
+ pushData(data) {
193
+ const result = this.push(data);
194
+ queueTick(() => this.resolveRead());
195
+ return result;
196
+ }
197
+ pushEndOfStream() {
198
+ this.pushData(null);
199
+ }
200
+ emitRemoteAddress() {
201
+ this.emit("remote-address", {
202
+ host: this.node.host,
203
+ port: this.node.port
204
+ });
205
+ }
206
+ resolveOpen(error) {
207
+ const cb = this.openCallback;
208
+ if (cb) {
209
+ this.openCallback = void 0;
210
+ cb(error);
211
+ }
212
+ }
213
+ resolveRead(error) {
214
+ const cb = this.readCallback;
215
+ if (cb) {
216
+ this.readCallback = void 0;
217
+ cb(error);
218
+ }
219
+ }
220
+ resolveDrain(error) {
221
+ const cb = this.drainCallback;
222
+ if (cb) {
223
+ this.drainCallback = void 0;
224
+ cb(error);
225
+ }
226
+ }
227
+ };
228
+
229
+ //#endregion
230
+ //#region src/lib/log.ts
231
+ const INDENT = " ";
232
+ /** ANSI escape: clear the entire current line and reset cursor to column 0. */
233
+ const CLEAR_LINE = "\r\x1B[2K";
234
+ /** Zero offset — cursor is on the spinner line itself. */
235
+ const NO_OFFSET = 0;
236
+ /** Write a line to stderr at the standard indent level. */
237
+ function write(message) {
238
+ process.stderr.write(`${INDENT}${message}\n`);
239
+ }
240
+ /** Write a blank line to stderr. */
241
+ function blank() {
242
+ process.stderr.write("\n");
243
+ }
244
+ /** Write a pre-formatted block (multiple lines) to stderr. */
245
+ function writeBlock(lines) {
246
+ for (const line of lines) process.stderr.write(`${INDENT}${line}\n`);
247
+ }
248
+ /** Write a status message to stderr at the standard indent level. */
249
+ function log(message) {
250
+ write(message);
251
+ }
252
+ /** Write an error message to stderr with a red prefix. */
253
+ function logError(message) {
254
+ process.stderr.write(`${red("✖")} ${message}\n`);
255
+ }
256
+ /** Clear the current line (wipe terminal-echoed ^C, etc.). Falls back to a newline on non-TTY. */
257
+ function clearLine() {
258
+ if (process.stderr.isTTY) process.stderr.write(CLEAR_LINE);
259
+ else process.stderr.write("\n");
260
+ }
261
+ /** ANSI escape: move cursor up N lines. */
262
+ function cursorUp(n) {
263
+ return `\u001B[${n}A`;
264
+ }
265
+ /** ANSI escape: move cursor down N lines. */
266
+ function cursorDown(n) {
267
+ return `\u001B[${n}B`;
268
+ }
269
+ /** Animate a single line in-place while content continues to print below it. */
270
+ function createSpinner(frames, intervalMs) {
271
+ let offset = NO_OFFSET;
272
+ let frameIndex = NO_OFFSET;
273
+ let timer = void 0;
274
+ function render() {
275
+ if (offset > NO_OFFSET) process.stderr.write(cursorUp(offset));
276
+ process.stderr.write(`${CLEAR_LINE}${INDENT}${frames[frameIndex]}`);
277
+ if (offset > NO_OFFSET) process.stderr.write(`${cursorDown(offset)}\r`);
278
+ frameIndex++;
279
+ if (frameIndex >= frames.length) frameIndex = NO_OFFSET;
280
+ }
281
+ return {
282
+ blank() {
283
+ blank();
284
+ offset++;
285
+ },
286
+ start() {
287
+ render();
288
+ process.stderr.write("\n");
289
+ offset++;
290
+ timer = globalThis.setInterval(render, intervalMs);
291
+ },
292
+ stop() {
293
+ if (timer) {
294
+ globalThis.clearInterval(timer);
295
+ timer = void 0;
296
+ }
297
+ },
298
+ write(message) {
299
+ write(message);
300
+ offset++;
301
+ }
302
+ };
303
+ }
304
+
305
+ //#endregion
306
+ //#region src/lib/lifecycle.ts
307
+ /** Exit code indicating failure. */
308
+ const EXIT_FAILURE = 1;
309
+ /** Grace period (ms) before force-killing on shutdown. */
310
+ const SHUTDOWN_TIMEOUT_MS = 2e3;
311
+ /**
312
+ * Create a lifecycle controller that manages SIGINT handling and graceful shutdown.
313
+ *
314
+ * Registers a one-shot SIGINT handler on creation. All shutdown state is
315
+ * encapsulated — callers just check `done()` and call `shutdown()`.
316
+ */
317
+ function createLifecycle(beam, spinner) {
318
+ let isShuttingDown = false;
319
+ function shutdown() {
320
+ if (isShuttingDown) return;
321
+ isShuttingDown = true;
322
+ spinner?.stop();
323
+ log(dim$1("SHUTTING DOWN"));
324
+ blank();
325
+ const timeout = globalThis.setTimeout(() => {
326
+ process.exit(EXIT_FAILURE);
327
+ }, SHUTDOWN_TIMEOUT_MS);
328
+ beam.destroy();
329
+ beam.on("close", () => {
330
+ globalThis.clearTimeout(timeout);
331
+ });
332
+ }
333
+ process.once("SIGINT", () => {
334
+ clearLine();
335
+ shutdown();
336
+ });
337
+ return {
338
+ done() {
339
+ return isShuttingDown;
340
+ },
341
+ shutdown
342
+ };
343
+ }
344
+
345
+ //#endregion
346
+ //#region src/lib/pulse.ts
347
+ /** Delay between spinner frames (ms). */
348
+ const INTERVAL_MS = 250;
349
+ /** Frame index of the first peak in the pulse animation. */
350
+ const PEAK_A = 4;
351
+ /** Frame index of the second peak in the pulse animation. */
352
+ const PEAK_B = 12;
353
+ /** Wrapped peak used to calculate distance for the last frames in the loop. */
354
+ const PEAK_WRAP = 20;
355
+ /** Distance from peak at which the frame is rendered at full brightness. */
356
+ const DIST_PEAK = 0;
357
+ /** Distance from peak at which the frame is rendered at normal brightness. */
358
+ const DIST_NEAR = 1;
359
+ /** Raw dot-pulse frames that sweep left-to-right and back. */
360
+ const RAW_FRAMES = [
361
+ "· ",
362
+ "·· ",
363
+ "··· ",
364
+ "···· ",
365
+ "·····",
366
+ " ····",
367
+ " ···",
368
+ " ··",
369
+ " ·",
370
+ " ··",
371
+ " ···",
372
+ " ····",
373
+ "·····",
374
+ "···· ",
375
+ "··· ",
376
+ "·· "
377
+ ];
378
+ /**
379
+ * Generate the styled spinner frames for the HBEAM pulse animation.
380
+ *
381
+ * Each frame is rendered with a brightness gradient: bold at peak,
382
+ * normal near peak, and dim everywhere else.
383
+ *
384
+ * @param label - The text label to prefix each frame (e.g. "HBEAM").
385
+ * @returns An object with the styled `frames` array and `intervalMs` timing.
386
+ */
387
+ function createPulseFrames(label) {
388
+ return {
389
+ frames: RAW_FRAMES.map((s, i) => {
390
+ const distanceToPeak = Math.min(Math.abs(i - PEAK_A), Math.abs(i - PEAK_B), Math.abs(i - PEAK_WRAP));
391
+ let glyph = dim(s);
392
+ if (distanceToPeak === DIST_PEAK) glyph = bold(s);
393
+ else if (distanceToPeak === DIST_NEAR) glyph = s;
394
+ return `${bold(label)} ${glyph}`;
395
+ }),
396
+ intervalMs: INTERVAL_MS
397
+ };
398
+ }
399
+
400
+ //#endregion
401
+ //#region src/cli.ts
402
+ const ARGV_OFFSET = 2;
403
+ const EXIT_SUCCESS = 0;
404
+ const START_INDEX = 0;
405
+ const PASSPHRASE_PREVIEW_LENGTH = 8;
406
+ const argv = mri(process.argv.slice(ARGV_OFFSET), {
407
+ alias: {
408
+ h: "help",
409
+ r: "reuse",
410
+ v: "version"
411
+ },
412
+ boolean: [
413
+ "help",
414
+ "reuse",
415
+ "version"
416
+ ]
417
+ });
418
+ if (argv.help) {
419
+ writeBlock([
420
+ `${bold$1("hbeam")} — end-to-end encrypted pipe over HyperDHT`,
421
+ "",
422
+ `${bold$1("Usage:")}`,
423
+ ` hbeam ${dim$1("[passphrase]")} ${dim$1("[options]")}`,
424
+ "",
425
+ `${bold$1("Options:")}`,
426
+ ` ${dim$1("-r, --reuse")} Announce with an existing passphrase (restart server side)`,
427
+ ` ${dim$1("-h, --help")} Show this help`,
428
+ ` ${dim$1("-v, --version")} Show version`,
429
+ "",
430
+ `${bold$1("Examples:")}`,
431
+ ` ${dim$1("# Start a new pipe (generates a passphrase)")}`,
432
+ " echo 'hello' | hbeam",
433
+ "",
434
+ ` ${dim$1("# Connect to an existing pipe")}`,
435
+ " hbeam <passphrase>",
436
+ "",
437
+ ` ${dim$1("# Restart the server side with the same key")}`,
438
+ " echo 'hello again' | hbeam <passphrase> --reuse"
439
+ ]);
440
+ process.exit(EXIT_SUCCESS);
441
+ }
442
+ if (argv.version) {
443
+ write((await import("./package-BV0bMouL.mjs")).version ?? "0.0.0");
444
+ process.exit(EXIT_SUCCESS);
445
+ }
446
+ const [passphrase] = argv._;
447
+ const beam = new Beam(passphrase, argv.reuse ? { announce: true } : void 0);
448
+ const { frames, intervalMs } = createPulseFrames("HBEAM");
449
+ const spinner = beam.announce ? createSpinner(frames, intervalMs) : void 0;
450
+ const lifecycle = createLifecycle(beam, spinner);
451
+ if (beam.announce) {
452
+ blank();
453
+ spinner.start();
454
+ spinner.blank();
455
+ spinner.write(dim$1("PASSPHRASE"));
456
+ spinner.write(cyan(beam.key));
457
+ } else log(dim$1(`CONNECTING: ${cyan(passphrase ? passphrase.slice(START_INDEX, PASSPHRASE_PREVIEW_LENGTH) : "unknown")}...`));
458
+ beam.on("remote-address", ({ host, port }) => {
459
+ if (lifecycle.done()) return;
460
+ if (host) if (spinner) {
461
+ spinner.write(dim$1(`ONLINE ${gray(`[${host}:${port}]`)}`));
462
+ spinner.blank();
463
+ } else {
464
+ write(dim$1(`ONLINE ${gray(`[${host}:${port}]`)}`));
465
+ blank();
466
+ }
467
+ });
468
+ beam.on("connected", () => {
469
+ if (lifecycle.done()) return;
470
+ spinner?.stop();
471
+ log(bold$1("PIPE ACTIVE"));
472
+ write(gray("CTRL+C TO TERMINATE"));
473
+ blank();
474
+ });
475
+ beam.on("error", (error) => {
476
+ logError(`Error: ${error.message}`);
477
+ lifecycle.shutdown();
478
+ });
479
+ beam.on("end", () => beam.end());
480
+ process.stdin.pipe(beam).pipe(process.stdout);
481
+ if (typeof process.stdin.unref === "function") process.stdin.unref();
482
+
483
+ //#endregion
484
+ export { };
485
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":["dim","bold","dim"],"sources":["../src/lib/encoding.ts","../src/lib/dht.ts","../src/beam.ts","../src/lib/log.ts","../src/lib/lifecycle.ts","../src/lib/pulse.ts","../src/cli.ts"],"sourcesContent":["import * as b4a from 'b4a'\nimport b32 from 'hi-base32'\nimport sodium from 'sodium-universal'\n\n/** Encode a buffer as a lowercase base32 string without padding. */\nexport function toBase32(buf: Buffer): string {\n\treturn b32.encode(buf).replace(/=/g, '').toLowerCase()\n}\n\n/** Decode a base32 string back into a raw Buffer. */\nexport function fromBase32(str: string): Buffer {\n\treturn b4a.from(b32.decode.asBytes(str.toUpperCase()))\n}\n\n/** Generate cryptographically secure random bytes. */\nexport function randomBytes(length: number): Buffer {\n\tconst buffer = b4a.alloc(length)\n\tsodium.randombytes_buf(buffer)\n\treturn buffer\n}\n","import * as b4a from 'b4a'\nimport DHT from 'hyperdht'\n\nimport { fromBase32 } from './encoding.ts'\n\nimport type { EncryptedSocket, HyperDHTNode, KeyPair } from '../types.ts'\n\n/** Derive a Noise keypair from a base32-encoded passphrase. */\nexport function deriveKeyPair(passphrase: string): KeyPair {\n\treturn DHT.keyPair(fromBase32(passphrase))\n}\n\n/** Create an ephemeral HyperDHT node that is destroyed with the beam. */\nexport function createNode(): HyperDHTNode {\n\treturn new DHT({ ephemeral: true }) as unknown as HyperDHTNode\n}\n\n/** Wait for an encrypted socket to complete its Noise handshake. */\nexport function awaitOpen(socket: EncryptedSocket): Promise<void> {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tsocket.once('open', resolve)\n\t\tsocket.once('close', reject)\n\t\tsocket.once('error', reject)\n\t})\n}\n\n/** Create a firewall that rejects any connection not matching the keypair. */\nexport function createFirewall(keyPair: KeyPair): (remotePublicKey: Buffer) => boolean {\n\treturn (remotePublicKey: Buffer) => !b4a.equals(remotePublicKey, keyPair.publicKey)\n}\n","import queueTick from 'queue-tick'\nimport { Duplex } from 'streamx'\n\nimport { awaitOpen, createFirewall, createNode, deriveKeyPair } from './lib/dht.ts'\nimport { randomBytes, toBase32 } from './lib/encoding.ts'\n\nimport type {\n\tBeamOptions,\n\tConnectionInfo,\n\tEncryptedSocket,\n\tHyperDHTNode,\n\tHyperDHTServer,\n\tKeyPair,\n\tStreamCallback,\n} from './types.ts'\n\n/** Number of random bytes used to generate a passphrase seed. */\nconst KEY_SEED_BYTES = 32\n\n/**\n * A 1-to-1 end-to-end encrypted duplex stream powered by HyperDHT.\n *\n * Creates an encrypted tunnel between two peers using a shared passphrase.\n * If no passphrase is provided, one is generated and the beam listens for\n * an incoming connection (server mode). When a passphrase is provided, the\n * beam connects to the listening peer (client mode).\n *\n * @example\n * ```ts\n * const server = new Beam()\n * console.log(server.key) // Share this with the other side\n *\n * const client = new Beam(server.key)\n * ```\n */\nexport class Beam extends Duplex {\n\t/** Base32-encoded passphrase for peer discovery and key derivation. */\n\treadonly key: string\n\n\t/** Whether this beam is announcing (server) or connecting (client). */\n\treadonly announce: boolean\n\n\tprivate node: HyperDHTNode | undefined\n\tprivate server: HyperDHTServer | undefined = undefined\n\tprivate inbound: EncryptedSocket | undefined = undefined\n\tprivate outbound: EncryptedSocket | undefined = undefined\n\n\tprivate openCallback: StreamCallback | undefined = undefined\n\tprivate readCallback: StreamCallback | undefined = undefined\n\tprivate drainCallback: StreamCallback | undefined = undefined\n\n\tconstructor(keyOrOptions?: string | BeamOptions, options?: BeamOptions) {\n\t\tsuper()\n\n\t\tlet key: string | undefined = undefined\n\t\tlet opts: BeamOptions = {}\n\n\t\tif (typeof keyOrOptions === 'string') {\n\t\t\tkey = keyOrOptions\n\t\t\topts = options ?? {}\n\t\t} else {\n\t\t\topts = keyOrOptions ?? {}\n\t\t}\n\n\t\tlet shouldAnnounce = opts.announce ?? false\n\t\tif (!key) {\n\t\t\tkey = toBase32(randomBytes(KEY_SEED_BYTES))\n\t\t\tshouldAnnounce = true\n\t\t}\n\n\t\tthis.key = key\n\t\tthis.announce = shouldAnnounce\n\t\tthis.node = (opts.dht as HyperDHTNode) ?? undefined\n\t}\n\n\t/** Whether a peer connection has been established. */\n\tget connected(): boolean {\n\t\treturn this.outbound !== undefined\n\t}\n\n\t// Streamx lifecycle\n\n\toverride async _open(cb: StreamCallback): Promise<void> {\n\t\tthis.openCallback = cb\n\t\tconst keyPair = deriveKeyPair(this.key)\n\t\tthis.node ??= createNode()\n\n\t\tif (this.announce) {\n\t\t\tawait this.listenAsServer(keyPair)\n\t\t} else {\n\t\t\tawait this.connectAsClient(keyPair)\n\t\t}\n\t}\n\n\toverride _read(cb: StreamCallback): void {\n\t\tthis.readCallback = cb\n\t\tthis.inbound?.resume()\n\t}\n\n\toverride _write(data: unknown, cb: StreamCallback): void {\n\t\tif (this.outbound!.write(data as Buffer) !== false) {\n\t\t\tcb()\n\t\t\treturn\n\t\t}\n\t\tthis.drainCallback = cb\n\t}\n\n\toverride _final(cb: StreamCallback): void {\n\t\tconst done = (): void => {\n\t\t\tthis.outbound!.removeListener('finish', done)\n\t\t\tthis.outbound!.removeListener('error', done)\n\t\t\tcb()\n\t\t}\n\t\tthis.outbound!.end()\n\t\tthis.outbound!.on('finish', done)\n\t\tthis.outbound!.on('error', done)\n\t}\n\n\toverride _predestroy(): void {\n\t\tthis.inbound?.destroy()\n\t\tthis.outbound?.destroy()\n\t\tconst error = new Error('Destroyed')\n\t\tthis.resolveOpen(error)\n\t\tthis.resolveRead(error)\n\t\tthis.resolveDrain(error)\n\t}\n\n\toverride async _destroy(cb: StreamCallback): Promise<void> {\n\t\tif (!this.node) {\n\t\t\tcb()\n\t\t\treturn\n\t\t}\n\t\tif (this.server) {\n\t\t\tawait this.server.close().catch(() => {})\n\t\t}\n\t\tawait this.node.destroy().catch(() => {})\n\t\tcb()\n\t}\n\n\t// Connection setup\n\n\tprivate async listenAsServer(keyPair: KeyPair): Promise<void> {\n\t\tthis.server = this.node!.createServer({ firewall: createFirewall(keyPair) })\n\t\tthis.server.on('connection', (socket: EncryptedSocket) => this.handleConnection(socket))\n\n\t\ttry {\n\t\t\tawait this.server.listen(keyPair)\n\t\t} catch (error) {\n\t\t\tthis.resolveOpen(error as Error)\n\t\t\treturn\n\t\t}\n\t\tthis.emitRemoteAddress()\n\t}\n\n\tprivate async connectAsClient(keyPair: KeyPair): Promise<void> {\n\t\tconst socket: EncryptedSocket = this.node!.connect(keyPair.publicKey, { keyPair })\n\n\t\ttry {\n\t\t\tawait awaitOpen(socket)\n\t\t} catch (error) {\n\t\t\tthis.resolveOpen(error as Error)\n\t\t\treturn\n\t\t}\n\t\tthis.emitRemoteAddress()\n\t\tthis.handleConnection(socket)\n\t}\n\n\tprivate handleConnection(socket: EncryptedSocket): void {\n\t\tsocket.on('data', (data: Buffer) => {\n\t\t\tif (!this.inbound) {\n\t\t\t\tthis.inbound = socket\n\t\t\t\tthis.inbound.on('error', (err: Error) => this.destroy(err))\n\t\t\t\tthis.inbound.on('end', () => this.pushEndOfStream())\n\t\t\t}\n\t\t\tif (socket !== this.inbound) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.pushData(data) === false) {\n\t\t\t\tsocket.pause()\n\t\t\t}\n\t\t})\n\n\t\tsocket.on('end', () => {\n\t\t\tif (this.inbound) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pushEndOfStream()\n\t\t})\n\n\t\tif (!this.outbound) {\n\t\t\tthis.outbound = socket\n\t\t\tthis.outbound.on('error', (err: Error) => this.destroy(err))\n\t\t\tthis.outbound.on('drain', () => this.resolveDrain())\n\t\t\tthis.emit('connected')\n\t\t\tthis.resolveOpen()\n\t\t}\n\t}\n\n\t// Helpers\n\n\tprivate pushData(data: Buffer | null): boolean {\n\t\tconst result = this.push(data)\n\t\tqueueTick(() => this.resolveRead())\n\t\treturn result\n\t}\n\n\tprivate pushEndOfStream(): void {\n\t\t// oxlint-disable-next-line unicorn/no-null\n\t\tthis.pushData(null)\n\t}\n\n\tprivate emitRemoteAddress(): void {\n\t\tthis.emit('remote-address', {\n\t\t\thost: this.node!.host,\n\t\t\tport: this.node!.port,\n\t\t} satisfies ConnectionInfo)\n\t}\n\n\tprivate resolveOpen(error?: Error): void {\n\t\tconst cb = this.openCallback\n\t\tif (cb) {\n\t\t\tthis.openCallback = undefined\n\t\t\tcb(error)\n\t\t}\n\t}\n\n\tprivate resolveRead(error?: Error): void {\n\t\tconst cb = this.readCallback\n\t\tif (cb) {\n\t\t\tthis.readCallback = undefined\n\t\t\tcb(error)\n\t\t}\n\t}\n\n\tprivate resolveDrain(error?: Error): void {\n\t\tconst cb = this.drainCallback\n\t\tif (cb) {\n\t\t\tthis.drainCallback = undefined\n\t\t\tcb(error)\n\t\t}\n\t}\n}\n","import { red, yellow } from 'colorette'\n\nexport { bold, cyan, dim, gray, green, italic, red, yellow } from 'colorette'\n\nconst INDENT = ' '\n\n/** ANSI escape: clear the entire current line and reset cursor to column 0. */\nconst CLEAR_LINE = '\\r\\u001B[2K'\n\n/** Zero offset — cursor is on the spinner line itself. */\nconst NO_OFFSET = 0\n\n/** Write a line to stderr at the standard indent level. */\nexport function write(message: string): void {\n\tprocess.stderr.write(`${INDENT}${message}\\n`)\n}\n\n/** Write a blank line to stderr. */\nexport function blank(): void {\n\tprocess.stderr.write('\\n')\n}\n\n/** Write a pre-formatted block (multiple lines) to stderr. */\nexport function writeBlock(lines: string[]): void {\n\tfor (const line of lines) {\n\t\tprocess.stderr.write(`${INDENT}${line}\\n`)\n\t}\n}\n\n/** Write a status message to stderr at the standard indent level. */\nexport function log(message: string): void {\n\twrite(message)\n}\n\n/** Write an error message to stderr with a red prefix. */\nexport function logError(message: string): void {\n\tprocess.stderr.write(`${red('✖')} ${message}\\n`)\n}\n\n/** Write a warning/notice message to stderr with a yellow prefix. */\nexport function logWarn(message: string): void {\n\tprocess.stderr.write(`${yellow('!')} ${message}\\n`)\n}\n\n/** Clear the current line (wipe terminal-echoed ^C, etc.). Falls back to a newline on non-TTY. */\nexport function clearLine(): void {\n\tif (process.stderr.isTTY) {\n\t\tprocess.stderr.write(CLEAR_LINE)\n\t} else {\n\t\tprocess.stderr.write('\\n')\n\t}\n}\n\n// -- Spinner ----------------------------------------------------------------\n\n/** ANSI escape: move cursor up N lines. */\nfunction cursorUp(n: number): string {\n\treturn `\\u001B[${n}A`\n}\n\n/** ANSI escape: move cursor down N lines. */\nfunction cursorDown(n: number): string {\n\treturn `\\u001B[${n}B`\n}\n\n/** Handle for a line that animates in-place while content prints below. */\nexport interface Spinner {\n\t/** Write a blank line below the spinner and track the cursor offset. */\n\tblank(): void\n\t/** Render the first frame and begin the animation loop. */\n\tstart(): void\n\t/** Stop the animation loop. */\n\tstop(): void\n\t/** Write an indented line below the spinner and track the cursor offset. */\n\twrite(message: string): void\n}\n\n/** Animate a single line in-place while content continues to print below it. */\nexport function createSpinner(frames: readonly string[], intervalMs: number): Spinner {\n\tlet offset = NO_OFFSET\n\tlet frameIndex = NO_OFFSET\n\tlet timer: ReturnType<typeof globalThis.setInterval> | undefined = undefined\n\n\tfunction render(): void {\n\t\tif (offset > NO_OFFSET) {\n\t\t\tprocess.stderr.write(cursorUp(offset))\n\t\t}\n\t\tprocess.stderr.write(`${CLEAR_LINE}${INDENT}${frames[frameIndex]}`)\n\t\tif (offset > NO_OFFSET) {\n\t\t\tprocess.stderr.write(`${cursorDown(offset)}\\r`)\n\t\t}\n\t\tframeIndex++\n\t\tif (frameIndex >= frames.length) {\n\t\t\tframeIndex = NO_OFFSET\n\t\t}\n\t}\n\n\treturn {\n\t\tblank(): void {\n\t\t\tblank()\n\t\t\toffset++\n\t\t},\n\t\tstart(): void {\n\t\t\trender()\n\t\t\tprocess.stderr.write('\\n')\n\t\t\toffset++\n\t\t\ttimer = globalThis.setInterval(render, intervalMs)\n\t\t},\n\t\tstop(): void {\n\t\t\tif (timer) {\n\t\t\t\tglobalThis.clearInterval(timer)\n\t\t\t\ttimer = undefined\n\t\t\t}\n\t\t},\n\t\twrite(message: string): void {\n\t\t\twrite(message)\n\t\t\toffset++\n\t\t},\n\t}\n}\n","import { blank, clearLine, dim, log } from './log.ts'\n\nimport type { Beam } from '../beam.ts'\n\n/** Exit code indicating failure. */\nconst EXIT_FAILURE = 1\n\n/** Grace period (ms) before force-killing on shutdown. */\nconst SHUTDOWN_TIMEOUT_MS = 2000\n\n/** Controller for graceful shutdown of a beam session. */\nexport interface Lifecycle {\n\t/** Returns true if shutdown is in progress (use as an early-return guard). */\n\tdone(): boolean\n\t/** Tear down the beam, stop the spinner, and exit after a grace period. */\n\tshutdown(): void\n}\n\n/**\n * Create a lifecycle controller that manages SIGINT handling and graceful shutdown.\n *\n * Registers a one-shot SIGINT handler on creation. All shutdown state is\n * encapsulated — callers just check `done()` and call `shutdown()`.\n */\nexport function createLifecycle(beam: Beam, spinner?: { stop(): void }): Lifecycle {\n\tlet isShuttingDown = false\n\n\tfunction shutdown(): void {\n\t\tif (isShuttingDown) {\n\t\t\treturn\n\t\t}\n\t\tisShuttingDown = true\n\t\tspinner?.stop()\n\n\t\tlog(dim('SHUTTING DOWN'))\n\t\tblank()\n\n\t\tconst timeout = globalThis.setTimeout(() => {\n\t\t\tprocess.exit(EXIT_FAILURE)\n\t\t}, SHUTDOWN_TIMEOUT_MS)\n\n\t\tbeam.destroy()\n\t\tbeam.on('close', () => {\n\t\t\tglobalThis.clearTimeout(timeout)\n\t\t})\n\t}\n\n\tprocess.once('SIGINT', () => {\n\t\tclearLine()\n\t\tshutdown()\n\t})\n\n\treturn {\n\t\tdone(): boolean {\n\t\t\treturn isShuttingDown\n\t\t},\n\t\tshutdown,\n\t}\n}\n","import { bold, dim } from 'colorette'\n\n/** Delay between spinner frames (ms). */\nconst INTERVAL_MS = 250\n\n/** Frame index of the first peak in the pulse animation. */\nconst PEAK_A = 4\n\n/** Frame index of the second peak in the pulse animation. */\nconst PEAK_B = 12\n\n/** Wrapped peak used to calculate distance for the last frames in the loop. */\nconst PEAK_WRAP = 20\n\n/** Distance from peak at which the frame is rendered at full brightness. */\nconst DIST_PEAK = 0\n\n/** Distance from peak at which the frame is rendered at normal brightness. */\nconst DIST_NEAR = 1\n\n/** Raw dot-pulse frames that sweep left-to-right and back. */\nconst RAW_FRAMES: readonly string[] = [\n\t'· ',\n\t'·· ',\n\t'··· ',\n\t'···· ',\n\t'·····',\n\t' ····',\n\t' ···',\n\t' ··',\n\t' ·',\n\t' ··',\n\t' ···',\n\t' ····',\n\t'·····',\n\t'···· ',\n\t'··· ',\n\t'·· ',\n]\n\n/**\n * Generate the styled spinner frames for the HBEAM pulse animation.\n *\n * Each frame is rendered with a brightness gradient: bold at peak,\n * normal near peak, and dim everywhere else.\n *\n * @param label - The text label to prefix each frame (e.g. \"HBEAM\").\n * @returns An object with the styled `frames` array and `intervalMs` timing.\n */\nexport function createPulseFrames(label: string): { frames: string[]; intervalMs: number } {\n\tconst frames = RAW_FRAMES.map((s, i) => {\n\t\tconst distanceToPeak = Math.min(\n\t\t\tMath.abs(i - PEAK_A),\n\t\t\tMath.abs(i - PEAK_B),\n\t\t\tMath.abs(i - PEAK_WRAP),\n\t\t)\n\t\tlet glyph = dim(s)\n\t\tif (distanceToPeak === DIST_PEAK) {\n\t\t\tglyph = bold(s)\n\t\t} else if (distanceToPeak === DIST_NEAR) {\n\t\t\tglyph = s\n\t\t}\n\t\treturn `${bold(label)} ${glyph}`\n\t})\n\n\treturn { frames, intervalMs: INTERVAL_MS }\n}\n","#!/usr/bin/env node\n\nimport mri from 'mri'\n\nimport { Beam } from './beam.ts'\nimport { createLifecycle } from './lib/lifecycle.ts'\nimport {\n\tblank,\n\tbold,\n\tcreateSpinner,\n\tcyan,\n\tdim,\n\tgray,\n\tlog,\n\tlogError,\n\twrite,\n\twriteBlock,\n} from './lib/log.ts'\nimport { createPulseFrames } from './lib/pulse.ts'\n\nimport type { BeamOptions, ConnectionInfo } from './types.ts'\n\nconst ARGV_OFFSET = 2\nconst EXIT_SUCCESS = 0\nconst START_INDEX = 0\nconst PASSPHRASE_PREVIEW_LENGTH = 8\n\nconst argv = mri(process.argv.slice(ARGV_OFFSET), {\n\talias: { h: 'help', r: 'reuse', v: 'version' },\n\tboolean: ['help', 'reuse', 'version'],\n})\n\nif (argv.help) {\n\twriteBlock([\n\t\t`${bold('hbeam')} — end-to-end encrypted pipe over HyperDHT`,\n\t\t'',\n\t\t`${bold('Usage:')}`,\n\t\t` hbeam ${dim('[passphrase]')} ${dim('[options]')}`,\n\t\t'',\n\t\t`${bold('Options:')}`,\n\t\t` ${dim('-r, --reuse')} Announce with an existing passphrase (restart server side)`,\n\t\t` ${dim('-h, --help')} Show this help`,\n\t\t` ${dim('-v, --version')} Show version`,\n\t\t'',\n\t\t`${bold('Examples:')}`,\n\t\t` ${dim('# Start a new pipe (generates a passphrase)')}`,\n\t\t\" echo 'hello' | hbeam\",\n\t\t'',\n\t\t` ${dim('# Connect to an existing pipe')}`,\n\t\t' hbeam <passphrase>',\n\t\t'',\n\t\t` ${dim('# Restart the server side with the same key')}`,\n\t\t\" echo 'hello again' | hbeam <passphrase> --reuse\",\n\t])\n\tprocess.exit(EXIT_SUCCESS)\n}\n\nif (argv.version) {\n\tconst pkg = (await import('../package.json')) as { version?: string }\n\twrite(pkg.version ?? '0.0.0')\n\tprocess.exit(EXIT_SUCCESS)\n}\n\nconst [passphrase] = argv._ as string[]\nconst beamOptions: BeamOptions | undefined = argv.reuse ? { announce: true } : undefined\nconst beam = new Beam(passphrase, beamOptions)\n\nconst { frames, intervalMs } = createPulseFrames('HBEAM')\nconst spinner = beam.announce ? createSpinner(frames, intervalMs) : undefined\nconst lifecycle = createLifecycle(beam, spinner)\n\nif (beam.announce) {\n\tblank()\n\tspinner!.start()\n\tspinner!.blank()\n\tspinner!.write(dim('PASSPHRASE'))\n\tspinner!.write(cyan(beam.key))\n} else {\n\tconst displayKey = passphrase\n\t\t? passphrase.slice(START_INDEX, PASSPHRASE_PREVIEW_LENGTH)\n\t\t: 'unknown'\n\tlog(dim(`CONNECTING: ${cyan(displayKey)}...`))\n}\n\nbeam.on('remote-address', ({ host, port }: ConnectionInfo) => {\n\tif (lifecycle.done()) {\n\t\treturn\n\t}\n\tif (host) {\n\t\tif (spinner) {\n\t\t\tspinner.write(dim(`ONLINE ${gray(`[${host}:${port}]`)}`))\n\t\t\tspinner.blank()\n\t\t} else {\n\t\t\twrite(dim(`ONLINE ${gray(`[${host}:${port}]`)}`))\n\t\t\tblank()\n\t\t}\n\t}\n})\n\nbeam.on('connected', () => {\n\tif (lifecycle.done()) {\n\t\treturn\n\t}\n\tspinner?.stop()\n\tlog(bold('PIPE ACTIVE'))\n\twrite(gray('CTRL+C TO TERMINATE'))\n\tblank()\n})\n\nbeam.on('error', (error: Error) => {\n\tlogError(`Error: ${error.message}`)\n\tlifecycle.shutdown()\n})\n\nbeam.on('end', () => beam.end())\n\nprocess.stdin.pipe(beam).pipe(process.stdout)\n\nif (typeof process.stdin.unref === 'function') {\n\tprocess.stdin.unref()\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ //#region package.json
2
+ var version = "0.1.0-alpha.1";
3
+
4
+ //#endregion
5
+ export { version };
6
+ //# sourceMappingURL=package-BV0bMouL.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-BV0bMouL.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "hbeam",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "A 1-to-1 end-to-end encrypted pipe over HyperDHT.",
5
+ "keywords": [
6
+ "cli",
7
+ "dht",
8
+ "encryption",
9
+ "hyperdht",
10
+ "p2p"
11
+ ],
12
+ "license": "MIT",
13
+ "bin": {
14
+ "hbeam": "./dist/cli.mjs"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "type": "module",
21
+ "scripts": {
22
+ "build": "tsdown src/cli.ts --format esm --platform node --out-dir dist --sourcemap --clean --external sodium-universal --external udx-native",
23
+ "check": "bun run scripts/checks",
24
+ "fmt": "oxfmt",
25
+ "lint": "oxlint",
26
+ "prepack": "bun run build",
27
+ "prepare": "test -w .git/config && husky || true",
28
+ "pub": "bun run scripts/repo/publish",
29
+ "prepublishOnly": "bun run check && bun run build",
30
+ "test": "bun test"
31
+ },
32
+ "dependencies": {
33
+ "b4a": "^1.6.7",
34
+ "colorette": "^2.0.20",
35
+ "hi-base32": "^0.5.1",
36
+ "hyperdht": "^6.20.5",
37
+ "mri": "^1.2.0",
38
+ "queue-tick": "^1.0.1",
39
+ "sodium-universal": "^5.0.1",
40
+ "streamx": "^2.22.0"
41
+ },
42
+ "devDependencies": {
43
+ "@inquirer/prompts": "^8.2.0",
44
+ "@types/bun": "latest",
45
+ "husky": "^9.1.7",
46
+ "lint-staged": "^16.2.7",
47
+ "oxfmt": "^0.28.0",
48
+ "oxlint": "^1.43.0",
49
+ "tsdown": "^0.20.3"
50
+ },
51
+ "peerDependencies": {
52
+ "typescript": "^5"
53
+ },
54
+ "engines": {
55
+ "node": ">=20"
56
+ }
57
+ }