OverviewFilesCommitsIssuesDocsReleasesPeers

p2p-bridge commits / ce9fcff

Initial commit — atomspace-p2p-bridge
Hyperswarm TCP bridge for CogServer — exposes an OpenCog AtomSpace as a P2P-discoverable peer over the Holepunch DHT. Local TCP clients connect through the bridge to remote AtomSpaces without needing direct network reachability. Components: - bridge.js — main bridge: Hyperswarm + per-peer TCP relay - test-bridge.js — local end-to-end bridge sanity check - test-connect.js — connect to a remote bridge over the DHT - test-production-dht.js — production-DHT smoke test Depends on hyperident (file:../../hyper-fleeky/repos/hyperident) for device-linked peer keys. Extracted from hypercore-kb monorepo on 2026-04-15 — prior state had 399 node_modules files leaked into the KB index. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ce9fcff9c5f0cf08616fa3a3b69f256364db17ee
by plankatron <fleeky@gmail.com> · 2026-04-15T21:04:37.000Z

Changed files (7) +1339 -0

.gitignore +9 -0
……@@ -0,0 +1,9 @@
1# Dependencies (also matches symlinks to a sibling node_modules)
2node_modules
3
4# Editor and OS cruft
5.DS_Store
6*.log
7*.swp
8.vscode/
9.idea/
bridge.js +453 -0
……@@ -0,0 +1,453 @@
1#!/usr/bin/env node
2'use strict'
3
4//
5// atomspace-p2p-bridge — CogServer over Hyperswarm
6//
7// Identity-first design: uses hyperident profiles for persistent,
8// deterministic P2P identity. Both server and client derive the
9// same DHT topic from a profile name — no manual key exchange.
10//
11// Derivation chain:
12// hyperident mnemonic → SLIP-21(profileName) → seed
13// seed + "atomspace-bridge" salt → Ed25519 keypair
14// discoveryKey(publicKey) → DHT topic
15//
16// Server: runs alongside CogServer, announces on DHT
17// Client: creates local TCP proxy, connects via DHT lookup
18//
19// CogStorageNode ─TCP─► bridge client ─Hyperswarm─► bridge server ─TCP─► CogServer
20//
21
22const Hyperswarm = require('hyperswarm')
23const createTestnet = require('@hyperswarm/testnet')
24const Protomux = require('protomux')
25const c = require('compact-encoding')
26const net = require('net')
27const crypto = require('hypercore-crypto')
28const b4a = require('b4a')
29const fs = require('fs')
30const path = require('path')
31
32// hyperident is ESM — loaded dynamically
33let _identityManagerPromise = null
34function getIdentityManager () {
35 if (!_identityManagerPromise) {
36 _identityManagerPromise = import('hyperident/lib/identity-manager.js')
37 .then(m => m.IdentityManager)
38 .catch(() => null)
39 }
40 return _identityManagerPromise
41}
42
43const PROTOCOL = 'atomspace-proxy'
44const SALT = 'atomspace-bridge' // app-specific namespace
45
46// ---- Identity derivation ----
47// Derives a deterministic Ed25519 keypair from a hyperident profile.
48// The salt ensures this keypair is unique to the atomspace bridge
49// (other apps using the same profile get different keys).
50async function keyPairFromProfile (profileName) {
51 const IdentityManager = await getIdentityManager()
52 if (!IdentityManager) {
53 console.error('Error: hyperident not available')
54 console.error('Run: npm install hyperident')
55 process.exit(1)
56 }
57 const profileTopic = await IdentityManager.deriveTopicFromProfile(profileName)
58 const seed = crypto.hash(b4a.concat([profileTopic, b4a.from(SALT, 'utf8')]))
59 return crypto.keyPair(seed)
60}
61
62// Fallback: keypair from raw 32-byte seed
63function keyPairFromSeed (seed) {
64 return crypto.keyPair(seed)
65}
66
67// ---- Session message encoding ----
68function packSession (id, data) {
69 const buf = b4a.allocUnsafe(4 + data.length)
70 buf.writeUInt32BE(id, 0)
71 b4a.copy(data, buf, 4)
72 return buf
73}
74
75function unpackSession (buf) {
76 return {
77 id: buf.readUInt32BE(0),
78 data: buf.subarray(4)
79 }
80}
81
82// ---- Testnet bootstrap ----
83async function resolveBootstrap (testnetFile) {
84 if (!testnetFile) return null
85 const testnetPath = path.resolve(testnetFile)
86 if (fs.existsSync(testnetPath)) {
87 const data = JSON.parse(fs.readFileSync(testnetPath, 'utf8'))
88 console.log('Loaded testnet: ' + data.bootstrap.length + ' bootstrap nodes')
89 return data.bootstrap
90 }
91 const testnet = await createTestnet(3)
92 const bootstrap = testnet.bootstrap.map(n => ({ host: n.host, port: n.port }))
93 fs.writeFileSync(testnetPath, JSON.stringify({ bootstrap }, null, 2) + '\n')
94 console.log('Created testnet: ' + bootstrap.length + ' bootstrap nodes → ' + testnetPath)
95 return bootstrap
96}
97
98// ---- CLI ----
99const args = process.argv.slice(2)
100const mode = args[0]
101
102function getFlag (name, fallback) {
103 const i = args.indexOf(name)
104 return (i !== -1 && i + 1 < args.length) ? args[i + 1] : fallback
105}
106
107if (!mode || !['server', 'client'].includes(mode)) {
108 console.log('AtomSpace P2P Bridge — CogServer over Hyperswarm')
109 console.log('')
110 console.log('Usage:')
111 console.log(' node bridge.js server --profile <name> [options]')
112 console.log(' node bridge.js client --profile <name> [options]')
113 console.log(' node bridge.js client <public-key> [options]')
114 console.log('')
115 console.log('Identity (pick one):')
116 console.log(' --profile <name> Hyperident profile (recommended)')
117 console.log(' --seed <hex> Raw 64-char hex seed (fallback)')
118 console.log('')
119 console.log('Server options:')
120 console.log(' --port <n> CogServer port (default: 17001)')
121 console.log(' --testnet <file> Use local testnet for testing')
122 console.log('')
123 console.log('Client options:')
124 console.log(' --proxy-port <n> Local proxy port (default: 17002)')
125 console.log(' --testnet <file> Use local testnet (must match server)')
126 console.log('')
127 console.log('Examples:')
128 console.log(' # Both sides use same profile — no key exchange needed:')
129 console.log(' node bridge.js server --profile tabertron')
130 console.log(' node bridge.js client --profile tabertron')
131 console.log('')
132 console.log(' # Manual key (no hyperident):')
133 console.log(' node bridge.js server --seed <64-hex-chars>')
134 console.log(' node bridge.js client <public-key>')
135 console.log('')
136 console.log(' # Then in Guile:')
137 console.log(' (define csn (CogStorageNode "cog://localhost:17002/"))')
138 console.log(' (cog-open csn)')
139 process.exit(0)
140}
141
142if (mode === 'server') runServer()
143else runClient()
144
145// ============================================================
146// SERVER
147// ============================================================
148
149async function runServer () {
150 const cogPort = parseInt(getFlag('--port', '17001'))
151 const profileName = getFlag('--profile', null)
152 const seedHex = getFlag('--seed', null)
153 const testnetFile = getFlag('--testnet', null)
154
155 const bootstrap = await resolveBootstrap(testnetFile)
156
157 // Resolve identity — profile is primary, seed is fallback
158 let keyPair
159 if (profileName) {
160 keyPair = await keyPairFromProfile(profileName)
161 } else if (seedHex) {
162 keyPair = keyPairFromSeed(b4a.from(seedHex, 'hex'))
163 } else {
164 console.error('Error: identity required')
165 console.error('Use --profile <name> (recommended) or --seed <hex>')
166 process.exit(1)
167 }
168
169 const topic = crypto.discoveryKey(keyPair.publicKey)
170
171 const swarmOpts = { keyPair }
172 if (bootstrap) swarmOpts.bootstrap = bootstrap
173 const swarm = new Hyperswarm(swarmOpts)
174
175 let peerCount = 0
176 let totalSessions = 0
177
178 console.log('=== AtomSpace P2P Bridge: Server ===')
179 if (profileName) console.log('Profile: ' + profileName)
180 if (bootstrap) console.log('Testnet: ' + bootstrap.length + ' nodes')
181 console.log('Public key: ' + b4a.toString(keyPair.publicKey, 'hex'))
182 console.log('Topic: ' + b4a.toString(topic, 'hex'))
183 console.log('CogServer: localhost:' + cogPort)
184
185 const discovery = swarm.join(topic, { server: true, client: false })
186 await discovery.flushed()
187 console.log('DHT announce complete — waiting for peers...\n')
188
189 swarm.on('connection', (socket, info) => {
190 const peerId = b4a.toString(info.publicKey, 'hex').slice(0, 12)
191 peerCount++
192 console.log('[+] Peer ' + peerId + ' (total: ' + peerCount + ')')
193
194 const mux = Protomux.from(socket)
195 const sessions = new Map()
196
197 const channel = mux.createChannel({
198 protocol: PROTOCOL,
199 onopen () {
200 console.log(' Channel opened with ' + peerId)
201 },
202 onclose () {
203 console.log(' Channel closed with ' + peerId)
204 for (const [, tcp] of sessions) tcp.destroy()
205 sessions.clear()
206 }
207 })
208
209 // Message 0: open session (client → server)
210 channel.addMessage({
211 encoding: c.uint32,
212 onmessage (sessionId) {
213 totalSessions++
214 const tcp = net.connect(cogPort, '127.0.0.1')
215
216 tcp.on('connect', () => {
217 console.log(' Session ' + sessionId + ' → CogServer:' + cogPort +
218 ' (total: ' + totalSessions + ')')
219 })
220
221 tcp.on('data', (buf) => {
222 try { dataMsg.send(packSession(sessionId, buf)) } catch (e) {}
223 })
224
225 tcp.on('error', (err) => {
226 console.log(' Session ' + sessionId + ' TCP error: ' + err.message)
227 try { closeMsg.send(sessionId) } catch (e) {}
228 sessions.delete(sessionId)
229 })
230
231 tcp.on('close', () => {
232 try { closeMsg.send(sessionId) } catch (e) {}
233 sessions.delete(sessionId)
234 })
235
236 sessions.set(sessionId, tcp)
237 }
238 })
239
240 // Message 1: data (bidirectional)
241 const dataMsg = channel.addMessage({
242 encoding: c.raw,
243 onmessage (buf) {
244 const msg = unpackSession(buf)
245 const tcp = sessions.get(msg.id)
246 if (tcp && !tcp.destroyed) tcp.write(msg.data)
247 }
248 })
249
250 // Message 2: close session (bidirectional)
251 const closeMsg = channel.addMessage({
252 encoding: c.uint32,
253 onmessage (sessionId) {
254 const tcp = sessions.get(sessionId)
255 if (tcp) {
256 tcp.destroy()
257 sessions.delete(sessionId)
258 }
259 }
260 })
261
262 channel.open()
263
264 socket.on('close', () => {
265 peerCount--
266 console.log('[-] Peer ' + peerId + ' (total: ' + peerCount + ')')
267 for (const [, tcp] of sessions) tcp.destroy()
268 sessions.clear()
269 })
270
271 socket.on('error', (err) => {
272 console.log('[!] Peer ' + peerId + ' error: ' + err.message)
273 })
274 })
275}
276
277// ============================================================
278// CLIENT
279// ============================================================
280
281async function runClient () {
282 const profileName = getFlag('--profile', null)
283 const seedHex = getFlag('--seed', null)
284 const proxyPort = parseInt(getFlag('--proxy-port', '17002'))
285 const testnetFile = getFlag('--testnet', null)
286
287 const bootstrap = await resolveBootstrap(testnetFile)
288
289 // Resolve server identity — profile derives the same key the server uses
290 let serverKey
291 if (profileName) {
292 const keyPair = await keyPairFromProfile(profileName)
293 serverKey = keyPair.publicKey
294 } else if (seedHex) {
295 const keyPair = keyPairFromSeed(b4a.from(seedHex, 'hex'))
296 serverKey = keyPair.publicKey
297 } else {
298 // Raw public key as positional argument
299 const serverKeyHex = args[1]
300 if (!serverKeyHex || serverKeyHex.length !== 64 || serverKeyHex.startsWith('--')) {
301 console.error('Error: identity required')
302 console.error('Use --profile <name> (recommended) or provide <public-key>')
303 process.exit(1)
304 }
305 serverKey = b4a.from(serverKeyHex, 'hex')
306 }
307
308 const topic = crypto.discoveryKey(serverKey)
309
310 const swarmOpts = {}
311 if (bootstrap) swarmOpts.bootstrap = bootstrap
312 const swarm = new Hyperswarm(swarmOpts)
313 swarm.join(topic, { server: false, client: true })
314
315 console.log('=== AtomSpace P2P Bridge: Client ===')
316 if (profileName) console.log('Profile: ' + profileName)
317 if (bootstrap) console.log('Testnet: ' + bootstrap.length + ' nodes')
318 console.log('Server key: ' + b4a.toString(serverKey, 'hex').slice(0, 24) + '...')
319 console.log('Topic: ' + b4a.toString(topic, 'hex').slice(0, 24) + '...')
320 console.log('Proxy port: ' + proxyPort)
321 console.log('Looking up server on DHT...\n')
322
323 let channel = null
324 let openMsg = null
325 let dataMsg = null
326 let closeMsg = null
327 const localSessions = new Map()
328 let nextSessionId = 1
329 const pending = []
330
331 swarm.on('connection', (socket, info) => {
332 const peerId = b4a.toString(info.publicKey, 'hex').slice(0, 12)
333 console.log('[+] Connected to server ' + peerId)
334
335 const mux = Protomux.from(socket)
336
337 channel = mux.createChannel({
338 protocol: PROTOCOL,
339 onopen () {
340 console.log(' Channel ready — proxy active')
341 for (const fn of pending) fn()
342 pending.length = 0
343 },
344 onclose () {
345 console.log(' Channel closed')
346 channel = null
347 openMsg = null
348 dataMsg = null
349 closeMsg = null
350 for (const [, tcp] of localSessions) tcp.destroy()
351 localSessions.clear()
352 }
353 })
354
355 openMsg = channel.addMessage({
356 encoding: c.uint32,
357 onmessage () {}
358 })
359
360 dataMsg = channel.addMessage({
361 encoding: c.raw,
362 onmessage (buf) {
363 const msg = unpackSession(buf)
364 const tcp = localSessions.get(msg.id)
365 if (tcp && !tcp.destroyed) tcp.write(msg.data)
366 }
367 })
368
369 closeMsg = channel.addMessage({
370 encoding: c.uint32,
371 onmessage (sessionId) {
372 const tcp = localSessions.get(sessionId)
373 if (tcp) {
374 tcp.destroy()
375 localSessions.delete(sessionId)
376 }
377 }
378 })
379
380 channel.open()
381
382 socket.on('close', () => {
383 console.log('[-] Server disconnected, waiting for reconnect...')
384 channel = null
385 openMsg = null
386 dataMsg = null
387 closeMsg = null
388 })
389
390 socket.on('error', (err) => {
391 console.log('[!] Server error: ' + err.message)
392 })
393 })
394
395 // Local TCP proxy — CogStorageNode connects here
396 const proxy = net.createServer((tcp) => {
397 const sessionId = nextSessionId++
398 localSessions.set(sessionId, tcp)
399
400 if (channel && openMsg) {
401 openMsg.send(sessionId)
402 wireSession(sessionId, tcp)
403 console.log(' Session ' + sessionId + ' → server')
404 } else {
405 console.log(' Session ' + sessionId + ' queued (no server yet)')
406 pending.push(() => {
407 if (tcp.destroyed) return
408 openMsg.send(sessionId)
409 wireSession(sessionId, tcp)
410 console.log(' Session ' + sessionId + ' → server (dequeued)')
411 })
412 }
413 })
414
415 function wireSession (sessionId, tcp) {
416 tcp.on('data', (buf) => {
417 try {
418 if (dataMsg) dataMsg.send(packSession(sessionId, buf))
419 } catch (e) {}
420 })
421
422 tcp.on('error', () => {
423 try { if (closeMsg) closeMsg.send(sessionId) } catch (e) {}
424 localSessions.delete(sessionId)
425 })
426
427 tcp.on('close', () => {
428 try { if (closeMsg) closeMsg.send(sessionId) } catch (e) {}
429 localSessions.delete(sessionId)
430 })
431 }
432
433 proxy.listen(proxyPort, () => {
434 console.log('Proxy listening on port ' + proxyPort)
435 console.log('')
436 console.log('Use in Guile:')
437 console.log(' (use-modules (opencog persist-cog))')
438 console.log(' (define csn (CogStorageNode "cog://localhost:' + proxyPort + '/"))')
439 console.log(' (cog-open csn)')
440 console.log('')
441 })
442}
443
444// ---- Graceful shutdown ----
445
446process.on('SIGINT', () => {
447 console.log('\nShutting down...')
448 process.exit(0)
449})
450
451process.on('uncaughtException', (err) => {
452 console.error('Uncaught:', err.message)
453})
package-lock.json +547 -0
……@@ -0,0 +1,547 @@
1{
2 "name": "atomspace-p2p-bridge",
3 "version": "1.0.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "atomspace-p2p-bridge",
9 "version": "1.0.0",
10 "dependencies": {
11 "@hyperswarm/testnet": "^3.1.4",
12 "b4a": "^1.6.4",
13 "compact-encoding": "^2.12.0",
14 "hypercore-crypto": "^3.3.1",
15 "hyperident": "file:../../hyper-fleeky/repos/hyperident",
16 "hyperswarm": "^4.4.1",
17 "protomux": "^3.4.0"
18 },
19 "bin": {
20 "atomspace-bridge": "bridge.js"
21 }
22 },
23 "../../hyper-fleeky/repos/hyperident": {
24 "version": "0.1.0",
25 "license": "Apache-2.0",
26 "dependencies": {
27 "b4a": "^1.6.7",
28 "bare-crypto": "^1.11.1",
29 "bare-fs": "^2.3.2",
30 "bare-os": "^2.4.2",
31 "bare-path": "^2.1.3",
32 "hypercore-id-encoding": "^1.3.0",
33 "hyperswarm": "^4.12.1",
34 "keet-identity-key": "^3.1.0",
35 "sodium-universal": "^5.0.1"
36 },
37 "bin": {
38 "hyperident": "index.js"
39 },
40 "devDependencies": {
41 "brittle": "^3.0.0",
42 "pear-interface": "^1.0.0"
43 }
44 },
45 "node_modules/@hyperswarm/secret-stream": {
46 "version": "6.9.1",
47 "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz",
48 "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==",
49 "license": "Apache-2.0",
50 "dependencies": {
51 "b4a": "^1.1.0",
52 "hypercore-crypto": "^3.3.1",
53 "noise-curve-ed": "^2.0.1",
54 "noise-handshake": "^4.0.0",
55 "sodium-secretstream": "^1.1.0",
56 "sodium-universal": "^5.0.0",
57 "streamx": "^2.14.0",
58 "timeout-refresh": "^2.0.0",
59 "unslab": "^1.3.0"
60 }
61 },
62 "node_modules/@hyperswarm/testnet": {
63 "version": "3.1.4",
64 "resolved": "https://registry.npmjs.org/@hyperswarm/testnet/-/testnet-3.1.4.tgz",
65 "integrity": "sha512-Jw8JIZ9kdZ4R9jwJHsqctj5YeRqecw3aeCg2NS12HBXCqMioQIYzVqHs/8BWdOQCf34+BEXkwMQjba9CzaLwkg==",
66 "license": "Apache-2.0",
67 "dependencies": {
68 "hyperdht": "^6.6.0"
69 },
70 "bin": {
71 "hyperswarm-testnet": "bin.js"
72 }
73 },
74 "node_modules/adaptive-timeout": {
75 "version": "1.0.1",
76 "resolved": "https://registry.npmjs.org/adaptive-timeout/-/adaptive-timeout-1.0.1.tgz",
77 "integrity": "sha512-+seGiBtHqbnyZ0Quq/ZGxxwP66153WBABawa07/QeyuPFzbduPBjiGQAyCiriiLDzt3dkbMBskSlfqtRwblK8Q==",
78 "license": "Apache-2.0",
79 "dependencies": {
80 "xache": "^1.2.1"
81 }
82 },
83 "node_modules/b4a": {
84 "version": "1.7.3",
85 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz",
86 "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==",
87 "license": "Apache-2.0",
88 "peerDependencies": {
89 "react-native-b4a": "*"
90 },
91 "peerDependenciesMeta": {
92 "react-native-b4a": {
93 "optional": true
94 }
95 }
96 },
97 "node_modules/bare-addon-resolve": {
98 "version": "1.9.7",
99 "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.9.7.tgz",
100 "integrity": "sha512-cIo93Y4Maw8ZNrfq5qlvRBqCHUyeSzMyFBqJRNi91SOspeWiFq3ixiq0ExYWmtOBauIkCLod2Mng73xr7fmkXA==",
101 "license": "Apache-2.0",
102 "dependencies": {
103 "bare-module-resolve": "^1.10.0",
104 "bare-semver": "^1.0.0"
105 },
106 "peerDependencies": {
107 "bare-url": "*"
108 },
109 "peerDependenciesMeta": {
110 "bare-url": {
111 "optional": true
112 }
113 }
114 },
115 "node_modules/bare-events": {
116 "version": "2.8.2",
117 "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
118 "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
119 "license": "Apache-2.0",
120 "peerDependencies": {
121 "bare-abort-controller": "*"
122 },
123 "peerDependenciesMeta": {
124 "bare-abort-controller": {
125 "optional": true
126 }
127 }
128 },
129 "node_modules/bare-module-resolve": {
130 "version": "1.12.1",
131 "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz",
132 "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==",
133 "license": "Apache-2.0",
134 "dependencies": {
135 "bare-semver": "^1.0.0"
136 },
137 "peerDependencies": {
138 "bare-url": "*"
139 },
140 "peerDependenciesMeta": {
141 "bare-url": {
142 "optional": true
143 }
144 }
145 },
146 "node_modules/bare-semver": {
147 "version": "1.0.2",
148 "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz",
149 "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==",
150 "license": "Apache-2.0"
151 },
152 "node_modules/bits-to-bytes": {
153 "version": "1.3.0",
154 "resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz",
155 "integrity": "sha512-OJoHTpFXS9bXHBCekGTByf3MqM8CGblBDIduKQeeVVeiU9dDWywSSirXIBYGgg3d1zbVuvnMa1vD4r6PA0kOKg==",
156 "license": "ISC",
157 "dependencies": {
158 "b4a": "^1.5.0"
159 }
160 },
161 "node_modules/blind-relay": {
162 "version": "1.4.0",
163 "resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.4.0.tgz",
164 "integrity": "sha512-6xt7fDfCs6eGmNNym6I9N42jmjcMQn2qwwOVnkP9ZnrkXFk6c4/tdO1xqRmDEzKzV8gigd+DVdCUG/RUYnen7Q==",
165 "license": "Apache-2.0",
166 "dependencies": {
167 "b4a": "^1.6.4",
168 "bare-events": "^2.2.0",
169 "bits-to-bytes": "^1.3.0",
170 "compact-encoding": "^2.12.0",
171 "compact-encoding-bitfield": "^1.0.0",
172 "protomux": "^3.5.1",
173 "sodium-universal": "^5.0.0",
174 "streamx": "^2.15.1"
175 }
176 },
177 "node_modules/bogon": {
178 "version": "1.2.0",
179 "resolved": "https://registry.npmjs.org/bogon/-/bogon-1.2.0.tgz",
180 "integrity": "sha512-FqOBr/1VMzCOsoJd+fzNUMarUYki2+TKt07A2+xaulsNx4r53iJ7MV5k0jbqg7W2U0CsLqxCZOrFibdG6h6HSg==",
181 "license": "MIT",
182 "dependencies": {
183 "compact-encoding": "^2.11.0",
184 "compact-encoding-net": "^1.2.0"
185 }
186 },
187 "node_modules/compact-encoding": {
188 "version": "2.18.0",
189 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.18.0.tgz",
190 "integrity": "sha512-goACAOlhMI2xo5jGOMUDfOLnGdRE1jGfyZ+zie8N5114nHrbPIqf6GLUtzbLof6DSyrERlYRm3EcBplte5LcQw==",
191 "license": "Apache-2.0",
192 "dependencies": {
193 "b4a": "^1.3.0"
194 }
195 },
196 "node_modules/compact-encoding-bitfield": {
197 "version": "1.0.0",
198 "resolved": "https://registry.npmjs.org/compact-encoding-bitfield/-/compact-encoding-bitfield-1.0.0.tgz",
199 "integrity": "sha512-3nMVKUg+PF72UHfainmCL8uKvyWfxsjqOtUY+HiMPGLPCTjnwzoKfFAMo1Ad7nwTPdjBqtGK5b3BOFTFW4EBTg==",
200 "license": "ISC",
201 "dependencies": {
202 "compact-encoding": "^2.4.1"
203 }
204 },
205 "node_modules/compact-encoding-net": {
206 "version": "1.2.0",
207 "resolved": "https://registry.npmjs.org/compact-encoding-net/-/compact-encoding-net-1.2.0.tgz",
208 "integrity": "sha512-LVXpNpF7PGQeHRVVLGgYWzuVoYAaDZvKUsUxRioGfkotzvOh4AzoQF1HBH3zMNaSnx7gJXuUr3hkjnijaH/Eng==",
209 "license": "ISC",
210 "dependencies": {
211 "compact-encoding": "^2.4.1"
212 }
213 },
214 "node_modules/dht-rpc": {
215 "version": "6.24.3",
216 "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.24.3.tgz",
217 "integrity": "sha512-XZXIBG+FBFhfiz+3uimuH3vBw8FmqLkqR/W0VCo/lbEq3qDlFAxCOZxUHBqcRT254EysirP8+rw+Ynv0e8zhtg==",
218 "license": "MIT",
219 "dependencies": {
220 "adaptive-timeout": "^1.0.1",
221 "b4a": "^1.6.1",
222 "bare-events": "^2.2.0",
223 "compact-encoding": "^2.11.0",
224 "compact-encoding-net": "^1.2.0",
225 "fast-fifo": "^1.1.0",
226 "kademlia-routing-table": "^1.0.1",
227 "nat-sampler": "^1.0.1",
228 "sodium-universal": "^5.0.0",
229 "streamx": "^2.13.2",
230 "time-ordered-set": "^2.0.0",
231 "udx-native": "^1.5.3"
232 }
233 },
234 "node_modules/events-universal": {
235 "version": "1.0.1",
236 "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
237 "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
238 "license": "Apache-2.0",
239 "dependencies": {
240 "bare-events": "^2.7.0"
241 }
242 },
243 "node_modules/fast-fifo": {
244 "version": "1.3.2",
245 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
246 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
247 "license": "MIT"
248 },
249 "node_modules/hypercore-crypto": {
250 "version": "3.6.1",
251 "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz",
252 "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==",
253 "license": "MIT",
254 "dependencies": {
255 "b4a": "^1.6.6",
256 "compact-encoding": "^2.15.0",
257 "sodium-universal": "^5.0.0"
258 }
259 },
260 "node_modules/hypercore-id-encoding": {
261 "version": "1.3.0",
262 "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz",
263 "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==",
264 "license": "Apache-2.0",
265 "dependencies": {
266 "b4a": "^1.5.3",
267 "z32": "^1.0.0"
268 }
269 },
270 "node_modules/hyperdht": {
271 "version": "6.29.0",
272 "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.0.tgz",
273 "integrity": "sha512-okIpelpIhRTBDCCe2CNWfLHbVSQ5DPbl1Npb1zet8SqrkCsYF27RHJ04IaYqiigHI9y0KZgjOHdZbejalbFVoQ==",
274 "license": "MIT",
275 "dependencies": {
276 "@hyperswarm/secret-stream": "^6.6.2",
277 "b4a": "^1.3.1",
278 "bare-events": "^2.2.0",
279 "blind-relay": "^1.3.0",
280 "bogon": "^1.0.0",
281 "compact-encoding": "^2.4.1",
282 "compact-encoding-net": "^1.0.1",
283 "dht-rpc": "^6.15.1",
284 "hypercore-crypto": "^3.3.0",
285 "hypercore-id-encoding": "^1.2.0",
286 "noise-curve-ed": "^2.0.0",
287 "noise-handshake": "^4.0.0",
288 "record-cache": "^1.1.1",
289 "safety-catch": "^1.0.1",
290 "signal-promise": "^1.0.3",
291 "sodium-universal": "^5.0.1",
292 "streamx": "^2.16.1",
293 "unslab": "^1.3.0",
294 "xache": "^1.1.0"
295 },
296 "bin": {
297 "hyperdht": "bin.js"
298 }
299 },
300 "node_modules/hyperident": {
301 "resolved": "../../hyper-fleeky/repos/hyperident",
302 "link": true
303 },
304 "node_modules/hyperswarm": {
305 "version": "4.16.0",
306 "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.16.0.tgz",
307 "integrity": "sha512-f86RsI1qP/nbBTv4wYxOhu1JnWvGZgiC1w69aRFn0xGSavLMWjvbbVspVApOnRjiPKpYVVGACXdrvEOrL3rHdw==",
308 "license": "MIT",
309 "dependencies": {
310 "b4a": "^1.3.1",
311 "bare-events": "^2.2.0",
312 "hyperdht": "^6.21.0",
313 "safety-catch": "^1.0.2",
314 "shuffled-priority-queue": "^2.1.0",
315 "streamx": "^2.22.1",
316 "unslab": "^1.3.0"
317 }
318 },
319 "node_modules/kademlia-routing-table": {
320 "version": "1.0.6",
321 "resolved": "https://registry.npmjs.org/kademlia-routing-table/-/kademlia-routing-table-1.0.6.tgz",
322 "integrity": "sha512-Ve6jwIlUCYvUzBnXnzVRHDZCFgXURW9gmF3r7n05kZs/2rNbLHXwGdcq0qIaSwdmJCvtosgR4JensnVU65hzNQ==",
323 "license": "MIT",
324 "dependencies": {
325 "bare-events": "^2.2.0"
326 }
327 },
328 "node_modules/nanoassert": {
329 "version": "2.0.0",
330 "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
331 "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==",
332 "license": "ISC"
333 },
334 "node_modules/nat-sampler": {
335 "version": "1.0.1",
336 "resolved": "https://registry.npmjs.org/nat-sampler/-/nat-sampler-1.0.1.tgz",
337 "integrity": "sha512-yQvyNN7xbqR8crTKk3U8gRgpcV1Az+vfCEijiHu9oHHsnIl8n3x+yXNHl42M6L3czGynAVoOT9TqBfS87gDdcw==",
338 "license": "MIT"
339 },
340 "node_modules/noise-curve-ed": {
341 "version": "2.1.0",
342 "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz",
343 "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==",
344 "license": "ISC",
345 "dependencies": {
346 "b4a": "^1.1.0",
347 "nanoassert": "^2.0.0",
348 "sodium-universal": "^5.0.0"
349 }
350 },
351 "node_modules/noise-handshake": {
352 "version": "4.2.0",
353 "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz",
354 "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==",
355 "license": "Apache-2.0",
356 "dependencies": {
357 "b4a": "^1.1.0",
358 "nanoassert": "^2.0.0",
359 "sodium-universal": "^5.0.0"
360 }
361 },
362 "node_modules/protomux": {
363 "version": "3.10.1",
364 "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz",
365 "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==",
366 "license": "MIT",
367 "dependencies": {
368 "b4a": "^1.3.1",
369 "compact-encoding": "^2.5.1",
370 "queue-tick": "^1.0.0",
371 "safety-catch": "^1.0.1",
372 "unslab": "^1.3.0"
373 }
374 },
375 "node_modules/queue-tick": {
376 "version": "1.0.1",
377 "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
378 "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
379 "license": "MIT"
380 },
381 "node_modules/record-cache": {
382 "version": "1.2.0",
383 "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz",
384 "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==",
385 "license": "MIT",
386 "dependencies": {
387 "b4a": "^1.3.1"
388 }
389 },
390 "node_modules/require-addon": {
391 "version": "1.2.0",
392 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
393 "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==",
394 "license": "Apache-2.0",
395 "dependencies": {
396 "bare-addon-resolve": "^1.3.0"
397 },
398 "engines": {
399 "bare": ">=1.10.0"
400 }
401 },
402 "node_modules/safety-catch": {
403 "version": "1.0.2",
404 "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.2.tgz",
405 "integrity": "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==",
406 "license": "MIT"
407 },
408 "node_modules/shuffled-priority-queue": {
409 "version": "2.1.0",
410 "resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz",
411 "integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==",
412 "license": "MIT",
413 "dependencies": {
414 "unordered-set": "^2.0.1"
415 }
416 },
417 "node_modules/signal-promise": {
418 "version": "1.0.3",
419 "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz",
420 "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==",
421 "license": "MIT"
422 },
423 "node_modules/sodium-native": {
424 "version": "5.0.10",
425 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz",
426 "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==",
427 "license": "MIT",
428 "dependencies": {
429 "require-addon": "^1.1.0",
430 "which-runtime": "^1.2.1"
431 },
432 "engines": {
433 "bare": ">=1.16.0"
434 }
435 },
436 "node_modules/sodium-secretstream": {
437 "version": "1.2.0",
438 "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz",
439 "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==",
440 "license": "MIT",
441 "dependencies": {
442 "b4a": "^1.1.1",
443 "sodium-universal": "^5.0.0"
444 }
445 },
446 "node_modules/sodium-universal": {
447 "version": "5.0.1",
448 "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz",
449 "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==",
450 "license": "MIT",
451 "dependencies": {
452 "sodium-native": "^5.0.1"
453 },
454 "peerDependencies": {
455 "sodium-javascript": "~0.8.0"
456 },
457 "peerDependenciesMeta": {
458 "sodium-javascript": {
459 "optional": true
460 }
461 }
462 },
463 "node_modules/streamx": {
464 "version": "2.23.0",
465 "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
466 "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
467 "license": "MIT",
468 "dependencies": {
469 "events-universal": "^1.0.0",
470 "fast-fifo": "^1.3.2",
471 "text-decoder": "^1.1.0"
472 }
473 },
474 "node_modules/text-decoder": {
475 "version": "1.2.3",
476 "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
477 "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
478 "license": "Apache-2.0",
479 "dependencies": {
480 "b4a": "^1.6.4"
481 }
482 },
483 "node_modules/time-ordered-set": {
484 "version": "2.0.1",
485 "resolved": "https://registry.npmjs.org/time-ordered-set/-/time-ordered-set-2.0.1.tgz",
486 "integrity": "sha512-VJEKmgSN2UiOLB8BpN8Sh2b9LGMHTP5OPrQRpnKjvOheOyzk0mufbjzjKTIG2gO4A+Y+vDJ+0TcLbpUmMLsg8A==",
487 "license": "MIT"
488 },
489 "node_modules/timeout-refresh": {
490 "version": "2.0.1",
491 "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz",
492 "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==",
493 "license": "MIT"
494 },
495 "node_modules/udx-native": {
496 "version": "1.19.2",
497 "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.19.2.tgz",
498 "integrity": "sha512-RNYh+UhfryCsF5hE2ZOuIqcZ+qdipXK3UsarwxWJwsUQZFE3ybwz0mPjwb5ev1PMBcjFahWiepS/q0wwL51c2g==",
499 "license": "Apache-2.0",
500 "dependencies": {
501 "b4a": "^1.5.0",
502 "bare-events": "^2.2.0",
503 "require-addon": "^1.1.0",
504 "streamx": "^2.22.0"
505 },
506 "engines": {
507 "bare": ">=1.17.4"
508 }
509 },
510 "node_modules/unordered-set": {
511 "version": "2.0.1",
512 "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz",
513 "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==",
514 "license": "MIT"
515 },
516 "node_modules/unslab": {
517 "version": "1.3.0",
518 "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz",
519 "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==",
520 "license": "Apache-2.0",
521 "dependencies": {
522 "b4a": "^1.6.6"
523 }
524 },
525 "node_modules/which-runtime": {
526 "version": "1.3.2",
527 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz",
528 "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==",
529 "license": "Apache-2.0"
530 },
531 "node_modules/xache": {
532 "version": "1.2.1",
533 "resolved": "https://registry.npmjs.org/xache/-/xache-1.2.1.tgz",
534 "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==",
535 "license": "MIT"
536 },
537 "node_modules/z32": {
538 "version": "1.1.0",
539 "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz",
540 "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==",
541 "license": "MIT",
542 "dependencies": {
543 "b4a": "^1.5.3"
544 }
545 }
546 }
547}
package.json +19 -0
……@@ -0,0 +1,19 @@
1{
2 "name": "atomspace-p2p-bridge",
3 "version": "1.0.0",
4 "description": "Hyperswarm TCP bridge for CogServer — P2P AtomSpace networking",
5 "main": "bridge.js",
6 "type": "commonjs",
7 "bin": {
8 "atomspace-bridge": "./bridge.js"
9 },
10 "dependencies": {
11 "@hyperswarm/testnet": "^3.1.4",
12 "b4a": "^1.6.4",
13 "compact-encoding": "^2.12.0",
14 "hypercore-crypto": "^3.3.1",
15 "hyperident": "file:../../hyper-fleeky/repos/hyperident",
16 "hyperswarm": "^4.4.1",
17 "protomux": "^3.4.0"
18 }
19}
test-bridge.js +156 -0
……@@ -0,0 +1,156 @@
1#!/usr/bin/env node
2'use strict'
3
4// Full bridge integration test:
5// 1. Start a mock CogServer (TCP echo server on 17001)
6// 2. Start bridge server with testnet
7// 3. Start bridge client pointing to server
8// 4. Connect to client proxy port
9// 5. Verify data flows through
10
11const net = require('net')
12const { spawn } = require('child_process')
13const path = require('path')
14const crypto = require('hypercore-crypto')
15const b4a = require('b4a')
16
17const BRIDGE = path.join(__dirname, 'bridge.js')
18const COG_PORT = 17099 // Mock CogServer
19const PROXY_PORT = 17098 // Client proxy
20const TEST_SEED = b4a.toString(crypto.randomBytes(32), 'hex')
21
22async function test () {
23 console.log('=== Bridge Integration Test ===\n')
24
25 // Step 1: Mock CogServer — echoes with "OK: " prefix
26 const mockCog = net.createServer((sock) => {
27 console.log('[mock-cog] Client connected')
28 sock.on('data', (d) => {
29 console.log('[mock-cog] Received:', d.toString().trim())
30 sock.write('OK: ' + d.toString())
31 })
32 sock.on('error', () => {})
33 })
34
35 await new Promise((resolve) => mockCog.listen(COG_PORT, resolve))
36 console.log('[mock-cog] Listening on port', COG_PORT)
37
38 // Step 2: Start bridge server with testnet
39 const server = spawn('node', [
40 BRIDGE, 'server',
41 '--port', String(COG_PORT),
42 '--seed', TEST_SEED,
43 '--testnet', '.test-testnet.json'
44 ], { cwd: __dirname })
45
46 let serverKey = null
47
48 server.stdout.on('data', (d) => {
49 const line = d.toString()
50 process.stdout.write('[server] ' + line)
51 const m = line.match(/Public key: ([a-f0-9]{64})/)
52 if (m) serverKey = m[1]
53 })
54 server.stderr.on('data', (d) => process.stderr.write('[server-err] ' + d.toString()))
55
56 // Wait for server to announce on DHT
57 await new Promise((resolve) => {
58 const check = setInterval(() => {
59 if (serverKey) {
60 clearInterval(check)
61 resolve()
62 }
63 }, 200)
64 })
65
66 // Extra wait for DHT announce
67 await new Promise(r => setTimeout(r, 2000))
68
69 console.log('\n[test] Server key:', serverKey)
70
71 // Step 3: Start bridge client using testnet
72 const client = spawn('node', [
73 BRIDGE, 'client', serverKey,
74 '--proxy-port', String(PROXY_PORT),
75 '--testnet', '.test-testnet.json'
76 ], { cwd: __dirname })
77
78 let clientReady = false
79
80 client.stdout.on('data', (d) => {
81 const line = d.toString()
82 process.stdout.write('[client] ' + line)
83 if (line.includes('Channel ready') || line.includes('Connected to server')) {
84 clientReady = true
85 }
86 })
87 client.stderr.on('data', (d) => process.stderr.write('[client-err] ' + d.toString()))
88
89 // Wait for client to connect to server
90 await new Promise((resolve) => {
91 const check = setInterval(() => {
92 if (clientReady) {
93 clearInterval(check)
94 resolve()
95 }
96 }, 200)
97 // Timeout after 15s
98 setTimeout(() => {
99 clearInterval(check)
100 resolve()
101 }, 15000)
102 })
103
104 if (!clientReady) {
105 console.log('\n[test] FAILED: Client never connected to server')
106 cleanup()
107 return
108 }
109
110 await new Promise(r => setTimeout(r, 1000))
111
112 // Step 4: Connect to proxy port and send data
113 console.log('\n[test] Connecting to proxy port', PROXY_PORT)
114 const conn = net.connect(PROXY_PORT, '127.0.0.1')
115
116 const result = await new Promise((resolve, reject) => {
117 conn.on('connect', () => {
118 console.log('[test] Connected to proxy — sending test data')
119 conn.write('(cog-version)\n')
120 })
121
122 conn.on('data', (d) => {
123 console.log('[test] Received:', d.toString().trim())
124 resolve(d.toString())
125 })
126
127 conn.on('error', (err) => {
128 console.log('[test] Connection error:', err.message)
129 reject(err)
130 })
131
132 setTimeout(() => resolve(null), 5000)
133 })
134
135 // Step 5: Verify
136 if (result && result.includes('OK:')) {
137 console.log('\n✓ SUCCESS — data flowed through P2P bridge!')
138 } else {
139 console.log('\n✗ FAILED — no response from mock CogServer through bridge')
140 }
141
142 function cleanup () {
143 conn && conn.destroy()
144 server.kill('SIGINT')
145 client.kill('SIGINT')
146 mockCog.close()
147 setTimeout(() => process.exit(0), 500)
148 }
149
150 cleanup()
151}
152
153test().catch(err => {
154 console.error('Test error:', err)
155 process.exit(1)
156})
test-connect.js +78 -0
……@@ -0,0 +1,78 @@
1#!/usr/bin/env node
2'use strict'
3
4// Minimal same-machine Hyperswarm connectivity test
5// Tests the fix: testnet + flushed()
6
7const Hyperswarm = require('hyperswarm')
8const createTestnet = require('@hyperswarm/testnet')
9const crypto = require('hypercore-crypto')
10const b4a = require('b4a')
11
12async function test () {
13 console.log('Creating testnet with 3 DHT bootstrap nodes...')
14 const testnet = await createTestnet(3)
15 console.log('Testnet ready:', testnet.bootstrap.map(n => n.host + ':' + n.port))
16
17 const topic = crypto.hash(b4a.from('atomspace-bridge-test'))
18
19 // Server swarm
20 const server = new Hyperswarm({ bootstrap: testnet.bootstrap })
21 server.on('connection', (conn, info) => {
22 console.log('[server] Got connection from', b4a.toString(info.publicKey, 'hex').slice(0, 12))
23 conn.on('data', d => console.log('[server] Received:', d.toString()))
24 conn.write('hello from server')
25 conn.on('error', () => {})
26 })
27
28 // Client swarm
29 const client = new Hyperswarm({ bootstrap: testnet.bootstrap })
30 client.on('connection', (conn, info) => {
31 console.log('[client] Got connection to', b4a.toString(info.publicKey, 'hex').slice(0, 12))
32 conn.on('data', d => console.log('[client] Received:', d.toString()))
33 conn.write('hello from client')
34 conn.on('error', () => {})
35 })
36
37 // Server: join + wait for DHT announce
38 console.log('\nServer joining topic...')
39 const t0 = Date.now()
40 const discovery = server.join(topic, { server: true, client: false })
41 await discovery.flushed()
42 console.log('Server announced on DHT (' + (Date.now() - t0) + 'ms)')
43
44 // Client: join + lookup
45 console.log('Client joining topic...')
46 const t1 = Date.now()
47 client.join(topic, { server: false, client: true })
48 await client.flush()
49 console.log('Client connected (' + (Date.now() - t1) + 'ms)')
50
51 // Wait a beat for both connection events to fire
52 await new Promise(r => setTimeout(r, 500))
53
54 console.log('\nServer connections:', server.connections.size)
55 console.log('Client connections:', client.connections.size)
56
57 if (server.connections.size > 0 && client.connections.size > 0) {
58 console.log('\n✓ SUCCESS — peers connected!')
59 } else {
60 console.log('\n✗ FAILED — no connections')
61 }
62
63 // Give time for data exchange
64 await new Promise(r => setTimeout(r, 500))
65
66 // Cleanup
67 await server.destroy()
68 await client.destroy()
69 await testnet.destroy()
70
71 console.log('\nDone.')
72 process.exit(0)
73}
74
75test().catch(err => {
76 console.error('Test failed:', err)
77 process.exit(1)
78})
test-production-dht.js +77 -0
……@@ -0,0 +1,77 @@
1#!/usr/bin/env node
2'use strict'
3
4// Test same-machine connectivity on PRODUCTION DHT
5// with the flushed() fix applied
6
7const Hyperswarm = require('hyperswarm')
8const crypto = require('hypercore-crypto')
9const b4a = require('b4a')
10
11async function test () {
12 const topic = crypto.hash(b4a.from('atomspace-bridge-test-' + Date.now()))
13
14 console.log('Topic:', b4a.toString(topic, 'hex').slice(0, 24) + '...')
15 console.log('Using PRODUCTION DHT (no testnet)\n')
16
17 // Server swarm
18 const server = new Hyperswarm()
19 let serverConnected = false
20 server.on('connection', (conn, info) => {
21 serverConnected = true
22 console.log('[server] Got connection from', b4a.toString(info.publicKey, 'hex').slice(0, 12))
23 conn.on('data', d => console.log('[server] Received:', d.toString()))
24 conn.write('hello from server')
25 conn.on('error', () => {})
26 })
27
28 // Client swarm
29 const client = new Hyperswarm()
30 let clientConnected = false
31 client.on('connection', (conn, info) => {
32 clientConnected = true
33 console.log('[client] Got connection to', b4a.toString(info.publicKey, 'hex').slice(0, 12))
34 conn.on('data', d => console.log('[client] Received:', d.toString()))
35 conn.write('hello from client')
36 conn.on('error', () => {})
37 })
38
39 // Server: join + wait for DHT announce (THE FIX)
40 console.log('Server: joining topic + waiting for flushed()...')
41 const t0 = Date.now()
42 const discovery = server.join(topic, { server: true, client: false })
43 await discovery.flushed()
44 const announceMs = Date.now() - t0
45 console.log('Server: DHT announce complete (' + announceMs + 'ms)')
46 console.log('Server: DHT bootstrapped')
47
48 // Client: join + wait for connection
49 console.log('\nClient: joining topic + flushing...')
50 const t1 = Date.now()
51 client.join(topic, { server: false, client: true })
52 await client.flush()
53 const lookupMs = Date.now() - t1
54 console.log('Client: flush complete (' + lookupMs + 'ms)')
55
56 // Wait for connection events
57 await new Promise(r => setTimeout(r, 2000))
58
59 console.log('\nServer connections:', server.connections.size, '(event fired:', serverConnected + ')')
60 console.log('Client connections:', client.connections.size, '(event fired:', clientConnected + ')')
61
62 if (serverConnected && clientConnected) {
63 console.log('\n✓ SUCCESS — production DHT works with flushed()!')
64 console.log(' Announce: ' + announceMs + 'ms, Lookup: ' + lookupMs + 'ms')
65 } else {
66 console.log('\n✗ FAILED — need testnet for same-machine connections')
67 }
68
69 await server.destroy()
70 await client.destroy()
71 process.exit(0)
72}
73
74test().catch(err => {
75 console.error('Test failed:', err)
76 process.exit(1)
77})