| … | … | @@ -0,0 +1,74 @@ |
| 1 | // LIVE swarm e2e — over the REAL DHT (real Hyperswarm bootstrap, NOT @hyperswarm/testnet, NOT in-process |
| 2 | // pipes). Two INDEPENDENT swarm nodes discover each other on the DHT and replicate a community room: |
| 3 | // - SEEDER : an owner room + a real Hyperswarm serving it — the exact mechanism the CLI `serve` uses |
| 4 | // (base.replicate on every connection, join(discoveryKey)). |
| 5 | // - READER : favela-web's exact keyless read path — CommunityRooms(store, realSwarm).members(roomKey). |
| 6 | // Proves: owner `serve` ↔ favela-web keyless read, over a live swarm. Needs NETWORK (DHT bootstrap). |
| 7 | // Run: npm run e2e (NOT part of the offline `npm test`). |
| 8 | import process from 'bare-process' |
| 9 | import Corestore from 'corestore' |
| 10 | import Hyperswarm from 'hyperswarm' |
| 11 | import { CommunityRoom, CommunityRooms } from '../index.js' |
| 12 | |
| 13 | const stamp = Date.now() |
| 14 | const SEED_STORE = 'data/e2e-seed-' + stamp |
| 15 | const READ_STORE = 'data/e2e-read-' + stamp |
| 16 | const MEMBERS = ['did:plc:alice2222222222222222222', 'did:plc:bob222222222222222222222', 'did:plc:carol22222222222222222222'] |
| 17 | const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) |
| 18 | const log = (...a) => console.log('[e2e]', ...a) |
| 19 | |
| 20 | let seedStore, readStore, seedSwarm, readSwarm, rooms, ownerRoom |
| 21 | async function cleanup () { |
| 22 | try { if (seedSwarm) await seedSwarm.destroy() } catch {} |
| 23 | try { if (readSwarm) await readSwarm.destroy() } catch {} |
| 24 | try { if (rooms) await rooms.close() } catch {} |
| 25 | try { if (ownerRoom) await ownerRoom.close() } catch {} |
| 26 | try { if (seedStore) await seedStore.close() } catch {} |
| 27 | try { if (readStore) await readStore.close() } catch {} |
| 28 | } |
| 29 | |
| 30 | async function main () { |
| 31 | // ── SEEDER: owner creates + populates a room, then serves it on a real swarm ── |
| 32 | seedStore = new Corestore(SEED_STORE) |
| 33 | ownerRoom = await CommunityRoom.create(seedStore, { name: 'Live E2E' }) |
| 34 | const KEY = ownerRoom.key |
| 35 | for (const did of MEMBERS) await ownerRoom.addMember(did) |
| 36 | await ownerRoom.update() |
| 37 | log('owner created + populated room', KEY, '(' + (await ownerRoom.members()).length + ' members)') |
| 38 | |
| 39 | seedSwarm = new Hyperswarm() |
| 40 | seedSwarm.on('connection', (conn) => { conn.on('error', () => {}); ownerRoom.base.replicate(conn) }) // the CLI `serve` mechanism |
| 41 | seedSwarm.join(ownerRoom.discoveryKey, { server: true, client: true }) |
| 42 | await seedSwarm.flush() |
| 43 | log('seeder announced on the DHT (discoveryKey', Buffer.from(ownerRoom.discoveryKey).toString('hex').slice(0, 16) + '…)') |
| 44 | |
| 45 | // ── READER: favela-web's exact keyless read over an INDEPENDENT real swarm ── |
| 46 | readStore = new Corestore(READ_STORE) |
| 47 | readSwarm = new Hyperswarm() |
| 48 | rooms = new CommunityRooms(readStore, readSwarm) // joins the room's discoveryKey on first members() |
| 49 | log('reader joining the DHT + waiting for a live connection + replication…') |
| 50 | |
| 51 | let got = [] |
| 52 | const end = Date.now() + 90000 |
| 53 | let firstSeen = 0 |
| 54 | while (Date.now() < end) { |
| 55 | try { got = await rooms.members(KEY) } catch {} |
| 56 | const conns = readSwarm.connections.size |
| 57 | if (conns && !firstSeen) { firstSeen = Date.now(); log('reader connected to a peer (' + conns + ') — replicating…') } |
| 58 | if (got.length >= MEMBERS.length) break |
| 59 | await sleep(1500) |
| 60 | } |
| 61 | |
| 62 | const room = await rooms.room(KEY) |
| 63 | const keyless = !room.writable |
| 64 | const ok = keyless && got.length === MEMBERS.length && MEMBERS.every((m) => got.includes(m)) |
| 65 | log('reader keyless (not writable):', keyless) |
| 66 | log('reader read', got.length, '/', MEMBERS.length, 'members over the live swarm:', JSON.stringify(got)) |
| 67 | log(ok |
| 68 | ? '\nPASS — an independent keyless reader discovered the served room on the real DHT and read the full roster.' |
| 69 | : '\nFAIL — reader did not converge on the roster over the live swarm within 90s.') |
| 70 | return ok |
| 71 | } |
| 72 | |
| 73 | main().then(async (ok) => { await cleanup(); process.exit(ok ? 0 : 1) }) |
| 74 | .catch(async (e) => { console.error('[e2e] ERROR:', e && (e.stack || e.message)); await cleanup(); process.exit(1) }) |