// attachPresence — the transport glue for SignerPresence. On a StateBase swarm connection, announce OUR
// member key and learn the PEER's, feeding a SignerPresence so the signer election tracks who is connected.
// Ephemeral by construction — it appends NOTHING to any log; presence lives and dies with the connection.
//
// Mirrors serve-membrane's announce-push channel exactly (`Protomux.from(conn).createChannel` → addMessage →
// open → send), the keet "one mux per connection" model, so it coexists with base.replicate on the same
// stream. The peer's key arrives via onmessage → markPresent; the connection closing → markAbsent (grace).
import Protomux from 'protomux'
import c from 'compact-encoding'
export const PRESENCE_PROTOCOL = 'atproto-room/signer-presence/v1'
// conn: a hyperswarm/replication stream. selfKey: our member key (hex string). presence: a SignerPresence.
// id: an OPTIONAL per-DID channel id (the roomKey buffer). One shared multi-DID daemon connection carries
// several DIDs' presence over ONE mux; protomux keys channels on (protocol, id) (toKey), so distinct ids
// let each DID's presence channel coexist. Without an id every DID collides on (PRESENCE_PROTOCOL, null) and
// createChannel returns null for all but the first → only the first DID's co-owners ever exchange presence
// (the rest each think they're the sole signer → fork). Single-DID callers pass no id (id=null, unchanged).
// Returns a cleanup fn (idempotent). Safe to call on every connection AND to double-attach the same (conn,
// id): protomux returns null on the duplicate, so the 2nd attach is a harmless no-op (the 1st stays live).
// A non-presence peer simply never sends, so it's never marked present (and thus never eligible to sign).
// getHead: optional () => our current signed head rev (a TID string) — announced so the peer's election
// can tell whether WE are caught up (currency). Returns { send(headRev?), close() }: `send` RE-announces
// our head when it changes (the caller wires this to head advances) so a stale head can't mis-elect a
// signer; `close` tears the channel down. (A non-object return would break callers that register `send`.)
// onReceive: optional (peerKey) => void, called on every received announce (a diagnostic hook).
export function attachPresence (conn, { selfKey, presence, id = null, getHead = null, onReceive = null } = {}) {
// Returns a DETACH function (call it to tear down — the original contract) with a `.send(headRev)`
// property for re-announcing our head when it changes, and `.attached`: whether this call actually got the
// channel. ⛔ A duplicate (protocol, id) on a mux is a deliberate no-op — and was SILENT: 2026-10-14 loser-club's
// co-owners each saw only themselves while wj62's presence over the same connections worked, and nothing
// could say whether an attach had been a no-op. `.attached === false` says so.
const noop = () => {}; noop.send = () => {}; noop.attached = false
if (!conn || !selfKey || !presence) return noop
const self = String(selfKey)
let peerKey = null
let done = false
let chan = null
let msg = null
const clear = () => { if (peerKey) presence.markAbsent(peerKey) }
const announce = (headRev) => {
if (!msg) return
try { msg.send({ key: self, headRev: headRev !== undefined ? (headRev || null) : (getHead ? getHead() : null) }) } catch {}
}
try {
const mux = Protomux.from(conn)
// ⛔ A LATE ATTACH MUST STILL PAIR (2026-10-14, loser-club). Protomux REJECTS a remote open that finds no local
// channel and no pair() handler (_requestSession → _rejectSession), and the rejected side's channel closes.
// A DID that boots after the connection is up (a resync side store) attached after its peer had already
// opened: the peer's open was rejected, then ours hit a peer with no channel and no handler and was rejected
// too — both attached, both received nothing, both elected themselves. So the channel is (re)opened on
// demand: once now, and again from pair() whenever the peer opens while we have no live channel.
const openChannel = () => {
if (done) return false
if (chan) return true
const ch = mux.createChannel({
protocol: PRESENCE_PROTOCOL,
id,
onclose: () => {
if (chan !== ch) return
chan = null
msg = null
// A channel that closes with the connection still up (rejected, or the peer detached) loses the peer.
if (peerKey) { presence.markAbsent(peerKey); peerKey = null }
}
})
if (!ch) return false // duplicate (protocol,id) on this mux → another attach owns it
const m = ch.addMessage({
encoding: c.json,
onmessage: (data) => {
if (!data || !data.key) return
peerKey = String(data.key)
if (onReceive) { try { onReceive(peerKey) } catch {} }
presence.markPresent(peerKey) // presence (fires onChange on connect)
if (data.headRev !== undefined) presence.noteHead(peerKey, data.headRev || null) // currency (fires onChange on a head change)
}
})
chan = ch
msg = m
ch.open()
announce() // initial announce with our current head
return true
}
if (!openChannel()) return noop // duplicate → harmless no-op (the first attach owns pairing too)
mux.pair({ protocol: PRESENCE_PROTOCOL, id }, () => { openChannel() })
const onclose = () => { if (done) return; done = true; clear() }
conn.on('close', onclose)
const detach = () => {
if (done) return
done = true
try { mux.unpair({ protocol: PRESENCE_PROTOCOL, id }) } catch {}
try { conn.off('close', onclose) } catch {}
clear()
}
detach.send = (headRev) => announce(headRev) // re-announce our head (currency) when it changes
detach.attached = true
return detach
} catch { return noop }
}