| … | … | @@ -37,7 +37,7 @@ steps: |
| 37 | 37 | # NOTE: requires the sibling repos present on the build machine (KB path or $HOME). |
| 38 | 38 | - name: vendor extracted atproto-repo + atproto-identity siblings |
| 39 | 39 | run: | |
| 40 | for pkg in atproto-repo atproto-identity atproto-store; do | |
| 40 | for pkg in atproto-repo atproto-identity atproto-store atproto-did-peer; do | |
| 41 | 41 | SRC=/storage/projects/code/hypercore-kb/hyper-fleeky/$pkg |
| 42 | 42 | [ -d "$SRC" ] || SRC="$HOME/hyper-fleeky/$pkg" |
| 43 | 43 | rm -rf "node_modules/$pkg" && cp -rL "$SRC" "node_modules/$pkg" && echo "vendored $pkg from $SRC" |
| … | … | @@ -16,7 +16,9 @@ import { openCorestore } from 'atproto-store/lib/corestore.js' |
| 16 | 16 | import { loadConfig } from '../lib/config.js' |
| 17 | 17 | import { startControl } from '../lib/control.js' |
| 18 | 18 | import { FleetRelay } from '../lib/fleet-relay.js' |
| 19 | import { PeerRouter } from '../lib/peer-router.js' | |
| 19 | import { DidPeerRouter } from 'atproto-did-peer' | |
| 20 | import { resolvePlcVerifyKey } from '../lib/verify-key.js' | |
| 21 | import { discoveryTopic } from 'atproto-identity/lib/discovery.js' | |
| 20 | 22 | |
| 21 | 23 | const cfg = loadConfig() |
| 22 | 24 | |
| … | … | @@ -26,7 +28,7 @@ const isBare = typeof Bare !== 'undefined' |
| 26 | 28 | const { createPdsServer } = await import(isBare ? '../lib/server-bare.js' : '../lib/server.js') |
| 27 | 29 | |
| 28 | 30 | // Bare has no global `fetch`; PLC resolution (verify a fronted peer's DID key before serving |
| 29 | // it — peer-router.js resolvePlcVerifyKey) needs one. Install the WHATWG-standard bare-fetch | |
| 31 | // it — verify-key.js resolvePlcVerifyKey) needs one. Install the WHATWG-standard bare-fetch | |
| 30 | 32 | // global so it works on the prod (bare) runtime. Without this, front() throws "fetch is not |
| 31 | 33 | // defined" and NO peer is fronted — silently dropping wj62. Node already has global fetch. |
| 32 | 34 | if (isBare) await import('bare-fetch/global') |
| … | … | @@ -58,7 +60,21 @@ const handleMapFile = path.join(cfg.storage, 'handle-map.json') |
| 58 | 60 | let handles = {} |
| 59 | 61 | try { handles = JSON.parse(fs.readFileSync(handleMapFile, 'utf8')) || {} } catch {} |
| 60 | 62 | const persistHandles = (obj) => { try { fs.mkdirSync(cfg.storage, { recursive: true }) } catch {}; fs.writeFileSync(handleMapFile, JSON.stringify(obj)) } |
| 61 | const peers = new PeerRouter(swarm, frontStore, { fronted: cfg.frontedDids, relay, handles, onHandles: persistHandles }) | |
| 63 | // The canonical consumer primitive (atproto-did-peer DidPeerRouter — unified across bridge/favela in | |
| 64 | // EXTRACTION-PLAN Phase 1). Owns-swarm mode: it auto-wires onconnection + joins each fronted topic in its | |
| 65 | // ctor (via `join`) + runs the pull-only refresh/failover timer. `rpc: true` opens one protomux-rpc per | |
| 66 | // connection for the peer-signs-over-RPC write/session/blob path. `resolveVerifyKey` fronts a DID with no | |
| 67 | // pre-seeded key via PLC. `relay` folds the freshest co-owner's firehose into the aggregate subscribeRepos. | |
| 68 | const peers = new DidPeerRouter(frontStore, { | |
| 69 | swarm, | |
| 70 | join: (did) => { try { swarm.join(discoveryTopic(did), { server: true, client: true }); swarm.flush().catch(() => {}) } catch {} }, | |
| 71 | fronted: cfg.frontedDids, | |
| 72 | relay, | |
| 73 | resolveVerifyKey: resolvePlcVerifyKey, | |
| 74 | rpc: true, | |
| 75 | handles, | |
| 76 | onHandles: persistHandles | |
| 77 | }) | |
| 62 | 78 | |
| 63 | 79 | // 1. bsky HTTP+WS membrane — keyless per-DID routing to the fronted peers. |
| 64 | 80 | const server = createPdsServer({ peers, serverDid: cfg.serverDid, availableUserDomains: [cfg.userDomain], favelaWebUpstream: cfg.favelaWebUpstream }) |
| … | … | @@ -0,0 +1,18 @@ |
| 1 | // resolvePlcVerifyKey — resolve a DID's atproto signing pubkey from its DID document (PLC). The doc's | |
| 2 | // verificationMethod.publicKeyMultibase is the same multibase multikey as a did:key's suffix, so we decode | |
| 3 | // it via `did:key:<multibase>`. `doFetch` is injectable (bare-fetch in prod, global fetch in node/tests). | |
| 4 | // | |
| 5 | // Kept bridge-side (not in the shared atproto-did-peer module) so that module needs no atproto-identity | |
| 6 | // runtime dep. Moved here from the retired lib/peer-router.js (EXTRACTION-PLAN Phase 1b). Passed to | |
| 7 | // DidPeerRouter as its `resolveVerifyKey` so a fronted DID with no add({verifyKey}) resolves via PLC. | |
| 8 | import { decodeDidKey } from 'atproto-identity/lib/did-key.js' | |
| 9 | ||
| 10 | export async function resolvePlcVerifyKey (did, { plcUrl = 'https://plc.directory', doFetch = fetch } = {}) { | |
| 11 | const res = await doFetch(`${plcUrl}/${did}`) | |
| 12 | if (!res.ok) throw new Error(`resolvePlcVerifyKey: PLC ${res.status} for ${did}`) | |
| 13 | const doc = await res.json() | |
| 14 | const vm = (doc.verificationMethod || []).find((m) => m.id && m.id.endsWith('#atproto')) || (doc.verificationMethod || [])[0] | |
| 15 | if (!vm || !vm.publicKeyMultibase) throw new Error(`resolvePlcVerifyKey: no verificationMethod for ${did}`) | |
| 16 | const { pubkey } = decodeDidKey(`did:key:${vm.publicKeyMultibase}`) | |
| 17 | return pubkey | |
| 18 | } |
| … | … | @@ -12,6 +12,7 @@ |
| 12 | 12 | "atproto-repo": "file:../../atproto-repo", |
| 13 | 13 | "atproto-identity": "file:../../atproto-identity", |
| 14 | 14 | "atproto-store": "file:../../atproto-store", |
| 15 | "atproto-did-peer": "file:../../atproto-did-peer", | |
| 15 | 16 | "favela-community": "file:../../favela-community", |
| 16 | 17 | "protomux": "^3.11.0", |
| 17 | 18 | "protomux-rpc": "^1.10.0", |
| … | … | @@ -23,7 +23,7 @@ import { keyFromPrivate } from 'atproto-identity' |
| 23 | 23 | import { discoveryTopic } from 'atproto-identity/lib/discovery.js' |
| 24 | 24 | import { createPdsServer } from '../lib/server.js' |
| 25 | 25 | import { startControl } from '../lib/control.js' |
| 26 | import { PeerRouter } from '../lib/peer-router.js' | |
| 26 | import { DidPeerRouter } from 'atproto-did-peer' | |
| 27 | 27 | |
| 28 | 28 | process.env.PGH_SIDECAR_SOCK = join(tmpdir(), `no-sidecar-${process.pid}.sock`) // no registration |
| 29 | 29 | |
| … | … | @@ -55,10 +55,17 @@ test('pure membrane (pds=null) fronts a peer, synthesizes describeServer/listRep |
| 55 | 55 | }) |
| 56 | 56 | await peerSwarm.join(discoveryTopic(DID), { server: true, client: false }).flushed() |
| 57 | 57 | |
| 58 | // THE MEMBRANE: no Pds at all — just a PeerRouter fronting DID + the HTTP server with pds=null. | |
| 58 | // THE MEMBRANE: no Pds at all — the shared DidPeerRouter fronting DID + the HTTP server with pds=null. | |
| 59 | // Constructed EXACTLY as pds-serve.js does (owns-swarm + join + rpc) — verifies the prod repoint. | |
| 59 | 60 | const routerStore = openCorestore(mkdtempSync(join(tmpdir(), 'monly-router-'))) |
| 60 | 61 | const routerSwarm = new Hyperswarm({ bootstrap: testnet.bootstrap }) |
| 61 | const router = new PeerRouter(routerSwarm, routerStore, { fronted: [DID], resolveVerifyKey: async () => keyFromPrivate(PRIV).publicKey }) | |
| 62 | const router = new DidPeerRouter(routerStore, { | |
| 63 | swarm: routerSwarm, | |
| 64 | join: (did) => { try { routerSwarm.join(discoveryTopic(did), { server: true, client: true }); routerSwarm.flush().catch(() => {}) } catch {} }, | |
| 65 | fronted: [DID], | |
| 66 | resolveVerifyKey: async () => keyFromPrivate(PRIV).publicKey, | |
| 67 | rpc: true | |
| 68 | }) | |
| 62 | 69 | const server = createPdsServer({ peers: router, serverDid: SERVER_DID, availableUserDomains: DOMAINS }) |
| 63 | 70 | await new Promise((r) => server.listen(0, '127.0.0.1', r)) |
| 64 | 71 | const base = `http://127.0.0.1:${server.address().port}` |
| … | … | @@ -20,7 +20,7 @@ import { FirehoseSequencer } from 'atproto-store/lib/sequencer.js' |
| 20 | 20 | import { keyFromPrivate } from 'atproto-identity' |
| 21 | 21 | import { discoveryTopic } from 'atproto-identity/lib/discovery.js' |
| 22 | 22 | import { createPdsServer } from '../lib/server.js' |
| 23 | import { PeerRouter } from '../lib/peer-router.js' | |
| 23 | import { DidPeerRouter } from 'atproto-did-peer' | |
| 24 | 24 | |
| 25 | 25 | const DID = 'did:plc:membrane' |
| 26 | 26 | const HANDLE = 'membrane.7rnx.net' |
| … | … | @@ -48,10 +48,17 @@ test('HTTP membrane routes a fronted DID to its peer (reads + createSession)', a |
| 48 | 48 | }) |
| 49 | 49 | await peerSwarm.join(discoveryTopic(DID), { server: true, client: false }).flushed() |
| 50 | 50 | |
| 51 | // bridge: a keyless membrane — just a PeerRouter fronting DID + the HTTP membrane (no Pds) | |
| 51 | // bridge: a keyless membrane — the shared DidPeerRouter fronting DID + the HTTP membrane (no Pds). | |
| 52 | // Constructed EXACTLY as pds-serve.js does (owns-swarm + join + rpc), so this verifies the prod repoint. | |
| 52 | 53 | const routerStore = openCorestore(mkdtempSync(join(tmpdir(), 'mem-router-'))) |
| 53 | 54 | const routerSwarm = new Hyperswarm({ bootstrap: testnet.bootstrap }) |
| 54 | const router = new PeerRouter(routerSwarm, routerStore, { fronted: [DID], resolveVerifyKey: async () => keyFromPrivate(PRIV).publicKey }) | |
| 55 | const router = new DidPeerRouter(routerStore, { | |
| 56 | swarm: routerSwarm, | |
| 57 | join: (did) => { try { routerSwarm.join(discoveryTopic(did), { server: true, client: true }); routerSwarm.flush().catch(() => {}) } catch {} }, | |
| 58 | fronted: [DID], | |
| 59 | resolveVerifyKey: async () => keyFromPrivate(PRIV).publicKey, | |
| 60 | rpc: true | |
| 61 | }) | |
| 55 | 62 | const server = createPdsServer({ peers: router, serverDid: 'did:web:pds.7rnx.net' }) |
| 56 | 63 | await new Promise((r) => server.listen(0, '127.0.0.1', r)) |
| 57 | 64 | const base = `http://127.0.0.1:${server.address().port}` |