hbeam 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -5
- package/dist/cli.mjs +119 -33
- package/dist/cli.mjs.map +1 -1
- package/dist/package-DH5DapXH.mjs +6 -0
- package/dist/package-DH5DapXH.mjs.map +1 -0
- package/package.json +2 -2
- package/dist/package-ByG93psB.mjs +0 -6
- package/dist/package-ByG93psB.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,15 +1,69 @@
|
|
|
1
1
|
# hbeam
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A 1-to-1 end-to-end encrypted pipe over [HyperDHT](https://github.com/holepunchto/hyperdht).
|
|
4
|
+
|
|
5
|
+
Pipe data between two machines through a peer-to-peer encrypted tunnel. No server, no accounts — just a shared passphrase.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g hbeam
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## CLI
|
|
14
|
+
|
|
15
|
+
### Send
|
|
16
|
+
|
|
17
|
+
Pipe data in and hbeam generates a passphrase (copied to your clipboard):
|
|
4
18
|
|
|
5
19
|
```bash
|
|
6
|
-
|
|
20
|
+
echo 'hello world' | hbeam
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
HBEAM ·····
|
|
25
|
+
PASSPHRASE
|
|
26
|
+
nbsk4wlqmfuw...
|
|
7
27
|
```
|
|
8
28
|
|
|
9
|
-
|
|
29
|
+
### Receive
|
|
30
|
+
|
|
31
|
+
Pass the passphrase on the other machine to receive:
|
|
10
32
|
|
|
11
33
|
```bash
|
|
12
|
-
|
|
34
|
+
hbeam nbsk4wlqmfuw...
|
|
13
35
|
```
|
|
14
36
|
|
|
15
|
-
|
|
37
|
+
### Listen with a known passphrase
|
|
38
|
+
|
|
39
|
+
Re-use a specific passphrase with `--listen`:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
echo 'hello again' | hbeam <passphrase> --listen
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Options
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
-l, --listen Listen (announce) using the provided passphrase
|
|
49
|
+
-h, --help Show help
|
|
50
|
+
-v, --version Show version
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## How it works
|
|
54
|
+
|
|
55
|
+
1. A 32-byte random seed is generated and encoded as a base32 passphrase.
|
|
56
|
+
2. A Noise keypair is deterministically derived from the passphrase using `sodium-universal`.
|
|
57
|
+
3. An ephemeral HyperDHT node announces (server) or connects (client) using that keypair.
|
|
58
|
+
4. The Noise protocol negotiates an encrypted session between the two peers.
|
|
59
|
+
5. Data flows through a `streamx` duplex stream — stdin/stdout on the CLI, or any readable/writable in code.
|
|
60
|
+
|
|
61
|
+
All traffic is end-to-end encrypted. The DHT is only used for peer discovery; it never sees the plaintext.
|
|
62
|
+
|
|
63
|
+
## Requirements
|
|
64
|
+
|
|
65
|
+
- Node.js >= 20
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { Transform } from "node:stream";
|
|
2
3
|
import mri from "mri";
|
|
3
4
|
import queueTick from "queue-tick";
|
|
4
5
|
import { Duplex } from "streamx";
|
|
@@ -6,7 +7,8 @@ import * as b4a from "b4a";
|
|
|
6
7
|
import DHT from "hyperdht";
|
|
7
8
|
import b32 from "hi-base32";
|
|
8
9
|
import sodium from "sodium-universal";
|
|
9
|
-
import {
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { bold, bold as bold$1, cyan, dim, dim as dim$1, gray, red, red as red$1 } from "colorette";
|
|
10
12
|
|
|
11
13
|
//#region src/lib/encoding.ts
|
|
12
14
|
/** Encode a buffer as a lowercase base32 string without padding. */
|
|
@@ -226,16 +228,68 @@ var Beam = class extends Duplex {
|
|
|
226
228
|
}
|
|
227
229
|
};
|
|
228
230
|
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/lib/clipboard.ts
|
|
233
|
+
const EXIT_SUCCESS$1 = 0;
|
|
234
|
+
const EMPTY_ARGS = [];
|
|
235
|
+
const DARWIN_COMMANDS = [{
|
|
236
|
+
args: EMPTY_ARGS,
|
|
237
|
+
command: "pbcopy"
|
|
238
|
+
}];
|
|
239
|
+
const WINDOWS_COMMANDS = [{
|
|
240
|
+
args: EMPTY_ARGS,
|
|
241
|
+
command: "clip"
|
|
242
|
+
}];
|
|
243
|
+
const LINUX_COMMANDS = [
|
|
244
|
+
{
|
|
245
|
+
args: EMPTY_ARGS,
|
|
246
|
+
command: "wl-copy"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
args: ["-selection", "clipboard"],
|
|
250
|
+
command: "xclip"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
args: ["--clipboard", "--input"],
|
|
254
|
+
command: "xsel"
|
|
255
|
+
}
|
|
256
|
+
];
|
|
257
|
+
function getClipboardCommands() {
|
|
258
|
+
if (process.platform === "darwin") return DARWIN_COMMANDS;
|
|
259
|
+
if (process.platform === "win32") return WINDOWS_COMMANDS;
|
|
260
|
+
return LINUX_COMMANDS;
|
|
261
|
+
}
|
|
262
|
+
function tryClipboardCommand(text, item) {
|
|
263
|
+
const result = spawnSync(item.command, item.args, {
|
|
264
|
+
input: text,
|
|
265
|
+
stdio: [
|
|
266
|
+
"pipe",
|
|
267
|
+
"ignore",
|
|
268
|
+
"ignore"
|
|
269
|
+
]
|
|
270
|
+
});
|
|
271
|
+
return !result.error && result.status === EXIT_SUCCESS$1;
|
|
272
|
+
}
|
|
273
|
+
/** Copy text to the system clipboard using common platform commands. */
|
|
274
|
+
function copyToClipboard(text) {
|
|
275
|
+
for (const item of getClipboardCommands()) if (tryClipboardCommand(text, item)) return true;
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
229
279
|
//#endregion
|
|
230
280
|
//#region src/lib/log.ts
|
|
231
281
|
const INDENT = " ";
|
|
282
|
+
/** Width of the visual separator line. */
|
|
283
|
+
const SEPARATOR_WIDTH = 36;
|
|
284
|
+
/** A dim dashed line used to bracket piped content. */
|
|
285
|
+
const SEPARATOR = dim("╌".repeat(SEPARATOR_WIDTH));
|
|
232
286
|
/** ANSI escape: clear the entire current line and reset cursor to column 0. */
|
|
233
287
|
const CLEAR_LINE = "\r\x1B[2K";
|
|
234
288
|
/** Zero offset — cursor is on the spinner line itself. */
|
|
235
289
|
const NO_OFFSET = 0;
|
|
236
290
|
/** Write a line to stderr at the standard indent level. */
|
|
237
|
-
function write(message) {
|
|
238
|
-
process.stderr.write(`${
|
|
291
|
+
function write(message, indent = INDENT) {
|
|
292
|
+
process.stderr.write(`${indent}${message}\n`);
|
|
239
293
|
}
|
|
240
294
|
/** Write a blank line to stderr. */
|
|
241
295
|
function blank() {
|
|
@@ -249,9 +303,9 @@ function writeBlock(lines) {
|
|
|
249
303
|
function log(message) {
|
|
250
304
|
write(message);
|
|
251
305
|
}
|
|
252
|
-
/** Write an error message to stderr
|
|
306
|
+
/** Write an error message to stderr at the standard indent level. */
|
|
253
307
|
function logError(message) {
|
|
254
|
-
process.stderr.write(`${red("
|
|
308
|
+
process.stderr.write(`${INDENT}${red("ERROR")} ${message}\n`);
|
|
255
309
|
}
|
|
256
310
|
/** Clear the current line (wipe terminal-echoed ^C, etc.). Falls back to a newline on non-TTY. */
|
|
257
311
|
function clearLine() {
|
|
@@ -305,7 +359,7 @@ function createSpinner(frames, intervalMs) {
|
|
|
305
359
|
//#endregion
|
|
306
360
|
//#region src/lib/lifecycle.ts
|
|
307
361
|
/** Exit code indicating failure. */
|
|
308
|
-
const EXIT_FAILURE = 1;
|
|
362
|
+
const EXIT_FAILURE$1 = 1;
|
|
309
363
|
/** Grace period (ms) before force-killing on shutdown. */
|
|
310
364
|
const SHUTDOWN_TIMEOUT_MS = 2e3;
|
|
311
365
|
/**
|
|
@@ -323,7 +377,7 @@ function createLifecycle(beam, spinner) {
|
|
|
323
377
|
log(dim$1("SHUTTING DOWN"));
|
|
324
378
|
blank();
|
|
325
379
|
const timeout = globalThis.setTimeout(() => {
|
|
326
|
-
process.exit(EXIT_FAILURE);
|
|
380
|
+
process.exit(EXIT_FAILURE$1);
|
|
327
381
|
}, SHUTDOWN_TIMEOUT_MS);
|
|
328
382
|
beam.destroy();
|
|
329
383
|
beam.on("close", () => {
|
|
@@ -345,11 +399,11 @@ function createLifecycle(beam, spinner) {
|
|
|
345
399
|
//#endregion
|
|
346
400
|
//#region src/lib/pulse.ts
|
|
347
401
|
/** Delay between spinner frames (ms). */
|
|
348
|
-
const INTERVAL_MS =
|
|
402
|
+
const INTERVAL_MS = 125;
|
|
349
403
|
/** Frame index of the first peak in the pulse animation. */
|
|
350
|
-
const PEAK_A =
|
|
404
|
+
const PEAK_A = 5;
|
|
351
405
|
/** Frame index of the second peak in the pulse animation. */
|
|
352
|
-
const PEAK_B =
|
|
406
|
+
const PEAK_B = 13;
|
|
353
407
|
/** Wrapped peak used to calculate distance for the last frames in the loop. */
|
|
354
408
|
const PEAK_WRAP = 20;
|
|
355
409
|
/** Distance from peak at which the frame is rendered at full brightness. */
|
|
@@ -358,6 +412,7 @@ const DIST_PEAK = 0;
|
|
|
358
412
|
const DIST_NEAR = 1;
|
|
359
413
|
/** Raw dot-pulse frames that sweep left-to-right and back. */
|
|
360
414
|
const RAW_FRAMES = [
|
|
415
|
+
" ",
|
|
361
416
|
"· ",
|
|
362
417
|
"·· ",
|
|
363
418
|
"··· ",
|
|
@@ -373,7 +428,9 @@ const RAW_FRAMES = [
|
|
|
373
428
|
"·····",
|
|
374
429
|
"···· ",
|
|
375
430
|
"··· ",
|
|
376
|
-
"·· "
|
|
431
|
+
"·· ",
|
|
432
|
+
"· ",
|
|
433
|
+
" "
|
|
377
434
|
];
|
|
378
435
|
/**
|
|
379
436
|
* Generate the styled spinner frames for the HBEAM pulse animation.
|
|
@@ -401,17 +458,17 @@ function createPulseFrames(label) {
|
|
|
401
458
|
//#region src/cli.ts
|
|
402
459
|
const ARGV_OFFSET = 2;
|
|
403
460
|
const EXIT_SUCCESS = 0;
|
|
404
|
-
const
|
|
405
|
-
const
|
|
461
|
+
const EXIT_FAILURE = 1;
|
|
462
|
+
const NO_INDENT = "";
|
|
406
463
|
const argv = mri(process.argv.slice(ARGV_OFFSET), {
|
|
407
464
|
alias: {
|
|
408
465
|
h: "help",
|
|
409
|
-
|
|
466
|
+
l: "listen",
|
|
410
467
|
v: "version"
|
|
411
468
|
},
|
|
412
469
|
boolean: [
|
|
413
470
|
"help",
|
|
414
|
-
"
|
|
471
|
+
"listen",
|
|
415
472
|
"version"
|
|
416
473
|
]
|
|
417
474
|
});
|
|
@@ -423,7 +480,7 @@ if (argv.help) {
|
|
|
423
480
|
` hbeam ${dim$1("[passphrase]")} ${dim$1("[options]")}`,
|
|
424
481
|
"",
|
|
425
482
|
`${bold$1("Options:")}`,
|
|
426
|
-
` ${dim$1("-
|
|
483
|
+
` ${dim$1("-l, --listen")} Listen using the provided passphrase`,
|
|
427
484
|
` ${dim$1("-h, --help")} Show this help`,
|
|
428
485
|
` ${dim$1("-v, --version")} Show version`,
|
|
429
486
|
"",
|
|
@@ -434,50 +491,79 @@ if (argv.help) {
|
|
|
434
491
|
` ${dim$1("# Connect to an existing pipe")}`,
|
|
435
492
|
" hbeam <passphrase>",
|
|
436
493
|
"",
|
|
437
|
-
` ${dim$1("#
|
|
438
|
-
" echo 'hello again' | hbeam <passphrase> --
|
|
494
|
+
` ${dim$1("# Listen with a specific passphrase")}`,
|
|
495
|
+
" echo 'hello again' | hbeam <passphrase> --listen"
|
|
439
496
|
]);
|
|
440
497
|
process.exit(EXIT_SUCCESS);
|
|
441
498
|
}
|
|
442
499
|
if (argv.version) {
|
|
443
|
-
write((await import("./package-
|
|
500
|
+
write((await import("./package-DH5DapXH.mjs")).version ?? "0.0.0", NO_INDENT);
|
|
444
501
|
process.exit(EXIT_SUCCESS);
|
|
445
502
|
}
|
|
446
503
|
const [passphrase] = argv._;
|
|
447
|
-
|
|
504
|
+
if (argv.listen && !passphrase) {
|
|
505
|
+
logError("The --listen flag requires an existing passphrase.");
|
|
506
|
+
write(dim$1("Usage: hbeam <passphrase> --listen"));
|
|
507
|
+
process.exit(EXIT_FAILURE);
|
|
508
|
+
}
|
|
509
|
+
const beam = new Beam(passphrase, argv.listen ? { announce: true } : void 0);
|
|
448
510
|
const { frames, intervalMs } = createPulseFrames("HBEAM");
|
|
449
|
-
const spinner =
|
|
511
|
+
const spinner = createSpinner(frames, intervalMs);
|
|
450
512
|
const lifecycle = createLifecycle(beam, spinner);
|
|
513
|
+
blank();
|
|
514
|
+
spinner.start();
|
|
515
|
+
spinner.blank();
|
|
451
516
|
if (beam.announce) {
|
|
452
|
-
blank();
|
|
453
|
-
spinner.start();
|
|
454
|
-
spinner.blank();
|
|
455
517
|
spinner.write(dim$1("PASSPHRASE"));
|
|
456
518
|
spinner.write(cyan(beam.key));
|
|
457
|
-
|
|
519
|
+
copyToClipboard(beam.key);
|
|
520
|
+
} else {
|
|
521
|
+
spinner.write(dim$1("CONNECTING"));
|
|
522
|
+
spinner.write(cyan(passphrase ?? "unknown"));
|
|
523
|
+
}
|
|
458
524
|
beam.on("remote-address", ({ host, port }) => {
|
|
459
525
|
if (lifecycle.done()) return;
|
|
460
|
-
if (host)
|
|
526
|
+
if (host) {
|
|
461
527
|
spinner.write(dim$1(`ONLINE ${gray(`[${host}:${port}]`)}`));
|
|
462
528
|
spinner.blank();
|
|
463
|
-
} else {
|
|
464
|
-
write(dim$1(`ONLINE ${gray(`[${host}:${port}]`)}`));
|
|
465
|
-
blank();
|
|
466
529
|
}
|
|
467
530
|
});
|
|
468
531
|
beam.on("connected", () => {
|
|
469
532
|
if (lifecycle.done()) return;
|
|
470
|
-
spinner
|
|
533
|
+
spinner.stop();
|
|
471
534
|
log(bold$1("PIPE ACTIVE"));
|
|
472
535
|
write(gray("CTRL+C TO TERMINATE"));
|
|
473
536
|
blank();
|
|
474
537
|
});
|
|
475
538
|
beam.on("error", (error) => {
|
|
476
|
-
|
|
477
|
-
|
|
539
|
+
spinner.stop();
|
|
540
|
+
const isPeerNotFound = error.message.includes("PEER_NOT_FOUND");
|
|
541
|
+
if (isPeerNotFound) log(red$1(dim$1("PEER NOT FOUND")));
|
|
542
|
+
else if (error.message.includes("connection reset by peer")) log(dim$1("PEER DISCONNECTED"));
|
|
543
|
+
else logError(error.message);
|
|
544
|
+
blank();
|
|
545
|
+
if (!isPeerNotFound) lifecycle.shutdown();
|
|
478
546
|
});
|
|
479
547
|
beam.on("end", () => beam.end());
|
|
480
|
-
|
|
548
|
+
let receivedData = false;
|
|
549
|
+
const indent = new Transform({
|
|
550
|
+
flush(cb) {
|
|
551
|
+
if (receivedData) {
|
|
552
|
+
blank();
|
|
553
|
+
write(SEPARATOR);
|
|
554
|
+
}
|
|
555
|
+
cb(void 0, "\n");
|
|
556
|
+
},
|
|
557
|
+
transform(chunk, _encoding, cb) {
|
|
558
|
+
if (!receivedData) {
|
|
559
|
+
receivedData = true;
|
|
560
|
+
write(SEPARATOR);
|
|
561
|
+
blank();
|
|
562
|
+
}
|
|
563
|
+
cb(void 0, chunk.toString().replace(/^(?!$)/gm, INDENT));
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
process.stdin.pipe(beam).pipe(indent).pipe(process.stdout);
|
|
481
567
|
if (typeof process.stdin.unref === "function") process.stdin.unref();
|
|
482
568
|
|
|
483
569
|
//#endregion
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["EXIT_SUCCESS","EXIT_FAILURE","dim","bold","dim","red"],"sources":["../src/lib/encoding.ts","../src/lib/dht.ts","../src/beam.ts","../src/lib/clipboard.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 { spawnSync } from 'node:child_process'\n\ntype ClipboardCommand = Readonly<{\n\targs: readonly string[]\n\tcommand: string\n}>\n\nconst EXIT_SUCCESS = 0\nconst EMPTY_ARGS: readonly string[] = []\n\nconst DARWIN_COMMANDS: readonly ClipboardCommand[] = [{ args: EMPTY_ARGS, command: 'pbcopy' }]\n\nconst WINDOWS_COMMANDS: readonly ClipboardCommand[] = [{ args: EMPTY_ARGS, command: 'clip' }]\n\nconst LINUX_COMMANDS: readonly ClipboardCommand[] = [\n\t{ args: EMPTY_ARGS, command: 'wl-copy' },\n\t{ args: ['-selection', 'clipboard'], command: 'xclip' },\n\t{ args: ['--clipboard', '--input'], command: 'xsel' },\n]\n\nfunction getClipboardCommands(): readonly ClipboardCommand[] {\n\tif (process.platform === 'darwin') {\n\t\treturn DARWIN_COMMANDS\n\t}\n\tif (process.platform === 'win32') {\n\t\treturn WINDOWS_COMMANDS\n\t}\n\treturn LINUX_COMMANDS\n}\n\nfunction tryClipboardCommand(text: string, item: ClipboardCommand): boolean {\n\tconst result = spawnSync(item.command, item.args, {\n\t\tinput: text,\n\t\tstdio: ['pipe', 'ignore', 'ignore'],\n\t})\n\treturn !result.error && result.status === EXIT_SUCCESS\n}\n\n/** Copy text to the system clipboard using common platform commands. */\nexport function copyToClipboard(text: string): boolean {\n\tfor (const item of getClipboardCommands()) {\n\t\tif (tryClipboardCommand(text, item)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n","import { dim, red, yellow } from 'colorette'\n\nexport { bold, cyan, dim, gray, green, italic, red, yellow } from 'colorette'\n\nexport const INDENT = ' '\n\n/** Width of the visual separator line. */\nconst SEPARATOR_WIDTH = 36\n\n/** A dim dashed line used to bracket piped content. */\nexport const SEPARATOR = dim('╌'.repeat(SEPARATOR_WIDTH))\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, indent: string = INDENT): 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 at the standard indent level. */\nexport function logError(message: string): void {\n\tprocess.stderr.write(`${INDENT}${red('ERROR')} ${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 = 125\n\n/** Frame index of the first peak in the pulse animation. */\nconst PEAK_A = 5\n\n/** Frame index of the second peak in the pulse animation. */\nconst PEAK_B = 13\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\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 { Transform } from 'node:stream'\n\nimport mri from 'mri'\n\nimport { Beam } from './beam.ts'\nimport { copyToClipboard } from './lib/clipboard.ts'\nimport { createLifecycle } from './lib/lifecycle.ts'\nimport {\n\tblank,\n\tbold,\n\tcreateSpinner,\n\tcyan,\n\tdim,\n\tgray,\n\tINDENT,\n\tlog,\n\tlogError,\n\tred,\n\tSEPARATOR,\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 EXIT_FAILURE = 1\n\nconst NO_INDENT = ''\n\nconst argv = mri(process.argv.slice(ARGV_OFFSET), {\n\talias: { h: 'help', l: 'listen', v: 'version' },\n\tboolean: ['help', 'listen', '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('-l, --listen')} Listen using the provided passphrase`,\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('# Listen with a specific passphrase')}`,\n\t\t\" echo 'hello again' | hbeam <passphrase> --listen\",\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', NO_INDENT)\n\tprocess.exit(EXIT_SUCCESS)\n}\n\nconst [passphrase] = argv._ as string[]\n\nif (argv.listen && !passphrase) {\n\tlogError('The --listen flag requires an existing passphrase.')\n\twrite(dim('Usage: hbeam <passphrase> --listen'))\n\tprocess.exit(EXIT_FAILURE)\n}\n\nconst beamOptions: BeamOptions | undefined = argv.listen ? { announce: true } : undefined\nconst beam = new Beam(passphrase, beamOptions)\n\nconst { frames, intervalMs } = createPulseFrames('HBEAM')\nconst spinner = createSpinner(frames, intervalMs)\nconst lifecycle = createLifecycle(beam, spinner)\n\nblank()\nspinner.start()\nspinner.blank()\n\nif (beam.announce) {\n\tspinner.write(dim('PASSPHRASE'))\n\tspinner.write(cyan(beam.key))\n\tcopyToClipboard(beam.key)\n} else {\n\tspinner.write(dim('CONNECTING'))\n\tspinner.write(cyan(passphrase ?? 'unknown'))\n}\n\nbeam.on('remote-address', ({ host, port }: ConnectionInfo) => {\n\tif (lifecycle.done()) {\n\t\treturn\n\t}\n\tif (host) {\n\t\tspinner.write(dim(`ONLINE ${gray(`[${host}:${port}]`)}`))\n\t\tspinner.blank()\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\tspinner.stop()\n\tconst isPeerNotFound = error.message.includes('PEER_NOT_FOUND')\n\tif (isPeerNotFound) {\n\t\tlog(red(dim('PEER NOT FOUND')))\n\t} else if (error.message.includes('connection reset by peer')) {\n\t\tlog(dim('PEER DISCONNECTED'))\n\t} else {\n\t\tlogError(error.message)\n\t}\n\tblank()\n\tif (!isPeerNotFound) {\n\t\tlifecycle.shutdown()\n\t}\n})\n\nbeam.on('end', () => beam.end())\n\nlet receivedData = false\n\nconst indent = new Transform({\n\tflush(cb) {\n\t\tif (receivedData) {\n\t\t\tblank()\n\t\t\twrite(SEPARATOR)\n\t\t}\n\t\tcb(undefined, '\\n')\n\t},\n\ttransform(chunk: Buffer, _encoding, cb) {\n\t\tif (!receivedData) {\n\t\t\treceivedData = true\n\t\t\twrite(SEPARATOR)\n\t\t\tblank()\n\t\t}\n\t\tconst lines = chunk.toString().replace(/^(?!$)/gm, INDENT)\n\t\tcb(undefined, lines)\n\t},\n})\n\nprocess.stdin.pipe(beam).pipe(indent).pipe(process.stdout)\n\nif (typeof process.stdin.unref === 'function') {\n\tprocess.stdin.unref()\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-DH5DapXH.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hbeam",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A 1-to-1 end-to-end encrypted pipe over HyperDHT.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"lint": "oxlint",
|
|
26
26
|
"prepack": "bun run build",
|
|
27
27
|
"prepare": "test -w .git/config && husky || true",
|
|
28
|
-
"pub": "bun run scripts/repo/publish",
|
|
29
28
|
"prepublishOnly": "bun run check && bun run build",
|
|
29
|
+
"pub": "bun run scripts/repo/publish",
|
|
30
30
|
"test": "bun test"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package-ByG93psB.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
|