@heart-it/p2p-hello 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +50 -0
  2. package/index.js +98 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # p2p-hello — heartIT lab #1
2
+
3
+ Companion lab for the **P2P from Scratch** series on [heartit.tech](https://heartit.tech).
4
+
5
+ Two strangers run one command with the same passphrase. Their machines find
6
+ each other through the Hyperswarm DHT, hole-punch a direct UDP path, open a
7
+ Noise-XX-encrypted stream, and say hello. No server, no account; kill it and
8
+ nothing remains.
9
+
10
+ ```
11
+ npx @heart-it/p2p-hello swordfish
12
+ ```
13
+
14
+ Run it in two terminals — better, on two networks (laptop + phone hotspot),
15
+ because same-LAN peers connect directly without needing the punch.
16
+
17
+ ## What you'll see
18
+
19
+ - the 32-byte **topic** (a salted hash of your passphrase — the DHT sees the
20
+ hash, never the phrase)
21
+ - the peer's **Noise key** (their ephemeral identity for this session)
22
+ - the **UDP path** — their actual `host:port`, meaning the connection was
23
+ hole-punched, not relayed
24
+ - an end-to-end **encrypted hello** (secret-stream won't give you less)
25
+
26
+ Each line maps to a part of the series: DHT announce/lookup, reflexive
27
+ address discovery, the punch, and the encrypted transport on top.
28
+
29
+ ## Publishing checklist (maintainer)
30
+
31
+ **npm (current distribution):**
32
+
33
+ ```
34
+ npm publish --access public
35
+ ```
36
+
37
+ **Pear (when `pear install` stabilizes):** the `pear` block in package.json is
38
+ already staged for it. Mind two things:
39
+
40
+ 1. `pear touch` mints the pear:// link once; then `pear stage pear://<key> .`
41
+ and `pear release pear://<key>`; keep it available with
42
+ `pear seed pear://<key>` on an always-on peer.
43
+ 2. `pear stage` has **no default ignores** since v2.4 — the `pear.stage.ignore`
44
+ list in package.json is load-bearing. Anything staged into the drive is
45
+ public and content-addressed forever; verify with `pear info` before the
46
+ first release. (`pear run` is deprecated/removed — end users arrive via
47
+ `pear install` once it ships stable.)
48
+
49
+ The lab is intentionally ~80 lines. Readers should be able to hold the whole
50
+ thing in their head — that's the point.
package/index.js ADDED
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * p2p-hello — heartIT lab #1 (companion to the "P2P from Scratch" series)
4
+ *
5
+ * Two strangers run this with the same passphrase. Their machines find each
6
+ * other through the Hyperswarm DHT, hole-punch a direct UDP path, open an
7
+ * encrypted stream (Noise XX via secret-stream — you never asked for
8
+ * encryption; the stack refuses to give you less), and say hello.
9
+ *
10
+ * No server. No account. Kill it and nothing remains.
11
+ *
12
+ * npx @heart-it/p2p-hello <passphrase>
13
+ */
14
+
15
+ 'use strict'
16
+
17
+ /* Explicit require so the imports map in package.json can swap in
18
+ * bare-process under Bare/Pear — same file runs on both runtimes. */
19
+ const process = require('process')
20
+ const Hyperswarm = require('hyperswarm')
21
+ const sodium = require('sodium-universal')
22
+ const b4a = require('b4a')
23
+
24
+ const passphrase = process.argv[2]
25
+
26
+ if (!passphrase) {
27
+ console.error('usage: p2p-hello <passphrase>')
28
+ console.error(' run it in two terminals (or send a friend) with the same passphrase')
29
+ process.exit(1)
30
+ }
31
+
32
+ /* The swarm topic is a 32-byte hash of the passphrase, salted with the lab
33
+ * name so "swordfish" here never collides with "swordfish" in another app.
34
+ * Only people who know the phrase can derive the topic — the DHT sees the
35
+ * hash, never the phrase. */
36
+ const topic = b4a.alloc(32)
37
+ sodium.crypto_generichash(topic, b4a.from('heartit/p2p-hello::' + passphrase))
38
+
39
+ const swarm = new Hyperswarm()
40
+ let peers = 0
41
+
42
+ console.log('→ topic ' + b4a.toString(topic, 'hex'))
43
+ console.log('→ announcing on the DHT and looking for peers… (ctrl+c to leave)')
44
+
45
+ swarm.on('connection', function (conn, info) {
46
+ /* Every socket needs an error handler — peers vanish mid-flight and
47
+ * that is normal weather in a P2P system, not an exception. */
48
+ conn.on('error', function () {})
49
+
50
+ peers++
51
+ const remoteKey = b4a.toString(conn.remotePublicKey, 'hex')
52
+ const raw = conn.rawStream
53
+
54
+ console.log('')
55
+ console.log('✓ peer connected')
56
+ console.log(' noise key ' + remoteKey.slice(0, 16) + '… (their ephemeral identity)')
57
+ if (raw && raw.remoteHost) {
58
+ /* Private-range host = same network (direct, no punch needed);
59
+ * public host = the DHT-assisted hole-punch did its job. */
60
+ const local = /^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|127\.)/.test(raw.remoteHost)
61
+ console.log(' udp path ' + raw.remoteHost + ':' + raw.remotePort +
62
+ (local
63
+ ? ' (same network — direct, no punch needed)'
64
+ : ' (hole-punched — their actual public socket, no relay)'))
65
+ }
66
+ console.log(' encryption Noise XX handshake complete — everything below is end-to-end encrypted')
67
+ console.log('')
68
+
69
+ conn.write('hello from ' + b4a.toString(swarm.keyPair.publicKey, 'hex').slice(0, 8) + '… 👋')
70
+
71
+ conn.on('data', function (data) {
72
+ console.log(' they say: ' + b4a.toString(data))
73
+ console.log('')
74
+ console.log('That was a direct, encrypted, serverless exchange between two machines')
75
+ console.log('that had never heard of each other. The series explains every hop.')
76
+ })
77
+
78
+ conn.on('close', function () {
79
+ peers--
80
+ console.log('✗ peer left' + (peers === 0 ? ' — waiting for others…' : ''))
81
+ })
82
+ })
83
+
84
+ /* server: true → announce ourselves; client: true → look for others.
85
+ * Both on, so any two copies of this lab can find each other. */
86
+ swarm.join(topic, { server: true, client: true })
87
+
88
+ swarm.flush().then(function () {
89
+ console.log('→ fully announced. If nobody appears, you are first — leave this open')
90
+ console.log(' and run the same command on another machine or network.')
91
+ })
92
+
93
+ process.once('SIGINT', function () {
94
+ console.log('\n→ leaving the swarm…')
95
+ swarm.destroy().then(function () {
96
+ process.exit(0)
97
+ })
98
+ })
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@heart-it/p2p-hello",
3
+ "version": "0.1.0",
4
+ "description": "heartIT lab #1 — two strangers, one passphrase, a hole-punched encrypted connection. Companion to the P2P from Scratch series.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "p2p-hello": "./index.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "dependencies": {
13
+ "b4a": "^1.6.0",
14
+ "bare-process": "^4.0.0",
15
+ "hyperswarm": "^4.17.0",
16
+ "sodium-universal": "^5.0.0"
17
+ },
18
+ "imports": {
19
+ "process": {
20
+ "bare": "bare-process",
21
+ "default": "process"
22
+ }
23
+ },
24
+ "pear": {
25
+ "name": "p2p-hello",
26
+ "stage": {
27
+ "entrypoints": ["./index.js"],
28
+ "ignore": ["/.git", "/.github", "/node_modules", "/test", "/.env", "/.DS_Store", "/README.md"]
29
+ }
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/heart-IT/p2p-from-scratch-labs",
34
+ "directory": "p2p-hello"
35
+ },
36
+ "license": "MIT",
37
+ "keywords": ["p2p", "hyperswarm", "holepunch", "hole-punching", "lab"]
38
+ }