@robota-sdk/agent-transport-webrtc 3.0.0-beta.100

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 (84) hide show
  1. package/CHANGELOG.md +694 -0
  2. package/LICENSE +661 -0
  3. package/README.md +111 -0
  4. package/dist/node/index.cjs +1 -0
  5. package/dist/node/index.d.cts +1650 -0
  6. package/dist/node/index.d.cts.map +1 -0
  7. package/dist/node/index.d.ts +1650 -0
  8. package/dist/node/index.d.ts.map +1 -0
  9. package/dist/node/index.js +2 -0
  10. package/dist/node/index.js.map +1 -0
  11. package/package.json +93 -0
  12. package/src/__tests__/admission-secure-by-default.test.ts +63 -0
  13. package/src/__tests__/admission-steps.test.ts +46 -0
  14. package/src/__tests__/device-mesh-relay.test.ts +441 -0
  15. package/src/__tests__/device-mesh.test.ts +682 -0
  16. package/src/__tests__/discovering-mesh-relay.test.ts +234 -0
  17. package/src/__tests__/enrollment-link.test.ts +103 -0
  18. package/src/__tests__/fake-datachannel.ts +83 -0
  19. package/src/__tests__/handoff-grant-gate.test.ts +403 -0
  20. package/src/__tests__/local-peer-proof-gate.test.ts +215 -0
  21. package/src/__tests__/mesh-admission-contract.test.ts +38 -0
  22. package/src/__tests__/mesh-discovery.test.ts +820 -0
  23. package/src/__tests__/mesh-fixtures.ts +148 -0
  24. package/src/__tests__/mesh-internet.test.ts +942 -0
  25. package/src/__tests__/mesh-turn-relay.test.ts +161 -0
  26. package/src/__tests__/negotiated-certificate.test.ts +76 -0
  27. package/src/__tests__/nostr-source-condition.test.ts +27 -0
  28. package/src/__tests__/operator-approval-gate.test.ts +308 -0
  29. package/src/__tests__/outbound-delivery-carrier.test.ts +155 -0
  30. package/src/__tests__/pairing-e2e.test.ts +237 -0
  31. package/src/__tests__/pairing-gate-e3.test.ts +194 -0
  32. package/src/__tests__/pairing-gate-e4.test.ts +110 -0
  33. package/src/__tests__/pairing-gate.test.ts +296 -0
  34. package/src/__tests__/pairing-relay-in-the-middle.test.ts +225 -0
  35. package/src/__tests__/rtc-channel-close.test.ts +52 -0
  36. package/src/__tests__/rtc-peer-candidates.test.ts +61 -0
  37. package/src/__tests__/session-attachment-app-view.test.ts +66 -0
  38. package/src/__tests__/session-attachment-budget.test.ts +136 -0
  39. package/src/__tests__/turn-server-bind.test.ts +59 -0
  40. package/src/__tests__/turn-server.test.ts +335 -0
  41. package/src/__tests__/turn-test-client.ts +256 -0
  42. package/src/__tests__/webrtc-implementation.test.ts +83 -0
  43. package/src/__tests__/webrtc-transport.test.ts +349 -0
  44. package/src/__tests__/ws-mesh-relay-client.test.ts +95 -0
  45. package/src/__tests__/ws-signaling-client.test.ts +108 -0
  46. package/src/admission-steps.ts +55 -0
  47. package/src/channel-delivery.ts +44 -0
  48. package/src/datachannel-loader.ts +86 -0
  49. package/src/device-mesh-node.ts +1019 -0
  50. package/src/discovering-mesh-relay.ts +675 -0
  51. package/src/enrollment-link.ts +465 -0
  52. package/src/handoff-grant-gate.ts +186 -0
  53. package/src/index.ts +139 -0
  54. package/src/local-peer-proof.ts +135 -0
  55. package/src/mainline-dht-store.ts +174 -0
  56. package/src/mesh-dht.ts +446 -0
  57. package/src/mesh-discovery.ts +87 -0
  58. package/src/mesh-item-store.ts +287 -0
  59. package/src/mesh-lan-listener.ts +193 -0
  60. package/src/mesh-mdns.ts +406 -0
  61. package/src/mesh-peer-link.ts +600 -0
  62. package/src/mesh-public-relays.ts +25 -0
  63. package/src/mesh-records.ts +357 -0
  64. package/src/mesh-relay.ts +98 -0
  65. package/src/mesh-signal.ts +116 -0
  66. package/src/mesh-turn-relay.ts +238 -0
  67. package/src/negotiated-certificate.ts +72 -0
  68. package/src/nostr-mesh-relay.ts +391 -0
  69. package/src/pairing-channel-lifecycle.ts +31 -0
  70. package/src/pairing-controllers.ts +81 -0
  71. package/src/pairing-frames.ts +34 -0
  72. package/src/pairing-gate-options.ts +133 -0
  73. package/src/pairing-gate.ts +397 -0
  74. package/src/rtc-peer.ts +429 -0
  75. package/src/session-attachment.ts +96 -0
  76. package/src/signaling.ts +62 -0
  77. package/src/stun-message.ts +341 -0
  78. package/src/transport-lifecycle-error.ts +11 -0
  79. package/src/turn-server.ts +1028 -0
  80. package/src/webrtc-delivery-lifecycle.ts +54 -0
  81. package/src/webrtc-transport-options.ts +87 -0
  82. package/src/webrtc-transport.ts +368 -0
  83. package/src/ws-mesh-relay-client.ts +117 -0
  84. package/src/ws-signaling-client.ts +139 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["refuse","WS_OPEN","MAX_SDP_CHARS","MAX_CANDIDATE_CHARS","MAX_CANDIDATES","PEER_CLOSE_GRACE_MS","asError","DEFAULT_CONNECT_TIMEOUT_MS","randomId","WS_OPEN","DEFAULT_LOOKUP_TIMEOUT_MS","verifySignature"],"sources":["../../src/channel-delivery.ts","../../src/negotiated-certificate.ts","../../src/datachannel-loader.ts","../../src/rtc-peer.ts","../../src/admission-steps.ts","../../src/handoff-grant-gate.ts","../../src/local-peer-proof.ts","../../src/pairing-channel-lifecycle.ts","../../src/pairing-controllers.ts","../../src/pairing-frames.ts","../../src/session-attachment.ts","../../src/pairing-gate.ts","../../src/transport-lifecycle-error.ts","../../src/webrtc-delivery-lifecycle.ts","../../src/webrtc-transport.ts","../../src/signaling.ts","../../src/ws-signaling-client.ts","../../src/mesh-signal.ts","../../src/mesh-peer-link.ts","../../src/stun-message.ts","../../src/turn-server.ts","../../src/mesh-turn-relay.ts","../../src/device-mesh-node.ts","../../src/mesh-relay.ts","../../src/ws-mesh-relay-client.ts","../../src/mesh-discovery.ts","../../src/mesh-lan-listener.ts","../../src/mesh-mdns.ts","../../src/discovering-mesh-relay.ts","../../src/mesh-records.ts","../../src/mesh-dht.ts","../../src/mesh-item-store.ts","../../src/mainline-dht-store.ts","../../src/nostr-mesh-relay.ts","../../src/mesh-public-relays.ts","../../src/enrollment-link.ts"],"sourcesContent":["/**\n * The data channel's outbound delivery boundary (ARCH-030).\n *\n * Both places that build a bare `createSessionMessageHandler` in this package — `PairingGate`'s no-`resumeBridge`\n * branch and `WebRtcTransport`'s no-secret branch — need exactly the same thing: serialize a\n * `TServerMessage` onto an `RTCDataChannel` and route a send failure into that carrier's own failure\n * policy. Written inline at both sites it was two copies of one decision, and it pushed both files past\n * their size ceilings. One owner, named after what it is.\n */\n\nimport { createOutboundDelivery } from '@robota-sdk/agent-transport';\n\nimport type { TOutboundDeliver } from '@robota-sdk/agent-transport';\n\n/**\n * The slice of `RTCDataChannel` an outbound boundary needs.\n *\n * `bufferedAmount` is optional because the slice is structural — a test double may omit it — but a\n * real `RTCDataChannel` always has it, so the production path reports a real number rather than a\n * placeholder (ARCH-030 / issue #1734).\n */\nexport interface IDataChannelSink {\n send: (data: string) => void;\n readonly bufferedAmount?: number;\n}\n\n/**\n * Build the connection's outbound boundary over a data channel.\n *\n * @param channel - the data channel to serialize onto. `send` may throw once the channel is closing.\n * @param onDeliveryError - this carrier's failure policy, invoked at most once (the boundary latches).\n */\nexport function createChannelDelivery(\n channel: IDataChannelSink,\n onDeliveryError: (error: Error, event: string) => void,\n): TOutboundDeliver {\n return createOutboundDelivery(\n (message) => channel.send(JSON.stringify(message)),\n (error, event) => onDeliveryError(error, event),\n // Read at CALL time, not captured: `bufferedAmount` is a live property and a value read once\n // would answer with the backpressure of the moment the boundary was built.\n () => channel.bufferedAmount,\n );\n}\n","/**\n * The remote fingerprint a channel binding names: the one of the certificate the DTLS layer accepted,\n * never SDP text.\n *\n * The SDP arrives over an untrusted signaling path. The DTLS layer (OpenSSL, through libdatachannel)\n * accepts the remote certificate only after the peer has signed its handshake with that certificate's\n * key and the certificate matches the fingerprint of the remote description. Callers additionally\n * require the description to advertise exactly one fingerprint, so the accepted certificate and the\n * bound value cannot differ: binding it names the one party that holds the key.\n */\n\nimport type { RtcPeer } from './rtc-peer.js';\n\nconst FINGERPRINT_VALUE = /^[0-9A-F]{2}(?::[0-9A-F]{2})+$/;\n\n/**\n * Call `onVerified` with the remote certificate's fingerprint once the connection is established, or\n * `onFailed` if it fails or closes first, or the fingerprint is missing or of another algorithm than\n * `algorithm`. Returns an unsubscribe function.\n *\n * The connection reports `connected` only after DTLS has completed, and the data channel runs over that\n * DTLS session.\n */\nexport function whenRemoteCertificateVerified(\n peer: RtcPeer,\n algorithm: string,\n onVerified: (fingerprint: string) => void,\n onFailed: (reason: string) => void,\n): () => void {\n let done = false;\n const settle = (): void => {\n if (done) return;\n done = true;\n const fingerprint = peer.remoteFingerprint();\n if (fingerprint === undefined) {\n onFailed('the DTLS layer exposed no remote certificate');\n return;\n }\n const value = fingerprint.value.toUpperCase();\n if (fingerprint.algorithm.toLowerCase() !== algorithm.toLowerCase()) {\n onFailed(\n `the remote certificate was checked with ${fingerprint.algorithm}, not ${algorithm}`,\n );\n return;\n }\n if (!FINGERPRINT_VALUE.test(value)) {\n onFailed('the remote certificate fingerprint is malformed');\n return;\n }\n onVerified(value);\n };\n const fail = (state: string): void => {\n if (done) return;\n done = true;\n onFailed(`the connection ended ${state} before DTLS completed`);\n };\n if (peer.state === 'connected') {\n settle();\n return () => undefined;\n }\n if (peer.state === 'failed' || peer.state === 'closed') {\n fail(peer.state);\n return () => undefined;\n }\n const unsubscribe = peer.onStateChange((state) => {\n if (state === 'connected') settle();\n else if (state === 'failed' || state === 'closed') fail(state);\n else return;\n unsubscribe();\n });\n return unsubscribe;\n}\n","/**\n * The WebRTC implementation this transport runs on: `node-datachannel` (libdatachannel, with DTLS by\n * OpenSSL), an optional dependency loaded lazily.\n *\n * It is optional because it ships a native binary per platform. Where it cannot load — not installed,\n * or no prebuilt binary for this platform — the transport is unavailable and says so at the point of\n * use. There is deliberately no other implementation to fall back to: channel binding relies on this\n * DTLS stack verifying handshake signatures.\n */\nimport { createRequire } from 'node:module';\n\n/** The slice of a `node-datachannel` data channel this transport drives. */\nexport interface INdcDataChannel {\n close(): void;\n sendMessage(message: string): boolean;\n isOpen(): boolean;\n bufferedAmount(): number;\n /** The label the opening side gave the channel. */\n getLabel?(): string;\n onOpen(callback: () => void): void;\n onClosed(callback: () => void): void;\n onError(callback: (error: string) => void): void;\n onMessage(callback: (message: string | Buffer | ArrayBuffer) => void): void;\n}\n\n/** An ICE server as `node-datachannel` takes it. */\nexport interface INdcIceServer {\n readonly hostname: string;\n readonly port: number;\n readonly username?: string;\n readonly password?: string;\n readonly relayType?: 'TurnUdp' | 'TurnTcp' | 'TurnTls';\n}\n\nexport interface INdcConfig {\n iceServers: (string | INdcIceServer)[];\n iceTransportPolicy?: 'all' | 'relay';\n disableAutoNegotiation?: boolean;\n}\n\n/** The slice of a `node-datachannel` peer connection this transport drives. */\nexport interface INdcPeerConnection {\n close(): void;\n setLocalDescription(type?: 'offer' | 'answer'): void;\n setRemoteDescription(sdp: string, type: 'offer' | 'answer'): void;\n localDescription(): { type: string; sdp: string } | null;\n remoteFingerprint(): { value: string; algorithm: string };\n addRemoteCandidate(candidate: string, mid: string): void;\n createDataChannel(label: string): INdcDataChannel;\n state(): string;\n /** Diagnostics; not every build exposes them. */\n iceState?(): string;\n gatheringState?(): string;\n signalingState?(): string;\n onLocalDescription(callback: (sdp: string, type: string) => void): void;\n onLocalCandidate(callback: (candidate: string, mid: string) => void): void;\n onStateChange(callback: (state: string) => void): void;\n onDataChannel(callback: (channel: INdcDataChannel) => void): void;\n}\n\n/** The subset of the `node-datachannel` module surface this transport constructs. */\nexport interface IDataChannelModule {\n PeerConnection: new (name: string, config: INdcConfig) => INdcPeerConnection;\n}\n\n/** Resolve a module by id. The default resolves the real module; tests inject a throwing resolver. */\nexport type TModuleResolver = (id: string) => unknown;\n\nconst defaultResolver: TModuleResolver = (id) => {\n const requireFrom = createRequire(import.meta.url);\n // eslint-disable-next-line @typescript-eslint/no-require-imports -- optional native dependency; resolved at runtime, never bundled\n return requireFrom(id);\n};\n\nexport function loadDataChannel(resolve: TModuleResolver = defaultResolver): IDataChannelModule {\n try {\n const module = resolve('node-datachannel') as Partial<IDataChannelModule> | undefined;\n if (typeof module?.PeerConnection !== 'function') throw new Error('no PeerConnection export');\n return module as IDataChannelModule;\n } catch {\n throw new Error(\n `WebRTC transport unavailable — the optional dependency \"node-datachannel\" is not installed or has no ` +\n `prebuilt binary for this platform (${process.platform}-${process.arch}).`,\n );\n }\n}\n","/**\n * One WebRTC peer connection and its data channels, over `node-datachannel`.\n *\n * The native binding takes a single callback per event and aborts the process when a callback throws,\n * so every callback here is registered once, fans out to handlers, and never lets an exception reach\n * the binding: a throwing handler closes the connection instead. Negotiation is explicit (no automatic\n * offers or answers), so a connection produces exactly the descriptions its owner asks for, and no ICE\n * server is contacted unless one is configured.\n */\nimport {\n loadDataChannel,\n type IDataChannelModule,\n type INdcDataChannel,\n type INdcIceServer,\n type INdcPeerConnection,\n} from './datachannel-loader.js';\n\nimport type { IIceServer } from './webrtc-transport-options.js';\n\nexport type TRtcPeerState =\n 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed';\nexport type TRtcChannelState = 'connecting' | 'open' | 'closed';\n\n/** Why and how a connection or channel ended, for a refusal a person can act on. */\nexport interface IRtcPeerDiagnostics {\n readonly state: TRtcPeerState;\n readonly ice?: string;\n readonly gathering?: string;\n readonly signaling?: string;\n /** The states the connection went through, in order. */\n readonly history: readonly TRtcPeerState[];\n /** `local`: this side closed it; `handler-error`: a handler threw and the connection was closed. */\n readonly closedBy?: 'local' | 'handler-error';\n readonly handlerError?: string;\n readonly localCandidates: number;\n readonly remoteCandidates: number;\n}\n\nconst MAX_HISTORY = 16;\n\nfunction reading(read: (() => string) | undefined): string | undefined {\n try {\n return read?.();\n } catch {\n return undefined;\n }\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport interface IRtcCandidate {\n readonly candidate: string;\n readonly mid: string;\n}\n\nexport interface IRtcPeerOptions {\n readonly iceServers?: readonly IIceServer[];\n /** Relay (TURN) candidates only. */\n readonly forceTurn?: boolean;\n /** Test seam: the `node-datachannel` module. */\n readonly loadDataChannel?: () => IDataChannelModule;\n}\n\n/**\n * How long a closed channel's stream stays up. The implementation resets the stream as soon as it is\n * told to close, and a message sent just before is then lost to the peer.\n */\nconst CHANNEL_CLOSE_GRACE_MS = 250;\n\n/** Remote candidates kept while the remote description is still on its way. */\nconst MAX_EARLY_CANDIDATES = 64;\n\nconst DEFAULT_PORTS: Readonly<Record<string, number>> = { stun: 3478, turn: 3478, turns: 5349 };\n\n/** One configured ICE server in the binding's form. Throws on a url it cannot represent. */\nexport function toNativeIceServer(server: IIceServer): string | INdcIceServer {\n const match =\n /^(stuns?|turns?):([^:?]+|\\[[^\\]]+\\])(?::(\\d{1,5}))?(?:\\?transport=(udp|tcp))?$/i.exec(\n server.urls,\n );\n if (!match) throw new Error(`unsupported ICE server url: ${server.urls}`);\n const scheme = match[1]!.toLowerCase();\n const hostname = match[2]!.replace(/^\\[|\\]$/g, '');\n const port = match[3] !== undefined ? Number(match[3]) : (DEFAULT_PORTS[scheme] ?? 3478);\n if (scheme === 'stun' || scheme === 'stuns') return `stun:${hostname}:${port}`;\n return {\n hostname,\n port,\n ...(server.username !== undefined ? { username: server.username } : {}),\n ...(server.credential !== undefined ? { password: server.credential } : {}),\n relayType:\n scheme === 'turns' ? 'TurnTls' : match[4]?.toLowerCase() === 'tcp' ? 'TurnTcp' : 'TurnUdp',\n };\n}\n\nfunction textOf(message: string | Buffer | ArrayBuffer): string {\n if (typeof message === 'string') return message;\n return Buffer.from(message as ArrayBuffer).toString('utf8');\n}\n\nexport class RtcChannel {\n private stateValue: TRtcChannelState;\n private closeCauseValue?: string;\n private closedLocally = false;\n private readonly messageHandlers = new Set<(text: string) => void>();\n private readonly stateHandlers = new Set<(state: TRtcChannelState) => void>();\n\n public constructor(\n private readonly native: INdcDataChannel,\n private readonly fault: (error: unknown) => void,\n ) {\n this.stateValue = native.isOpen() ? 'open' : 'connecting';\n native.onOpen(() => this.guard(() => this.setState('open')));\n native.onClosed(() => this.guard(() => this.closedWith('closed by the peer or the transport')));\n native.onError((error) => this.guard(() => this.closedWith(`error: ${error}`)));\n native.onMessage((message) =>\n this.guard(() => {\n // Closed here, the stream only waits out its grace: nothing more is taken from it.\n if (this.closedLocally) return;\n const text = textOf(message);\n for (const handler of this.messageHandlers) handler(text);\n }),\n );\n }\n\n private guard(action: () => void): void {\n try {\n action();\n } catch (error) {\n // A throwing handler must not reach the native binding; the connection is closed instead.\n this.closeCauseValue ??= `handler error: ${describeError(error)}`;\n this.fault(error);\n }\n }\n\n private closedWith(cause: string): void {\n this.closeCauseValue ??= cause;\n this.setState('closed');\n }\n\n /** Why the channel closed, once it has. */\n public get closeCause(): string | undefined {\n return this.closeCauseValue;\n }\n\n private setState(state: TRtcChannelState): void {\n if (this.stateValue === state || this.stateValue === 'closed') return;\n this.stateValue = state;\n for (const handler of this.stateHandlers) handler(state);\n }\n\n public get readyState(): TRtcChannelState {\n return this.stateValue;\n }\n\n /** The label the opening side gave the channel; empty when the implementation does not say. */\n public get label(): string {\n try {\n return this.native.getLabel?.() ?? '';\n } catch {\n return '';\n }\n }\n\n public get bufferedAmount(): number {\n try {\n return this.native.bufferedAmount();\n } catch {\n return 0;\n }\n }\n\n /** Throws when the channel is not open or the message cannot be sent. */\n public send(text: string): void {\n if (this.stateValue !== 'open') throw new Error('data channel is not open');\n this.native.sendMessage(text);\n }\n\n public onMessage(handler: (text: string) => void): () => void {\n this.messageHandlers.add(handler);\n return () => this.messageHandlers.delete(handler);\n }\n\n public onStateChange(handler: (state: TRtcChannelState) => void): () => void {\n this.stateHandlers.add(handler);\n return () => this.stateHandlers.delete(handler);\n }\n\n /**\n * Closed for this side at once; the stream is reset after a grace, so what was sent just before\n * still reaches the peer.\n */\n public close(): void {\n if (this.stateValue === 'closed') return;\n this.closedLocally = true;\n const native = this.native;\n setTimeout(() => {\n try {\n native.close();\n } catch {\n /* already closing */\n }\n }, CHANNEL_CLOSE_GRACE_MS).unref?.();\n this.closedWith('closed by this side');\n }\n}\n\nexport class RtcPeer {\n private readonly native: INdcPeerConnection;\n private stateValue: TRtcPeerState = 'new';\n private readonly stateHandlers = new Set<(state: TRtcPeerState) => void>();\n private readonly candidateHandlers = new Set<(candidate: IRtcCandidate) => void>();\n private readonly channelHandlers = new Set<(channel: RtcChannel) => void>();\n private readonly descriptionWaiters: ((sdp: string) => void)[] = [];\n private hasRemoteDescription = false;\n private readonly history: TRtcPeerState[] = [];\n private closedBy?: 'local' | 'handler-error';\n private handlerError?: string;\n private localCandidateCount = 0;\n private remoteCandidateCount = 0;\n /**\n * Local candidates gathered before the remote description, handed out right after it. An offerer's\n * candidates let the answerer reach it and start DTLS; the binding makes the remote description the\n * DTLS layer checks the peer's certificate against only after the ICE layer has it, so a handshake\n * that arrives while the answer is being applied is refused. Holding the candidates until the\n * answer is applied keeps the answerer from reaching this side before then.\n */\n private readonly heldLocalCandidates: IRtcCandidate[] = [];\n /** Remote candidates that arrived before the remote description, applied right after it. */\n private readonly earlyCandidates: IRtcCandidate[] = [];\n private closed = false;\n\n public constructor(options: IRtcPeerOptions = {}) {\n const module = (options.loadDataChannel ?? loadDataChannel)();\n this.native = new module.PeerConnection('agent', {\n iceServers: (options.iceServers ?? []).map(toNativeIceServer),\n ...(options.forceTurn === true ? { iceTransportPolicy: 'relay' as const } : {}),\n disableAutoNegotiation: true,\n });\n this.native.onStateChange((state) =>\n this.guard(() => {\n if (state === this.stateValue || this.stateValue === 'closed') return;\n this.stateValue = state as TRtcPeerState;\n this.remember(this.stateValue);\n for (const handler of this.stateHandlers) handler(this.stateValue);\n }),\n );\n this.native.onLocalCandidate((candidate, mid) =>\n this.guard(() => {\n // The binding writes the SDP attribute form; the wire form has no `a=` prefix.\n const value = candidate.startsWith('a=') ? candidate.slice(2) : candidate;\n if (value.length === 0) return;\n if (!this.hasRemoteDescription) {\n this.heldLocalCandidates.push({ candidate: value, mid });\n return;\n }\n this.handOut({ candidate: value, mid });\n }),\n );\n this.native.onLocalDescription((sdp) =>\n this.guard(() => {\n for (const waiter of this.descriptionWaiters.splice(0)) waiter(sdp);\n }),\n );\n this.native.onDataChannel((native) =>\n this.guard(() => {\n const channel = new RtcChannel(native, (error) => this.fault(error));\n for (const handler of this.channelHandlers) handler(channel);\n }),\n );\n }\n\n private guard(action: () => void): void {\n try {\n action();\n } catch (error) {\n // A throwing handler must not reach the native binding; the connection is closed instead.\n this.fault(error);\n }\n }\n\n private fault(error: unknown): void {\n if (this.closed) return;\n this.closedBy = 'handler-error';\n this.handlerError = describeError(error);\n this.close();\n }\n\n private remember(state: TRtcPeerState): void {\n if (this.history.length < MAX_HISTORY) this.history.push(state);\n }\n\n /** What the connection went through, for explaining why it ended. */\n public diagnostics(): IRtcPeerDiagnostics {\n const ice = reading(this.native.iceState?.bind(this.native));\n const gathering = reading(this.native.gatheringState?.bind(this.native));\n const signaling = reading(this.native.signalingState?.bind(this.native));\n return {\n state: this.stateValue,\n ...(ice !== undefined ? { ice } : {}),\n ...(gathering !== undefined ? { gathering } : {}),\n ...(signaling !== undefined ? { signaling } : {}),\n history: [...this.history],\n ...(this.closedBy !== undefined ? { closedBy: this.closedBy } : {}),\n ...(this.handlerError !== undefined ? { handlerError: this.handlerError } : {}),\n localCandidates: this.localCandidateCount,\n remoteCandidates: this.remoteCandidateCount,\n };\n }\n\n public get state(): TRtcPeerState {\n return this.stateValue;\n }\n\n public createDataChannel(label: string): RtcChannel {\n return new RtcChannel(this.native.createDataChannel(label), (error) => this.fault(error));\n }\n\n private localDescription(type: 'offer' | 'answer'): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n this.descriptionWaiters.push(resolve);\n try {\n this.native.setLocalDescription(type);\n } catch (error) {\n const at = this.descriptionWaiters.indexOf(resolve);\n if (at >= 0) this.descriptionWaiters.splice(at, 1);\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n });\n }\n\n /** Create and apply the local offer; resolves with its SDP. */\n public createOffer(): Promise<string> {\n return this.localDescription('offer');\n }\n\n /** Apply the remote offer, then create and apply the answer; resolves with its SDP. */\n public async acceptOffer(sdp: string): Promise<string> {\n this.native.setRemoteDescription(sdp, 'offer');\n this.remoteDescriptionApplied();\n return this.localDescription('answer');\n }\n\n public acceptAnswer(sdp: string): void {\n this.native.setRemoteDescription(sdp, 'answer');\n this.remoteDescriptionApplied();\n }\n\n private remoteDescriptionApplied(): void {\n this.hasRemoteDescription = true;\n for (const candidate of this.earlyCandidates.splice(0)) this.addRemoteCandidate(candidate);\n const held = this.heldLocalCandidates.splice(0);\n this.guard(() => {\n for (const candidate of held) this.handOut(candidate);\n });\n }\n\n private handOut(candidate: IRtcCandidate): void {\n if (this.closed) return;\n this.localCandidateCount += 1;\n for (const handler of this.candidateHandlers) handler(candidate);\n }\n\n /**\n * Apply a remote candidate. One that arrives before the remote description waits for it (bounded);\n * one the binding refuses is dropped.\n */\n public addRemoteCandidate(candidate: IRtcCandidate): void {\n if (this.closed) return;\n if (!this.hasRemoteDescription) {\n if (this.earlyCandidates.length < MAX_EARLY_CANDIDATES) this.earlyCandidates.push(candidate);\n return;\n }\n try {\n this.native.addRemoteCandidate(candidate.candidate, candidate.mid);\n this.remoteCandidateCount += 1;\n } catch {\n // allow-fallback: an unusable candidate is one path fewer, not a failure of the connection\n }\n }\n\n /**\n * The fingerprint of the certificate the DTLS layer accepted, once connected; `undefined` before.\n * The DTLS layer (OpenSSL) accepts a certificate only after verifying the peer's handshake signature\n * with it and matching it against the remote description's fingerprint.\n */\n public remoteFingerprint(): { readonly value: string; readonly algorithm: string } | undefined {\n if (this.stateValue !== 'connected') return undefined;\n try {\n const fingerprint = this.native.remoteFingerprint();\n return fingerprint.value.length > 0 ? fingerprint : undefined;\n } catch {\n return undefined;\n }\n }\n\n public onStateChange(handler: (state: TRtcPeerState) => void): () => void {\n this.stateHandlers.add(handler);\n return () => this.stateHandlers.delete(handler);\n }\n\n public onLocalCandidate(handler: (candidate: IRtcCandidate) => void): () => void {\n this.candidateHandlers.add(handler);\n return () => this.candidateHandlers.delete(handler);\n }\n\n public onDataChannel(handler: (channel: RtcChannel) => void): () => void {\n this.channelHandlers.add(handler);\n return () => this.channelHandlers.delete(handler);\n }\n\n public close(): void {\n if (this.closed) return;\n this.closed = true;\n this.closedBy ??= 'local';\n try {\n this.native.close();\n } catch {\n /* already closed */\n }\n if (this.stateValue !== 'closed') {\n this.stateValue = 'closed';\n this.remember('closed');\n for (const handler of this.stateHandlers) handler('closed');\n }\n }\n}\n","/**\n * Which admission step a channel still owes before the session may be exposed.\n *\n * Extracted from `pairing-gate.ts` because it is the one decision in that file that is a pure\n * function of configuration and state — and because it is the ORDERING that carries the security\n * argument, so it deserves to be readable and testable without a channel, a handshake or a session.\n *\n * The order is not arbitrary. Each step must run on a channel the previous one has already bound:\n *\n * handshake binds the CHANNEL — it terminates at the peer that knew the secret\n * local-proof binds the ENVIRONMENT — that peer reached a 0700 directory owned by this user\n * handoff-grant binds the TRANSFER — one user, one destination, one channel, signed\n * operator-approval the receiving operator allows THIS connection — asked last, once every proof\n * has run, so the operator is asked only about a peer that is who it claims\n *\n * Each is opt-in, and configuring two means requiring both. That is strictly more restrictive than\n * requiring either, which is the safe direction — and it is the correct reading for a hand-off\n * between two sessions on ONE machine, which legitimately has both to present.\n */\n\n/** The step still owed, or null when the session may be exposed. */\nexport type TAdmissionStep = 'local-proof' | 'handoff-grant' | 'operator-approval' | null;\n\n/** What the gate has configured. Named structurally so this module needs none of the gate's types. */\nexport interface IConfiguredAdmissionSteps {\n readonly localPeer?: unknown;\n readonly handoffGrant?: unknown;\n readonly connectionApproval?: unknown;\n}\n\n/**\n * The next step, given what is configured and what has already run.\n *\n * `completed` is the gate's current state rather than a set of finished steps, because the states\n * ARE the record: reaching `handoff-grant` is only possible by having satisfied `local-proof` when\n * one was required.\n */\nexport function nextAdmissionStep(\n configured: IConfiguredAdmissionSteps,\n completed: string,\n): TAdmissionStep {\n const approved = completed === 'operator-approval';\n if (\n configured.localPeer !== undefined &&\n completed !== 'local-proof' &&\n completed !== 'handoff-grant' &&\n !approved\n ) {\n return 'local-proof';\n }\n if (configured.handoffGrant !== undefined && completed !== 'handoff-grant' && !approved)\n return 'handoff-grant';\n if (configured.connectionApproval !== undefined && !approved) return 'operator-approval';\n return null;\n}\n","/**\n * SEC-011 (issue #1865): the channel gate consumes the cross-device hand-off grant.\n *\n * The same shape SEC-010's local-proof step established, for the same reason. #1810 is explicit that\n * this package must not implement cryptographic policy, so nothing here verifies a signature,\n * decides an expiry, or knows what revocation is. The gate holds the state machine; the verdict is\n * injected and reported.\n *\n * ## What the grant proves, and what the channel proves\n *\n * The pairing handshake binds the CHANNEL: after it, this data channel provably terminates at the\n * peer that knew the secret, and the DTLS fingerprints are covered so it cannot be spliced. It says\n * nothing about who that peer is.\n *\n * The grant binds the TRANSFER: one user, one source device, one destination, one `handoffId`, one\n * `sessionId`, one nonce, and the fingerprint of the channel it may be presented over. It says\n * nothing about whether the channel it arrived on is the one it names.\n *\n * Presenting the grant OVER the already-bound channel is what joins them, and the fingerprint claim\n * is what makes the join checkable: a grant lifted from one channel and replayed on another names a\n * fingerprint the verifier does not observe. That is `channel-substituted`, and it is a distinct\n * rejection precisely so it cannot be softened into a pass.\n *\n * ## Why it runs after the handshake and before the session\n *\n * Both edges are load-bearing, and both for SEC-010's reasons.\n *\n * BEFORE the handshake completes, the channel is not authenticated — a grant presented there would\n * be handed to an unproven counterpart, and this step would weaken what it exists to strengthen.\n *\n * BEFORE the session is exposed, because \"fail closed before content\" is the first failure rule. A\n * peer that never presents a grant must not have been talking to the session already; refusing it\n * afterwards is not refusing it.\n *\n * ## The trust it produces is its own\n *\n * `same-user-different-host` is not `same-user-same-host`. #1812 pins that they are different values\n * and this gate is the reason: a cross-device authorization must never satisfy a check that wanted\n * same-machine. It travels as a value on the admission rather than being re-derived from a boolean\n * by whoever reads it next.\n */\n\nimport type { IPeerAdmission } from '@robota-sdk/agent-interface-session-mobility';\n\n/** The frame a source device presents to show it holds a grant for this transfer. */\nexport interface IHandoffGrantFrame {\n readonly t: 'handoff-grant';\n /**\n * The signed grant, carried opaquely.\n *\n * `unknown` on purpose: this package cannot import the grant type without importing the package\n * that owns the crypto, and typing it structurally here would create a second declaration of a\n * signed object's shape — which is how a field ends up checked in one copy and not the other.\n * The verifier owns the type; this gate owns the envelope.\n */\n readonly grant: unknown;\n}\n\n/**\n * The cross-device authorization this gate requires before exposing the session.\n *\n * Absent → the gate behaves exactly as before. Requiring a grant is opt-in because an ordinary\n * remote peer has no hand-off to authorize, and a gate that demanded one unconditionally would\n * refuse every legitimate remote session.\n */\nexport interface IHandoffGrantProof {\n /**\n * Judge a presented grant. Owned by the verifier — this package asks and does not decide.\n *\n * Async because verifying a signature is: `verifyHandoffGrant` returns a promise, and a\n * synchronous seam here would force the owner to block or to pre-verify, and pre-verifying means\n * deciding before the channel that the grant is bound to even exists.\n *\n * Returns the admission the consumer receives, so the trust level travels as a value.\n */\n readonly verify: (grant: unknown) => Promise<IPeerAdmission>;\n /**\n * Ask the person at THIS machine whether to accept the transfer. Absent → no consent is required.\n *\n * Called ONLY on a verdict the verifier admitted, and given that verdict, for two reasons that\n * both matter:\n *\n * The prompt can then name a PROVEN origin. Asking before verification would let anyone who can\n * open a channel raise a dialog on someone's machine claiming to be any device they like, which\n * turns the consent step into an attack surface instead of a control.\n *\n * And consent is not cryptographic policy, so it does not belong inside `verify`. A verifier that\n * also asked a human would make \"is this grant valid\" and \"does this person want it\" one answer,\n * and the first is the one that must be decidable without a person present.\n *\n * Returning false — or throwing, or having no renderer to ask — refuses. Denial fails CLOSED.\n */\n readonly consent?: (admission: IPeerAdmission) => Promise<boolean>;\n /**\n * Fired on the outcome, admitted or not.\n *\n * On BOTH, deliberately: a consumer told only about successes cannot distinguish \"no hand-off was\n * offered\" from \"a hand-off was refused\", and those call for different operator responses — the\n * second is the one that belongs in front of a person.\n */\n readonly onAdmission?: (admission: IPeerAdmission) => void;\n}\n\n/**\n * True when a parsed pre-accept value is a grant frame.\n *\n * Module-private: `judgeHandoffGrant` is the only thing that should ask. Exporting it would offer a\n * caller a way to check the shape and then act on it without going through the judge, which is how\n * an admission decision ends up made in two places.\n */\nfunction isHandoffGrantFrame(value: unknown): value is IHandoffGrantFrame {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { t?: unknown }).t === 'handoff-grant' &&\n (value as { grant?: unknown }).grant !== undefined\n );\n}\n\n/** A refusal shaped like every other admission, so no caller has to special-case the failure path. */\nfunction refuse(reason: string): IPeerAdmission {\n return { admitted: false, trust: 'unproven', reason };\n}\n\n/**\n * Judge one pre-accept frame against the configured grant proof.\n *\n * Every path that is not an admitted verification returns a refusal — including a missing config,\n * which cannot happen through the gate but would be a fail-OPEN if it ever did.\n */\nexport async function judgeHandoffGrant(\n parsed: unknown,\n proof: IHandoffGrantProof | undefined,\n): Promise<IPeerAdmission> {\n if (proof === undefined)\n return refuse('no hand-off grant verifier is configured for this channel');\n if (!isHandoffGrantFrame(parsed)) {\n return refuse('expected a handoff-grant frame carrying the signed grant');\n }\n try {\n const admission = await proof.verify(parsed.grant);\n // The verifier owns the verdict, but not the ability to widen it. A cross-device grant cannot\n // establish that the peer is on THIS machine, so an admission claiming it did is refused rather\n // than passed on — an implementation that returned the stronger level would otherwise satisfy\n // every check that wanted same-machine, which is the one substitution #1812 forbids by name.\n if (admission.admitted && admission.trust === 'same-user-same-host') {\n return refuse(\n 'the grant verifier returned same-user-same-host. A cross-device grant proves the user, ' +\n 'not the machine — see SEC-010 for what same-host requires.',\n );\n }\n if (!admission.admitted || proof.consent === undefined) return admission;\n // The person, after the proof. A refusal here is not a weaker outcome than a cryptographic one:\n // the session is equally not exposed, and the reason says which step declined so the operator is\n // not left reading \"unauthorized\" when what happened is that they said no.\n if (!(await proof.consent(admission))) {\n return refuse('the person at this machine declined the transfer');\n }\n return admission;\n } catch (error) {\n // allow-fallback: fail-CLOSED, not a fallback to a degraded path. A verifier that throws has not\n // reached a decision, and \"not reached\" is not \"allowed\" — the refusal below is strictly more\n // restrictive than the success path, never less. Letting it propagate would be worse: the throw\n // unwinds through the channel's message subscription and leaves the gate parked in its grant\n // state with the channel open, which is a hang — fail-open wearing a crash's clothes.\n return refuse(\n `the grant verifier could not decide: ${\n error instanceof Error ? error.message : 'unknown error'\n }`,\n );\n }\n}\n\n/**\n * Build the frame a source device presents on the channel.\n *\n * Lives beside the judge that reads it, so the two cannot drift: a sender that hand-built the object\n * would keep compiling after the frame gains a field, and the failure would surface as a refusal on\n * the far side with no hint that the SENDER is the stale half.\n *\n * Deliberately not a \"send\" — it returns the frame and leaves transmission to whoever owns the\n * channel, for the same reason `localProofFrame` does.\n */\nexport function handoffGrantFrame(grant: unknown): IHandoffGrantFrame {\n return { t: 'handoff-grant', grant };\n}\n","/**\n * SEC-010 TC-08: joining the rendezvous proof to the channel the session will run on.\n *\n * ## What each half proves, and why neither is enough alone\n *\n * The pairing handshake binds the CHANNEL: after it, this data channel provably terminates at the\n * peer that knew the secret, and the DTLS fingerprints are covered so it cannot be spliced. It says\n * nothing about where that peer runs.\n *\n * The guarded rendezvous binds the ENVIRONMENT: a peer that reached a 0700 directory owned by this\n * user could only have come from this machine, as this user. It says nothing about which channel\n * that peer later opens.\n *\n * Presenting the rendezvous nonce OVER the already-bound channel is what joins them. The channel is\n * authenticated by the time this runs, so nobody else can inject the frame; the nonce is single-use,\n * so nobody can re-present one they observed. Together they say: the party on this channel is the\n * party the kernel vouched for, on this attempt.\n *\n * ## Why this runs after the handshake and before the session\n *\n * Both edges are load-bearing.\n *\n * BEFORE the handshake completes, the channel is not yet authenticated — a nonce presented there\n * would be a secret handed to an unproven counterpart, and this step would weaken the very thing it\n * exists to strengthen.\n *\n * BEFORE the session is exposed, because \"fail closed before content\" is SEC-010's first failure\n * rule. If the proof arrived after exposure, a peer that never presents one would have already been\n * talking to the session, and refusing it afterwards is not refusing it.\n *\n * ## This package decides nothing\n *\n * #1810 is explicit that WebRTC must not implement cryptographic policy. Single-use, expiry and\n * revocation belong to the grant ledger in `@robota-sdk/agent-remote-pairing/local`, which is\n * node-only; this module takes an injected `redeem` and reports what it was told. That also keeps\n * `node:fs` out of this package, and keeps the ledger testable without a data channel.\n */\n\nimport type { IPeerAdmission } from '@robota-sdk/agent-interface-session-mobility';\n\n/** The frame a local peer presents to show it reached the guarded rendezvous. */\nexport interface ILocalProofFrame {\n readonly t: 'local-proof';\n readonly nonce: string;\n}\n\n/**\n * The local-environment proof this gate requires before exposing the session.\n *\n * Absent → the gate behaves exactly as before. Requiring the proof is opt-in because a remote peer\n * over WebRTC has no rendezvous to have reached, and a gate that demanded one unconditionally would\n * refuse every legitimate remote session.\n */\nexport interface ILocalPeerProof {\n /**\n * Redeem a presented nonce. Owned by the ledger — this package asks and does not decide.\n *\n * Returns the admission the consumer receives, so the trust level travels as a value rather than\n * being re-derived from a boolean by whoever reads it next.\n */\n readonly redeem: (nonce: string) => IPeerAdmission;\n /**\n * Fired on the outcome, admitted or not.\n *\n * On BOTH, deliberately: a consumer told only about successes cannot distinguish \"no local peer\n * connected\" from \"a local peer was refused\", and those call for different operator responses.\n */\n readonly onAdmission?: (admission: IPeerAdmission) => void;\n}\n\n/**\n * True when a parsed pre-accept value is a local-proof frame carrying a nonce.\n *\n * Module-private: `judgeLocalProof` is the only thing that should ask. Exporting it would offer a\n * caller a way to check the shape and then act on it without going through the judge, which is how\n * an admission decision ends up made in two places.\n */\nfunction isLocalProofFrame(value: unknown): value is ILocalProofFrame {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { t?: unknown }).t === 'local-proof' &&\n typeof (value as { nonce?: unknown }).nonce === 'string'\n );\n}\n\n/** A refusal shaped like every other admission, so no caller has to special-case the failure path. */\nfunction refuse(reason: string): IPeerAdmission {\n return { admitted: false, trust: 'unproven', reason };\n}\n\n/**\n * Judge one pre-accept frame against the configured proof.\n *\n * Every path that is not an admitted redemption returns a refusal — including a missing config,\n * which cannot happen through the gate but would be a fail-OPEN if it ever did.\n */\nexport function judgeLocalProof(\n parsed: unknown,\n proof: ILocalPeerProof | undefined,\n): IPeerAdmission {\n if (proof === undefined) return refuse('no local-peer proof is configured for this channel');\n if (!isLocalProofFrame(parsed)) {\n return refuse('expected a local-proof frame carrying the rendezvous nonce');\n }\n try {\n return proof.redeem(parsed.nonce);\n } catch (error) {\n // allow-fallback: fail-CLOSED, not a fallback to a degraded path. A ledger that throws has not\n // reached a decision, and \"not reached\" is not \"allowed\" — the refusal below is strictly more\n // restrictive than the success path, never less. Letting it propagate would be worse: the throw\n // unwinds through the channel's message subscription and leaves the gate parked in its proof\n // state with the channel open, which is a hang — fail-open wearing a crash's clothes.\n return refuse(\n `the rendezvous ledger could not decide: ${\n error instanceof Error ? error.message : 'unknown error'\n }`,\n );\n }\n}\n\n/**\n * Build the frame a local peer presents on the channel.\n *\n * Lives beside the judge that reads it, so the two cannot drift: a sender that hand-built the object\n * would keep compiling after the frame gains a field, and the failure would surface as a refusal on\n * the far side with no hint that the SENDER is the stale half.\n *\n * Deliberately not a \"send\" — it returns the frame and leaves transmission to whoever owns the\n * channel. A helper that both built and sent would need a channel to be testable, and would put the\n * frame's shape and the carrier's lifecycle in one place.\n */\nexport function localProofFrame(nonce: string): ILocalProofFrame {\n return { t: 'local-proof', nonce };\n}\n","import type { IPairingChannel } from './pairing-gate-options.js';\n\nfunction send(channel: IPairingChannel, data: string): void {\n try {\n channel.send(data);\n } catch {\n // The peer is gone; pairing frames are best-effort once the carrier is closing.\n }\n}\n\nfunction close(channel: IPairingChannel): void {\n try {\n channel.close();\n } catch {\n // The carrier is already closing or closed.\n }\n}\n\nfunction reportDeliveryError(\n callback: ((error: Error, event: string) => void) | undefined,\n error: Error,\n event: string,\n): void {\n try {\n callback?.(error, event);\n } catch {\n // A diagnostic callback cannot prevent carrier teardown or escape into the committed operation.\n }\n}\n\nexport const pairingChannel = Object.freeze({ send, close, reportDeliveryError });\n","import type { IIdentityContext } from '@robota-sdk/agent-remote-pairing';\n/**\n * Constructing the admission controllers the gate drives.\n *\n * \"Which controller runs, with what inputs\" is a different subject from \"what state is the gate in\n * and what may reach the session\" — the same boundary that already moved the frame vocabulary into\n * `pairing-frames.ts` and the best-effort channel calls into `pairing-channel-lifecycle.ts`.\n *\n * Split out because `pairing-gate.ts` had reached the size limit, where the rule is to split rather\n * than extend. These two functions were the largest thing in it that was not about gating, and they\n * touch nothing of the gate's own state: they take inputs, wire a controller's `send` to the\n * channel, and hand back the controller plus a settled/rejected outcome for the caller to act on.\n */\n\nimport { startHostReconnect, startPairingHandshake } from '@robota-sdk/agent-remote-pairing';\n\nimport { pairingChannel } from './pairing-channel-lifecycle.js';\n\nimport type { IHostReconnectConfig, IPairingChannel } from './pairing-gate-options.js';\nimport type {\n IPairingResult,\n IReconnectResult,\n TPairingRole,\n} from '@robota-sdk/agent-remote-pairing';\n\n/** What both controllers need to talk on the channel and bind to it. */\nexport interface IControllerContext {\n readonly cryptoContext: IIdentityContext;\n readonly channel: IPairingChannel;\n readonly localFingerprint: string;\n readonly remoteFingerprint: string;\n /** Handshake timeout (ms); fail closed on expiry. */\n readonly timeoutMs?: number;\n}\n\n/**\n * Start the B3 first-pair handshake.\n *\n * `onAccepted`/`onRejected` rather than handing the promise back, so a caller cannot forget to\n * attach a rejection path — an unhandled rejection here would leave a gate waiting forever on a\n * handshake that already failed, which is a fail-OPEN shaped as a hang.\n */\nexport function startFirstPairController(\n context: IControllerContext,\n secret: string,\n role: TPairingRole,\n onAccepted: (result: IPairingResult) => void,\n onRejected: () => void,\n start: typeof startPairingHandshake = startPairingHandshake,\n): ReturnType<typeof startPairingHandshake> {\n const controller = start(context.cryptoContext, {\n secret,\n role,\n localFingerprint: context.localFingerprint,\n remoteFingerprint: context.remoteFingerprint,\n send: (frame) => pairingChannel.send(context.channel, JSON.stringify(frame)),\n ...(context.timeoutMs !== undefined ? { timeoutMs: context.timeoutMs } : {}),\n });\n controller.result.then(onAccepted, onRejected);\n return controller;\n}\n\n/** Start the E3 host-side reconnect exchange against a pinned device key. */\nexport function startReconnectController(\n context: IControllerContext,\n config: IHostReconnectConfig,\n onAccepted: (result: IReconnectResult) => void,\n onRejected: () => void,\n): ReturnType<typeof startHostReconnect> {\n const controller = startHostReconnect(context.cryptoContext, {\n hostIdentityId: config.hostIdentityId,\n localFingerprint: context.localFingerprint,\n remoteFingerprint: context.remoteFingerprint,\n hostPrivateKey: config.hostPrivateKey,\n resolveDevicePublicKey: config.resolveDevicePublicKey,\n send: (frame) => pairingChannel.send(context.channel, JSON.stringify(frame)),\n ...(context.timeoutMs !== undefined ? { timeoutMs: context.timeoutMs } : {}),\n });\n controller.result.then(onAccepted, onRejected);\n return controller;\n}\n","/**\n * Predicates over the pairing/reconnect frame vocabulary the gate admits pre-accept.\n *\n * Issue #2046: these are thin adapters over the OWNER codec in `@robota-sdk/agent-remote-pairing`,\n * which decodes every required field (presence, base64url, length ceiling) rather than only the `t`\n * discriminator this file used to check. A true answer means the narrowed value is well-formed; the\n * browser `ResponderGate` imports the same codec, so one corpus governs both carriers.\n */\n\nimport {\n decodeEnrollFrame,\n decodePairingFrame,\n decodeReconnectFrame,\n type IEnrollFrame,\n type TPairingFrame,\n type TReconnectFrame,\n} from '@robota-sdk/agent-remote-pairing';\n\nexport type { IEnrollFrame };\n\n/** True when a parsed value is a well-formed B3 pairing frame (`pair-nonce` | `pair-confirm`). */\nexport function isPairingFrame(value: unknown): value is TPairingFrame {\n return decodePairingFrame(value).ok;\n}\n\n/** True when a parsed value is a well-formed reconnect frame (`rc-hello` | `rc-host` | `rc-device`). */\nexport function isReconnectFrame(value: unknown): value is TReconnectFrame {\n return decodeReconnectFrame(value).ok;\n}\n\n/** True when a parsed value is a well-formed first-pair enrollment frame carrying a device SPKI. */\nexport function isEnrollFrame(value: unknown): value is IEnrollFrame {\n return decodeEnrollFrame(value).ok;\n}\n","/**\n * Wiring an admitted channel to the session — the step immediately after the gate says yes.\n *\n * \"How session frames travel once admission is settled\" is a different subject from \"what may reach\n * the session at all\", which is the gate's. Split out for the same reason the frame vocabulary and\n * the controllers already were: `pairing-gate.ts` is at its size limit, and the rule there is to\n * split rather than extend.\n *\n * Two carriers, chosen by whether a resume bridge exists. Neither decides anything about admission —\n * by the time either runs, that question is closed.\n */\n\nimport {\n HISTORY_PAGE_MAX_BYTES,\n createSessionMessageHandler,\n type ISessionMessageHandlerOptions,\n type SessionResumeBridge,\n} from '@robota-sdk/agent-transport';\n\nimport { createChannelDelivery } from './channel-delivery.js';\n\nimport type { IPairingChannel } from './pairing-gate-options.js';\nimport type { IProtocolSession } from '@robota-sdk/agent-transport';\n\nexport interface IAttachSessionOptions {\n readonly channel: IPairingChannel;\n readonly session: IProtocolSession;\n readonly resumeBridge?: SessionResumeBridge;\n readonly createHandler?: typeof createSessionMessageHandler;\n readonly personalUsageReporter?: ISessionMessageHandlerOptions['personalUsageReporter'];\n readonly usageReporter?: ISessionMessageHandlerOptions['usageReporter'];\n readonly storedSessionUsageReporter?: ISessionMessageHandlerOptions['storedSessionUsageReporter'];\n readonly surface?: ISessionMessageHandlerOptions['surface'];\n readonly commandSurfaceLocality?: ISessionMessageHandlerOptions['commandSurfaceLocality'];\n}\n\nexport interface IAttachedSession {\n /** Where post-accept inbound frames go. */\n readonly onSessionMessage: (data: string) => void;\n /** Detach (bridge) or dispose (handler). The gate calls this on teardown. */\n readonly cleanup: () => void;\n}\n\n/**\n * Attach the admitted channel to the session.\n *\n * `viaReconnect` is threaded through rather than inferred: on a reconnect the bridge must hold live\n * forwarding until the client's `resume` replays the buffered tail, and getting that backwards\n * delivers new frames ahead of the replay — an ordering bug that looks like data loss.\n */\nexport function attachSession(\n options: IAttachSessionOptions,\n viaReconnect: boolean,\n onDeliveryError: (error: Error, event: string) => void,\n): IAttachedSession {\n const bridge = options.resumeBridge;\n if (bridge) {\n // REMOTE-013 E4: route the session through the persistent bridge. Attach this channel as the\n // sink; post-accept inbound frames (incl. resume/ack) go to the bridge; cleanup DETACHES it\n // (never disposes — the bridge is owned by the transport across reconnects).\n bridge.attach((data) => options.channel.send(data), {\n awaitResume: viaReconnect,\n onDeliveryError,\n // ARCH-030: without this the bridge's boundary has no backpressure reading, so the replay\n // path — the burst this budget exists for — runs unbudgeted while every other outbound path\n // on the same connection is guarded. Read at call time; `bufferedAmount` is live.\n pendingBytes: () => options.channel.bufferedAmount,\n });\n return {\n onSessionMessage: (data) => bridge.onClientMessage(data),\n cleanup: () => bridge.detach(),\n };\n }\n\n const create = options.createHandler ?? createSessionMessageHandler;\n // ARCH-030: the gate is the carrier on this branch — its own channel sink, its own failure policy.\n const { onMessage, cleanup } = create({\n session: options.session,\n deliver: createChannelDelivery(options.channel, onDeliveryError),\n // A data channel carries a fraction of the inbound limit; a frame over it makes `send` throw and\n // closes the channel, so an app view or tool result larger than a history page is refused.\n maxFrameBytes: HISTORY_PAGE_MAX_BYTES,\n ...(options.personalUsageReporter\n ? { personalUsageReporter: options.personalUsageReporter }\n : {}),\n ...(options.usageReporter ? { usageReporter: options.usageReporter } : {}),\n ...(options.storedSessionUsageReporter\n ? { storedSessionUsageReporter: options.storedSessionUsageReporter }\n : {}),\n ...(options.surface ? { surface: options.surface } : {}),\n ...(options.commandSurfaceLocality\n ? { commandSurfaceLocality: options.commandSurfaceLocality }\n : {}),\n });\n return { onSessionMessage: onMessage, cleanup };\n}\n","/**\n * Pairing gate for the WebRTC data channel (REMOTE-008 Stage B4-2b; extended for REMOTE-012 Stage E3 TOFU\n * reconnect).\n *\n * The data channel is phase-separated: pre-accept it carries pairing/reconnect frames, post-accept only\n * session messages. An eager subscription feeds {@link PairingGate.onInbound}; the session bridge is not\n * built until acceptance, so no pre-accept peer frame can reach the live session.\n *\n * E3 selects first-pair (`pair-nonce`, then identity-key enrollment) or reconnect (`rc-hello`, pinned\n * identities) from the client's first frame and exposes the session only after mutual acceptance.\n *\n * Fail-closed: a non-admission frame pre-accept never reaches the session early — while this side's verdict is\n * pending it is HELD (bounded; a peer that says more is refused) and routed again once the gate moves on, since\n * the peer may finish its half first and speak at once; it is discarded if admission fails. The handshake\n * `result` is the ONLY accept signal;\n * on reject/timeout the channel is closed and no session bridge is ever created; a post-close frame is ignored.\n *\n * When no E3 `reconnect` config is supplied the gate is **exactly** the B4 first-pair-only gate (eager host\n * `pair-nonce`, no enrollment, no reconnect) — preserving existing behavior.\n *\n * SEC-010 adds an optional `local-proof` step between handshake acceptance and session exposure; see\n * `local-peer-proof.ts` for why both of those edges are load-bearing.\n */\n\nimport {\n deriveIdentityId,\n importPublicKey,\n startPairingHandshake,\n type IPairingResult,\n} from '@robota-sdk/agent-remote-pairing';\n\nimport { nextAdmissionStep } from './admission-steps.js';\nimport type {\n IConnectionApproval,\n IConnectionApprovalContext,\n IHostReconnectConfig,\n IPairingChannel,\n IPairingGateOptions,\n} from './pairing-gate-options.js';\n\n// Re-exported so every existing import of these names keeps working: the split moved where they are\n// DECLARED, and moving where they are imported from would be a migration this change is not.\nexport type {\n IConnectionApproval,\n IConnectionApprovalContext,\n IHostReconnectConfig,\n IPairingChannel,\n IPairingGateOptions,\n};\nimport { judgeHandoffGrant } from './handoff-grant-gate.js';\nimport { judgeLocalProof } from './local-peer-proof.js';\nimport { pairingChannel } from './pairing-channel-lifecycle.js';\nimport { startFirstPairController, startReconnectController } from './pairing-controllers.js';\nimport { isEnrollFrame, isPairingFrame, isReconnectFrame } from './pairing-frames.js';\nimport { attachSession } from './session-attachment.js';\n\nimport type { IEnrollFrame } from './pairing-frames.js';\n\n/** How much a peer may say while the operator decides: a client's opening requests, not a stream. */\nconst HELD_FRAMES_MAX = 32;\nconst HELD_CHARS_MAX = 256 * 1024;\n\ntype TGateState =\n | 'awaiting-mode'\n | 'pairing'\n | 'enrolling'\n | 'reconnecting'\n | 'local-proof'\n | 'handoff-grant'\n | 'operator-approval'\n | 'accepted'\n | 'closed';\n\nexport class PairingGate {\n private state: TGateState;\n /** Session message router — built ONLY on accept (nothing reaches the session before). */\n private onSessionMessage?: (data: string) => void;\n private handlerCleanup?: () => void;\n private pairingController?: ReturnType<typeof startPairingHandshake>;\n private reconnectController?: ReturnType<typeof startReconnectController>;\n /** The first-pair result, held so it can be surfaced on accept (E4 uses its sessionKey). */\n private pendingResult?: IPairingResult;\n /** Held across the local-proof step so the reconnect ordering rule survives the detour. */\n private pendingViaReconnect = false;\n /** The device the handshake proved, for the operator's question. */\n private pendingDeviceId?: string;\n /** A first-pair device key, pinned only once every step has admitted the channel. */\n private pendingEnrollment?: { readonly deviceId: string; readonly spki: string };\n /**\n * Session frames the peer sent while the operator was deciding. The peer's own handshake has\n * already accepted, so it starts talking (a resume, a history request) at once; dropping those\n * would leave an approved device waiting on answers to questions the host never saw. They reach\n * the session only after a yes, and a peer that sends more than a bounded amount is refused.\n */\n private heldFrames: string[] = [];\n private heldChars = 0;\n /** Withdraws the question put to the operator when the connection goes away first. */\n private approvalAbort?: AbortController;\n\n constructor(private readonly options: IPairingGateOptions) {\n if (options.reconnect) {\n // E3 mode: stay reactive — the client's first frame selects first-pair vs reconnect.\n this.state = 'awaiting-mode';\n } else {\n // Legacy B4 mode: eagerly start the first-pair handshake (host sends pair-nonce immediately).\n this.state = 'pairing';\n this.startFirstPair();\n }\n }\n\n /**\n * Route one inbound channel frame. Pre-accept: admission frames → the active controller, everything else\n * HELD (bounded) until the gate moves on. Post-accept: session messages → the session bridge. Post-close:\n * ignored.\n */\n onInbound(data: string): void {\n if (this.state === 'closed') return;\n if (this.state === 'accepted') {\n this.onSessionMessage?.(data);\n return;\n }\n if (this.state === 'operator-approval') {\n this.hold(data);\n return;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(data);\n } catch {\n return; // undecodable pre-accept frame → drop\n }\n\n if (this.state === 'awaiting-mode') {\n // First frame selects the mode (E3).\n if (isReconnectFrame(parsed) && parsed.t === 'rc-hello') {\n this.state = 'reconnecting';\n this.startReconnect();\n this.reconnectController?.onFrame(parsed);\n } else if (isPairingFrame(parsed) && parsed.t === 'pair-nonce') {\n this.state = 'pairing';\n this.startFirstPair();\n this.pairingController?.onFrame(parsed);\n }\n // anything else pre-mode → drop\n return;\n }\n\n if (this.state === 'pairing') {\n if (isPairingFrame(parsed)) this.pairingController?.onFrame(parsed);\n else this.hold(data);\n return;\n }\n\n if (this.state === 'reconnecting') {\n if (isReconnectFrame(parsed)) this.reconnectController?.onFrame(parsed);\n else this.hold(data);\n return;\n }\n\n if (this.state === 'local-proof') {\n const admission = judgeLocalProof(parsed, this.options.localPeer);\n this.options.localPeer?.onAdmission?.(admission);\n if (admission.admitted) this.accept(this.pendingResult, this.pendingViaReconnect);\n else this.rejectAndClose();\n return;\n }\n\n if (this.state === 'handoff-grant') {\n // Verifying a signature is async, and this handler is not. The promise is consumed here rather\n // than returned so a rejection cannot escape into the channel's message subscription — an\n // unhandled rejection would leave the gate parked in this state with the channel open, which\n // is a hang. `judgeHandoffGrant` already converts a throw into a refusal; this is the second\n // layer, because a gate that can hang is a gate that fails open by waiting.\n void this.judgeGrantFrame(parsed);\n return;\n }\n\n if (this.state === 'enrolling') {\n // Awaiting the peer's identity public key to pin, then expose the session.\n if (isEnrollFrame(parsed)) this.completeEnrollment(parsed.spki);\n else this.hold(data);\n return;\n }\n }\n\n /** Tear down: cleanup the session bridge (if built) and mark closed. Idempotent. */\n cleanup(): void {\n this.state = 'closed';\n this.withdrawApproval();\n this.handlerCleanup?.();\n this.handlerCleanup = undefined;\n this.onSessionMessage = undefined;\n }\n\n /**\n * The channel closed. Before acceptance that is a refusal: nobody is left to admit, so a question\n * still open with the operator is withdrawn and a later answer admits nothing. After acceptance the\n * owning transport handles the drop.\n */\n onChannelClosed(): void {\n if (this.state === 'accepted' || this.state === 'closed') return;\n this.rejectAndClose();\n }\n\n private withdrawApproval(): void {\n this.heldFrames = [];\n this.heldChars = 0;\n this.approvalAbort?.abort();\n this.approvalAbort = undefined;\n }\n\n private startFirstPair(): void {\n // `IPairingGateOptions` already carries every field `IControllerContext` asks for, so it is\n // passed straight through — projecting it field by field would be a copy to keep in step.\n this.pairingController = startFirstPairController(\n this.options,\n this.options.secret,\n this.options.role,\n (result) => this.onFirstPairAccepted(result),\n () => this.rejectAndClose(),\n this.options.startHandshake ?? startPairingHandshake,\n );\n }\n\n private startReconnect(): void {\n const cfg = this.options.reconnect;\n if (!cfg) {\n this.rejectAndClose();\n return;\n }\n this.reconnectController = startReconnectController(\n this.options,\n cfg,\n // reconnect → hold live forwarding until the client's resume replays\n (result) => {\n this.pendingDeviceId = result.deviceId;\n this.accept(undefined, true);\n },\n () => this.rejectAndClose(),\n );\n }\n\n /** B3 handshake accepted. Without E3: expose immediately. With E3: run first-pair enrollment first. */\n private onFirstPairAccepted(result: IPairingResult): void {\n if (this.state !== 'pairing') return;\n this.pendingResult = result;\n const cfg = this.options.reconnect;\n if (!cfg) {\n this.accept(result);\n return;\n }\n // E3 enrollment: advertise the host public key; the peer's `enroll-key` completes it (then expose).\n this.state = 'enrolling';\n pairingChannel.send(\n this.options.channel,\n JSON.stringify({ t: 'enroll-key', spki: cfg.hostPublicSpki } satisfies IEnrollFrame),\n );\n this.replayHeld();\n }\n\n private completeEnrollment(deviceSpki: string): void {\n if (this.state !== 'enrolling') return;\n const cfg = this.options.reconnect;\n if (!cfg) {\n this.rejectAndClose();\n return;\n }\n void (async (): Promise<void> => {\n try {\n // Validate the SPKI parses as a public key before pinning (fail closed on garbage).\n await importPublicKey(deviceSpki);\n const deviceId = await deriveIdentityId(deviceSpki);\n if (this.state !== 'enrolling') return;\n this.pendingDeviceId = deviceId;\n this.pendingEnrollment = { deviceId, spki: deviceSpki };\n this.accept(this.pendingResult);\n } catch {\n this.rejectAndClose();\n }\n })();\n }\n\n private accept(result?: IPairingResult, viaReconnect = false): void {\n if (this.state === 'closed' || this.state === 'accepted') return;\n // The steps this channel still owes. Demanded here rather than earlier because the handshake\n // must already have bound the channel, and before anything below because that is where the\n // session becomes reachable. The ordering argument lives in `admission-steps.ts`.\n const owed = nextAdmissionStep(this.options, this.state);\n if (owed !== null) {\n this.pendingResult = result ?? this.pendingResult;\n this.pendingViaReconnect = viaReconnect;\n this.state = owed;\n if (owed === 'operator-approval') void this.askOperator();\n this.replayHeld();\n return;\n }\n // Pin a first-pair device only now: a device a later step refused is not remembered.\n const enrollment = this.pendingEnrollment;\n this.pendingEnrollment = undefined;\n if (enrollment !== undefined) {\n try {\n this.options.reconnect?.onEnroll(enrollment.deviceId, enrollment.spki);\n } catch {\n this.rejectAndClose();\n return;\n }\n }\n const attached = attachSession(this.options, viaReconnect, (error, event) =>\n this.handleSessionDeliveryError(error, event),\n );\n this.onSessionMessage = attached.onSessionMessage;\n this.handlerCleanup = attached.cleanup;\n this.state = 'accepted';\n this.options.onAccept?.(result);\n const held = this.heldFrames;\n this.heldFrames = [];\n this.heldChars = 0;\n for (const frame of held) {\n if (this.state !== 'accepted') return;\n this.onSessionMessage?.(frame);\n }\n }\n\n /** The gate moved on: frames held under the previous state are routed again under the new one. */\n private replayHeld(): void {\n const held = this.heldFrames;\n this.heldFrames = [];\n this.heldChars = 0;\n // Routed in order; once accepted, the rest go to the session through the same entry point.\n for (const frame of held) {\n if (this.state === 'closed') return;\n this.onInbound(frame);\n }\n }\n\n private hold(data: string): void {\n this.heldChars += data.length;\n this.heldFrames.push(data);\n if (this.heldFrames.length > HELD_FRAMES_MAX || this.heldChars > HELD_CHARS_MAX) {\n this.rejectAndClose();\n }\n }\n\n /**\n * The `operator-approval` state: ask, then admit only on an explicit yes. A failure to ask is a no,\n * and an answer that arrives after the channel was torn down admits nothing.\n */\n private async askOperator(): Promise<void> {\n const abort = new AbortController();\n this.approvalAbort = abort;\n const context: IConnectionApprovalContext = {\n ...(this.pendingDeviceId !== undefined ? { deviceId: this.pendingDeviceId } : {}),\n viaReconnect: this.pendingViaReconnect,\n signal: abort.signal,\n };\n let allowed = false;\n try {\n allowed = (await this.options.connectionApproval?.approve(context)) === true;\n } catch {\n allowed = false;\n }\n if (this.state !== 'operator-approval' || abort.signal.aborted) return;\n this.approvalAbort = undefined;\n if (allowed) this.accept(this.pendingResult, this.pendingViaReconnect);\n else this.rejectAndClose();\n }\n\n /** The async half of the `handoff-grant` state, kept off the synchronous message path. */\n private async judgeGrantFrame(parsed: unknown): Promise<void> {\n const admission = await judgeHandoffGrant(parsed, this.options.handoffGrant);\n // A channel torn down while the signature was being checked must not be re-accepted by a verdict\n // that arrives afterwards. Checked after the await, because that is the window it exists for.\n if (this.state !== 'handoff-grant') return;\n this.options.handoffGrant?.onAdmission?.(admission);\n if (admission.admitted) this.accept(this.pendingResult, this.pendingViaReconnect);\n else this.rejectAndClose();\n }\n\n private rejectAndClose(): void {\n if (this.state === 'closed') return;\n this.state = 'closed';\n this.withdrawApproval();\n pairingChannel.close(this.options.channel);\n this.options.onReject?.();\n }\n\n private handleSessionDeliveryError(error: Error, event: string): void {\n if (this.state === 'closed') return;\n this.state = 'closed';\n this.handlerCleanup?.();\n this.handlerCleanup = undefined;\n this.onSessionMessage = undefined;\n pairingChannel.reportDeliveryError(this.options.onDeliveryError, error, event);\n pairingChannel.close(this.options.channel);\n }\n}\n","import type { ITransportLifecycleError } from '@robota-sdk/agent-interface-transport';\n\nexport function createTransportLifecycleError(\n code: ITransportLifecycleError['code'],\n): ITransportLifecycleError {\n return Object.assign(new Error(`WebRtcTransport ${code}.`), {\n name: 'TransportLifecycleError' as const,\n code,\n transportName: 'webrtc',\n });\n}\n","/** The part of a data channel the lifecycle closes. */\ninterface IClosableChannel {\n close(): void;\n}\n\ninterface IWebRtcDeliveryLifecycleOptions {\n readonly cleanup: () => void;\n readonly onDropped: () => void;\n readonly onDeliveryError: (error: Error, event: string) => void;\n}\n\n/** Owns carrier-drop state so delivery failures cannot leak into committed session operations. */\nexport class WebRtcDeliveryLifecycle {\n private generation = 0;\n private paired = false;\n private dropped = false;\n\n constructor(private readonly options: IWebRtcDeliveryLifecycleOptions) {}\n\n reset(generation: number): void {\n this.generation = generation;\n this.paired = false;\n this.dropped = false;\n }\n\n accept(generation: number): void {\n if (generation === this.generation) this.paired = true;\n }\n\n handleDrop(generation: number): void {\n if (generation !== this.generation || !this.paired || this.dropped) return;\n this.dropped = true;\n this.options.cleanup();\n this.options.onDropped();\n }\n\n handleFailure(channel: IClosableChannel, generation: number, error: Error, event: string): void {\n if (generation !== this.generation || this.dropped) return;\n const notifyDrop = this.paired;\n if (notifyDrop) this.dropped = true;\n try {\n this.options.onDeliveryError(error, event);\n } catch {\n // Observer failure cannot interrupt carrier cleanup or a committed session operation.\n }\n this.options.cleanup();\n try {\n channel.close();\n } catch {\n // already closing/closed\n }\n if (notifyDrop) this.options.onDropped();\n }\n}\n","import { HISTORY_PAGE_MAX_BYTES, createSessionMessageHandler } from '@robota-sdk/agent-transport';\nimport { resolveAdmission } from '@robota-sdk/agent-transport/node';\nimport {\n extractDtlsFingerprint,\n extractDtlsFingerprintAttribute,\n} from '@robota-sdk/agent-remote-pairing';\nimport type { IConfigurableTransport } from '@robota-sdk/agent-interface-transport';\n\nimport type { IProtocolSession } from '@robota-sdk/agent-transport';\n\nimport { createChannelDelivery } from './channel-delivery.js';\nimport { whenRemoteCertificateVerified } from './negotiated-certificate.js';\nimport { RtcPeer, type IRtcCandidate, type RtcChannel } from './rtc-peer.js';\nimport { PairingGate } from './pairing-gate.js';\nimport { createTransportLifecycleError } from './transport-lifecycle-error.js';\nimport { WebRtcDeliveryLifecycle } from './webrtc-delivery-lifecycle.js';\nimport type { IWebRtcTransportOptions } from './webrtc-transport-options.js';\n\n/** Frames a peer may send before the gate exists; the pairing handshake needs only a few. */\nconst MAX_PENDING_FRAMES = 16;\n/** Local ICE candidates held until the offer is out. */\nconst MAX_PENDING_CANDIDATES = 64;\n\n/**\n * WebRTC P2P transport (REMOTE-001/002): carries an `IProtocolSession` over an `RTCDataChannel` using the\n * SAME transport-neutral session bridge as the WebSocket transport (`createSessionMessageHandler` from\n * `@robota-sdk/agent-transport`). The host is the offerer: it creates the data channel + offer and wires the\n * channel as soon as it is created. `defaultEnabled: false` — nothing starts it automatically. Admission is\n * fixed at construction: a pairing `secret` gates the session behind the pairing handshake, and running\n * without one requires `open: true` with an `openReason`. The signaling client is injected and can be an\n * in-memory loopback for tests.\n */\nexport class WebRtcTransport implements IConfigurableTransport<IProtocolSession> {\n public readonly name = 'webrtc';\n public readonly lifecycle = Object.freeze({ kind: 'service' as const });\n public readonly defaultEnabled = false;\n public readonly optionsSchema = {} as const;\n\n private session?: IProtocolSession;\n private peer?: RtcPeer;\n private unsubscribeSignal?: () => void;\n private cleanupHandler?: () => void;\n /** Invalidates pending async startup work and scopes pairing/drop state to one start generation. */\n private generation = 0;\n /** Local DTLS fingerprint captured for pairing channel binding. */\n private localFingerprint?: string;\n /** Pairing gate for the current channel. */\n private pairingGate?: PairingGate;\n /** Whether this start generation has sent its offer; local candidates wait for it. */\n private offerSent = false;\n private localCandidates: IRtcCandidate[] = [];\n /** Whether this start generation has taken its one answer; any later answer is ignored. */\n private answered = false;\n /** Pre-gate channel frames, replayed into the gate once it exists. Bounded. */\n private pendingFrames: string[] = [];\n private stopAwaitingCertificate?: () => void;\n private readonly deliveryLifecycle: WebRtcDeliveryLifecycle;\n\n public constructor(private readonly options: IWebRtcTransportOptions) {\n this.deliveryLifecycle = new WebRtcDeliveryLifecycle({\n cleanup: () => {\n this.cleanupHandler?.();\n this.pairingGate?.cleanup();\n },\n onDropped: () => this.options.onDropped?.(),\n onDeliveryError: (error, event) => this.options.onDeliveryError?.(error, event),\n });\n // A pairing secret and explicit open admission are contradictory, so fail before signaling.\n if (this.options.secret && this.options.open === true) {\n throw new Error(\n 'WebRtcTransport: `secret` and `open: true` are contradictory. A pairing secret gates the ' +\n 'data channel; `open` runs without a gate. Pass one.',\n );\n }\n if (this.options.secret === undefined || this.options.secret === '') {\n if (this.options.open !== true) {\n throw new Error(\n 'WebRtcTransport: no pairing `secret` and no explicit `open`. Pass a `secret` to gate the ' +\n 'data channel, or `{ open: true, openReason: \"…\" }` to run without pairing on purpose.',\n );\n }\n // WebRTC has no bearer credential; use the shared seam only to validate the open reason.\n void resolveAdmission({\n open: true,\n ...(this.options.openReason !== undefined ? { openReason: this.options.openReason } : {}),\n });\n }\n }\n\n public validateOptions(): boolean {\n return true;\n }\n\n public attach(session: IProtocolSession): void {\n this.session = session;\n }\n private createPeer(): RtcPeer {\n return new RtcPeer({\n ...(this.options.iceServers ? { iceServers: this.options.iceServers } : {}),\n ...(this.options.forceTurn ? { forceTurn: true } : {}),\n ...(this.options.loadDataChannel ? { loadDataChannel: this.options.loadDataChannel } : {}),\n });\n }\n\n private wireSignaling(\n peer: RtcPeer,\n channel: RtcChannel,\n session: IProtocolSession,\n generation: number,\n ): void {\n const signaling = this.options.signaling;\n peer.onLocalCandidate((candidate) => {\n if (generation !== this.generation || peer !== this.peer) return;\n // A candidate can be gathered before the offer is out; it follows the offer, never precedes it.\n if (this.offerSent) this.sendCandidate(candidate);\n else if (this.localCandidates.length < MAX_PENDING_CANDIDATES)\n this.localCandidates.push(candidate);\n });\n let signalChain: Promise<void> = Promise.resolve();\n this.unsubscribeSignal = signaling.onSignal((message) => {\n if (generation !== this.generation) return;\n signalChain = signalChain\n .then(async () => {\n if (generation !== this.generation || peer !== this.peer) return;\n if (message.kind === 'answer') {\n // One answer per start: the one fingerprint checked is the one the binding names.\n if (this.answered) return;\n this.answered = true;\n const algorithm = this.remoteFingerprintAlgorithm(channel, message.data);\n if (this.options.secret && algorithm === undefined) return;\n const sdp = (message.data as { sdp?: unknown } | null)?.sdp;\n if (typeof sdp !== 'string') return;\n peer.acceptAnswer(sdp);\n if (algorithm !== undefined)\n this.awaitVerifiedCertificate(peer, channel, session, algorithm, generation);\n } else if (message.kind === 'ice') {\n const candidate = toRemoteCandidate(message.data);\n if (candidate !== undefined) peer.addRemoteCandidate(candidate);\n }\n })\n .catch(() => {\n // A description the connection refuses ends this start, never with an unhandled rejection.\n if (generation === this.generation) this.failPairing(channel);\n });\n });\n }\n\n private sendCandidate(candidate: IRtcCandidate): void {\n this.options.signaling.send({\n kind: 'ice',\n data: { candidate: candidate.candidate, sdpMid: candidate.mid, sdpMLineIndex: 0 },\n });\n }\n\n private requireCurrentPeer(peer: RtcPeer, generation: number): void {\n if (generation === this.generation && this.peer === peer) return;\n peer.close();\n throw new Error('WebRtcTransport startup was stopped.');\n }\n\n public async start(): Promise<void> {\n const session = this.session;\n if (!session) throw createTransportLifecycleError('not-attached');\n if (this.peer) throw createTransportLifecycleError('already-started');\n const generation = ++this.generation;\n this.deliveryLifecycle.reset(generation);\n this.answered = false;\n this.offerSent = false;\n this.localCandidates = [];\n this.pendingFrames = [];\n\n const peer = this.createPeer();\n this.peer = peer;\n const signaling = this.options.signaling;\n const channel = peer.createDataChannel('agent-session');\n this.wireChannel(channel, session, generation);\n this.wireSignaling(peer, channel, session, generation);\n\n const sdp = await peer.createOffer();\n this.requireCurrentPeer(peer, generation);\n // Capture the local DTLS fingerprint for the pairing channel-binding (offer SDP).\n if (this.options.secret) this.localFingerprint = extractDtlsFingerprint(sdp);\n signaling.send({ kind: 'offer', data: { type: 'offer', sdp } });\n this.offerSent = true;\n for (const candidate of this.localCandidates.splice(0)) this.sendCandidate(candidate);\n }\n\n /**\n * With a pairing secret, the answer must advertise exactly one DTLS fingerprint; its algorithm is the one the\n * verified certificate is hashed with. A refused answer fails pairing and closes the channel.\n */\n private remoteFingerprintAlgorithm(channel: RtcChannel, answer: unknown): string | undefined {\n if (!this.options.secret) return undefined;\n const sdp = (answer as { sdp?: unknown }).sdp;\n try {\n if (typeof sdp !== 'string') throw new Error('answer carries no SDP');\n return extractDtlsFingerprintAttribute(sdp).algorithm;\n } catch {\n this.failPairing(channel);\n return undefined;\n }\n }\n\n private failPairing(channel: RtcChannel): void {\n channel.close();\n this.options.onPairingFailed?.();\n }\n\n /** Build the pairing gate once the DTLS layer has verified the remote certificate. */\n private awaitVerifiedCertificate(\n peer: RtcPeer,\n channel: RtcChannel,\n session: IProtocolSession,\n algorithm: string,\n generation: number,\n ): void {\n this.stopAwaitingCertificate = whenRemoteCertificateVerified(\n peer,\n algorithm,\n (remoteFingerprint) => {\n if (generation !== this.generation) return;\n // The gate speaks first, so it starts once the channel can carry its frame.\n if (channel.readyState === 'open') {\n this.startPairing(channel, session, remoteFingerprint, generation);\n return;\n }\n const unsubscribe = channel.onStateChange((state) => {\n if (state !== 'open' || generation !== this.generation) return;\n unsubscribe();\n this.startPairing(channel, session, remoteFingerprint, generation);\n });\n },\n () => {\n if (generation === this.generation) this.failPairing(channel);\n },\n );\n }\n\n private startPairing(\n channel: RtcChannel,\n session: IProtocolSession,\n remoteFingerprint: string,\n generation: number,\n ): void {\n const secret = this.options.secret;\n if (!secret || !this.localFingerprint) return;\n this.pairingGate = new PairingGate({\n cryptoContext: this.options.cryptoContext,\n channel: { send: (d) => channel.send(d), close: () => channel.close() },\n session,\n secret,\n role: 'initiator',\n localFingerprint: this.localFingerprint,\n remoteFingerprint,\n onAccept: (result) => {\n if (generation !== this.generation) return;\n this.deliveryLifecycle.accept(generation);\n this.options.onPaired?.(result);\n },\n ...(this.options.onPairingFailed ? { onReject: this.options.onPairingFailed } : {}),\n ...(this.options.reconnect ? { reconnect: this.options.reconnect } : {}),\n ...(this.options.localPeer ? { localPeer: this.options.localPeer } : {}),\n ...(this.options.connectionApproval\n ? { connectionApproval: this.options.connectionApproval }\n : {}),\n ...(this.options.resumeBridge ? { resumeBridge: this.options.resumeBridge } : {}),\n ...(this.options.personalUsageReporter\n ? { personalUsageReporter: this.options.personalUsageReporter }\n : {}),\n ...(this.options.usageReporter ? { usageReporter: this.options.usageReporter } : {}),\n ...(this.options.storedSessionUsageReporter\n ? { storedSessionUsageReporter: this.options.storedSessionUsageReporter }\n : {}),\n surface: 'remote',\n // #3282 §4 part b-2: WebRTC proves no locality; installing a plugin refuses it.\n commandSurfaceLocality: 'remote',\n onDeliveryError: (error, event) =>\n this.deliveryLifecycle.handleFailure(channel, generation, error, event),\n });\n const gate = this.pairingGate;\n for (const frame of this.pendingFrames.splice(0)) gate.onInbound(frame);\n }\n\n private wireChannel(channel: RtcChannel, session: IProtocolSession, generation: number): void {\n // Subscribe eagerly: a remote's first frame is not buffered before a listener exists.\n // With a secret the gate keeps pre-accept non-pairing frames from the session; otherwise the session is exposed directly.\n if (this.options.secret) {\n channel.onMessage((frame) => {\n if (generation !== this.generation) return;\n if (this.pairingGate) this.pairingGate.onInbound(frame);\n else if (this.pendingFrames.length < MAX_PENDING_FRAMES) this.pendingFrames.push(frame);\n });\n // A post-accept close detaches the resume bridge and starts reconnect without ending the session.\n channel.onStateChange((state) => {\n if (generation !== this.generation) return;\n if (state === 'closed') {\n // Before acceptance the gate must hear it too: a question still open with the operator is\n // about a connection that no longer exists.\n this.pairingGate?.onChannelClosed();\n this.deliveryLifecycle.handleDrop(generation);\n }\n });\n this.cleanupHandler = () => this.pairingGate?.cleanup();\n return;\n }\n\n // ARCH-030: the transport is the carrier on the no-secret branch — its own sink, its own lifecycle.\n const { onMessage, cleanup } = createSessionMessageHandler({\n session,\n deliver: createChannelDelivery(channel, (error, event) =>\n this.deliveryLifecycle.handleFailure(channel, generation, error, event),\n ),\n ...(this.options.personalUsageReporter\n ? { personalUsageReporter: this.options.personalUsageReporter }\n : {}),\n ...(this.options.usageReporter ? { usageReporter: this.options.usageReporter } : {}),\n ...(this.options.storedSessionUsageReporter\n ? { storedSessionUsageReporter: this.options.storedSessionUsageReporter }\n : {}),\n surface: 'remote',\n // #3282 §4 part b-2: WebRTC proves no locality; installing a plugin refuses it.\n commandSurfaceLocality: 'remote',\n // A data channel carries a fraction of the inbound limit (see the pairing-gate branch).\n maxFrameBytes: HISTORY_PAGE_MAX_BYTES,\n });\n this.cleanupHandler = cleanup;\n channel.onMessage((frame) => {\n if (generation !== this.generation) return;\n onMessage(frame);\n });\n }\n\n public async stop(): Promise<void> {\n this.generation += 1;\n this.cleanupHandler?.();\n this.unsubscribeSignal?.();\n this.cleanupHandler = undefined;\n this.unsubscribeSignal = undefined;\n this.pairingGate = undefined;\n this.localFingerprint = undefined;\n this.answered = false;\n this.offerSent = false;\n this.localCandidates = [];\n this.pendingFrames = [];\n this.stopAwaitingCertificate?.();\n this.stopAwaitingCertificate = undefined;\n this.deliveryLifecycle.reset(this.generation);\n if (this.peer) {\n this.peer.close();\n this.peer = undefined;\n }\n this.session = undefined;\n }\n}\n\n/** A remote ICE candidate as a browser sends it (`RTCIceCandidateInit`), or `undefined` for none. */\nfunction toRemoteCandidate(data: unknown): { candidate: string; mid: string } | undefined {\n if (typeof data !== 'object' || data === null) return undefined;\n const record = data as { candidate?: unknown; sdpMid?: unknown; sdpMLineIndex?: unknown };\n if (typeof record.candidate !== 'string' || record.candidate.length === 0) return undefined;\n const mid =\n typeof record.sdpMid === 'string'\n ? record.sdpMid\n : typeof record.sdpMLineIndex === 'number'\n ? String(record.sdpMLineIndex)\n : '0';\n return { candidate: record.candidate, mid };\n}\n","/**\n * Signaling port (REMOTE-002). The WebRTC transport is decoupled from any concrete signaling server via this\n * port: it exchanges opaque SDP/ICE blobs by rendezvous id. The port carries **only** SDP offers/answers + ICE\n * candidates — never session content — so a signaling server (or the in-memory test pair) is content-blind.\n */\n\n/** The kinds of signal a peer exchanges through the relay. */\nexport type TSignalKind = 'offer' | 'answer' | 'ice';\n\n/** An opaque signaling message relayed by rendezvous id. `data` is an SDP description or an ICE candidate. */\nexport interface ISignalMessage {\n readonly kind: TSignalKind;\n /** Opaque payload (serialized SDP or ICE candidate). The signaling layer never inspects it. */\n readonly data: unknown;\n}\n\n/** Port a WebRTC peer uses to reach its counterpart at a rendezvous id. */\nexport interface ISignalingClient {\n /** Send a signal to the counterpart at the rendezvous. */\n send(message: ISignalMessage): void;\n /** Subscribe to signals from the counterpart. Returns an unsubscribe function. */\n onSignal(handler: (message: ISignalMessage) => void): () => void;\n /** Tear down the signaling channel. */\n close(): void;\n}\n\n/**\n * An in-process pair of {@link ISignalingClient}s wired directly to each other — for tests + loopback only\n * (no server, no network). Each side's `send` is delivered to the other side's handlers on a microtask.\n */\nexport function createInMemorySignalingPair(): [ISignalingClient, ISignalingClient] {\n const handlersA: ((m: ISignalMessage) => void)[] = [];\n const handlersB: ((m: ISignalMessage) => void)[] = [];\n let open = true;\n\n function make(\n ownHandlers: ((m: ISignalMessage) => void)[],\n peerHandlers: ((m: ISignalMessage) => void)[],\n ): ISignalingClient {\n return {\n send(message) {\n if (!open) return;\n queueMicrotask(() => {\n for (const h of peerHandlers) h(message);\n });\n },\n onSignal(handler) {\n ownHandlers.push(handler);\n return () => {\n const i = ownHandlers.indexOf(handler);\n if (i >= 0) ownHandlers.splice(i, 1);\n };\n },\n close() {\n open = false;\n ownHandlers.length = 0;\n },\n };\n }\n\n return [make(handlersA, handlersB), make(handlersB, handlersA)];\n}\n","/**\n * Production `ISignalingClient` over a WebSocket connection to the `@robota-sdk/remote-signaling` relay\n * (REMOTE-004 Stage B2). This is the **Node host-side** client (the browser remote client, Stage D, implements\n * `ISignalingClient` on the native `WebSocket`).\n *\n * On open it `join`s the rendezvous and flushes any signals produced before the socket opened (the offer is\n * created in `WebRtcTransport.start()`, which can run before the socket connects — buffering avoids dropping it).\n * Relay `error` frames, socket errors, and a close-before-join are surfaced through an explicit `onError`\n * callback — **never a silent degrade** (no-fallback, mirroring `loadReplayProvider`/`loadDataChannel`). The relay is\n * content-blind: this client only ever emits `join` + `signal` frames and only ever consumes `joined` / `signal`\n * / `error` frames.\n */\nimport WebSocket from 'ws';\n\nimport type { ISignalingClient, ISignalMessage, TSignalKind } from './signaling.js';\n\n/** A minimal WebSocket-like surface — so tests can inject a fake without a real socket. */\nexport interface IWebSocketLike {\n send(data: string): void;\n close(): void;\n readyState: number;\n on(event: 'open' | 'message' | 'error' | 'close', handler: (arg: unknown) => void): void;\n}\n\nexport interface IWsSignalingClientOptions {\n /** Relay URL, e.g. `ws://127.0.0.1:1234`. */\n readonly url: string;\n /** Rendezvous id to join. */\n readonly rendezvous: string;\n /**\n * Explicit error sink. Called on a relay `error` frame, a socket error, or a close-before-join — the signaling\n * channel cannot function and the caller must know (no silent degrade).\n */\n readonly onError?: (error: Error) => void;\n /** Called once the relay confirms the rendezvous `join` (the `joined` frame). */\n readonly onReady?: () => void;\n /** Injectable socket factory (defaults to a real `ws` WebSocket) — for tests. */\n readonly createSocket?: (url: string) => IWebSocketLike;\n}\n\nconst SIGNAL_KINDS: ReadonlySet<string> = new Set<TSignalKind>(['offer', 'answer', 'ice']);\nconst WS_OPEN = 1;\n\nfunction defaultCreateSocket(url: string): IWebSocketLike {\n return new WebSocket(url) as unknown as IWebSocketLike;\n}\n\nexport class WsSignalingClient implements ISignalingClient {\n private readonly socket: IWebSocketLike;\n private readonly handlers: ((message: ISignalMessage) => void)[] = [];\n private readonly outbox: ISignalMessage[] = [];\n private joined = false;\n private closed = false;\n\n public constructor(private readonly options: IWsSignalingClientOptions) {\n const factory = options.createSocket ?? defaultCreateSocket;\n this.socket = factory(options.url);\n this.socket.on('open', () => this.handleOpen());\n this.socket.on('message', (data) => this.handleMessage(data));\n this.socket.on('error', (err) =>\n this.fail(err instanceof Error ? err : new Error(String(err))),\n );\n this.socket.on('close', () => this.handleClose());\n }\n\n private handleOpen(): void {\n this.socket.send(JSON.stringify({ type: 'join', rendezvous: this.options.rendezvous }));\n // Flush any signals produced before the socket opened (e.g. the offer from WebRtcTransport.start()).\n for (const message of this.outbox) this.writeSignal(message);\n this.outbox.length = 0;\n }\n\n private handleMessage(data: unknown): void {\n let frame: unknown;\n try {\n frame = JSON.parse(typeof data === 'string' ? data : String(data));\n } catch {\n return; // a non-JSON frame from the relay is ignored (the relay only emits JSON)\n }\n if (typeof frame !== 'object' || frame === null) return;\n const record = frame as Record<string, unknown>;\n if (record.type === 'joined') {\n this.joined = true;\n this.options.onReady?.();\n return;\n }\n if (record.type === 'error') {\n this.fail(new Error(`signaling relay rejected the connection: ${String(record.reason)}`));\n return;\n }\n if (\n record.type === 'signal' &&\n typeof record.kind === 'string' &&\n SIGNAL_KINDS.has(record.kind)\n ) {\n const message: ISignalMessage = { kind: record.kind as TSignalKind, data: record.data };\n for (const handler of this.handlers) handler(message);\n }\n }\n\n private handleClose(): void {\n if (this.closed) return; // an intentional close() is not an error\n if (!this.joined) {\n this.fail(new Error('signaling socket closed before the rendezvous was joined'));\n }\n }\n\n private fail(error: Error): void {\n this.options.onError?.(error);\n }\n\n private writeSignal(message: ISignalMessage): void {\n this.socket.send(JSON.stringify({ type: 'signal', kind: message.kind, data: message.data }));\n }\n\n public send(message: ISignalMessage): void {\n if (this.closed) return;\n if (this.socket.readyState === WS_OPEN) {\n this.writeSignal(message);\n } else {\n this.outbox.push(message); // buffer until open (see handleOpen)\n }\n }\n\n public onSignal(handler: (message: ISignalMessage) => void): () => void {\n this.handlers.push(handler);\n return () => {\n const index = this.handlers.indexOf(handler);\n if (index >= 0) this.handlers.splice(index, 1);\n };\n }\n\n public close(): void {\n this.closed = true;\n this.handlers.length = 0;\n this.outbox.length = 0;\n this.socket.close();\n }\n}\n","/**\n * What two device mesh nodes say to each other through the relay, and the frames their data channel\n * carries. Everything here arrives from an untrusted relay or an unadmitted peer, so every field is\n * bounded and shape-checked before it is used, and a failure names no value.\n *\n * `hello` is presence: a node announces its instance to a peer's inbox. The instance is a random id\n * per node run, so a restarted peer is told apart from its previous run without naming the device.\n * An attempt (`cid`) scopes an offer, its answer and their ICE candidates to one connection.\n */\n\n/** A per-run instance id or a connection-attempt id: 128 random bits, base64url. */\nconst RANDOM_ID = /^[A-Za-z0-9_-]{22}$/;\n/** An SDP a relay frame can carry; the relay itself caps a frame well below this. */\nconst MAX_SDP_CHARS = 48 * 1024;\nconst MAX_CANDIDATE_CHARS = 2048;\n/** Largest message body a data-channel `mesh-msg` frame carries. */\nexport const MAX_MESH_MESSAGE_CHARS = 60_000;\n\nexport const MESH_SIGNAL_VERSION = 1;\n\nexport interface IMeshHello {\n readonly v: typeof MESH_SIGNAL_VERSION;\n readonly kind: 'hello';\n readonly from: string;\n}\n\nexport interface IMeshDescription {\n readonly v: typeof MESH_SIGNAL_VERSION;\n readonly kind: 'offer' | 'answer';\n readonly from: string;\n readonly to: string;\n readonly cid: string;\n readonly sdp: string;\n}\n\nexport interface IMeshIce {\n readonly v: typeof MESH_SIGNAL_VERSION;\n readonly kind: 'ice';\n readonly from: string;\n readonly to: string;\n readonly cid: string;\n readonly candidate: {\n readonly candidate: string;\n readonly sdpMid?: string;\n readonly sdpMLineIndex?: number;\n };\n}\n\nexport type TMeshSignal = IMeshHello | IMeshDescription | IMeshIce;\n\nfunction isId(value: unknown): value is string {\n return typeof value === 'string' && RANDOM_ID.test(value);\n}\n\nfunction isBoundedString(value: unknown, max: number): value is string {\n return typeof value === 'string' && value.length > 0 && value.length <= max;\n}\n\n/** A relay-delivered signal, or `undefined` when it is not one this node would act on. */\nexport function decodeMeshSignal(value: unknown): TMeshSignal | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const r = value as Record<string, unknown>;\n if (r.v !== MESH_SIGNAL_VERSION || !isId(r.from)) return undefined;\n if (r.kind === 'hello') return { v: MESH_SIGNAL_VERSION, kind: 'hello', from: r.from };\n if (!isId(r.to) || !isId(r.cid)) return undefined;\n if (r.kind === 'offer' || r.kind === 'answer') {\n if (!isBoundedString(r.sdp, MAX_SDP_CHARS)) return undefined;\n return { v: MESH_SIGNAL_VERSION, kind: r.kind, from: r.from, to: r.to, cid: r.cid, sdp: r.sdp };\n }\n if (r.kind === 'ice') {\n const c = r.candidate;\n if (typeof c !== 'object' || c === null || Array.isArray(c)) return undefined;\n const cr = c as Record<string, unknown>;\n if (!isBoundedString(cr.candidate, MAX_CANDIDATE_CHARS)) return undefined;\n if (cr.sdpMid !== undefined && !isBoundedString(cr.sdpMid, 64)) return undefined;\n if (\n cr.sdpMLineIndex !== undefined &&\n !(\n typeof cr.sdpMLineIndex === 'number' &&\n Number.isInteger(cr.sdpMLineIndex) &&\n cr.sdpMLineIndex >= 0 &&\n cr.sdpMLineIndex < 64\n )\n ) {\n return undefined;\n }\n return {\n v: MESH_SIGNAL_VERSION,\n kind: 'ice',\n from: r.from,\n to: r.to,\n cid: r.cid,\n candidate: {\n candidate: cr.candidate,\n ...(cr.sdpMid !== undefined ? { sdpMid: cr.sdpMid as string } : {}),\n ...(cr.sdpMLineIndex !== undefined ? { sdpMLineIndex: cr.sdpMLineIndex as number } : {}),\n },\n };\n }\n return undefined;\n}\n\n/** The one frame an admitted link carries. */\nexport interface IMeshMessageFrame {\n readonly t: 'mesh-msg';\n readonly body: string;\n}\n\nexport function decodeMeshMessageFrame(value: unknown): IMeshMessageFrame | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const r = value as Record<string, unknown>;\n if (r.t !== 'mesh-msg' || typeof r.body !== 'string' || r.body.length > MAX_MESH_MESSAGE_CHARS) {\n return undefined;\n }\n return { t: 'mesh-msg', body: r.body };\n}\n","/**\n * One WebRTC connection between two of one user's devices, in either role, admitted by the device\n * handshake.\n *\n * The handshake is bound to the negotiated channel on both roles the same way: the remote fingerprint\n * is read from the certificate the DTLS layer verified, never from SDP text, and the remote\n * description must advertise exactly one fingerprint and only one is taken per link, so the certificate\n * the DTLS layer checks and the value the binding names are one and the same.\n *\n * Until admission the channel carries only handshake frames. Anything that is not one — or anything\n * after the peer's proof but before this side's verdict, beyond a small bound — is held or refused,\n * never delivered, and every refusal closes the connection. Only an admitted link delivers messages.\n *\n * Files travel on data channels of their own, one per transfer, opened by the sending side once the\n * link is admitted, so a transfer never shares a channel with messages. A file channel that arrives\n * before admission, or beyond a small number at once, is closed unread.\n */\nimport {\n extractDtlsFingerprint,\n extractDtlsFingerprintAttribute,\n} from '@robota-sdk/agent-remote-pairing';\n\nimport { whenRemoteCertificateVerified } from './negotiated-certificate.js';\nimport { MAX_MESH_MESSAGE_CHARS, decodeMeshMessageFrame } from './mesh-signal.js';\n\nimport type {\n IDeviceHandshakeController,\n IDeviceHandshakeResult,\n} from '@robota-sdk/agent-remote-pairing';\nimport type { IRtcPeerDiagnostics, RtcChannel, RtcPeer } from './rtc-peer.js';\n\n/** The offerer creates the data channel and the offer; the answerer answers it. */\nexport type TMeshLinkRole = 'offerer' | 'answerer';\n\n/** Why a link ended. `closed` is an orderly close by either side after or before admission. */\nexport type TMeshLinkEnd =\n 'closed' | 'signaling' | 'channel-binding' | 'handshake' | 'protocol' | 'timeout';\n\n/** How far a link got before it ended. */\nexport type TMeshLinkStage =\n 'signaling' | 'connecting' | 'awaiting-channel' | 'handshake' | 'confirming' | 'admitted';\n\n/**\n * Why a link ended, with what the connection under it went through, so a refusal says more than\n * \"closed\". `cause` is the underlying error when there was one (e.g. the device handshake refusal).\n */\nexport class MeshLinkEndedError extends Error {\n readonly end: TMeshLinkEnd;\n readonly stage: TMeshLinkStage;\n readonly role: TMeshLinkRole;\n readonly peer?: IRtcPeerDiagnostics;\n /** Why the data channel closed, when it had. */\n readonly channelClose?: string;\n override readonly cause?: unknown;\n\n constructor(details: {\n readonly end: TMeshLinkEnd;\n readonly stage: TMeshLinkStage;\n readonly role: TMeshLinkRole;\n readonly detail?: string;\n readonly peer?: IRtcPeerDiagnostics;\n readonly channelClose?: string;\n readonly cause?: unknown;\n }) {\n const parts = [`mesh link ended (${details.end}) during ${details.stage} as ${details.role}`];\n if (details.detail !== undefined) parts.push(details.detail);\n if (details.cause !== undefined) {\n parts.push(details.cause instanceof Error ? details.cause.message : String(details.cause));\n }\n const peer = details.peer;\n if (peer !== undefined) {\n parts.push(\n `connection ${peer.state} [${peer.history.join(' > ')}], ice ${peer.ice ?? '?'}, gathering ${\n peer.gathering ?? '?'\n }, candidates ${peer.localCandidates} sent / ${peer.remoteCandidates} applied, closed by ${\n peer.closedBy ?? 'the peer or the network'\n }${peer.handlerError !== undefined ? ` (${peer.handlerError})` : ''}`,\n );\n }\n if (details.channelClose !== undefined) parts.push(`channel ${details.channelClose}`);\n super(parts.join('; '));\n this.name = 'MeshLinkEndedError';\n this.end = details.end;\n this.stage = details.stage;\n this.role = details.role;\n if (peer !== undefined) this.peer = peer;\n if (details.channelClose !== undefined) this.channelClose = details.channelClose;\n if (details.cause !== undefined) this.cause = details.cause;\n }\n}\n\nexport type TMeshLinkSignal =\n | { readonly kind: 'offer' | 'answer'; readonly sdp: string }\n | {\n readonly kind: 'ice';\n readonly candidate: { candidate: string; sdpMid?: string; sdpMLineIndex?: number };\n };\n\n/** The DTLS fingerprints of one admitted connection, as each side sees them. */\nexport interface IMeshChannelBinding {\n readonly localFingerprint: string;\n /** Of the certificate the DTLS layer verified. */\n readonly remoteFingerprint: string;\n}\n\nexport interface IMeshHandshakeBinding {\n readonly localFingerprint: string;\n /** Of the certificate the DTLS layer verified. */\n readonly remoteFingerprint: string;\n readonly send: (frame: unknown) => void;\n}\n\nexport interface IMeshPeerLinkOptions {\n readonly role: TMeshLinkRole;\n /**\n * A new peer connection; each one has a DTLS certificate of its own. A failure ends the link as a\n * signaling failure with it as the cause.\n */\n readonly createPeer: () => RtcPeer | Promise<RtcPeer>;\n readonly sendSignal: (signal: TMeshLinkSignal) => void;\n /** Start this side of the device handshake over the bound channel. */\n readonly startHandshake: (binding: IMeshHandshakeBinding) => IDeviceHandshakeController;\n /** From creation to admission. */\n readonly connectTimeoutMs: number;\n readonly onAdmitted: (result: IDeviceHandshakeResult) => void;\n /**\n * Whether this side takes the peer the handshake admitted, checked before this side says so to the\n * peer (e.g. the device the pair's inbox belongs to). Refusing ends the link as a handshake refusal.\n */\n readonly accepts?: (result: IDeviceHandshakeResult) => boolean;\n /** Called once, whatever ended the link, with why. */\n readonly onEnded: (end: TMeshLinkEnd, error: MeshLinkEndedError) => void;\n}\n\n/** Frames held before the handshake exists, or between the peer's proof and this side's verdict. */\nconst MAX_HELD_FRAMES = 16;\n/** ICE candidates per direction; a connection gathers a handful, and the relay supplies the peer's. */\nconst MAX_CANDIDATES = 64;\nconst DATA_CHANNEL_LABEL = 'agent-mesh';\n/** The label of a channel that carries one file transfer. */\nconst FILE_CHANNEL_LABEL = 'agent-file';\n/** File channels open at once on one link. */\nconst MAX_FILE_CHANNELS = 4;\n/** From opening a file channel to its being open. */\nconst FILE_CHANNEL_OPEN_MS = 10_000;\n/** Time for a channel close to leave before the connection under it is closed. */\nconst PEER_CLOSE_GRACE_MS = 250;\n\n/**\n * `confirming`: this side admitted the peer and said so; the link is admitted once the peer says the\n * same, so a side the peer refused never believes it is connected.\n */\ntype TState = 'connecting' | 'confirming' | 'admitted' | 'ended';\n\n/** Sent once this side's handshake admitted the peer; nothing else precedes a message. */\nconst ADMITTED_FRAME = JSON.stringify({ t: 'mesh-admitted' });\n\nexport class MeshPeerLink {\n private state: TState = 'connecting';\n private peer?: RtcPeer;\n private channel?: RtcChannel;\n private localFingerprint?: string;\n private remoteFingerprint?: string;\n private tookRemoteDescription = false;\n private creatingPeer = false;\n /** Local candidates wait for the description: they can be gathered before it is sent. */\n private sentDescription = false;\n private readonly localCandidates: Extract<TMeshLinkSignal, { kind: 'ice' }>[] = [];\n private remoteCandidates = 0;\n private handshake?: IDeviceHandshakeController;\n private fedProof = false;\n private readonly held: string[] = [];\n private readonly handlers = new Set<(body: string) => void>();\n private readonly endHandlers = new Set<(end: TMeshLinkEnd) => void>();\n private readonly fileChannels = new Set<RtcChannel>();\n private readonly fileHandlers = new Set<(channel: RtcChannel) => void>();\n private signalChain: Promise<void> = Promise.resolve();\n private stopAwaitingCertificate?: () => void;\n private readonly timer: ReturnType<typeof setTimeout>;\n private admissionResult?: IDeviceHandshakeResult;\n\n public constructor(private readonly options: IMeshPeerLinkOptions) {\n this.timer = setTimeout(() => this.end('timeout'), options.connectTimeoutMs);\n }\n\n public get role(): TMeshLinkRole {\n return this.options.role;\n }\n\n public get admitted(): boolean {\n return this.state === 'admitted';\n }\n\n public get ended(): boolean {\n return this.isEnded();\n }\n\n /** A method, not a narrowed field read: the state changes across every `await`. */\n private isEnded(): boolean {\n return this.state === 'ended';\n }\n\n /** The admission, once admitted. */\n public get admission(): IDeviceHandshakeResult | undefined {\n return this.state === 'admitted' ? this.admissionResult : undefined;\n }\n\n /**\n * The DTLS fingerprints the handshake was bound to — this side's, and the one the DTLS layer\n * verified for the peer — once admitted. Anything bound to this connection binds to these.\n */\n public get channelBinding(): IMeshChannelBinding | undefined {\n if (this.state !== 'admitted') return undefined;\n if (this.localFingerprint === undefined || this.remoteFingerprint === undefined)\n return undefined;\n return { localFingerprint: this.localFingerprint, remoteFingerprint: this.remoteFingerprint };\n }\n\n /** Offerer: create the data channel and send the offer. */\n public async offer(): Promise<void> {\n if (this.options.role !== 'offerer' || this.creatingPeer || this.isEnded()) return;\n this.creatingPeer = true;\n try {\n const peer = await this.createPeer();\n if (peer === undefined) return;\n this.adoptChannel(peer.createDataChannel(DATA_CHANNEL_LABEL));\n const sdp = await peer.createOffer();\n if (this.isEnded()) return;\n this.localFingerprint = extractDtlsFingerprint(sdp);\n this.sendDescription({ kind: 'offer', sdp });\n } catch (error) {\n this.end('signaling', error);\n }\n }\n\n /** A remote description or ICE candidate from the peer, applied in arrival order. */\n public onSignal(signal: TMeshLinkSignal): void {\n if (this.isEnded()) return;\n this.signalChain = this.signalChain\n .then(() => this.applySignal(signal))\n .catch((error: unknown) => this.end('signaling', error));\n }\n\n private async applySignal(signal: TMeshLinkSignal): Promise<void> {\n if (this.isEnded()) return;\n if (signal.kind === 'ice') {\n // Candidates only after the one remote description; earlier ones have nothing to apply to.\n if (!this.tookRemoteDescription || !this.peer) return;\n if (++this.remoteCandidates > MAX_CANDIDATES) return;\n this.peer.addRemoteCandidate({\n candidate: signal.candidate.candidate,\n mid: signal.candidate.sdpMid ?? String(signal.candidate.sdpMLineIndex ?? 0),\n });\n return;\n }\n const expected = this.options.role === 'offerer' ? 'answer' : 'offer';\n // One remote description per link: the one fingerprint checked is the one the binding names.\n if (signal.kind !== expected || this.tookRemoteDescription) return;\n this.tookRemoteDescription = true;\n let algorithm: string;\n try {\n algorithm = extractDtlsFingerprintAttribute(signal.sdp).algorithm;\n } catch (error) {\n this.end('channel-binding', error);\n return;\n }\n if (this.options.role === 'offerer') {\n const peer = this.peer;\n if (!peer) return;\n peer.acceptAnswer(signal.sdp);\n this.awaitCertificate(peer, algorithm);\n return;\n }\n const peer = await this.createPeer();\n if (peer === undefined) return;\n const sdp = await peer.acceptOffer(signal.sdp);\n if (this.isEnded()) return;\n this.localFingerprint = extractDtlsFingerprint(sdp);\n this.sendDescription({ kind: 'answer', sdp });\n this.awaitCertificate(peer, algorithm);\n }\n\n /** The link's connection; `undefined` when the link ended while it was being created. */\n private async createPeer(): Promise<RtcPeer | undefined> {\n const peer = await this.options.createPeer();\n if (this.isEnded()) {\n peer.close();\n return undefined;\n }\n this.peer = peer;\n peer.onLocalCandidate((candidate) => {\n if (this.isEnded()) return;\n const signal: Extract<TMeshLinkSignal, { kind: 'ice' }> = {\n kind: 'ice',\n candidate: { candidate: candidate.candidate, sdpMid: candidate.mid },\n };\n if (this.sentDescription) this.options.sendSignal(signal);\n else if (this.localCandidates.length < MAX_CANDIDATES) this.localCandidates.push(signal);\n });\n peer.onStateChange((state) => {\n if (state === 'failed' || state === 'closed')\n this.end('closed', undefined, `connection ${state}`);\n });\n peer.onDataChannel((channel) => this.incomingChannel(channel));\n return peer;\n }\n\n /**\n * A channel the peer opened. The answerer's first one is the link's own; after admission a file\n * channel goes to whoever takes files; anything else is not ours to read.\n */\n private incomingChannel(channel: RtcChannel): void {\n if (this.options.role === 'answerer' && this.channel === undefined && !this.isEnded()) {\n this.adoptChannel(channel);\n return;\n }\n if (\n this.state !== 'admitted' ||\n channel.label !== FILE_CHANNEL_LABEL ||\n this.fileHandlers.size === 0 ||\n this.fileChannels.size >= MAX_FILE_CHANNELS\n ) {\n channel.close();\n return;\n }\n this.trackFileChannel(channel);\n for (const handler of this.fileHandlers) handler(channel);\n }\n\n private trackFileChannel(channel: RtcChannel): void {\n this.fileChannels.add(channel);\n channel.onStateChange((state) => {\n if (state === 'closed') this.fileChannels.delete(channel);\n });\n }\n\n /** Open a channel for one file transfer. Only an admitted link opens one. */\n public openFileChannel(): Promise<RtcChannel> {\n const peer = this.peer;\n if (this.state !== 'admitted' || peer === undefined) {\n return Promise.reject(new Error('mesh link is not admitted'));\n }\n if (this.fileChannels.size >= MAX_FILE_CHANNELS) {\n return Promise.reject(new Error('too many file transfers are open on this link'));\n }\n const channel = peer.createDataChannel(FILE_CHANNEL_LABEL);\n this.trackFileChannel(channel);\n return new Promise<RtcChannel>((resolve, reject) => {\n if (channel.readyState === 'open') {\n resolve(channel);\n return;\n }\n const timer = setTimeout(() => {\n stop();\n channel.close();\n reject(new Error('the file channel did not open'));\n }, FILE_CHANNEL_OPEN_MS);\n const stop = channel.onStateChange((state) => {\n if (state === 'connecting') return;\n clearTimeout(timer);\n stop();\n if (state === 'open') resolve(channel);\n else reject(new Error('the file channel closed before it opened'));\n });\n });\n }\n\n /** File channels the admitted peer opens. Returns an unsubscribe. */\n public onFileChannel(handler: (channel: RtcChannel) => void): () => void {\n this.fileHandlers.add(handler);\n return () => this.fileHandlers.delete(handler);\n }\n\n private sendDescription(signal: Extract<TMeshLinkSignal, { kind: 'offer' | 'answer' }>): void {\n this.options.sendSignal(signal);\n this.sentDescription = true;\n for (const candidate of this.localCandidates.splice(0)) this.options.sendSignal(candidate);\n }\n\n private awaitCertificate(peer: RtcPeer, algorithm: string): void {\n this.stopAwaitingCertificate = whenRemoteCertificateVerified(\n peer,\n algorithm,\n (fingerprint) => {\n if (this.isEnded()) return;\n this.remoteFingerprint = fingerprint;\n this.maybeStartHandshake();\n },\n (reason) => this.end('channel-binding', new Error(reason)),\n );\n }\n\n private adoptChannel(channel: RtcChannel): void {\n this.channel = channel;\n // Subscribe at once: the implementation does not buffer a frame that arrives before a listener.\n channel.onMessage((text) => this.inbound(text));\n channel.onStateChange((state) => {\n if (state === 'open') this.maybeStartHandshake();\n else if (state === 'closed') this.end('closed', undefined, 'data channel closed');\n });\n if (channel.readyState === 'open') this.maybeStartHandshake();\n }\n\n private maybeStartHandshake(): void {\n const channel = this.channel;\n if (this.state !== 'connecting' || this.handshake !== undefined || channel === undefined)\n return;\n if (this.localFingerprint === undefined || this.remoteFingerprint === undefined) return;\n if (channel.readyState !== 'open') return;\n let controller: IDeviceHandshakeController;\n try {\n controller = this.options.startHandshake({\n localFingerprint: this.localFingerprint,\n remoteFingerprint: this.remoteFingerprint,\n send: (frame) => this.write(JSON.stringify(frame)),\n });\n } catch (error) {\n this.end('handshake', error);\n return;\n }\n this.handshake = controller;\n controller.result.then(\n (result) => this.confirm(result),\n (error: unknown) => this.end('handshake', error),\n );\n const early = this.held.splice(0);\n for (const frame of early) this.feed(frame);\n }\n\n private inbound(text: string): void {\n if (this.isEnded()) return;\n if (this.state === 'admitted') {\n this.deliver(text);\n return;\n }\n if (this.state === 'confirming') {\n this.confirmed(text);\n return;\n }\n if (this.handshake === undefined) {\n this.hold(text);\n return;\n }\n this.feed(text);\n }\n\n private hold(text: string): void {\n if (this.held.length >= MAX_HELD_FRAMES) {\n this.end('protocol');\n return;\n }\n this.held.push(text);\n }\n\n /** Pre-admission: the peer's handshake frames go to the handshake; what follows its proof waits. */\n private feed(text: string): void {\n if (this.fedProof) {\n this.hold(text);\n return;\n }\n let frame: unknown;\n try {\n frame = JSON.parse(text);\n } catch {\n frame = text; // the handshake refuses it as malformed, and the link ends\n }\n if (\n typeof frame === 'object' &&\n frame !== null &&\n (frame as { t?: unknown }).t === 'dh-prove'\n ) {\n this.fedProof = true;\n }\n this.handshake?.onFrame(frame);\n }\n\n private confirm(result: IDeviceHandshakeResult): void {\n if (this.state !== 'connecting') return;\n if (this.options.accepts !== undefined && !this.options.accepts(result)) {\n this.end('handshake', new Error('the admitted device is not the one this link is for'));\n return;\n }\n this.state = 'confirming';\n this.admissionResult = result;\n this.write(ADMITTED_FRAME);\n const held = this.held.splice(0);\n for (const frame of held) this.inbound(frame);\n }\n\n /** The peer's first frame after its own verdict must say it admitted this side. */\n private confirmed(text: string): void {\n let frame: unknown;\n try {\n frame = JSON.parse(text);\n } catch {\n frame = undefined;\n }\n const result = this.admissionResult;\n if (\n result === undefined ||\n typeof frame !== 'object' ||\n frame === null ||\n (frame as { t?: unknown }).t !== 'mesh-admitted'\n ) {\n this.end('protocol');\n return;\n }\n clearTimeout(this.timer);\n this.state = 'admitted';\n this.options.onAdmitted(result);\n }\n\n private deliver(text: string): void {\n let frame: unknown;\n try {\n frame = JSON.parse(text);\n } catch {\n frame = undefined;\n }\n const message = decodeMeshMessageFrame(frame);\n if (message === undefined) {\n this.end('protocol');\n return;\n }\n for (const handler of this.handlers) handler(message.body);\n }\n\n private write(text: string): void {\n try {\n this.channel?.send(text);\n } catch (error) {\n this.end('closed', error);\n }\n }\n\n /** Send one message. Only an admitted link sends. */\n public send(body: string): void {\n if (this.state !== 'admitted') throw new Error('mesh link is not admitted');\n if (body.length > MAX_MESH_MESSAGE_CHARS) {\n throw new Error(`mesh message exceeds ${MAX_MESH_MESSAGE_CHARS} characters`);\n }\n this.write(JSON.stringify({ t: 'mesh-msg', body }));\n }\n\n /** Messages from the admitted peer. Returns an unsubscribe. */\n public onMessage(handler: (body: string) => void): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler);\n }\n\n /** Fires once when the link ends, for whatever reason. Returns an unsubscribe. */\n public onEnd(handler: (end: TMeshLinkEnd) => void): () => void {\n this.endHandlers.add(handler);\n return () => this.endHandlers.delete(handler);\n }\n\n public close(): void {\n this.end('closed', undefined, 'closed by this side');\n }\n\n /** How far the link got. */\n public get stage(): TMeshLinkStage {\n if (this.state === 'admitted' || this.state === 'confirming') return this.state;\n if (this.handshake !== undefined) return 'handshake';\n if (this.remoteFingerprint !== undefined) return 'awaiting-channel';\n if (this.tookRemoteDescription) return 'connecting';\n return 'signaling';\n }\n\n private end(end: TMeshLinkEnd, cause?: unknown, detail?: string): void {\n if (this.isEnded()) return;\n // Read before anything below closes the channel and the connection.\n const error = new MeshLinkEndedError({\n end,\n stage: this.stage,\n role: this.options.role,\n ...(detail !== undefined ? { detail } : {}),\n ...(this.peer !== undefined ? { peer: this.peer.diagnostics() } : {}),\n ...(this.channel?.closeCause !== undefined ? { channelClose: this.channel.closeCause } : {}),\n ...(cause !== undefined ? { cause } : {}),\n });\n this.state = 'ended';\n clearTimeout(this.timer);\n this.stopAwaitingCertificate?.();\n this.held.length = 0;\n // A frame the handshake cannot decode settles it now, so its timer does not outlive the link.\n this.handshake?.onFrame(undefined);\n this.channel?.close();\n for (const channel of this.fileChannels) channel.close();\n this.fileChannels.clear();\n this.fileHandlers.clear();\n const peer = this.peer;\n // The channel's close goes out first, so the peer learns of it rather than waiting out a timeout.\n if (peer) setTimeout(() => peer.close(), PEER_CLOSE_GRACE_MS).unref?.();\n this.options.onEnded(end, error);\n for (const handler of this.endHandlers) handler(end);\n this.endHandlers.clear();\n this.handlers.clear();\n }\n}\n","/**\n * STUN messages (RFC 8489) as TURN (RFC 8656) uses them over UDP: the header, the attributes a\n * relay reads and writes, MESSAGE-INTEGRITY with a long-term key, FINGERPRINT, and ChannelData.\n *\n * Everything decoded here is hostile input: a malformed message decodes to `undefined` rather than\n * throwing, and no length is trusted beyond the bytes actually received.\n */\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto';\nimport { isIPv4, isIPv6 } from 'node:net';\n\nexport const MAGIC_COOKIE = 0x2112a442;\nconst HEADER_BYTES = 20;\nconst MAX_MESSAGE_BYTES = 65_535;\n\nexport const StunMethod = {\n Binding: 0x001,\n Allocate: 0x003,\n Refresh: 0x004,\n Send: 0x006,\n Data: 0x007,\n CreatePermission: 0x008,\n ChannelBind: 0x009,\n} as const;\n\nexport const StunClass = {\n Request: 0,\n Indication: 1,\n Success: 2,\n Error: 3,\n} as const;\nexport type TStunClass = (typeof StunClass)[keyof typeof StunClass];\n\nexport const StunAttr = {\n MappedAddress: 0x0001,\n Username: 0x0006,\n MessageIntegrity: 0x0008,\n ErrorCode: 0x0009,\n UnknownAttributes: 0x000a,\n ChannelNumber: 0x000c,\n Lifetime: 0x000d,\n XorPeerAddress: 0x0012,\n Data: 0x0013,\n Realm: 0x0014,\n Nonce: 0x0015,\n XorRelayedAddress: 0x0016,\n RequestedAddressFamily: 0x0017,\n EvenPort: 0x0018,\n RequestedTransport: 0x0019,\n DontFragment: 0x001a,\n MessageIntegritySha256: 0x001c,\n PasswordAlgorithm: 0x001d,\n Userhash: 0x001e,\n XorMappedAddress: 0x0020,\n ReservationToken: 0x0022,\n Priority: 0x0024,\n UseCandidate: 0x0025,\n Fingerprint: 0x8028,\n} as const;\n\nexport interface IStunAttribute {\n readonly type: number;\n readonly value: Buffer;\n /** Where the attribute's header starts in the message. */\n readonly offset: number;\n}\n\nexport interface IStunMessage {\n readonly method: number;\n readonly cls: TStunClass;\n readonly transactionId: Buffer;\n readonly attributes: readonly IStunAttribute[];\n /** The whole message as received. */\n readonly raw: Buffer;\n}\n\nexport interface ITransportAddress {\n readonly address: string;\n readonly port: number;\n}\n\nfunction messageType(method: number, cls: TStunClass): number {\n return (\n (method & 0x000f) |\n ((method & 0x0070) << 1) |\n ((method & 0x0f80) << 2) |\n ((cls & 1) << 4) |\n ((cls & 2) << 7)\n );\n}\n\n/** Whether the first bytes look like a STUN message (the two top bits zero, the magic cookie). */\nexport function isStunMessage(data: Buffer): boolean {\n return (\n data.length >= HEADER_BYTES && (data[0]! & 0xc0) === 0 && data.readUInt32BE(4) === MAGIC_COOKIE\n );\n}\n\n/** Decode a STUN message; `undefined` when it is not a well-formed one. */\nexport function decodeStun(data: Buffer): IStunMessage | undefined {\n if (!isStunMessage(data)) return undefined;\n const type = data.readUInt16BE(0);\n const length = data.readUInt16BE(2);\n if (length % 4 !== 0 || HEADER_BYTES + length > data.length) return undefined;\n const method = (type & 0x000f) | ((type & 0x00e0) >> 1) | ((type & 0x3e00) >> 2);\n const cls = (((type & 0x0010) >> 4) | ((type & 0x0100) >> 7)) as TStunClass;\n const attributes: IStunAttribute[] = [];\n let offset = HEADER_BYTES;\n const end = HEADER_BYTES + length;\n while (offset < end) {\n if (offset + 4 > end) return undefined;\n const attrType = data.readUInt16BE(offset);\n const attrLength = data.readUInt16BE(offset + 2);\n if (offset + 4 + attrLength > end) return undefined;\n attributes.push({\n type: attrType,\n value: data.subarray(offset + 4, offset + 4 + attrLength),\n offset,\n });\n offset += 4 + attrLength + ((4 - (attrLength % 4)) % 4);\n }\n if (offset !== end) return undefined;\n return {\n method,\n cls,\n transactionId: Buffer.from(data.subarray(8, 20)),\n attributes,\n raw: data.subarray(0, end),\n };\n}\n\n/** The first attribute of `type`, if any. */\nexport function attribute(message: IStunMessage, type: number): Buffer | undefined {\n return message.attributes.find((a) => a.type === type)?.value;\n}\n\nexport function attributes(message: IStunMessage, type: number): Buffer[] {\n return message.attributes.filter((a) => a.type === type).map((a) => a.value);\n}\n\n/**\n * The message as MESSAGE-INTEGRITY covers it: attributes after it are dropped (RFC 8489 §14.5), since\n * anyone on the path can append them. A message without one is returned as it is.\n */\nexport function integrityProtected(message: IStunMessage): IStunMessage {\n const at = message.attributes.findIndex((a) => a.type === StunAttr.MessageIntegrity);\n if (at < 0) return message;\n return { ...message, attributes: message.attributes.slice(0, at + 1) };\n}\n\n/** Decode an XOR-*-ADDRESS value; `undefined` when malformed. */\nexport function decodeXorAddress(\n value: Buffer,\n transactionId: Buffer,\n): ITransportAddress | undefined {\n if (value.length < 8) return undefined;\n const family = value[1];\n const port = value.readUInt16BE(2) ^ (MAGIC_COOKIE >>> 16);\n if (family === 0x01 && value.length === 8) {\n const raw = value.readUInt32BE(4) ^ MAGIC_COOKIE;\n const address = [raw >>> 24, (raw >>> 16) & 0xff, (raw >>> 8) & 0xff, raw & 0xff].join('.');\n return { address, port };\n }\n if (family === 0x02 && value.length === 20) {\n const mask = Buffer.alloc(16);\n mask.writeUInt32BE(MAGIC_COOKIE, 0);\n transactionId.copy(mask, 4);\n const groups: string[] = [];\n for (let i = 0; i < 16; i += 2) {\n groups.push(((value.readUInt16BE(4 + i) ^ mask.readUInt16BE(i)) & 0xffff).toString(16));\n }\n return { address: groups.join(':'), port };\n }\n return undefined;\n}\n\nfunction ipv6Bytes(address: string): Buffer {\n const [head = '', tail = ''] = address.split('::') as [string, string?];\n const left = head.length > 0 ? head.split(':') : [];\n const right = address.includes('::') && tail.length > 0 ? tail.split(':') : [];\n const groups = address.includes('::')\n ? [...left, ...Array<string>(8 - left.length - right.length).fill('0'), ...right]\n : left;\n const out = Buffer.alloc(16);\n groups.forEach((group, i) => out.writeUInt16BE(parseInt(group, 16) & 0xffff, i * 2));\n return out;\n}\n\n/** Encode an address as an XOR-*-ADDRESS value. */\nexport function encodeXorAddress(address: ITransportAddress, transactionId: Buffer): Buffer {\n const port = (address.port ^ (MAGIC_COOKIE >>> 16)) & 0xffff;\n if (isIPv4(address.address)) {\n const out = Buffer.alloc(8);\n out[1] = 0x01;\n out.writeUInt16BE(port, 2);\n const raw = address.address.split('.').reduce((acc, part) => (acc << 8) | Number(part), 0);\n out.writeUInt32BE((raw ^ MAGIC_COOKIE) >>> 0, 4);\n return out;\n }\n if (!isIPv6(address.address)) throw new Error(`not an IP address: ${address.address}`);\n const out = Buffer.alloc(20);\n out[1] = 0x02;\n out.writeUInt16BE(port, 2);\n const mask = Buffer.alloc(16);\n mask.writeUInt32BE(MAGIC_COOKIE, 0);\n transactionId.copy(mask, 4);\n const bytes = ipv6Bytes(address.address);\n for (let i = 0; i < 16; i += 1) out[4 + i] = bytes[i]! ^ mask[i]!;\n return out;\n}\n\n/** The long-term credential key (RFC 8489 §9.2.2): MD5(username \":\" realm \":\" password). */\nexport function longTermKey(username: string, realm: string, password: string): Buffer {\n return createHash('md5').update(`${username}:${realm}:${password}`, 'utf8').digest();\n}\n\n/**\n * Whether the message carries a MESSAGE-INTEGRITY that `key` produced. Only the attributes before it\n * count, with the header length adjusted as the sender computed it.\n */\nexport function verifyIntegrity(message: IStunMessage, key: Buffer): boolean {\n const at = message.attributes.find((a) => a.type === StunAttr.MessageIntegrity);\n if (at === undefined || at.value.length !== 20) return false;\n const covered = Buffer.from(message.raw.subarray(0, at.offset));\n covered.writeUInt16BE(at.offset - HEADER_BYTES + 24, 2);\n const expected = createHmac('sha1', key).update(covered).digest();\n return timingSafeEqual(expected, at.value);\n}\n\nconst CRC_TABLE = (() => {\n const table = new Uint32Array(256);\n for (let n = 0; n < 256; n += 1) {\n let c = n;\n for (let k = 0; k < 8; k += 1) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n table[n] = c >>> 0;\n }\n return table;\n})();\n\nfunction crc32(data: Buffer): number {\n let crc = 0xffffffff;\n for (const byte of data) crc = CRC_TABLE[(crc ^ byte) & 0xff]! ^ (crc >>> 8);\n return (crc ^ 0xffffffff) >>> 0;\n}\n\nexport interface IStunEncodeOptions {\n /** Append MESSAGE-INTEGRITY under this long-term key. */\n readonly integrityKey?: Buffer;\n /** Append FINGERPRINT (default true). */\n readonly fingerprint?: boolean;\n}\n\n/** Encode a STUN message. */\nexport function encodeStun(\n method: number,\n cls: TStunClass,\n transactionId: Buffer,\n attrs: readonly { readonly type: number; readonly value: Buffer }[],\n options: IStunEncodeOptions = {},\n): Buffer {\n const parts: Buffer[] = [];\n const header = Buffer.alloc(HEADER_BYTES);\n header.writeUInt16BE(messageType(method, cls), 0);\n header.writeUInt32BE(MAGIC_COOKIE, 4);\n transactionId.copy(header, 8, 0, 12);\n parts.push(header);\n const push = (type: number, value: Buffer): void => {\n const head = Buffer.alloc(4);\n head.writeUInt16BE(type, 0);\n head.writeUInt16BE(value.length, 2);\n parts.push(head, value, Buffer.alloc((4 - (value.length % 4)) % 4));\n };\n for (const a of attrs) push(a.type, a.value);\n const current = (): Buffer => Buffer.concat(parts);\n if (options.integrityKey !== undefined) {\n const covered = current();\n covered.writeUInt16BE(covered.length - HEADER_BYTES + 24, 2);\n push(\n StunAttr.MessageIntegrity,\n createHmac('sha1', options.integrityKey).update(covered).digest(),\n );\n }\n if (options.fingerprint !== false) {\n const covered = current();\n covered.writeUInt16BE(covered.length - HEADER_BYTES + 8, 2);\n const value = Buffer.alloc(4);\n value.writeUInt32BE((crc32(covered) ^ 0x5354554e) >>> 0, 0);\n push(StunAttr.Fingerprint, value);\n }\n const out = current();\n out.writeUInt16BE(out.length - HEADER_BYTES, 2);\n if (out.length > MAX_MESSAGE_BYTES) throw new Error('STUN message too large');\n return out;\n}\n\n/** An ERROR-CODE value. */\nexport function errorCode(code: number, reason: string): Buffer {\n const text = Buffer.from(reason, 'utf8');\n const out = Buffer.alloc(4 + text.length);\n out[2] = Math.floor(code / 100);\n out[3] = code % 100;\n text.copy(out, 4);\n return out;\n}\n\n/** The code of an ERROR-CODE value; `undefined` when malformed. */\nexport function readErrorCode(value: Buffer | undefined): number | undefined {\n if (value === undefined || value.length < 4) return undefined;\n return (value[2]! & 0x07) * 100 + value[3]!;\n}\n\nexport function uint32(value: number): Buffer {\n const out = Buffer.alloc(4);\n out.writeUInt32BE(value >>> 0, 0);\n return out;\n}\n\n/** Whether the first bytes are a ChannelData message (the two top bits `01`). */\nexport function isChannelData(data: Buffer): boolean {\n return data.length >= 4 && (data[0]! & 0xc0) === 0x40;\n}\n\n/** Channel numbers a client may bind (RFC 8656 §12). */\nexport const MIN_CHANNEL = 0x4000;\nexport const MAX_CHANNEL = 0x4fff;\n\nexport function decodeChannelData(\n data: Buffer,\n): { readonly channel: number; readonly payload: Buffer } | undefined {\n if (!isChannelData(data)) return undefined;\n const channel = data.readUInt16BE(0);\n const length = data.readUInt16BE(2);\n if (4 + length > data.length) return undefined;\n return { channel, payload: data.subarray(4, 4 + length) };\n}\n\nexport function encodeChannelData(channel: number, payload: Buffer): Buffer {\n const header = Buffer.alloc(4);\n header.writeUInt16BE(channel, 0);\n header.writeUInt16BE(payload.length, 2);\n return Buffer.concat([header, payload, Buffer.alloc((4 - (payload.length % 4)) % 4)]);\n}\n","/**\n * A TURN server (RFC 8656) over UDP: the subset a WebRTC client needs to reach a peer through a\n * relayed address — Allocate, Refresh, CreatePermission, ChannelBind, Send and Data indications,\n * ChannelData — with the long-term credential mechanism (RFC 8489 §9.2).\n *\n * It relays datagrams and nothing else: what crosses it is the peers' own DTLS, which it cannot\n * open. Who may allocate is the caller's answer ({@link ITurnServerOptions.authorize}), asked once\n * per allocation; the key it yields then authenticates every later request on that allocation, so\n * an allocation outlives its credential's expiry but not its owner's removal ({@link TurnServer.retain}).\n * Quotas bound what one owner can take: allocations, relayed bytes per second, and how long an\n * allocation may live.\n */\nimport { createHmac, randomBytes, randomInt, timingSafeEqual } from 'node:crypto';\nimport { createSocket, type RemoteInfo, type Socket } from 'node:dgram';\nimport { isIPv4 } from 'node:net';\nimport { networkInterfaces } from 'node:os';\n\nimport {\n StunAttr,\n StunClass,\n StunMethod,\n attribute,\n attributes,\n decodeChannelData,\n decodeStun,\n decodeXorAddress,\n encodeChannelData,\n encodeStun,\n encodeXorAddress,\n errorCode,\n integrityProtected,\n isChannelData,\n longTermKey,\n verifyIntegrity,\n uint32,\n MAX_CHANNEL,\n MIN_CHANNEL,\n type IStunMessage,\n type ITransportAddress,\n type TStunClass,\n} from './stun-message.js';\n\n/** Who a username belongs to, and the password it was issued with. */\nexport interface ITurnAuthorization {\n /** The owner quotas are counted against and {@link TurnServer.retain} names. */\n readonly owner: string;\n readonly password: string;\n}\n\nexport interface ITurnQuotas {\n /** Allocations one owner may hold at once. */\n readonly allocationsPerOwner: number;\n /** Allocations the server holds at once. */\n readonly totalAllocations: number;\n /** Bytes relayed per second for one owner, both directions together; more is dropped. */\n readonly bytesPerSecondPerOwner: number;\n /** The longest lifetime one Allocate or Refresh grants. */\n readonly maxLifetimeSeconds: number;\n /** How long an allocation may live however often it is refreshed. */\n readonly maxAllocationAgeMs: number;\n readonly permissionsPerAllocation: number;\n readonly channelsPerAllocation: number;\n}\n\nexport const DEFAULT_TURN_QUOTAS: ITurnQuotas = {\n allocationsPerOwner: 8,\n totalAllocations: 64,\n bytesPerSecondPerOwner: 1024 * 1024,\n maxLifetimeSeconds: 3600,\n maxAllocationAgeMs: 12 * 60 * 60 * 1000,\n permissionsPerAllocation: 32,\n channelsPerAllocation: 32,\n};\n\n/**\n * Answers to requests nobody has authenticated, per second. Such a request may carry a forged\n * source address, so what it is answered with must not be worth sending it for.\n */\nexport interface IUnauthenticatedLimits {\n readonly perSourcePerSecond: number;\n readonly totalPerSecond: number;\n}\n\nexport const DEFAULT_UNAUTHENTICATED_LIMITS: IUnauthenticatedLimits = {\n perSourcePerSecond: 10,\n // High: the per-source limit and the size rule already take away any gain from a forged source,\n // and a low total would let anyone crowd out genuine clients' challenges.\n totalPerSecond: 2_000,\n};\n\nexport interface ITurnServerOptions {\n /** The address the server and its relayed sockets bind (default every IPv4 interface). */\n readonly host?: string;\n /** The server's UDP port (default 3478; 0 picks a free one). */\n readonly port?: number;\n /**\n * The ports relayed sockets take, inclusive (default: any free port). A relay behind a NAT needs\n * this range forwarded to it as well as its own port.\n */\n readonly relayPorts?: { readonly min: number; readonly max: number };\n /** The address relayed transport addresses carry (default `host`, or this machine's first external IPv4). */\n readonly relayAddress?: string;\n /**\n * Default `r`: a realm travels in the clear, so it names nothing. At most 4 bytes: a challenge is\n * never larger than the request it answers, and a longer realm would not fit the first request of\n * the mesh's WebRTC stack.\n */\n readonly realm?: string;\n /** Who `username` belongs to and its password; `undefined` refuses it. Asked once per allocation. */\n readonly authorize: (username: string) => Promise<ITurnAuthorization | undefined>;\n readonly quotas?: Partial<ITurnQuotas>;\n /** How many requests nobody authenticated are answered, per source address and in all. */\n readonly unauthenticatedLimits?: Partial<IUnauthenticatedLimits>;\n /**\n * Whether the default peer rule lets data reach private, link-local and shared (carrier-grade\n * NAT) ranges: the relay host's own network (default true — a relayed connection to a device on\n * that network needs it).\n */\n readonly allowPrivatePeers?: boolean;\n /**\n * Whether data may be relayed to or from `address`. Default: not unspecified, multicast or\n * broadcast addresses, not loopback unless the relay itself is on loopback, and not private\n * ranges when `allowPrivatePeers` is false.\n */\n readonly allowPeer?: (address: string) => boolean;\n readonly now?: () => number;\n /** A socket failed; the server keeps running. */\n readonly onError?: (error: Error) => void;\n}\n\nconst DEFAULT_LIFETIME_SECONDS = 600;\nconst PERMISSION_LIFETIME_MS = 300_000;\nconst CHANNEL_LIFETIME_MS = 600_000;\nconst NONCE_LIFETIME_MS = 600_000;\nconst SWEEP_INTERVAL_MS = 5_000;\nconst UDP = 17;\nconst MAX_REALM_BYTES = 4;\n/** The smallest unauthenticated answer: a header and an ERROR-CODE with no reason phrase. */\nconst MIN_ANSWER_BYTES = 28;\n/** Random ports of the relayed range tried before the range is walked in order. */\nconst RELAY_PORT_TRIES = 8;\n\n/** Attributes a request may carry that this server understands or may ignore by RFC. */\nconst UNDERSTOOD = new Set<number>([\n StunAttr.MappedAddress,\n StunAttr.Username,\n StunAttr.MessageIntegrity,\n StunAttr.ErrorCode,\n StunAttr.ChannelNumber,\n StunAttr.Lifetime,\n StunAttr.XorPeerAddress,\n StunAttr.Data,\n StunAttr.Realm,\n StunAttr.Nonce,\n StunAttr.RequestedAddressFamily,\n StunAttr.RequestedTransport,\n StunAttr.MessageIntegritySha256,\n StunAttr.PasswordAlgorithm,\n StunAttr.XorMappedAddress,\n StunAttr.Priority,\n StunAttr.UseCandidate,\n]);\n\ninterface IChannel {\n readonly number: number;\n readonly peer: ITransportAddress;\n expiresAt: number;\n}\n\ninterface IAllocation {\n readonly key: string;\n readonly client: ITransportAddress;\n readonly owner: string;\n readonly username: string;\n readonly integrityKey: Buffer;\n readonly transactionId: Buffer;\n readonly socket: Socket;\n readonly relayed: ITransportAddress;\n readonly createdAt: number;\n expiresAt: number;\n /** Peer IP → expiry. */\n readonly permissions: Map<string, number>;\n readonly channelsByNumber: Map<number, IChannel>;\n readonly channelsByPeer: Map<string, IChannel>;\n response: Buffer;\n}\n\ninterface IBucket {\n tokens: number;\n at: number;\n}\n\nfunction clientKey(rinfo: RemoteInfo | ITransportAddress): string {\n return `${rinfo.address}:${rinfo.port}`;\n}\n\nfunction firstExternalIpv4(): string {\n for (const list of Object.values(networkInterfaces())) {\n for (const entry of list ?? []) {\n if (entry.family === 'IPv4' && !entry.internal) return entry.address;\n }\n }\n return '127.0.0.1';\n}\n\nfunction isLoopback(address: string): boolean {\n return address.startsWith('127.') || address === '::1';\n}\n\n/** 10/8, 172.16/12, 192.168/16, 169.254/16 (link-local) and 100.64/10 (shared, carrier-grade NAT). */\nfunction isPrivateIpv4(address: string): boolean {\n const [a = 0, b = 0] = address.split('.').map(Number);\n return (\n a === 10 ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 169 && b === 254) ||\n (a === 100 && b >= 64 && b <= 127)\n );\n}\n\nfunction defaultAllowPeer(\n relayOnLoopback: boolean,\n allowPrivate: boolean,\n): (address: string) => boolean {\n return (address) => {\n if (!isIPv4(address)) return false;\n const first = Number(address.split('.')[0]);\n if (first === 0 || first >= 224) return false; // unspecified, multicast, reserved, broadcast\n if (first === 127) return relayOnLoopback;\n return allowPrivate || !isPrivateIpv4(address);\n };\n}\n\n/** A UDP socket bound to `port` on `host`; rejects with the bind error. */\nasync function bindUdp(port: number, host: string): Promise<Socket> {\n const socket = createSocket('udp4');\n try {\n await new Promise<void>((resolve, reject) => {\n socket.once('error', reject);\n socket.bind(port, host, () => {\n socket.off('error', reject);\n resolve();\n });\n });\n return socket;\n } catch (error) {\n try {\n socket.close();\n } catch {\n /* never bound */\n }\n throw error;\n }\n}\n\n/** A UDP socket bound to `port` on `host`; `undefined` when it cannot be. */\nasync function tryBindUdp(port: number, host: string): Promise<Socket | undefined> {\n try {\n return await bindUdp(port, host);\n } catch {\n // allow-fallback: a relayed port that cannot be bound; the caller tries another or refuses the request\n return undefined;\n }\n}\n\n/** Why the server's own socket could not be bound, in words that say what to change. */\nfunction bindFailure(error: unknown, host: string, port: number): Error {\n const code = error instanceof Error ? (error as NodeJS.ErrnoException).code : undefined;\n const why =\n code === 'EADDRINUSE'\n ? 'the port is taken'\n : code === 'EADDRNOTAVAIL'\n ? `${host} is not an address of this machine`\n : code === 'EACCES'\n ? `port ${port} needs privileges this process does not have`\n : asError(error).message;\n return new Error(`the TURN relay cannot listen on UDP ${host}:${port}: ${why}`, {\n cause: error,\n });\n}\n\nfunction localAddresses(): Set<string> {\n const out = new Set<string>();\n for (const list of Object.values(networkInterfaces())) {\n for (const entry of list ?? []) out.add(entry.address);\n }\n return out;\n}\n\n/**\n * Refill `bucket` at `rate` per second up to one second's worth (at least `need`), and say whether it\n * now holds `need`. Time that runs backwards (a clock step) refills nothing and takes nothing, so a\n * step cannot drain a budget for as long as the step.\n */\nfunction refill(bucket: IBucket, rate: number, now: number, need = 1): boolean {\n const elapsed = Math.max(0, now - bucket.at);\n bucket.tokens = Math.min(Math.max(rate, need), bucket.tokens + (elapsed / 1000) * rate);\n bucket.at = now;\n return bucket.tokens >= need;\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport class TurnServer {\n private readonly allocations = new Map<string, IAllocation>();\n private readonly buckets = new Map<string, IBucket>();\n private readonly quotas: ITurnQuotas;\n private readonly realm: string;\n private readonly limits: IUnauthenticatedLimits;\n /** Answers to requests nobody has authenticated yet, per source address and in all. */\n private readonly answerBuckets = new Map<string, IBucket>();\n private answerBudget?: IBucket;\n private readonly nonceSecret = randomBytes(32);\n private readonly ownAddresses = localAddresses();\n private readonly allowPeer: (address: string) => boolean;\n private readonly sweeper: ReturnType<typeof setInterval>;\n /** Once {@link TurnServer.retain} has named them: the only owners that may allocate. */\n private retained?: ReadonlySet<string>;\n private closed = false;\n\n private constructor(\n private readonly socket: Socket,\n private readonly options: ITurnServerOptions,\n private readonly bindHost: string,\n private readonly relayAddress: string,\n ) {\n this.quotas = { ...DEFAULT_TURN_QUOTAS, ...options.quotas };\n this.realm = options.realm ?? 'r';\n this.limits = { ...DEFAULT_UNAUTHENTICATED_LIMITS, ...options.unauthenticatedLimits };\n this.allowPeer =\n options.allowPeer ??\n defaultAllowPeer(isLoopback(relayAddress), options.allowPrivatePeers ?? true);\n socket.on('message', (data, rinfo) => this.receive(data, rinfo));\n socket.on('error', (error) => options.onError?.(error));\n this.sweeper = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);\n this.sweeper.unref?.();\n }\n\n /** Bind the server's socket and start serving. */\n public static async start(options: ITurnServerOptions): Promise<TurnServer> {\n const realmBytes = Buffer.byteLength(options.realm ?? 'r');\n if (realmBytes === 0 || realmBytes > MAX_REALM_BYTES) {\n throw new Error(`TURN realm must be 1 to ${MAX_REALM_BYTES} bytes`);\n }\n const host = options.host ?? '0.0.0.0';\n const port = options.port ?? 3478;\n let socket: Socket;\n try {\n socket = await bindUdp(port, host);\n } catch (error) {\n throw bindFailure(error, host, port);\n }\n const relayAddress = options.relayAddress ?? (host === '0.0.0.0' ? firstExternalIpv4() : host);\n return new TurnServer(socket, options, host, relayAddress);\n }\n\n /** Where clients reach the server. */\n public get address(): ITransportAddress {\n const bound = this.socket.address();\n return {\n address: this.bindHost === '0.0.0.0' ? this.relayAddress : bound.address,\n port: bound.port,\n };\n }\n\n /** Allocations held, by one owner or in all. */\n public allocationCount(owner?: string): number {\n if (owner === undefined) return this.allocations.size;\n let count = 0;\n for (const allocation of this.allocations.values()) if (allocation.owner === owner) count += 1;\n return count;\n }\n\n /**\n * Close every allocation whose owner is not one of `owners`, and allocate for these owners only\n * from now on — also for a request whose authorization was answered before this call.\n */\n public retain(owners: ReadonlySet<string>): void {\n this.retained = new Set(owners);\n for (const allocation of [...this.allocations.values()]) {\n if (!owners.has(allocation.owner)) this.remove(allocation);\n }\n for (const owner of [...this.buckets.keys()])\n if (!owners.has(owner)) this.buckets.delete(owner);\n }\n\n public close(): Promise<void> {\n if (this.closed) return Promise.resolve();\n this.closed = true;\n clearInterval(this.sweeper);\n for (const allocation of [...this.allocations.values()]) this.remove(allocation);\n return new Promise((resolve) => this.socket.close(() => resolve()));\n }\n\n private now(): number {\n return (this.options.now ?? Date.now)();\n }\n\n private receive(data: Buffer, rinfo: RemoteInfo): void {\n if (this.closed) return;\n if (isChannelData(data)) {\n this.fromClientChannel(data, rinfo);\n return;\n }\n const message = decodeStun(data);\n if (message === undefined) return;\n if (message.cls === StunClass.Indication) {\n if (message.method === StunMethod.Send) this.fromClientSend(message, rinfo);\n return;\n }\n if (message.cls !== StunClass.Request) return;\n this.request(message, rinfo).catch((error: unknown) => this.options.onError?.(asError(error)));\n }\n\n private send(data: Buffer, to: ITransportAddress): void {\n if (this.closed) return;\n this.socket.send(data, to.port, to.address, (error) => {\n if (error) this.options.onError?.(error);\n });\n }\n\n private reply(\n request: IStunMessage,\n to: ITransportAddress,\n attrs: { type: number; value: Buffer }[],\n key?: Buffer,\n ): Buffer {\n const response = encodeStun(request.method, StunClass.Success, request.transactionId, attrs, {\n ...(key !== undefined ? { integrityKey: key } : {}),\n });\n this.send(response, to);\n return response;\n }\n\n private fail(\n request: IStunMessage,\n to: ITransportAddress,\n code: number,\n reason: string,\n extra: { type: number; value: Buffer }[] = [],\n key?: Buffer,\n ): void {\n const response = encodeStun(\n request.method,\n StunClass.Error,\n request.transactionId,\n [{ type: StunAttr.ErrorCode, value: errorCode(code, reason) }, ...extra],\n { ...(key !== undefined ? { integrityKey: key } : {}) },\n );\n this.send(response, to);\n }\n\n /** A nonce: when it was issued and a MAC binding that to the client's address, 16 characters. */\n private nonce(client: ITransportAddress): string {\n const issued = Buffer.alloc(4);\n issued.writeUInt32BE(Math.floor(this.now() / 1000) >>> 0, 0);\n return Buffer.concat([issued, this.nonceMac(issued, client)]).toString('base64url');\n }\n\n private nonceMac(issued: Buffer, client: ITransportAddress): Buffer {\n return createHmac('sha256', this.nonceSecret)\n .update(issued)\n .update(clientKey(client))\n .digest()\n .subarray(0, 8);\n }\n\n private nonceValid(nonce: string, client: ITransportAddress): boolean {\n if (!/^[A-Za-z0-9_-]{16}$/.test(nonce)) return false;\n const bytes = Buffer.from(nonce, 'base64url');\n const issued = bytes.subarray(0, 4);\n if (!timingSafeEqual(bytes.subarray(4), this.nonceMac(issued, client))) return false;\n const age = this.now() - issued.readUInt32BE(0) * 1000;\n return age >= -60_000 && age <= NONCE_LIFETIME_MS;\n }\n\n /**\n * Answer a request nobody has authenticated. Its source may be forged, so the answer is never\n * larger than the request (no reflection gain), and answers are limited per source address and in\n * all; what is over is dropped unanswered.\n */\n private answerUnauthenticated(\n request: IStunMessage,\n to: ITransportAddress,\n cls: TStunClass,\n attrs: () => { type: number; value: Buffer }[],\n ): void {\n const now = this.now();\n const total = (this.answerBudget ??= { tokens: this.limits.totalPerSecond, at: now });\n const source = this.answerBuckets.get(to.address) ?? {\n tokens: this.limits.perSourcePerSecond,\n at: now,\n };\n this.answerBuckets.set(to.address, source);\n // No answer is smaller than this: a request below it is dropped before any work.\n if (request.raw.length < MIN_ANSWER_BYTES) return;\n // Both budgets are checked before anything is computed, and charged only for an answer sent.\n if (!refill(source, this.limits.perSourcePerSecond, now)) return;\n if (!refill(total, this.limits.totalPerSecond, now)) return;\n const response = encodeStun(request.method, cls, request.transactionId, attrs(), {\n fingerprint: false,\n });\n if (response.length > request.raw.length) return;\n source.tokens -= 1;\n total.tokens -= 1;\n this.send(response, to);\n }\n\n /** An error answer to an unauthenticated request; the reason phrase is left empty to keep it small. */\n private refuseUnauthenticated(\n request: IStunMessage,\n to: ITransportAddress,\n code: number,\n extra: () => { type: number; value: Buffer }[] = () => [],\n ): void {\n this.answerUnauthenticated(request, to, StunClass.Error, () => [\n { type: StunAttr.ErrorCode, value: errorCode(code, '') },\n ...extra(),\n ]);\n }\n\n private challenge(request: IStunMessage, to: ITransportAddress, code: 401 | 438): void {\n this.refuseUnauthenticated(request, to, code, () => [\n { type: StunAttr.Realm, value: Buffer.from(this.realm) },\n { type: StunAttr.Nonce, value: Buffer.from(this.nonce(to)) },\n ]);\n }\n\n /**\n * Authenticate a request. On an existing allocation only its own username and key count;\n * otherwise the caller's authorization is asked. `undefined`: already answered with an error.\n */\n private async authenticate(\n request: IStunMessage,\n client: ITransportAddress,\n allocation: IAllocation | undefined,\n ): Promise<\n | { readonly auth: ITurnAuthorization; readonly key: Buffer; readonly username: string }\n | undefined\n > {\n const integrity = attribute(request, StunAttr.MessageIntegrity);\n if (integrity === undefined) {\n this.challenge(request, client, 401);\n return undefined;\n }\n const username = attribute(request, StunAttr.Username)?.toString('utf8');\n const realm = attribute(request, StunAttr.Realm)?.toString('utf8');\n const nonce = attribute(request, StunAttr.Nonce)?.toString('utf8');\n if (username === undefined || realm === undefined || nonce === undefined) {\n this.refuseUnauthenticated(request, client, 400);\n return undefined;\n }\n if (realm !== this.realm) {\n this.challenge(request, client, 401);\n return undefined;\n }\n if (!this.nonceValid(nonce, client)) {\n this.challenge(request, client, 438);\n return undefined;\n }\n if (allocation !== undefined) {\n if (username !== allocation.username) {\n this.refuseUnauthenticated(request, client, 441);\n return undefined;\n }\n if (!verifyIntegrity(request, allocation.integrityKey)) {\n this.challenge(request, client, 401);\n return undefined;\n }\n return {\n auth: { owner: allocation.owner, password: '' },\n key: allocation.integrityKey,\n username,\n };\n }\n let auth: ITurnAuthorization | undefined;\n try {\n auth = await this.options.authorize(username);\n } catch {\n // allow-fallback: an authorization that cannot be answered refuses, like an unknown username\n auth = undefined;\n }\n if (this.closed) return undefined;\n const key = auth === undefined ? undefined : longTermKey(username, this.realm, auth.password);\n if (auth === undefined || key === undefined || !verifyIntegrity(request, key)) {\n this.challenge(request, client, 401);\n return undefined;\n }\n return { auth, key, username };\n }\n\n private unknownAttributes(request: IStunMessage): number[] {\n return request.attributes\n .map((a) => a.type)\n .filter((type) => type < 0x8000 && !UNDERSTOOD.has(type));\n }\n\n private async request(request: IStunMessage, rinfo: RemoteInfo): Promise<void> {\n const client: ITransportAddress = { address: rinfo.address, port: rinfo.port };\n if (request.method === StunMethod.Binding) {\n this.answerUnauthenticated(request, client, StunClass.Success, () => [\n { type: StunAttr.XorMappedAddress, value: encodeXorAddress(client, request.transactionId) },\n ]);\n return;\n }\n const known: number[] = [\n StunMethod.Allocate,\n StunMethod.Refresh,\n StunMethod.CreatePermission,\n StunMethod.ChannelBind,\n ];\n if (!known.includes(request.method)) {\n this.refuseUnauthenticated(request, client, 400);\n return;\n }\n const held = this.allocations.get(clientKey(client));\n const existing = this.live(held) ? held : undefined;\n if (\n request.method === StunMethod.Allocate &&\n existing !== undefined &&\n existing.transactionId.equals(request.transactionId)\n ) {\n // A retransmission of the request that made the allocation; like any unauthenticated answer,\n // never larger than what asked for it.\n if (existing.response.length <= request.raw.length) this.send(existing.response, client);\n return;\n }\n const authenticated = await this.authenticate(request, client, existing);\n if (authenticated === undefined) return;\n // Only what MESSAGE-INTEGRITY covers counts: anyone on the path can append after it.\n await this.authenticatedRequest(integrityProtected(request), client, authenticated);\n }\n\n private async authenticatedRequest(\n request: IStunMessage,\n client: ITransportAddress,\n authenticated: {\n readonly auth: ITurnAuthorization;\n readonly key: Buffer;\n readonly username: string;\n },\n ): Promise<void> {\n const { key } = authenticated;\n const unknown = this.unknownAttributes(request);\n if (unknown.length > 0) {\n const list = Buffer.alloc(unknown.length * 2);\n unknown.forEach((type, i) => list.writeUInt16BE(type, i * 2));\n this.fail(\n request,\n client,\n 420,\n 'Unknown Attribute',\n [{ type: StunAttr.UnknownAttributes, value: list }],\n key,\n );\n return;\n }\n const found = this.allocations.get(clientKey(client));\n const current = this.live(found) ? found : undefined;\n if (request.method === StunMethod.Allocate) {\n await this.allocate(request, client, authenticated, current);\n return;\n }\n if (current === undefined || current.owner !== authenticated.auth.owner) {\n this.fail(request, client, 437, 'Allocation Mismatch', [], key);\n return;\n }\n if (request.method === StunMethod.Refresh) this.refresh(request, client, current);\n else if (request.method === StunMethod.CreatePermission) this.permit(request, client, current);\n else this.bindChannel(request, client, current);\n }\n\n private async allocate(\n request: IStunMessage,\n client: ITransportAddress,\n authenticated: {\n readonly auth: ITurnAuthorization;\n readonly key: Buffer;\n readonly username: string;\n },\n existing: IAllocation | undefined,\n ): Promise<void> {\n const { auth, key, username } = authenticated;\n if (existing !== undefined) {\n this.fail(request, client, 437, 'Allocation Mismatch', [], key);\n return;\n }\n const transport = attribute(request, StunAttr.RequestedTransport);\n if (transport === undefined || transport.length !== 4) {\n this.fail(request, client, 400, 'Bad Request', [], key);\n return;\n }\n if (transport[0] !== UDP) {\n this.fail(request, client, 442, 'Unsupported Transport Protocol', [], key);\n return;\n }\n const family = attribute(request, StunAttr.RequestedAddressFamily);\n if (family !== undefined && family[0] !== 0x01) {\n this.fail(request, client, 440, 'Address Family not Supported', [], key);\n return;\n }\n if (this.retained !== undefined && !this.retained.has(auth.owner)) {\n this.fail(request, client, 403, 'Forbidden', [], key);\n return;\n }\n if (this.allocations.size >= this.quotas.totalAllocations) {\n this.fail(request, client, 508, 'Insufficient Capacity', [], key);\n return;\n }\n if (this.allocationCount(auth.owner) >= this.quotas.allocationsPerOwner) {\n this.fail(request, client, 486, 'Allocation Quota Reached', [], key);\n return;\n }\n const socket = await this.relayedSocket();\n if (socket === undefined) {\n this.fail(request, client, 508, 'Insufficient Capacity', [], key);\n return;\n }\n // Checked again: other allocations may have been made, or the owner removed, while the socket bound.\n if (!this.closed && this.retained !== undefined && !this.retained.has(auth.owner)) {\n socket.close();\n this.fail(request, client, 403, 'Forbidden', [], key);\n return;\n }\n if (\n this.closed ||\n this.allocations.has(clientKey(client)) ||\n this.allocationCount(auth.owner) >= this.quotas.allocationsPerOwner ||\n this.allocations.size >= this.quotas.totalAllocations\n ) {\n socket.close();\n if (!this.closed) this.fail(request, client, 486, 'Allocation Quota Reached', [], key);\n return;\n }\n const now = this.now();\n const lifetime = this.grantedLifetime(request, now, now);\n const allocation: IAllocation = {\n key: clientKey(client),\n client,\n owner: auth.owner,\n username,\n integrityKey: key,\n transactionId: Buffer.from(request.transactionId),\n socket,\n relayed: { address: this.relayAddress, port: socket.address().port },\n createdAt: now,\n expiresAt: now + lifetime * 1000,\n permissions: new Map(),\n channelsByNumber: new Map(),\n channelsByPeer: new Map(),\n response: Buffer.alloc(0),\n };\n socket.on('message', (data, rinfo) => this.fromPeer(allocation, data, rinfo));\n socket.on('error', (error) => this.options.onError?.(error));\n this.allocations.set(allocation.key, allocation);\n allocation.response = this.reply(\n request,\n client,\n [\n {\n type: StunAttr.XorRelayedAddress,\n value: encodeXorAddress(allocation.relayed, request.transactionId),\n },\n { type: StunAttr.Lifetime, value: uint32(lifetime) },\n { type: StunAttr.XorMappedAddress, value: encodeXorAddress(client, request.transactionId) },\n ],\n key,\n );\n }\n\n /** A socket for a relayed address: any free port, or a free one of the configured range. */\n private async relayedSocket(): Promise<Socket | undefined> {\n const range = this.options.relayPorts;\n if (range === undefined) return tryBindUdp(0, this.bindHost);\n const size = range.max - range.min + 1;\n for (let i = 0; i < Math.min(size, RELAY_PORT_TRIES); i += 1) {\n const socket = await tryBindUdp(randomInt(range.min, range.max + 1), this.bindHost);\n if (socket !== undefined) return socket;\n }\n // Random picks can miss the last free ports of a small range: take them in order.\n const taken = new Set(this.allocationPorts());\n for (let port = range.min; port <= range.max; port += 1) {\n if (taken.has(port)) continue;\n const socket = await tryBindUdp(port, this.bindHost);\n if (socket !== undefined) return socket;\n }\n return undefined;\n }\n\n private allocationPorts(): number[] {\n return [...this.allocations.values()].map((allocation) => allocation.relayed.port);\n }\n\n /** Whether `peer` is this server's own listening socket: relaying there would loop TURN into itself. */\n private isServerItself(peer: ITransportAddress): boolean {\n return (\n peer.port === this.socket.address().port &&\n (isLoopback(peer.address) ||\n peer.address === this.relayAddress ||\n peer.address === this.bindHost ||\n (this.bindHost === '0.0.0.0' && this.ownAddresses.has(peer.address)))\n );\n }\n\n /** The lifetime granted: what was asked, within the quota and the allocation's remaining age. */\n private grantedLifetime(request: IStunMessage, createdAt: number, now: number): number {\n const asked = attribute(request, StunAttr.Lifetime);\n const requested =\n asked !== undefined && asked.length === 4 ? asked.readUInt32BE(0) : DEFAULT_LIFETIME_SECONDS;\n const remaining = Math.floor((createdAt + this.quotas.maxAllocationAgeMs - now) / 1000);\n return Math.max(0, Math.min(requested, this.quotas.maxLifetimeSeconds, remaining));\n }\n\n private refresh(request: IStunMessage, client: ITransportAddress, allocation: IAllocation): void {\n const now = this.now();\n const lifetime = this.grantedLifetime(request, allocation.createdAt, now);\n if (lifetime === 0) {\n this.remove(allocation);\n this.reply(\n request,\n client,\n [{ type: StunAttr.Lifetime, value: uint32(0) }],\n allocation.integrityKey,\n );\n return;\n }\n allocation.expiresAt = now + lifetime * 1000;\n this.reply(\n request,\n client,\n [{ type: StunAttr.Lifetime, value: uint32(lifetime) }],\n allocation.integrityKey,\n );\n }\n\n private peerAddresses(request: IStunMessage): ITransportAddress[] | undefined {\n const out: ITransportAddress[] = [];\n for (const value of attributes(request, StunAttr.XorPeerAddress)) {\n const peer = decodeXorAddress(value, request.transactionId);\n if (peer === undefined) return undefined;\n out.push(peer);\n }\n return out.length > 0 ? out : undefined;\n }\n\n private permit(request: IStunMessage, client: ITransportAddress, allocation: IAllocation): void {\n const key = allocation.integrityKey;\n const peers = this.peerAddresses(request);\n if (peers === undefined) {\n this.fail(request, client, 400, 'Bad Request', [], key);\n return;\n }\n if (peers.some((peer) => !isIPv4(peer.address))) {\n this.fail(request, client, 443, 'Peer Address Family Mismatch', [], key);\n return;\n }\n if (peers.some((peer) => !this.allowPeer(peer.address))) {\n this.fail(request, client, 403, 'Forbidden', [], key);\n return;\n }\n const added = new Set(\n peers.map((p) => p.address).filter((a) => !allocation.permissions.has(a)),\n );\n if (allocation.permissions.size + added.size > this.quotas.permissionsPerAllocation) {\n this.fail(request, client, 508, 'Insufficient Capacity', [], key);\n return;\n }\n const expiresAt = this.now() + PERMISSION_LIFETIME_MS;\n for (const peer of peers) allocation.permissions.set(peer.address, expiresAt);\n this.reply(request, client, [], key);\n }\n\n private bindChannel(\n request: IStunMessage,\n client: ITransportAddress,\n allocation: IAllocation,\n ): void {\n const key = allocation.integrityKey;\n const numberValue = attribute(request, StunAttr.ChannelNumber);\n const peers = this.peerAddresses(request);\n const number =\n numberValue !== undefined && numberValue.length === 4 ? numberValue.readUInt16BE(0) : -1;\n if (number < MIN_CHANNEL || number > MAX_CHANNEL || peers === undefined || peers.length !== 1) {\n this.fail(request, client, 400, 'Bad Request', [], key);\n return;\n }\n const peer = peers[0]!;\n if (!isIPv4(peer.address)) {\n this.fail(request, client, 443, 'Peer Address Family Mismatch', [], key);\n return;\n }\n if (!this.allowPeer(peer.address)) {\n this.fail(request, client, 403, 'Forbidden', [], key);\n return;\n }\n const byNumber = allocation.channelsByNumber.get(number);\n const byPeer = allocation.channelsByPeer.get(clientKey(peer));\n if (\n (byNumber !== undefined && clientKey(byNumber.peer) !== clientKey(peer)) ||\n (byPeer !== undefined && byPeer.number !== number)\n ) {\n this.fail(request, client, 400, 'Bad Request', [], key);\n return;\n }\n const isNew = byNumber === undefined;\n if (\n (isNew && allocation.channelsByNumber.size >= this.quotas.channelsPerAllocation) ||\n (!allocation.permissions.has(peer.address) &&\n allocation.permissions.size >= this.quotas.permissionsPerAllocation)\n ) {\n this.fail(request, client, 508, 'Insufficient Capacity', [], key);\n return;\n }\n const now = this.now();\n const channel: IChannel = byNumber ?? { number, peer, expiresAt: 0 };\n channel.expiresAt = now + CHANNEL_LIFETIME_MS;\n allocation.channelsByNumber.set(number, channel);\n allocation.channelsByPeer.set(clientKey(peer), channel);\n allocation.permissions.set(peer.address, now + PERMISSION_LIFETIME_MS);\n this.reply(request, client, [], key);\n }\n\n private permitted(allocation: IAllocation, address: string): boolean {\n const expiresAt = allocation.permissions.get(address);\n return expiresAt !== undefined && expiresAt > this.now();\n }\n\n /** Take `bytes` from the owner's budget for this second; false when it is spent. */\n private spend(owner: string, bytes: number): boolean {\n const now = this.now();\n const rate = this.quotas.bytesPerSecondPerOwner;\n const bucket = this.buckets.get(owner) ?? { tokens: rate, at: now };\n this.buckets.set(owner, bucket);\n if (!refill(bucket, rate, now, bytes)) return false;\n bucket.tokens -= bytes;\n return true;\n }\n\n private live(allocation: IAllocation | undefined): allocation is IAllocation {\n if (allocation === undefined) return false;\n if (allocation.expiresAt > this.now()) return true;\n this.remove(allocation);\n return false;\n }\n\n private fromClientSend(message: IStunMessage, rinfo: RemoteInfo): void {\n const allocation = this.allocations.get(clientKey(rinfo));\n if (!this.live(allocation)) return;\n const peerValue = attribute(message, StunAttr.XorPeerAddress);\n const data = attribute(message, StunAttr.Data);\n if (peerValue === undefined || data === undefined) return;\n const peer = decodeXorAddress(peerValue, message.transactionId);\n if (peer === undefined || !this.permitted(allocation, peer.address)) return;\n if (this.isServerItself(peer)) return;\n if (!this.spend(allocation.owner, data.length)) return;\n allocation.socket.send(data, peer.port, peer.address);\n }\n\n private fromClientChannel(data: Buffer, rinfo: RemoteInfo): void {\n const allocation = this.allocations.get(clientKey(rinfo));\n if (!this.live(allocation)) return;\n const frame = decodeChannelData(data);\n if (frame === undefined) return;\n const channel = allocation.channelsByNumber.get(frame.channel);\n if (channel === undefined || channel.expiresAt <= this.now()) return;\n if (!this.permitted(allocation, channel.peer.address)) return;\n if (this.isServerItself(channel.peer)) return;\n if (!this.spend(allocation.owner, frame.payload.length)) return;\n allocation.socket.send(frame.payload, channel.peer.port, channel.peer.address);\n }\n\n private fromPeer(allocation: IAllocation, data: Buffer, rinfo: RemoteInfo): void {\n if (this.allocations.get(allocation.key) !== allocation || !this.live(allocation)) return;\n if (!this.permitted(allocation, rinfo.address)) return;\n if (!this.spend(allocation.owner, data.length)) return;\n const channel = allocation.channelsByPeer.get(clientKey(rinfo));\n if (channel !== undefined && channel.expiresAt > this.now()) {\n this.send(encodeChannelData(channel.number, data), allocation.client);\n return;\n }\n const transactionId = randomBytes(12);\n this.send(\n encodeStun(StunMethod.Data, StunClass.Indication, transactionId, [\n {\n type: StunAttr.XorPeerAddress,\n value: encodeXorAddress({ address: rinfo.address, port: rinfo.port }, transactionId),\n },\n { type: StunAttr.Data, value: data },\n ]),\n allocation.client,\n );\n }\n\n private remove(allocation: IAllocation): void {\n if (this.allocations.get(allocation.key) === allocation)\n this.allocations.delete(allocation.key);\n try {\n allocation.socket.close();\n } catch {\n /* already closed */\n }\n }\n\n private sweep(): void {\n const now = this.now();\n // A source whose budget has refilled is forgotten, so the table holds recent senders only.\n for (const [address, bucket] of this.answerBuckets) {\n if (Math.abs(now - bucket.at) >= 1000) this.answerBuckets.delete(address);\n }\n for (const allocation of [...this.allocations.values()]) {\n if (allocation.expiresAt <= now) {\n this.remove(allocation);\n continue;\n }\n for (const [address, expiresAt] of allocation.permissions) {\n if (expiresAt <= now) allocation.permissions.delete(address);\n }\n for (const [number, channel] of allocation.channelsByNumber) {\n if (channel.expiresAt > now) continue;\n allocation.channelsByNumber.delete(number);\n allocation.channelsByPeer.delete(clientKey(channel.peer));\n }\n }\n }\n}\n","/**\n * The device mesh's relay of last resort: a TURN server on one of the user's always-on devices, used\n * only by the devices of the same roster, with short-lived credentials each pair derives on its own.\n *\n * A credential follows the TURN REST scheme — the username carries its expiry, the password is a MAC\n * of the username — but the MAC key is the pair's secret rather than one shared by every client: the\n * username is `<expiry>:<tag>`, where the tag is the pair's rotating relay-user tag, so it names no\n * device to whoever sees it, and only the relay device can tell whose it is. A device the lists no\n * longer name, or revoke, derives no tag and no password, and its allocations close.\n *\n * The relay moves datagrams between two ends that run DTLS with each other; it never holds a key of\n * that channel, and a connection through it is admitted by the device handshake exactly as a direct\n * one is.\n */\nimport { rendezvousEpoch, type IPairRendezvous } from '@robota-sdk/agent-remote-pairing';\n\nimport { TurnServer, type ITurnQuotas } from './turn-server.js';\n\nimport type { IIceServer } from './webrtc-transport-options.js';\n\n/** Where a paired device's relay listens. */\nexport interface IMeshRelayEndpoint {\n readonly host: string;\n readonly port: number;\n}\n\n/** A device the relay serves, with the pair's rendezvous. */\nexport interface IMeshRelayPeer {\n readonly deviceId: string;\n readonly rendezvous: IPairRendezvous;\n}\n\n/** How long a relay credential is good for, by default. */\nexport const DEFAULT_RELAY_CREDENTIAL_TTL_MS = 10 * 60 * 1000;\n/** The longest a relay accepts: a credential cannot outlive the tag epochs its relay looks up. */\nexport const MAX_RELAY_CREDENTIAL_TTL_MS = 60 * 60 * 1000;\n/** Clocks of two devices may disagree by this much. */\nconst CLOCK_SKEW_MS = 60 * 1000;\nconst TAG_BYTES = 16;\nconst USERNAME = /^(\\d{1,12}):([A-Za-z0-9_-]{22})$/;\n\nfunction base64Url(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64url');\n}\n\n/** A relay credential this device presents to `rendezvous`'s peer's relay, good until `now + ttlMs`. */\nexport async function meshRelayCredential(\n rendezvous: IPairRendezvous,\n now: number,\n ttlMs: number = DEFAULT_RELAY_CREDENTIAL_TTL_MS,\n): Promise<{ readonly username: string; readonly credential: string }> {\n const ttl = Math.min(Math.max(ttlMs, 1000), MAX_RELAY_CREDENTIAL_TTL_MS);\n const tag = (await rendezvous.tag('relay-user', 'outbound', rendezvousEpoch(now))).slice(\n 0,\n TAG_BYTES,\n );\n const username = `${Math.floor((now + ttl) / 1000)}:${base64Url(tag)}`;\n return { username, credential: await rendezvous.relayPassword('outbound', username) };\n}\n\nfunction turnUrl(endpoint: IMeshRelayEndpoint): string {\n const host = endpoint.host.includes(':') ? `[${endpoint.host}]` : endpoint.host;\n return `turn:${host}:${endpoint.port}?transport=udp`;\n}\n\n/** ICE servers for a paired device's relay endpoints, with this pair's credential. */\nexport async function meshRelayIceServers(\n rendezvous: IPairRendezvous,\n endpoints: readonly IMeshRelayEndpoint[],\n now: number,\n ttlMs?: number,\n): Promise<IIceServer[]> {\n if (endpoints.length === 0) return [];\n const { username, credential } = await meshRelayCredential(rendezvous, now, ttlMs);\n return endpoints.map((endpoint) => ({ urls: turnUrl(endpoint), username, credential }));\n}\n\n/**\n * Why a relay was needed: only relayed connections are allowed, or a direct attempt got as far as\n * trying paths and found none.\n */\nexport type TMeshRelayNeed = 'relay-only' | 'no-direct-path';\n\n/**\n * A connection needed a relay and none could be tried. Never a silent degrade — the connection is\n * refused with this error, which carries what the direct attempt went through as its `cause`.\n */\nexport class MeshRelayNeededError extends Error {\n public readonly deviceId: string;\n public readonly need: TMeshRelayNeed;\n\n public constructor(\n deviceId: string,\n need: TMeshRelayNeed,\n options?: { readonly cause?: unknown },\n ) {\n const why =\n need === 'relay-only'\n ? 'only relayed connections are allowed'\n : `no direct path was found (${\n options?.cause instanceof Error ? options.cause.message : String(options?.cause)\n })`;\n super(\n `a relay device is needed to reach device ${deviceId}: ${why}, and no paired device ` +\n 'advertises a relay and no TURN server is configured. Run the relay on an always-on ' +\n 'device, or configure a TURN server.',\n options?.cause !== undefined ? { cause: options.cause } : undefined,\n );\n this.name = 'MeshRelayNeededError';\n this.deviceId = deviceId;\n this.need = need;\n }\n}\n\nexport interface IMeshTurnRelayOptions {\n /** Default every IPv4 interface. */\n readonly host?: string;\n /** Default 3478; 0 picks a free port. */\n readonly port?: number;\n /** The address relayed transport addresses carry, e.g. this device's public address. */\n readonly relayAddress?: string;\n /** The ports relayed addresses take; behind a NAT, forward them with the relay's own port. */\n readonly relayPorts?: { readonly min: number; readonly max: number };\n readonly quotas?: Partial<ITurnQuotas>;\n /** Default true: see {@link TurnServer}. */\n readonly allowPrivatePeers?: boolean;\n /** Default: see {@link TurnServer}. */\n readonly allowPeer?: (address: string) => boolean;\n readonly now?: () => number;\n readonly onError?: (error: Error) => void;\n}\n\n/** This device's relay for its paired devices. */\nexport class MeshTurnRelay {\n private peers: readonly IMeshRelayPeer[] = [];\n /** Whose each relay-user tag is, for the peers in force and one epoch: derived once, not per request. */\n private owners?: {\n readonly epoch: number;\n readonly peers: readonly IMeshRelayPeer[];\n readonly byTag: Promise<ReadonlyMap<string, IMeshRelayPeer>>;\n };\n\n private constructor(\n private readonly server: TurnServer,\n private readonly now: () => number,\n ) {}\n\n public static async start(options: IMeshTurnRelayOptions = {}): Promise<MeshTurnRelay> {\n const now = options.now ?? Date.now;\n let relay: MeshTurnRelay | undefined;\n const server = await TurnServer.start({\n ...(options.host !== undefined ? { host: options.host } : {}),\n ...(options.port !== undefined ? { port: options.port } : {}),\n ...(options.relayAddress !== undefined ? { relayAddress: options.relayAddress } : {}),\n ...(options.relayPorts !== undefined ? { relayPorts: options.relayPorts } : {}),\n ...(options.quotas !== undefined ? { quotas: options.quotas } : {}),\n ...(options.allowPeer !== undefined ? { allowPeer: options.allowPeer } : {}),\n ...(options.allowPrivatePeers !== undefined\n ? { allowPrivatePeers: options.allowPrivatePeers }\n : {}),\n ...(options.onError !== undefined ? { onError: options.onError } : {}),\n now,\n // A request that arrives before the relay exists is refused, as an unknown user is.\n authorize: (username) =>\n relay === undefined ? Promise.resolve(undefined) : relay.authorize(username),\n });\n relay = new MeshTurnRelay(server, now);\n return relay;\n }\n\n /** Where the relay listens. */\n public get endpoint(): IMeshRelayEndpoint {\n const { address, port } = this.server.address;\n return { host: address, port };\n }\n\n /** Allocations held, by one device or in all. */\n public allocationCount(deviceId?: string): number {\n return this.server.allocationCount(deviceId);\n }\n\n /**\n * The devices the relay serves: the current, unrevoked peers of the lists in force. Any other\n * device's credentials stop working, and its allocations close at once.\n */\n public declarePeers(peers: readonly IMeshRelayPeer[]): void {\n this.peers = [...peers];\n this.server.retain(new Set(peers.map((peer) => peer.deviceId)));\n }\n\n private async authorize(\n username: string,\n ): Promise<{ readonly owner: string; readonly password: string } | undefined> {\n const match = USERNAME.exec(username);\n if (match === null) return undefined;\n const now = this.now();\n const expiresAt = Number(match[1]) * 1000;\n if (expiresAt <= now || expiresAt - now > MAX_RELAY_CREDENTIAL_TTL_MS + CLOCK_SKEW_MS) {\n return undefined;\n }\n const peer = (await this.tagOwners(rendezvousEpoch(now))).get(match[2]!);\n // Still a peer once the tags are derived: a revocation meanwhile wins.\n if (peer === undefined || !this.peers.some((p) => p.deviceId === peer.deviceId)) {\n return undefined;\n }\n return {\n owner: peer.deviceId,\n password: await peer.rendezvous.relayPassword('inbound', username),\n };\n }\n\n /** The peers' relay-user tags for `epoch` and the adjacent ones, by tag. */\n private tagOwners(epoch: number): Promise<ReadonlyMap<string, IMeshRelayPeer>> {\n const held = this.owners;\n if (held !== undefined && held.epoch === epoch && held.peers === this.peers) return held.byTag;\n const peers = this.peers;\n const byTag = (async () => {\n const out = new Map<string, IMeshRelayPeer>();\n for (const peer of peers) {\n for (const tag of await peer.rendezvous.lookupTags('relay-user', epoch)) {\n out.set(base64Url(tag.slice(0, TAG_BYTES)), peer);\n }\n }\n return out;\n })();\n // A failed derivation is tried again by the next request.\n byTag.catch(() => {\n if (this.owners?.byTag === byTag) this.owners = undefined;\n });\n this.owners = { epoch, peers, byTag };\n return byTag;\n }\n\n public close(): Promise<void> {\n this.peers = [];\n return this.server.close();\n }\n}\n","import type { IIdentityContext } from '@robota-sdk/agent-remote-pairing';\n/**\n * This device's place in the user's device mesh: present at one relay inbox per peer device, and at\n * most one WebRTC connection per device pair, each admitted by the device handshake.\n *\n * **Who offers is decided by the pair, not by timing.** The device with the lower id is the offerer\n * for that pair and the other one only ever answers, so two devices reaching for each other at once\n * never produce two connections and never race. A node that wants a connection announces itself\n * (`hello`); the offerer answers a `hello` with an offer and the answerer answers it with its own\n * `hello`. Each node run has a random instance id, so a peer that restarted is recognised as a new\n * run and gets a fresh connection, while a repeated `hello` from the run already being served is\n * ignored. An offer names the answerer's run, so an offer meant for a previous run is not taken.\n *\n * **The relay is only a mailbox.** Inbox topics come from the pair's secret, so the relay sees no\n * device id; everything read from it is decoded as hostile input. Nothing it says is authenticated,\n * so a new attempt never displaces an admitted connection: it runs beside it and replaces it only once\n * it is admitted itself, and attempts per pair are paced, so forged announcements can neither cut a\n * working connection nor make this node open connections without bound. Admission is the device\n * handshake over the negotiated channel, and a peer is accepted only as the device its inbox belongs\n * to.\n *\n * **Lists are live.** Newer lists adopted in a handshake, and lists the caller hands over through\n * {@link DeviceMeshNode.refresh}, apply from the next handshake on; a device they remove or revoke is\n * dropped along with its connection. Lists that become newer here are also pushed over every admitted\n * connection, and a pushed list is taken on the handshake's terms: only a newer one from this user's\n * signing key that verifies. Lists are not a capability: they travel whatever the peer may ask.\n */\nimport {\n decodeDeviceRevocationList,\n decodeDeviceRoster,\n decodeSigningKeyRevocation,\n derivePairRendezvous,\n startDeviceHandshake,\n verifyDeviceChain,\n type IDeviceCertificate,\n type IDeviceHandshakeIdentity,\n type IDeviceHandshakeResult,\n type IDeviceMeshAdmission,\n type IListHighWaterMarks,\n type IListUpdate,\n type IPairRendezvous,\n type IRelayInboxTopics,\n type ISessionDescriptor,\n type TDeviceCapability,\n} from '@robota-sdk/agent-remote-pairing';\n\nimport type { MeshLinkEndedError } from './mesh-peer-link.js';\nimport {\n MeshPeerLink,\n type IMeshChannelBinding,\n type TMeshLinkEnd,\n type TMeshLinkRole,\n type TMeshLinkSignal,\n} from './mesh-peer-link.js';\nimport {\n MESH_SIGNAL_VERSION,\n decodeMeshSignal,\n type IMeshDescription,\n type TMeshSignal,\n} from './mesh-signal.js';\nimport {\n ConnectionAuthority,\n type IFileFrameChannel,\n type IOperatorApprover,\n} from '@robota-sdk/agent-interface-session-mobility';\n\nimport { RtcPeer, type RtcChannel } from './rtc-peer.js';\n\nimport {\n MeshRelayNeededError,\n meshRelayIceServers,\n type IMeshRelayEndpoint,\n type IMeshRelayPeer,\n} from './mesh-turn-relay.js';\n\nimport type { IMeshPeerRoute } from './mesh-discovery.js';\nimport type { IMeshRelay } from './mesh-relay.js';\nimport type { IDataChannelModule } from './datachannel-loader.js';\nimport type { IIceServer } from './webrtc-transport-options.js';\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 20_000;\n/** At most one new attempt, and one `hello` reply, per pair per this interval; later ones wait. */\nconst MIN_ATTEMPT_INTERVAL_MS = 1_000;\n/** Candidates kept for an offer that waits for its pacing slot. */\nconst MAX_WAITING_CANDIDATES = 64;\n/** How long an attempt waits to learn which relays paired devices advertise. */\nconst RELAY_ADVERT_LOOKUP_MS = 3_000;\n/** A message body that carries lists, not an application message; see {@link listsFrame}. */\nconst LISTS_FRAME = 'mesh-lists';\nconst LISTS_PREFIX = `{\"t\":\"${LISTS_FRAME}\"`;\n/** Paired devices' relays one attempt uses, and endpoints of each. */\nconst MAX_RELAY_DEVICES = 2;\nconst MAX_RELAY_ENDPOINTS = 3;\n\n/**\n * How a connection reaches a peer no direct path reaches: a relay a paired device runs first, then\n * the TURN servers the user configured. With neither, a connection that needs a relay is refused\n * with {@link MeshRelayNeededError} rather than left to fail silently.\n */\nexport interface IDeviceMeshRelayOptions {\n /** The relays `peers` advertise, by device id, as found over their pairwise records. */\n readonly advertised?: (\n peers: readonly IMeshPeerRoute[],\n signal: AbortSignal,\n ) => Promise<ReadonlyMap<string, readonly IMeshRelayEndpoint[]>>;\n /** TURN servers the user configured, tried once the advertised relays did not carry a connection. */\n readonly configured?: readonly IIceServer[];\n /** Relay candidates only: for a network where no direct path can work. */\n readonly relayOnly?: boolean;\n /** How long a relay credential lasts (default: see `meshRelayCredential`). */\n readonly credentialTtlMs?: number;\n}\n\n/** Which way an attempt reaches the peer: directly only, or through one kind of relay too. */\ntype TRelayStage = 'direct' | 'embedded' | 'configured';\n\nexport interface IDeviceMeshNodeOptions {\n readonly cryptoContext: IIdentityContext;\n readonly identity: IDeviceHandshakeIdentity;\n /** This device's signed descriptor of the session it offers to peers. */\n readonly sessionDescriptor: ISessionDescriptor;\n /** What this device lets a peer be asked for; an admission grants the intersection. */\n readonly localPolicy: readonly TDeviceCapability[];\n readonly relay: IMeshRelay;\n /**\n * The operator of this session, asked before a peer may use a capability that needs approval.\n * Absent: such capabilities are refused.\n */\n readonly operatorApprover?: IOperatorApprover;\n readonly iceServers?: readonly IIceServer[];\n readonly relays?: IDeviceMeshRelayOptions;\n /** This device's own relay: told which devices it serves whenever the lists in force change. */\n readonly relayServer?: { declarePeers(peers: readonly IMeshRelayPeer[]): void };\n /** Before a remote admission, ask a signing-key holder for newer lists (see the device handshake). */\n readonly fetchLatestLists?: (signal: AbortSignal) => Promise<unknown>;\n /** Newer verified lists adopted during a handshake, for the caller to persist. */\n readonly onListsAdopted?: (update: IListUpdate) => void;\n /** From the first signal of an attempt to its admission (default 20 s). */\n readonly connectTimeoutMs?: number;\n readonly handshakeTimeoutMs?: number;\n /** Test seam: inject the `node-datachannel` module. */\n readonly loadDataChannel?: () => IDataChannelModule;\n readonly now?: () => number;\n}\n\n/** An admitted connection to one peer device. */\nexport interface IDeviceMeshLink {\n readonly admission: IDeviceMeshAdmission;\n readonly result: IDeviceHandshakeResult;\n /** The DTLS fingerprints the handshake bound this connection to. */\n readonly channelBinding: IMeshChannelBinding;\n /** What the peer may do on this connection; ask it before acting on the peer's request. */\n readonly authority: ConnectionAuthority;\n send(body: string): void;\n onMessage(handler: (body: string) => void): () => void;\n /**\n * A channel of its own for one file transfer. What travels on it is the file carrier's; the peer\n * decides on it with its operator, and this side's authority is not asked.\n */\n openFileChannel(): Promise<IFileFrameChannel>;\n /**\n * Channels the peer opened to send a file. Whoever takes them decides on each transfer, asking\n * {@link IDeviceMeshLink.authority}; with no handler, such channels are closed unread.\n */\n onFileChannel(handler: (channel: IFileFrameChannel) => void): () => void;\n onClose(handler: () => void): () => void;\n close(): void;\n}\n\n/** A data channel as a file frame channel. */\nfunction fileFrameChannel(channel: RtcChannel): IFileFrameChannel {\n return {\n send: (frame) => channel.send(frame),\n onFrame: (handler) => channel.onMessage(handler),\n onClose: (handler) => {\n if (channel.readyState === 'closed') {\n queueMicrotask(handler);\n return () => undefined;\n }\n return channel.onStateChange((state) => {\n if (state === 'closed') handler();\n });\n },\n close: () => channel.close(),\n };\n}\n\n/** A connection attempt that ended before admission. */\nexport interface IDeviceMeshRefusal {\n readonly deviceId: string;\n readonly end: TMeshLinkEnd;\n /**\n * Why: a {@link MeshLinkEndedError} (its `cause` is e.g. the handshake refusal), a\n * {@link MeshRelayNeededError} when the peer needs a relay and none could be tried, or why the\n * device was dropped.\n */\n readonly error?: unknown;\n}\n\ninterface IAttempt {\n readonly cid: string;\n /** The peer's run this attempt connects to. */\n readonly remoteInstance: string;\n readonly link: MeshPeerLink;\n exposed?: IDeviceMeshLink;\n /** Set once the attempt's connection is created. */\n relayStage?: TRelayStage;\n}\n\n/** Runs an action at most once per interval; a later one waits, and only the latest waiting one runs. */\nclass Pacer {\n private last?: number;\n private waiting?: () => void;\n private timer?: ReturnType<typeof setTimeout>;\n\n public run(action: () => void): void {\n const now = Date.now();\n if (this.last === undefined || now - this.last >= MIN_ATTEMPT_INTERVAL_MS) {\n this.last = now;\n action();\n return;\n }\n this.waiting = action;\n if (this.timer !== undefined) return;\n this.timer = setTimeout(\n () => {\n this.timer = undefined;\n const next = this.waiting;\n this.waiting = undefined;\n if (next !== undefined) this.run(next);\n },\n MIN_ATTEMPT_INTERVAL_MS - (now - this.last),\n );\n this.timer.unref?.();\n }\n\n public cancel(): void {\n if (this.timer !== undefined) clearTimeout(this.timer);\n this.timer = undefined;\n this.waiting = undefined;\n }\n}\n\ninterface IPeerState {\n readonly device: IDeviceCertificate;\n readonly role: TMeshLinkRole;\n readonly rendezvous: IPairRendezvous;\n readonly inbound: string;\n readonly outbound: string;\n /** The pair's connection. */\n admitted?: IAttempt;\n /** A connection being set up, beside the admitted one until it is admitted itself. */\n pending?: IAttempt;\n readonly attempts: Pacer;\n readonly replies: Pacer;\n /** An offer waiting for its pacing slot, and the candidates that arrived for it meanwhile. */\n waitingOffer?: { readonly cid: string; readonly from: string; readonly ice: TMeshLinkSignal[] };\n /** Relayed attempts in a row whose connection never came up: which relay stage is next. */\n relayFailures: number;\n}\n\nfunction randomId(): string {\n const bytes = new Uint8Array(16);\n globalThis.crypto.getRandomValues(bytes);\n return Buffer.from(bytes).toString('base64url');\n}\n\n/** Whether `next` is a newer list issued by the same signing key as `current`. */\nfunction newer<T extends { readonly seq: number; readonly signingKeyId?: string }>(\n current: T,\n next: T | undefined,\n): next is T {\n return next !== undefined && next.seq > current.seq && next.signingKeyId === current.signingKeyId;\n}\n\nfunction maxSeq(a: number | undefined, b: number | undefined): number | undefined {\n if (a === undefined) return b;\n if (b === undefined) return a;\n return Math.max(a, b);\n}\n\n/** The higher mark of each list, per signing key: marks only ever move forward. */\nfunction mergeMarks(a: IListHighWaterMarks, b: IListHighWaterMarks): IListHighWaterMarks {\n const merged = new Map<string, { rosterSeq?: number; revocationSeq?: number }>();\n for (const table of [a.bySigningKey ?? {}, b.bySigningKey ?? {}]) {\n for (const [keyId, marks] of Object.entries(table)) {\n const held = merged.get(keyId) ?? {};\n const rosterSeq = maxSeq(held.rosterSeq, marks.rosterSeq);\n const revocationSeq = maxSeq(held.revocationSeq, marks.revocationSeq);\n merged.set(keyId, {\n ...(rosterSeq !== undefined ? { rosterSeq } : {}),\n ...(revocationSeq !== undefined ? { revocationSeq } : {}),\n });\n }\n }\n // `fromEntries` defines own properties, so no key id can reach the prototype.\n const bySigningKey = Object.fromEntries(merged);\n const signingKeyRevocationSeq = maxSeq(a.signingKeyRevocationSeq, b.signingKeyRevocationSeq);\n return {\n ...(signingKeyRevocationSeq !== undefined ? { signingKeyRevocationSeq } : {}),\n bySigningKey,\n };\n}\n\n/**\n * `next`, but never older than what is in force: a list this node already holds from the same\n * signing key with a higher `seq` stays, so a refresh from a store that missed an adopted revocation\n * cannot roll it back. A new signing key's lists (after recovery) are taken as they are.\n */\nfunction mergeIdentity(\n current: IDeviceHandshakeIdentity,\n next: IDeviceHandshakeIdentity,\n): IDeviceHandshakeIdentity {\n const sameKey =\n next.signingKeyCertificate.signingKeyId === current.signingKeyCertificate.signingKeyId;\n return {\n ...next,\n ...(sameKey && newer(next.roster, current.roster) ? { roster: current.roster } : {}),\n ...(sameKey && newer(next.revocation, current.revocation)\n ? { revocation: current.revocation }\n : {}),\n ...(current.signingKeyRevocation.seq > next.signingKeyRevocation.seq\n ? { signingKeyRevocation: current.signingKeyRevocation }\n : {}),\n marks: mergeMarks(current.marks, next.marks),\n };\n}\n\n/**\n * Whether a list in `next` is newer than the one in `previous` under the same signing key. A new\n * signing key's lists are not pushed: a peer cannot take them without the new key's certificate,\n * which only a handshake carries.\n */\nfunction listsAdvanced(\n previous: IDeviceHandshakeIdentity,\n next: IDeviceHandshakeIdentity,\n): boolean {\n if (next.signingKeyCertificate.signingKeyId !== previous.signingKeyCertificate.signingKeyId) {\n return false;\n }\n return (\n next.roster.seq > previous.roster.seq ||\n next.revocation.seq > previous.revocation.seq ||\n next.signingKeyRevocation.seq > previous.signingKeyRevocation.seq\n );\n}\n\n/**\n * The lists a peer pushed, when `body` carries them. Pushed lists travel as a message whose body\n * names them, so a peer that does not know them treats them as an application message it ignores.\n */\nfunction listsFrame(body: string): Record<string, unknown> | undefined {\n if (!body.startsWith(LISTS_PREFIX)) return undefined;\n try {\n const value: unknown = JSON.parse(body);\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n } catch {\n // allow-fallback: a body that only looks like lists is not an application message either\n return {};\n }\n}\n\nexport class DeviceMeshNode {\n /** This run's instance id. */\n public readonly instance = randomId();\n private identity: IDeviceHandshakeIdentity;\n private sessionDescriptor: ISessionDescriptor;\n private readonly peers = new Map<string, IPeerState>();\n private readonly byInbound = new Map<string, IPeerState>();\n private readonly linkHandlers = new Set<(link: IDeviceMeshLink) => void>();\n private readonly refusalHandlers = new Set<(refusal: IDeviceMeshRefusal) => void>();\n private readonly waiters = new Set<(error: Error) => void>();\n private readonly unsubscribes: (() => void)[] = [];\n private started = false;\n private stopped = false;\n private sync: Promise<void> = Promise.resolve();\n\n public constructor(private readonly options: IDeviceMeshNodeOptions) {\n this.identity = options.identity;\n this.sessionDescriptor = options.sessionDescriptor;\n }\n\n public get deviceId(): string {\n return this.identity.deviceCertificate.deviceId;\n }\n\n /** Derive the inbox topics, be present at them, and announce this run to every peer device. */\n public async start(): Promise<void> {\n if (this.started) throw new Error('DeviceMeshNode already started');\n this.started = true;\n this.unsubscribes.push(\n this.options.relay.onMessage((topic, data) => this.receive(topic, data)),\n );\n await this.syncPeers();\n }\n\n /**\n * Use newer lists or a new session descriptor from the next handshake on. A list older than the\n * one in force is ignored. Peers the lists no longer name, or revoke, are dropped with their\n * connections; new ones are announced to.\n */\n public refresh(update: {\n readonly identity?: IDeviceHandshakeIdentity;\n readonly sessionDescriptor?: ISessionDescriptor;\n }): Promise<void> {\n if (update.sessionDescriptor !== undefined) this.sessionDescriptor = update.sessionDescriptor;\n if (update.identity === undefined) return Promise.resolve();\n const previous = this.identity;\n this.identity = mergeIdentity(previous, update.identity);\n const synced = this.syncPeers();\n // After the sync, so a device the lists drop is not sent them.\n // A failed sync is the caller's, through `synced`; the lists then wait for the next handshake.\n if (listsAdvanced(previous, this.identity)) {\n synced.then(\n () => this.pushLists(),\n () => undefined,\n );\n }\n return synced;\n }\n\n /** Send the lists in force over every admitted connection. */\n private pushLists(): void {\n if (this.stopped) return;\n const { roster, revocation, signingKeyRevocation } = this.identity;\n const body = JSON.stringify({ t: LISTS_FRAME, roster, revocation, signingKeyRevocation });\n for (const state of this.peers.values()) {\n try {\n state.admitted?.exposed?.send(body);\n } catch {\n // allow-fallback: a connection that cannot take them gets them in its next handshake\n }\n }\n }\n\n /**\n * Lists a peer pushed: taken only when newer than the ones in force, issued by this user's signing\n * key (a signing-key revocation by the master key), and verifying for this device; anything else is\n * ignored and the connection stays.\n */\n private async receiveLists(frame: Record<string, unknown>): Promise<void> {\n const current = this.identity;\n const roster = decodeDeviceRoster(this.options.cryptoContext, frame['roster']);\n const revocation = decodeDeviceRevocationList(this.options.cryptoContext, frame['revocation']);\n const signingKeyRevocation = decodeSigningKeyRevocation(\n this.options.cryptoContext,\n frame['signingKeyRevocation'],\n );\n const offered: { -readonly [K in keyof IListUpdate]?: IListUpdate[K] } = {\n ...(roster.ok && newer(current.roster, roster.value) ? { roster: roster.value } : {}),\n ...(revocation.ok && newer(current.revocation, revocation.value)\n ? { revocation: revocation.value }\n : {}),\n ...(signingKeyRevocation.ok &&\n signingKeyRevocation.value.seq > current.signingKeyRevocation.seq\n ? { signingKeyRevocation: signingKeyRevocation.value }\n : {}),\n };\n if (Object.keys(offered).length === 0) return;\n const verdict = await verifyDeviceChain(this.options.cryptoContext, {\n masterPublicKey: current.masterPublicKey,\n signingKeyCert: current.signingKeyCertificate,\n deviceCert: current.deviceCertificate,\n roster: offered.roster ?? current.roster,\n revocation: offered.revocation ?? current.revocation,\n signingKeyRevocation: offered.signingKeyRevocation ?? current.signingKeyRevocation,\n now: (this.options.now ?? Date.now)(),\n lastSeen: current.marks,\n required: { roster: true, revocation: true, signingKeyRevocation: true },\n // As in a handshake, a list is taken for being newer and genuine; its expiry is judged at use.\n listExpiryGraceMs: Number.POSITIVE_INFINITY,\n });\n if (!verdict.ok || this.stopped) return;\n const { rosterSeq, revocationSeq, signingKeyRevocationSeq } = verdict.accepted;\n this.adoptLists({\n ...offered,\n marks: {\n ...(offered.signingKeyRevocation !== undefined && signingKeyRevocationSeq !== undefined\n ? { signingKeyRevocationSeq }\n : {}),\n bySigningKey: {\n [verdict.signingKeyId]: {\n ...(offered.roster !== undefined && rosterSeq !== undefined ? { rosterSeq } : {}),\n ...(offered.revocation !== undefined && revocationSeq !== undefined\n ? { revocationSeq }\n : {}),\n },\n },\n },\n });\n }\n\n /** Recompute the peer set from the current lists; serialized so two refreshes never interleave. */\n private syncPeers(): Promise<void> {\n this.sync = this.sync.then(async () => {\n if (this.stopped) return;\n const identity = this.identity;\n const self = identity.deviceCertificate;\n const revoked = new Set(identity.revocation.revokedDeviceIds);\n const wanted = new Map<string, IDeviceCertificate>();\n for (const device of identity.roster.devices) {\n if (device.deviceId !== self.deviceId && !revoked.has(device.deviceId)) {\n wanted.set(device.deviceId, device);\n }\n }\n for (const [deviceId, state] of this.peers) {\n const device = wanted.get(deviceId);\n if (\n device !== undefined &&\n device.kaKey === state.device.kaKey &&\n device.kaEpoch === state.device.kaEpoch\n ) {\n continue;\n }\n this.drop(state);\n }\n const added: IPeerState[] = [];\n for (const [deviceId, device] of wanted) {\n if (this.peers.has(deviceId)) continue;\n let rendezvous: IPairRendezvous;\n let topics: IRelayInboxTopics;\n try {\n rendezvous = await derivePairRendezvous(this.options.cryptoContext, {\n ownKaPrivateKey: identity.kaPrivateKey,\n own: self,\n peerDeviceId: deviceId,\n lists: identity,\n });\n topics = await rendezvous.relayInbox();\n } catch {\n // allow-fallback: a device no pairwise secret can be agreed with cannot be reached; the rest can\n continue;\n }\n const state: IPeerState = {\n device,\n role: self.deviceId < device.deviceId ? 'offerer' : 'answerer',\n rendezvous,\n inbound: topics.inbound,\n outbound: topics.outbound,\n attempts: new Pacer(),\n replies: new Pacer(),\n relayFailures: 0,\n };\n this.peers.set(deviceId, state);\n this.byInbound.set(topics.inbound, state);\n added.push(state);\n }\n if (this.stopped) return;\n this.options.relayServer?.declarePeers(\n [...this.peers.values()].map((peer) => ({\n deviceId: peer.device.deviceId,\n rendezvous: peer.rendezvous,\n })),\n );\n this.options.relay.declarePresence([...this.byInbound.keys()]);\n const routes = this.peerRoutes();\n this.options.relay.declarePeers?.(routes);\n // Learn the peers' relays ahead of the first attempt, so it does not wait on the lookup.\n // allow-fallback: a lookup that fails here is run again by the attempt that needs it\n this.options.relays\n ?.advertised?.(routes, AbortSignal.timeout(RELAY_ADVERT_LOOKUP_MS))\n .catch(() => undefined);\n for (const state of added) this.hello(state);\n });\n const done = this.sync;\n // One failed sync must not stop every later one.\n this.sync = done.catch(() => undefined);\n return done;\n }\n\n private drop(state: IPeerState): void {\n this.peers.delete(state.device.deviceId);\n this.byInbound.delete(state.inbound);\n state.attempts.cancel();\n state.replies.cancel();\n const { pending, admitted } = state;\n state.pending = undefined;\n state.admitted = undefined;\n pending?.link.close();\n admitted?.link.close();\n if (pending !== undefined && !this.stopped) {\n const refusal: IDeviceMeshRefusal = {\n deviceId: state.device.deviceId,\n end: 'closed',\n error: new Error('peer device was revoked or is no longer in the roster'),\n };\n for (const handler of this.refusalHandlers) handler(refusal);\n }\n }\n\n /** The offerer for a pair is the device with the lower id. */\n public roleFor(deviceId: string): TMeshLinkRole | undefined {\n return this.peers.get(deviceId)?.role;\n }\n\n /** The admitted link to `deviceId`, if there is one. */\n public link(deviceId: string): IDeviceMeshLink | undefined {\n return this.peers.get(deviceId)?.admitted?.exposed;\n }\n\n /**\n * An admitted link to `deviceId`: the existing one, or a new one once the peer is reachable.\n * Rejects for a device this node will not talk to, when an attempt is refused, on timeout, or when\n * the node stops.\n */\n public connect(\n deviceId: string,\n timeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,\n ): Promise<IDeviceMeshLink> {\n if (this.stopped) return Promise.reject(new Error('device mesh node stopped'));\n const state = this.peers.get(deviceId);\n if (state === undefined) {\n return Promise.reject(new Error('not a rostered, unrevoked peer device'));\n }\n const existing = state.admitted?.exposed;\n if (existing !== undefined) return Promise.resolve(existing);\n return new Promise<IDeviceMeshLink>((resolve, reject) => {\n const done = (): void => {\n clearTimeout(timer);\n offLink();\n offRefusal();\n this.waiters.delete(fail);\n };\n const fail = (error: Error): void => {\n done();\n reject(error);\n };\n const timer = setTimeout(\n () => fail(new Error('peer device did not connect in time')),\n timeoutMs,\n );\n timer.unref?.();\n const offLink = this.onLink((link) => {\n if (link.admission.deviceId !== deviceId) return;\n done();\n resolve(link);\n });\n const offRefusal = this.onRefusal((refusal) => {\n if (refusal.deviceId !== deviceId) return;\n fail(\n refusal.error instanceof Error ? refusal.error : new Error(`connection ${refusal.end}`),\n );\n });\n this.waiters.add(fail);\n this.hello(state);\n });\n }\n\n public onLink(handler: (link: IDeviceMeshLink) => void): () => void {\n this.linkHandlers.add(handler);\n return () => this.linkHandlers.delete(handler);\n }\n\n public onRefusal(handler: (refusal: IDeviceMeshRefusal) => void): () => void {\n this.refusalHandlers.add(handler);\n return () => this.refusalHandlers.delete(handler);\n }\n\n public stop(): void {\n if (this.stopped) return;\n this.stopped = true;\n for (const unsubscribe of this.unsubscribes.splice(0)) unsubscribe();\n for (const state of [...this.peers.values()]) this.drop(state);\n for (const fail of [...this.waiters]) fail(new Error('device mesh node stopped'));\n this.linkHandlers.clear();\n this.refusalHandlers.clear();\n }\n\n private hello(state: IPeerState): void {\n if (this.stopped) return;\n this.options.relay.send(state.outbound, {\n v: MESH_SIGNAL_VERSION,\n kind: 'hello',\n from: this.instance,\n });\n }\n\n private receive(topic: string, data: unknown): void {\n if (this.stopped) return;\n const state = this.byInbound.get(topic);\n if (state === undefined) return;\n const signal = decodeMeshSignal(data);\n if (signal === undefined) return;\n if (signal.kind === 'hello') {\n this.onHello(state, signal.from);\n return;\n }\n if (signal.to !== this.instance) return; // meant for a previous run of this node\n if (signal.kind === 'offer') {\n this.onOffer(state, signal);\n return;\n }\n for (const attempt of [state.pending, state.admitted]) {\n if (attempt?.cid === signal.cid && attempt.remoteInstance === signal.from) {\n attempt.link.onSignal(toLinkSignal(signal));\n return;\n }\n }\n const waiting = state.waitingOffer;\n if (\n signal.kind === 'ice' &&\n waiting?.cid === signal.cid &&\n waiting.from === signal.from &&\n waiting.ice.length < MAX_WAITING_CANDIDATES\n ) {\n waiting.ice.push(toLinkSignal(signal));\n }\n }\n\n /** The run already being served, or being connected to. */\n private serves(state: IPeerState, remoteInstance: string): boolean {\n return (\n state.admitted?.remoteInstance === remoteInstance ||\n state.pending?.remoteInstance === remoteInstance\n );\n }\n\n private onHello(state: IPeerState, remoteInstance: string): void {\n if (this.serves(state, remoteInstance)) return;\n if (state.role === 'answerer') {\n // Only the offerer opens a connection; tell it this run is here.\n state.replies.run(() => this.hello(state));\n return;\n }\n state.attempts.run(() => {\n if (this.stopped || this.peers.get(state.device.deviceId) !== state) return;\n if (this.serves(state, remoteInstance)) return;\n void this.startAttempt(state, remoteInstance, randomId()).link.offer();\n });\n }\n\n private onOffer(state: IPeerState, signal: IMeshDescription): void {\n // Glare resolution: an offer toward the pair's offerer is not taken, whoever sent it.\n if (state.role !== 'answerer') return;\n if (state.admitted?.cid === signal.cid || state.pending?.cid === signal.cid) return;\n const waiting = { cid: signal.cid, from: signal.from, ice: [] as TMeshLinkSignal[] };\n state.waitingOffer = waiting;\n state.attempts.run(() => {\n if (state.waitingOffer === waiting) state.waitingOffer = undefined;\n if (this.stopped || this.peers.get(state.device.deviceId) !== state) return;\n if (state.admitted?.cid === signal.cid || state.pending?.cid === signal.cid) return;\n const { link } = this.startAttempt(state, signal.from, signal.cid);\n link.onSignal(toLinkSignal(signal));\n for (const candidate of waiting.ice) link.onSignal(candidate);\n });\n }\n\n /** A new attempt replaces any other pending one, never the admitted connection. */\n private startAttempt(state: IPeerState, remoteInstance: string, cid: string): IAttempt {\n const previous = state.pending;\n state.pending = undefined;\n previous?.link.close();\n const send = (signal: TMeshLinkSignal): void => {\n if (this.stopped) return;\n this.options.relay.send(state.outbound, {\n v: MESH_SIGNAL_VERSION,\n from: this.instance,\n to: remoteInstance,\n cid,\n ...signal,\n });\n };\n let attempt: IAttempt | undefined;\n const link: MeshPeerLink = new MeshPeerLink({\n role: state.role,\n createPeer: async () => {\n const { stage, servers } = await this.relayStage(state);\n if (attempt !== undefined) attempt.relayStage = stage;\n return this.createPeer(servers);\n },\n sendSignal: send,\n startHandshake: (binding) =>\n startDeviceHandshake(this.options.cryptoContext, {\n role: state.role === 'offerer' ? 'initiator' : 'responder',\n identity: this.identity,\n sessionDescriptor: this.sessionDescriptor,\n ...(state.role === 'offerer' ? { expectedPeerDeviceId: state.device.deviceId } : {}),\n localFingerprint: binding.localFingerprint,\n remoteFingerprint: binding.remoteFingerprint,\n // A data channel proves nothing about where the peer runs.\n locality: 'another-host',\n localPolicy: this.options.localPolicy,\n send: binding.send,\n ...(this.options.fetchLatestLists !== undefined\n ? { fetchLatestLists: this.options.fetchLatestLists }\n : {}),\n onListsAdopted: (update) => this.adoptLists(update),\n ...(this.options.now !== undefined ? { now: this.options.now } : {}),\n ...(this.options.handshakeTimeoutMs !== undefined\n ? { timeoutMs: this.options.handshakeTimeoutMs }\n : {}),\n }),\n connectTimeoutMs: this.options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,\n // The inbox the peer reached is the pair's: only that device is taken, before it is told so.\n accepts: (result) => result.admission.deviceId === state.device.deviceId,\n onAdmitted: (result) => this.admitted(state, link, result),\n onEnded: (end, error) => this.ended(state, link, end, error),\n });\n attempt = { cid, remoteInstance, link };\n state.pending = attempt;\n return attempt;\n }\n\n /** Lists the handshake verified and found newer: in force here at once, then handed to the caller. */\n private adoptLists(update: IListUpdate): void {\n const current = this.identity;\n const next: IDeviceHandshakeIdentity = {\n ...current,\n ...(newer(current.roster, update.roster) ? { roster: update.roster } : {}),\n ...(newer(current.revocation, update.revocation) ? { revocation: update.revocation } : {}),\n ...(update.signingKeyRevocation !== undefined &&\n update.signingKeyRevocation.seq > current.signingKeyRevocation.seq\n ? { signingKeyRevocation: update.signingKeyRevocation }\n : {}),\n marks: mergeMarks(current.marks, update.marks),\n };\n // allow-fallback: a failed sync keeps the lists in force; the adopted ones apply to handshakes regardless\n this.refresh({ identity: next }).catch(() => undefined);\n this.options.onListsAdopted?.(update);\n }\n\n private admitted(state: IPeerState, link: MeshPeerLink, result: IDeviceHandshakeResult): void {\n const attempt = state.pending;\n if (attempt === undefined || attempt.link !== link) return;\n // The inbox the peer reached is the pair's; any other admitted device is not this pair's peer.\n if (result.admission.deviceId !== state.device.deviceId) {\n link.close();\n return;\n }\n const admission = result.admission;\n const channelBinding = link.channelBinding;\n // Admitted means the handshake ran over both fingerprints; without them nothing is bound here.\n if (channelBinding === undefined) {\n link.close();\n return;\n }\n const authority = new ConnectionAuthority(\n {\n deviceId: admission.deviceId,\n sessionId: admission.sessionId,\n locality: admission.locality,\n capabilities: admission.capabilities,\n },\n this.options.operatorApprover,\n );\n link.onEnd(() => authority.close());\n // A message from the peer is delivered only when this connection's authority allows it; the\n // decision is taken once and every message waits on it, so order is kept.\n const messaging = authority.authorize('message');\n // Pushed lists are this node's, whatever the peer may ask; they never reach the application.\n link.onMessage((body) => {\n const frame = listsFrame(body);\n // allow-fallback: lists that cannot be taken leave the ones in force, as a failed handshake would\n if (frame !== undefined) this.receiveLists(frame).catch(() => undefined);\n });\n const exposed: IDeviceMeshLink = {\n admission,\n result,\n channelBinding,\n authority,\n send: (body) => link.send(body),\n onMessage: (handler) =>\n link.onMessage((body) => {\n if (listsFrame(body) !== undefined) return;\n void messaging.then((decision) => {\n if (decision.allowed && !link.ended) handler(body);\n else link.close();\n });\n }),\n openFileChannel: async () => fileFrameChannel(await link.openFileChannel()),\n onFileChannel: (handler) =>\n link.onFileChannel((channel) => handler(fileFrameChannel(channel))),\n onClose: (handler) => link.onEnd(() => handler()),\n close: () => link.close(),\n };\n attempt.exposed = exposed;\n state.relayFailures = 0;\n const previous = state.admitted;\n state.admitted = attempt;\n state.pending = undefined;\n previous?.link.close();\n this.options.relay.confirmPeer?.(admission.deviceId);\n for (const handler of this.linkHandlers) handler(exposed);\n }\n\n private ended(\n state: IPeerState,\n link: MeshPeerLink,\n end: TMeshLinkEnd,\n error: MeshLinkEndedError,\n ): void {\n if (state.admitted?.link === link) {\n state.admitted = undefined;\n return;\n }\n if (state.pending?.link !== link) return;\n const stage = state.pending.relayStage;\n state.pending = undefined;\n // This side took the peer's description, so the peer is up and paths were being tried, and\n // still none connected: ICE failed, or was still checking when time ran out. (An answer lost on\n // its way looks the same to the answerer; the cause attached says which.) Only this calls for\n // a relay; an attempt that ended before paths were tried, or after one connected, is reported\n // as it ended.\n const unreachable =\n error.stage === 'connecting' &&\n (end === 'timeout' || error.peer?.state === 'failed') &&\n error.peer?.history.includes('connected') !== true;\n let reported: unknown = error;\n if (error.cause instanceof MeshRelayNeededError) reported = error.cause;\n else if (unreachable && stage === 'direct') {\n reported = new MeshRelayNeededError(state.device.deviceId, 'no-direct-path', {\n cause: error,\n });\n } else if (unreachable) state.relayFailures += 1;\n const refusal: IDeviceMeshRefusal = {\n deviceId: state.device.deviceId,\n end,\n error: reported,\n };\n for (const handler of this.refusalHandlers) handler(refusal);\n }\n\n /**\n * The relays the next attempt to `state`'s peer uses: the ones paired devices advertise, then the\n * configured ones once those did not carry a connection, and round again. None: a direct attempt,\n * or — when only relay candidates may be used — a refusal naming the missing relay.\n */\n private async relayStage(\n state: IPeerState,\n ): Promise<{ readonly stage: TRelayStage; readonly servers: readonly IIceServer[] }> {\n const relays = this.options.relays;\n const stages: { readonly stage: TRelayStage; readonly servers: readonly IIceServer[] }[] = [];\n const embedded = await this.embeddedRelays(state);\n if (embedded.length > 0) stages.push({ stage: 'embedded', servers: embedded });\n if (relays?.configured !== undefined && relays.configured.length > 0) {\n stages.push({ stage: 'configured', servers: relays.configured });\n }\n if (stages.length === 0) {\n if (relays?.relayOnly === true) {\n throw new MeshRelayNeededError(state.device.deviceId, 'relay-only');\n }\n return { stage: 'direct', servers: [] };\n }\n return stages[state.relayFailures % stages.length]!;\n }\n\n /** The routes of every peer, `first`'s first: a lookup bounded to some peers covers it. */\n private peerRoutes(first?: IPeerState): IMeshPeerRoute[] {\n const states = [...this.peers.values()];\n const ordered = first === undefined ? states : [first, ...states.filter((p) => p !== first)];\n return ordered.map((peer) => ({\n deviceId: peer.device.deviceId,\n inbound: peer.inbound,\n outbound: peer.outbound,\n rendezvous: peer.rendezvous,\n }));\n }\n\n /**\n * ICE servers for the relays paired devices advertise, with this pair's credential for each: the\n * peer's own relay first. Only a device the lists in force name, unrevoked, is asked to relay.\n */\n private async embeddedRelays(state: IPeerState): Promise<IIceServer[]> {\n const advertised = this.options.relays?.advertised;\n if (advertised === undefined) return [];\n let found: ReadonlyMap<string, readonly IMeshRelayEndpoint[]>;\n try {\n found = await advertised(this.peerRoutes(state), AbortSignal.timeout(RELAY_ADVERT_LOOKUP_MS));\n } catch {\n // allow-fallback: no advertised relay is known; the configured ones, or the explicit refusal, follow\n return [];\n }\n const peerId = state.device.deviceId;\n const order = [peerId, ...[...found.keys()].filter((deviceId) => deviceId !== peerId)];\n const now = (this.options.now ?? Date.now)();\n const servers: IIceServer[] = [];\n let devices = 0;\n for (const deviceId of order) {\n if (devices >= MAX_RELAY_DEVICES) break;\n const endpoints = found.get(deviceId);\n const relayDevice = this.peers.get(deviceId);\n if (endpoints === undefined || endpoints.length === 0 || relayDevice === undefined) continue;\n devices += 1;\n servers.push(\n ...(await meshRelayIceServers(\n relayDevice.rendezvous,\n endpoints.slice(0, MAX_RELAY_ENDPOINTS),\n now,\n this.options.relays?.credentialTtlMs,\n )),\n );\n }\n return servers;\n }\n\n /**\n * A connection with a DTLS certificate of its own (the implementation makes one per connection):\n * a per-process certificate would be a stable identifier the relay could link across connections.\n * No ICE server is contacted unless one is configured or a paired device advertises its relay.\n */\n private createPeer(relayServers: readonly IIceServer[]): RtcPeer {\n const iceServers = [...(this.options.iceServers ?? []), ...relayServers];\n return new RtcPeer({\n ...(iceServers.length > 0 ? { iceServers } : {}),\n ...(this.options.relays?.relayOnly === true ? { forceTurn: true } : {}),\n ...(this.options.loadDataChannel !== undefined\n ? { loadDataChannel: this.options.loadDataChannel }\n : {}),\n });\n }\n}\n\nfunction toLinkSignal(signal: Exclude<TMeshSignal, { kind: 'hello' }>): TMeshLinkSignal {\n return signal.kind === 'ice'\n ? { kind: 'ice', candidate: { ...signal.candidate } }\n : { kind: signal.kind, sdp: signal.sdp };\n}\n","/**\n * The relay port a device mesh node signals through: be present at inbox topics, send an opaque\n * message to a topic, and hear back when nobody is present there.\n *\n * The relay is untrusted. It sees only opaque topics and signaling blobs; everything a node reads\n * from it is decoded as hostile input, and admission never depends on it.\n */\n\nimport type { IMeshPeerRoute } from './mesh-discovery.js';\n\nexport interface IMeshRelay {\n /** Be present at exactly these topics (replaces any earlier declaration). */\n declarePresence(topics: readonly string[]): void;\n /** Deliver `data` to whoever is present at `topic`. */\n send(topic: string, data: unknown): void;\n /** Messages delivered to a topic this relay client is present at. Returns an unsubscribe. */\n onMessage(handler: (topic: string, data: unknown) => void): () => void;\n /** Nobody was present at `topic` when a message was sent there. Returns an unsubscribe. */\n onAbsent(handler: (topic: string) => void): () => void;\n /**\n * The peers the node signals now (replaces any earlier declaration), for a relay that looks for\n * peers itself rather than only delivering to topics.\n */\n declarePeers?(peers: readonly IMeshPeerRoute[]): void;\n /** A connection to `deviceId` was admitted: the way its signals went worked. */\n confirmPeer?(deviceId: string): void;\n close(): void;\n}\n\nexport interface IInMemoryMeshRelayHub {\n /** A new relay client attached to this hub. */\n connect(): IMeshRelay;\n}\n\n/**\n * An in-process relay hub with the relay's delivery rules — for tests and loopback only: one holder\n * per topic (the newest), never an echo to the sender, `absent` when nobody holds the topic.\n * Deliveries run on a microtask, as a network would never deliver synchronously.\n */\nexport function createInMemoryMeshRelayHub(): IInMemoryMeshRelayHub {\n interface IClient {\n readonly messages: Set<(topic: string, data: unknown) => void>;\n readonly absents: Set<(topic: string) => void>;\n topics: Set<string>;\n open: boolean;\n }\n const holders = new Map<string, IClient>();\n\n return {\n connect(): IMeshRelay {\n const self: IClient = {\n messages: new Set(),\n absents: new Set(),\n topics: new Set(),\n open: true,\n };\n const withdraw = (): void => {\n for (const topic of self.topics) if (holders.get(topic) === self) holders.delete(topic);\n self.topics = new Set();\n };\n return {\n declarePresence(topics) {\n if (!self.open) return;\n withdraw();\n self.topics = new Set(topics);\n for (const topic of topics) holders.set(topic, self);\n },\n send(topic, data) {\n if (!self.open) return;\n // Cross the boundary as JSON, as a real relay would.\n const copy: unknown = JSON.parse(JSON.stringify(data));\n queueMicrotask(() => {\n const holder = holders.get(topic);\n if (holder === undefined || holder === self || !holder.open) {\n for (const handler of self.absents) handler(topic);\n return;\n }\n for (const handler of holder.messages) handler(topic, copy);\n });\n },\n onMessage(handler) {\n self.messages.add(handler);\n return () => self.messages.delete(handler);\n },\n onAbsent(handler) {\n self.absents.add(handler);\n return () => self.absents.delete(handler);\n },\n close() {\n self.open = false;\n withdraw();\n self.messages.clear();\n self.absents.clear();\n },\n };\n },\n };\n}\n","/**\n * {@link IMeshRelay} over a WebSocket to the self-hosted signaling relay's `presence` / `message`\n * frames.\n *\n * Frames sent before the socket opens are queued and flushed after the presence declaration, so a\n * message is never sent from a connection the relay does not yet consider present. A refusal from the\n * relay, a socket error, or a close the caller did not ask for goes to `onError` — never a silent\n * degrade.\n */\nimport WebSocket from 'ws';\n\nimport type { IMeshRelay } from './mesh-relay.js';\nimport type { IWebSocketLike } from './ws-signaling-client.js';\n\nexport interface IWsMeshRelayClientOptions {\n /** Relay URL, e.g. `wss://relay.example`. */\n readonly url: string;\n readonly onError?: (error: Error) => void;\n /** Called each time the relay confirms a presence declaration. */\n readonly onPresent?: () => void;\n /** Injectable socket factory (defaults to a real `ws` WebSocket) — for tests. */\n readonly createSocket?: (url: string) => IWebSocketLike;\n}\n\nconst WS_OPEN = 1;\n/** Messages queued before the socket opens; a connection attempt has a handful. */\nconst MAX_OUTBOX = 256;\n\nexport class WsMeshRelayClient implements IMeshRelay {\n private readonly socket: IWebSocketLike;\n private readonly messages = new Set<(topic: string, data: unknown) => void>();\n private readonly absents = new Set<(topic: string) => void>();\n private readonly outbox: string[] = [];\n private topics: readonly string[] | undefined;\n private closed = false;\n\n public constructor(private readonly options: IWsMeshRelayClientOptions) {\n const factory =\n options.createSocket ?? ((url: string) => new WebSocket(url) as unknown as IWebSocketLike);\n this.socket = factory(options.url);\n this.socket.on('open', () => this.flush());\n this.socket.on('message', (data) => this.receive(data));\n this.socket.on('error', (error) =>\n this.options.onError?.(error instanceof Error ? error : new Error(String(error))),\n );\n this.socket.on('close', () => {\n if (!this.closed) this.options.onError?.(new Error('signaling relay connection closed'));\n });\n }\n\n private flush(): void {\n if (this.topics !== undefined) this.write({ type: 'presence', topics: this.topics });\n for (const raw of this.outbox.splice(0)) this.socket.send(raw);\n }\n\n private write(frame: unknown): void {\n this.socket.send(JSON.stringify(frame));\n }\n\n private receive(data: unknown): void {\n let frame: unknown;\n try {\n frame = JSON.parse(typeof data === 'string' ? data : String(data));\n } catch {\n return; // the relay only emits JSON\n }\n if (typeof frame !== 'object' || frame === null) return;\n const record = frame as Record<string, unknown>;\n if (record.type === 'message' && typeof record.topic === 'string') {\n for (const handler of this.messages) handler(record.topic, record.data);\n } else if (record.type === 'absent' && typeof record.topic === 'string') {\n for (const handler of this.absents) handler(record.topic);\n } else if (record.type === 'present') {\n this.options.onPresent?.();\n } else if (record.type === 'error') {\n this.options.onError?.(\n new Error(`signaling relay refused a frame: ${String(record.reason)}`),\n );\n }\n }\n\n public declarePresence(topics: readonly string[]): void {\n if (this.closed) return;\n this.topics = [...topics];\n if (this.socket.readyState === WS_OPEN) this.write({ type: 'presence', topics: this.topics });\n }\n\n public send(topic: string, data: unknown): void {\n if (this.closed) return;\n const raw = JSON.stringify({ type: 'message', topic, data });\n if (this.socket.readyState === WS_OPEN) {\n this.socket.send(raw);\n } else if (this.outbox.length < MAX_OUTBOX) {\n this.outbox.push(raw);\n } else {\n this.options.onError?.(new Error('signaling relay is not reachable; message dropped'));\n }\n }\n\n public onMessage(handler: (topic: string, data: unknown) => void): () => void {\n this.messages.add(handler);\n return () => this.messages.delete(handler);\n }\n\n public onAbsent(handler: (topic: string) => void): () => void {\n this.absents.add(handler);\n return () => this.absents.delete(handler);\n }\n\n public close(): void {\n this.closed = true;\n this.messages.clear();\n this.absents.clear();\n this.outbox.length = 0;\n this.socket.close();\n }\n}\n","/**\n * Where a peer device might be reached for signaling, and where that answer comes from.\n *\n * A candidate is only an address to try. Whatever answers there is untrusted like any relay: it\n * carries signals, and the peer is still admitted by the device handshake or not at all. So a\n * candidate may be stale, forged or someone else's without granting anything.\n */\nimport type { IPairRendezvous } from '@robota-sdk/agent-remote-pairing';\n\n/** A direct signaling endpoint a peer might be listening on. */\nexport interface IMeshCandidate {\n readonly host: string;\n readonly port: number;\n}\n\n/** A peer device a node signals, with the pair's rendezvous and relay inbox topics. */\nexport interface IMeshPeerRoute {\n readonly deviceId: string;\n /** Where this device listens for the peer on a relay. */\n readonly inbound: string;\n /** Where this device sends to reach the peer on a relay. */\n readonly outbound: string;\n readonly rendezvous: IPairRendezvous;\n}\n\n/** One way of finding a peer's direct signaling endpoint. */\nexport interface IMeshCandidateSource {\n /** How long a lookup of this source may take; default: the caller's. */\n readonly timeoutMs?: number;\n /** Candidates for `peer`, best first; empty when this source knows none. Never throws. */\n candidates(peer: IMeshPeerRoute, signal: AbortSignal): Promise<readonly IMeshCandidate[]>;\n}\n\n/** The last candidates that worked for each peer device, most recent first. */\nexport interface IMeshAddressCache {\n recall(deviceId: string): readonly IMeshCandidate[];\n remember(deviceId: string, candidate: IMeshCandidate): void;\n /** Forget every device but these: a device that left the lists has no address worth keeping. */\n retain(deviceIds: readonly string[]): void;\n /** The port this device listened on last, to listen on again so peers' caches stay useful. */\n lastListenPort(): number | undefined;\n rememberListenPort(port: number): void;\n}\n\n/** Candidates per device the cache keeps. */\nexport const MAX_CACHED_CANDIDATES = 3;\n\n/** The cache, as the first candidate source. */\nexport function addressCacheSource(cache: IMeshAddressCache): IMeshCandidateSource {\n return { candidates: (peer) => Promise.resolve(cache.recall(peer.deviceId)) };\n}\n\nfunction sameCandidate(a: IMeshCandidate, b: IMeshCandidate): boolean {\n return a.host === b.host && a.port === b.port;\n}\n\n/** `candidate` first, then the others it does not repeat, bounded. */\nfunction withCandidateFirst(\n candidates: readonly IMeshCandidate[],\n candidate: IMeshCandidate,\n): IMeshCandidate[] {\n return [\n { host: candidate.host, port: candidate.port },\n ...candidates.filter((c) => !sameCandidate(c, candidate)),\n ].slice(0, MAX_CACHED_CANDIDATES);\n}\n\n/** An address cache that lives as long as the process — for tests and callers without a state dir. */\nexport function createInMemoryMeshAddressCache(): IMeshAddressCache {\n const byDevice = new Map<string, IMeshCandidate[]>();\n let listenPort: number | undefined;\n return {\n recall: (deviceId) => byDevice.get(deviceId) ?? [],\n remember: (deviceId, candidate) => {\n byDevice.set(deviceId, withCandidateFirst(byDevice.get(deviceId) ?? [], candidate));\n },\n retain: (deviceIds) => {\n const keep = new Set(deviceIds);\n for (const deviceId of [...byDevice.keys()])\n if (!keep.has(deviceId)) byDevice.delete(deviceId);\n },\n lastListenPort: () => listenPort,\n rememberListenPort: (port) => {\n listenPort = port;\n },\n };\n}\n","/**\n * This device's direct signaling endpoint on the local network: a peer that found it (by the address\n * cache or mDNS) delivers mesh signals here instead of through a relay.\n *\n * It is a mailbox with a relay's rules and nothing more. It delivers only to the rotating pairwise\n * topics this device holds and treats every byte as hostile: frames and connections are bounded, a\n * connection that floods is closed, a browser page is turned away, and nothing that arrives here is\n * trusted — the peer is admitted by the device handshake over the WebRTC channel these signals set\n * up, or not at all.\n *\n * A topic never crosses the network. Frames are addressed by its hash, and an endpoint proves it\n * holds a topic by a MAC under the topic over the prober's nonce, so an endpoint that merely echoes\n * what it is sent, or replays what it overheard, cannot pass for the peer and capture the pair's\n * signals.\n */\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto';\n\nimport { WebSocketServer, type WebSocket } from 'ws';\n\n/** A LAN address (a topic's hash) or a nonce: base64url. */\nconst LAN_ADDRESS = /^[A-Za-z0-9_-]{43}$/;\nconst NONCE = /^[A-Za-z0-9_-]{22}$/;\n/** Largest frame accepted; an SDP with its candidates fits well within it. */\nexport const MAX_LAN_FRAME_BYTES = 64 * 1024;\nconst MAX_CONNECTIONS = 32;\n/** Frames a connection may send per window before it is closed. */\nconst MAX_FRAMES_PER_WINDOW = 128;\nconst FRAME_WINDOW_MS = 10_000;\n/** A connection that says nothing for this long is closed. */\nconst IDLE_MS = 120_000;\nconst POLICY_VIOLATION = 1008;\n\nexport interface IMeshLanListener {\n /** The port it listens on. */\n readonly port: number;\n /** Hold exactly these topics (replaces any earlier set). */\n setPresence(topics: readonly string[]): void;\n /** A signal delivered to a held topic, named by the topic. Returns an unsubscribe. */\n onMessage(handler: (topic: string, data: unknown) => void): () => void;\n close(): Promise<void>;\n}\n\nexport interface IMeshLanListenerOptions {\n /** The port to listen on; when it is taken, any free port. Default: any free port. */\n readonly port?: number;\n /** The address to bind; default: every interface. */\n readonly host?: string;\n}\n\n/** Where frames for `topic` are addressed on the network. */\nexport function lanAddressOf(topic: string): string {\n return createHash('sha256').update(`lan-address:${topic}`).digest('base64url');\n}\n\n/** What an endpoint holding `topic` answers a probe carrying `nonce` with. */\nexport function lanPresenceProof(topic: string, nonce: string): string {\n return createHmac('sha256', topic).update(`lan-present:${nonce}`).digest('base64url');\n}\n\n/** Whether `proof` shows its sender holds `topic`. */\nexport function verifyLanPresenceProof(topic: string, nonce: string, proof: unknown): boolean {\n if (typeof proof !== 'string') return false;\n const expected = Buffer.from(lanPresenceProof(topic, nonce));\n const given = Buffer.from(proof);\n return given.length === expected.length && timingSafeEqual(given, expected);\n}\n\nfunction listen(port: number, host: string | undefined): Promise<WebSocketServer> {\n return new Promise((resolve, reject) => {\n const server = new WebSocketServer({\n port,\n ...(host !== undefined ? { host } : {}),\n maxPayload: MAX_LAN_FRAME_BYTES,\n perMessageDeflate: false,\n // A peer device sends no Origin; a browser always does, and no web page has business here.\n verifyClient: (info: { origin?: string }) => info.origin === undefined || info.origin === '',\n });\n const onError = (error: Error): void => {\n server.close();\n reject(error);\n };\n server.once('error', onError);\n server.once('listening', () => {\n server.off('error', onError);\n resolve(server);\n });\n });\n}\n\nfunction decodeFrame(\n raw: unknown,\n): { type: string; to: string; nonce?: string; data?: unknown } | undefined {\n let frame: unknown;\n try {\n frame = JSON.parse(String(raw));\n } catch {\n return undefined;\n }\n if (typeof frame !== 'object' || frame === null || Array.isArray(frame)) return undefined;\n const r = frame as Record<string, unknown>;\n if (typeof r.type !== 'string' || typeof r.to !== 'string' || !LAN_ADDRESS.test(r.to)) {\n return undefined;\n }\n if (r.type === 'probe') {\n return typeof r.nonce === 'string' && NONCE.test(r.nonce)\n ? { type: r.type, to: r.to, nonce: r.nonce }\n : undefined;\n }\n return { type: r.type, to: r.to, data: r.data };\n}\n\nexport async function startMeshLanListener(\n options: IMeshLanListenerOptions = {},\n): Promise<IMeshLanListener> {\n let server: WebSocketServer;\n try {\n server = await listen(options.port ?? 0, options.host);\n } catch (error) {\n // allow-fallback: the remembered port is only a preference; any free port serves, peers find it again\n if (options.port === undefined || options.port === 0) throw error;\n server = await listen(0, options.host);\n }\n const address = server.address();\n const port = typeof address === 'object' && address !== null ? address.port : 0;\n /** Address → topic. */\n let held = new Map<string, string>();\n const handlers = new Set<(topic: string, data: unknown) => void>();\n\n server.on('connection', (socket: WebSocket) => {\n if (server.clients.size > MAX_CONNECTIONS) {\n socket.close(POLICY_VIOLATION);\n return;\n }\n let frames = 0;\n let windowStart = Date.now();\n let idle = setTimeout(() => socket.close(), IDLE_MS);\n idle.unref?.();\n socket.on('close', () => clearTimeout(idle));\n socket.on('error', () => socket.close());\n socket.on('message', (raw: unknown) => {\n const now = Date.now();\n if (now - windowStart > FRAME_WINDOW_MS) {\n windowStart = now;\n frames = 0;\n }\n frames += 1;\n if (frames > MAX_FRAMES_PER_WINDOW) {\n socket.close(POLICY_VIOLATION);\n return;\n }\n clearTimeout(idle);\n idle = setTimeout(() => socket.close(), IDLE_MS);\n idle.unref?.();\n const frame = decodeFrame(raw);\n if (frame === undefined) return;\n const topic = held.get(frame.to);\n if (topic === undefined) {\n socket.send(JSON.stringify({ type: 'absent', to: frame.to }));\n return;\n }\n if (frame.type === 'probe' && frame.nonce !== undefined) {\n socket.send(\n JSON.stringify({\n type: 'present',\n to: frame.to,\n nonce: frame.nonce,\n proof: lanPresenceProof(topic, frame.nonce),\n }),\n );\n return;\n }\n if (frame.type !== 'message') return;\n for (const handler of handlers) handler(topic, frame.data);\n });\n });\n\n return {\n port,\n setPresence(topics) {\n held = new Map(topics.map((topic) => [lanAddressOf(topic), topic]));\n },\n onMessage(handler) {\n handlers.add(handler);\n return () => handlers.delete(handler);\n },\n close() {\n handlers.clear();\n held = new Map();\n for (const client of server.clients) client.terminate();\n return new Promise<void>((resolve) => server.close(() => resolve()));\n },\n };\n}\n","/**\n * Finding peer devices on the local network with multicast DNS service discovery (RFC 6762/6763),\n * without telling the network whose devices they are or how many.\n *\n * - The service type is generic and names no product.\n * - Every instance name is a pairwise tag for `dir(self → peer)` in the current epoch, so only that\n * peer recognises it, and it changes every epoch. The host name the records point at is random per\n * epoch rather than the machine's name.\n * - The instance count is padded with random tags to a fixed step, so the answer does not reveal\n * how many devices this one pairs with. The padding and host name hold for the whole epoch, so\n * comparing two answers from one epoch does not tell the real tags from the padding either.\n *\n * A lookup asks for the service, keeps the instances whose name is one of the peer's tags for the\n * adjacent epochs, and yields the answering address with the advertised port. That is a candidate\n * only: anyone on the network can answer too, and gains nothing, because the peer is admitted by the\n * device handshake or not at all. Since anyone can answer, a lookup does not stop at the first match:\n * it keeps listening a little longer, and takes only a few answers from any one address, so neither\n * an early answer nor many answers from one host can stand in for the peer's own.\n */\nimport { createRequire } from 'node:module';\nimport { networkInterfaces } from 'node:os';\n\nimport { rendezvousEpoch, RENDEZVOUS_EPOCH_MS } from '@robota-sdk/agent-remote-pairing';\n\nimport type { IMeshCandidate, IMeshCandidateSource, IMeshPeerRoute } from './mesh-discovery.js';\n\n/** The DNS-SD service type. Generic on purpose: it must not name the product. */\nexport const MESH_MDNS_SERVICE = '_rdv._tcp.local';\n/** Instance counts are padded up to a multiple of this. */\nexport const MESH_MDNS_PAD_STEP = 8;\n/** Bytes of a pairwise tag used as an instance name (hex-encoded). */\nconst INSTANCE_TAG_BYTES = 16;\nconst RECORD_TTL_S = 120;\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 1_000;\n/** After the first match, how much longer a lookup listens for more answers. */\nconst DEFAULT_ANSWER_GRACE_MS = 200;\n/** Candidates one lookup yields. */\nconst MAX_LOOKUP_CANDIDATES = 8;\n/** Candidates one lookup takes from one answering address. */\nconst MAX_CANDIDATES_PER_ADDRESS = 2;\n/** Answers go out at most this often; queries in between share the next one (RFC 6762 §6). */\nconst DEFAULT_MIN_ANSWER_INTERVAL_MS = 500;\n\n/** A DNS resource record, in the shape `multicast-dns` reads and writes. */\nexport interface IMdnsRecord {\n readonly name: string;\n readonly type: string;\n readonly ttl?: number;\n readonly data?: unknown;\n}\n\nexport interface IMdnsQuestion {\n readonly name: string;\n readonly type: string;\n}\n\nexport interface IMdnsPacket {\n readonly questions?: readonly IMdnsQuestion[];\n readonly answers?: readonly IMdnsRecord[];\n readonly additionals?: readonly IMdnsRecord[];\n}\n\nexport interface IMdnsRemote {\n readonly address: string;\n}\n\n/** The slice of a `multicast-dns` instance this module drives. */\nexport interface IMdnsTransport {\n on(\n event: 'query' | 'response',\n listener: (packet: IMdnsPacket, rinfo: IMdnsRemote) => void,\n ): void;\n on(event: 'error' | 'warning', listener: (error: Error) => void): void;\n removeListener(\n event: 'response',\n listener: (packet: IMdnsPacket, rinfo: IMdnsRemote) => void,\n ): void;\n query(packet: { readonly questions: readonly IMdnsQuestion[] }): void;\n respond(packet: {\n readonly answers: readonly IMdnsRecord[];\n readonly additionals?: readonly IMdnsRecord[];\n }): void;\n destroy(): void;\n}\n\nexport interface IMeshMdnsOptions {\n /** Default: a `multicast-dns` socket on the standard group and port. */\n readonly createTransport?: () => IMdnsTransport;\n /** This device's addresses for the A/AAAA records. Default: its non-internal interfaces. */\n readonly addresses?: () => readonly string[];\n readonly lookupTimeoutMs?: number;\n /** After the first match, how much longer a lookup listens (within `lookupTimeoutMs`). */\n readonly answerGraceMs?: number;\n readonly minAnswerIntervalMs?: number;\n /** mDNS cannot run (e.g. the port is unavailable): the other candidate sources still do. */\n readonly onError?: (error: Error) => void;\n readonly now?: () => number;\n}\n\nfunction defaultTransport(): IMdnsTransport {\n const requireFrom = createRequire(import.meta.url);\n // eslint-disable-next-line @typescript-eslint/no-require-imports -- resolved only when mDNS is used, so importing the package opens no socket\n const create = requireFrom('multicast-dns') as () => IMdnsTransport;\n return create();\n}\n\n/** This device's non-internal interface addresses (no IPv6 link-local). */\nexport function localInterfaceAddresses(): string[] {\n const out: string[] = [];\n for (const entries of Object.values(networkInterfaces())) {\n for (const entry of entries ?? []) {\n if (entry.internal) continue;\n if (\n entry.family === 'IPv4' ||\n (entry.family === 'IPv6' && !entry.address.startsWith('fe80'))\n ) {\n out.push(entry.address);\n }\n }\n }\n return out;\n}\n\nfunction randomHex(bytes: number): string {\n const out = new Uint8Array(bytes);\n globalThis.crypto.getRandomValues(out);\n return Buffer.from(out).toString('hex');\n}\n\nfunction instanceLabel(tag: Uint8Array): string {\n return Buffer.from(tag.subarray(0, INSTANCE_TAG_BYTES)).toString('hex');\n}\n\n/** How many instances `count` real ones are announced as. */\nexport function paddedInstanceCount(count: number): number {\n return Math.max(MESH_MDNS_PAD_STEP, Math.ceil(count / MESH_MDNS_PAD_STEP) * MESH_MDNS_PAD_STEP);\n}\n\nfunction sameName(a: string, b: string): boolean {\n return a.toLowerCase() === b.toLowerCase();\n}\n\nexport class MeshMdns implements IMeshCandidateSource {\n private transport?: IMdnsTransport;\n private routes: readonly IMeshPeerRoute[] = [];\n private port?: number;\n private records?: { readonly answers: IMdnsRecord[]; readonly additionals: IMdnsRecord[] };\n private instances = new Set<string>();\n private timer?: ReturnType<typeof setTimeout>;\n private generation = 0;\n private closed = false;\n /** This epoch's padding labels and host name. */\n private epochCover?: {\n readonly epoch: number;\n readonly padding: string[];\n readonly target: string;\n };\n /** What was last announced, to announce again only when it changes. */\n private announced?: string;\n private lastAnswer?: number;\n private answerTimer?: ReturnType<typeof setTimeout>;\n\n public constructor(private readonly options: IMeshMdnsOptions = {}) {}\n\n private now(): number {\n return (this.options.now ?? Date.now)();\n }\n\n /** The transport, created on first use. `undefined` once it failed or the source is closed. */\n private open(): IMdnsTransport | undefined {\n if (this.closed) return undefined;\n if (this.transport !== undefined) return this.transport;\n try {\n const transport = (this.options.createTransport ?? defaultTransport)();\n transport.on('query', (packet: IMdnsPacket) => this.onQuery(packet));\n transport.on('error', (error: Error) => this.options.onError?.(error));\n this.transport = transport;\n return transport;\n } catch (error) {\n this.closed = true;\n this.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n return undefined;\n }\n }\n\n /** Announce this device to `routes`' peers, as a signaling endpoint on `port`. */\n public advertise(routes: readonly IMeshPeerRoute[], port: number): Promise<void> {\n this.routes = routes;\n this.port = port;\n return this.rebuild();\n }\n\n private async rebuild(): Promise<void> {\n const generation = ++this.generation;\n const port = this.port;\n if (this.closed || port === undefined) return;\n const epoch = rendezvousEpoch(this.now());\n const tags = await Promise.all(\n this.routes.map((route) => route.rendezvous.tag('mdns', 'outbound', epoch)),\n );\n if (generation !== this.generation || this.closed) return;\n if (this.epochCover?.epoch !== epoch) {\n this.epochCover = { epoch, padding: [], target: `${randomHex(8)}.local` };\n }\n const { padding, target } = this.epochCover;\n const needed = paddedInstanceCount(tags.length) - tags.length;\n while (padding.length < needed) padding.push(randomHex(INSTANCE_TAG_BYTES));\n const labels = [...tags.map(instanceLabel), ...padding.slice(0, needed)].sort();\n const signature = JSON.stringify([epoch, port, labels]);\n const answers: IMdnsRecord[] = labels.map((label) => ({\n name: MESH_MDNS_SERVICE,\n type: 'PTR',\n ttl: RECORD_TTL_S,\n data: `${label}.${MESH_MDNS_SERVICE}`,\n }));\n const additionals: IMdnsRecord[] = [];\n for (const label of labels) {\n const name = `${label}.${MESH_MDNS_SERVICE}`;\n additionals.push(\n { name, type: 'SRV', ttl: RECORD_TTL_S, data: { priority: 0, weight: 0, port, target } },\n { name, type: 'TXT', ttl: RECORD_TTL_S, data: [] },\n );\n }\n for (const address of (this.options.addresses ?? localInterfaceAddresses)()) {\n additionals.push({\n name: target,\n type: address.includes(':') ? 'AAAA' : 'A',\n ttl: RECORD_TTL_S,\n data: address,\n });\n }\n this.records = { answers, additionals };\n this.instances = new Set(labels.map((label) => `${label}.${MESH_MDNS_SERVICE}`.toLowerCase()));\n this.schedule(epoch);\n if (signature === this.announced) return;\n this.announced = signature;\n // An unsolicited announcement, so peers already browsing hear of the new names at once.\n this.open()?.respond(this.records);\n }\n\n private schedule(epoch: number): void {\n if (this.timer !== undefined) clearTimeout(this.timer);\n const delay = Math.max(0, (epoch + 1) * RENDEZVOUS_EPOCH_MS - this.now());\n this.timer = setTimeout(() => {\n // allow-fallback: a failed rebuild keeps the last epoch's names; lookups try adjacent epochs\n this.rebuild().catch(() => undefined);\n }, delay);\n this.timer.unref?.();\n }\n\n private onQuery(packet: IMdnsPacket): void {\n const records = this.records;\n if (records === undefined || this.closed) return;\n const asked = (packet.questions ?? []).some(\n (q) =>\n (sameName(q.name, MESH_MDNS_SERVICE) && (q.type === 'PTR' || q.type === 'ANY')) ||\n this.instances.has(q.name.toLowerCase()),\n );\n if (asked) this.answer();\n }\n\n /** Answer now, or once the interval since the last answer has passed; queries meanwhile share it. */\n private answer(): void {\n if (this.answerTimer !== undefined) return;\n const interval = this.options.minAnswerIntervalMs ?? DEFAULT_MIN_ANSWER_INTERVAL_MS;\n const wait = this.lastAnswer === undefined ? 0 : this.lastAnswer + interval - this.now();\n const send = (): void => {\n this.answerTimer = undefined;\n if (this.closed || this.records === undefined) return;\n this.lastAnswer = this.now();\n this.transport?.respond(this.records);\n };\n if (wait <= 0) {\n send();\n return;\n }\n this.answerTimer = setTimeout(send, wait);\n this.answerTimer.unref?.();\n }\n\n public async candidates(\n peer: IMeshPeerRoute,\n signal: AbortSignal,\n ): Promise<readonly IMeshCandidate[]> {\n const transport = this.open();\n if (transport === undefined || signal.aborted) return [];\n const epoch = rendezvousEpoch(this.now());\n const wanted = new Set(\n (await peer.rendezvous.lookupTags('mdns', epoch)).map(\n (tag) => `${instanceLabel(tag)}.${MESH_MDNS_SERVICE}`,\n ),\n );\n if (signal.aborted || this.closed) return [];\n return new Promise<readonly IMeshCandidate[]>((resolve) => {\n const found: IMeshCandidate[] = [];\n let grace: ReturnType<typeof setTimeout> | undefined;\n const finish = (): void => {\n clearTimeout(timer);\n clearTimeout(grace);\n signal.removeEventListener('abort', finish);\n transport.removeListener('response', onResponse);\n resolve(found);\n };\n const onResponse = (packet: IMdnsPacket, rinfo: IMdnsRemote): void => {\n const records = [...(packet.answers ?? []), ...(packet.additionals ?? [])];\n for (const ptr of records) {\n if (ptr.type !== 'PTR' || !sameName(ptr.name, MESH_MDNS_SERVICE)) continue;\n if (typeof ptr.data !== 'string' || !wanted.has(ptr.data.toLowerCase())) continue;\n const srv = records.find((r) => r.type === 'SRV' && sameName(r.name, ptr.data as string));\n const port = (srv?.data as { port?: unknown } | undefined)?.port;\n if (typeof port !== 'number' || !Number.isInteger(port) || port <= 0 || port > 65_535) {\n continue;\n }\n // The address the answer came from: the peer's own records could name any address.\n // No one address takes more than a few of the places, so one host cannot fill them all.\n const fromAddress = found.filter((c) => c.host === rinfo.address);\n if (\n found.length < MAX_LOOKUP_CANDIDATES &&\n fromAddress.length < MAX_CANDIDATES_PER_ADDRESS &&\n !fromAddress.some((c) => c.port === port)\n ) {\n found.push({ host: rinfo.address, port });\n }\n }\n if (found.length === 0 || grace !== undefined) return;\n grace = setTimeout(finish, this.options.answerGraceMs ?? DEFAULT_ANSWER_GRACE_MS);\n grace.unref?.();\n };\n const timer = setTimeout(finish, this.options.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS);\n timer.unref?.();\n signal.addEventListener('abort', finish, { once: true });\n transport.on('response', onResponse);\n transport.query({ questions: [{ name: MESH_MDNS_SERVICE, type: 'PTR' }] });\n });\n }\n\n public close(): void {\n this.closed = true;\n this.generation += 1;\n if (this.timer !== undefined) clearTimeout(this.timer);\n if (this.answerTimer !== undefined) clearTimeout(this.answerTimer);\n this.transport?.destroy();\n this.transport = undefined;\n }\n}\n\nexport interface IInMemoryMdnsBus {\n /** A transport on this bus whose answers come from `address`. */\n transport(address: string): IMdnsTransport;\n /** Every packet answered on the bus, in order. */\n readonly responses: readonly IMdnsPacket[];\n}\n\n/**\n * An in-process multicast group — for tests and loopback only. A query reaches every member,\n * itself included, as multicast loops back; an answer reaches every member with the answering\n * member's address. Packets cross as JSON and are delivered on a microtask, as a network would.\n */\nexport function createInMemoryMdnsBus(): IInMemoryMdnsBus {\n type TListener = (packet: IMdnsPacket, rinfo: IMdnsRemote) => void;\n interface IMember {\n readonly address: string;\n readonly queries: Set<TListener>;\n readonly responses: Set<TListener>;\n }\n const members = new Set<IMember>();\n const responses: IMdnsPacket[] = [];\n const broadcast = (\n from: IMember,\n packet: IMdnsPacket,\n pick: (member: IMember) => Set<TListener>,\n ): void => {\n const copy = JSON.parse(JSON.stringify(packet)) as IMdnsPacket;\n queueMicrotask(() => {\n for (const member of members) {\n for (const listener of pick(member)) listener(copy, { address: from.address });\n }\n });\n };\n return {\n responses,\n transport(address) {\n const self: IMember = { address, queries: new Set(), responses: new Set() };\n members.add(self);\n return {\n on(event: string, listener: TListener) {\n if (event === 'query') self.queries.add(listener);\n if (event === 'response') self.responses.add(listener);\n },\n removeListener(_event, listener) {\n self.responses.delete(listener);\n },\n query(packet) {\n broadcast(self, packet, (m) => m.queries);\n },\n respond(packet) {\n responses.push(JSON.parse(JSON.stringify(packet)) as IMdnsPacket);\n broadcast(self, packet, (m) => m.responses);\n },\n destroy() {\n members.delete(self);\n },\n } as IMdnsTransport;\n },\n };\n}\n","/**\n * A mesh relay that looks for each peer itself before it falls back to a relay: the candidate\n * sources in order (the address cache, mDNS, then public records), each candidate probed at the\n * peer's direct signaling endpoint, and when none answers the signaling carriers in order (public\n * Nostr relays, then the self-hosted relay).\n *\n * Every way a signal can go is equally untrusted. Discovery only chooses where the signals of a\n * pair go; the node decodes whatever arrives as hostile input and admits a peer by the device\n * handshake alone, so a stale, forged or hijacked candidate can delay a connection but never admit\n * one. A candidate carries a pair's signals only once it proves it holds the pair's topic, and only\n * until an attempt over it fails to reach admission in time; then it is set aside and the next way\n * is tried, so a hostile endpoint cannot hold a pair off the relay. The time to admission counts from\n * a signal that can only precede one, so what trails an admitted attempt cannot set a working way aside;\n * a way that fails again is set aside for longer each time. A carrier that is not the last one is set\n * aside the same way, so relays that drop signals cannot hold a pair off the next carrier. On\n * the local network signals are addressed by rotating pairwise tags rather than the relay's inbox\n * topics. A candidate is remembered only once a connection it carried was admitted.\n */\nimport { rendezvousEpoch, RENDEZVOUS_EPOCH_MS } from '@robota-sdk/agent-remote-pairing';\nimport WebSocket from 'ws';\n\nimport {\n addressCacheSource,\n createInMemoryMeshAddressCache,\n type IMeshAddressCache,\n type IMeshCandidate,\n type IMeshCandidateSource,\n type IMeshPeerRoute,\n} from './mesh-discovery.js';\nimport {\n lanAddressOf,\n startMeshLanListener,\n verifyLanPresenceProof,\n type IMeshLanListener,\n} from './mesh-lan-listener.js';\nimport { MeshMdns, type IMeshMdnsOptions } from './mesh-mdns.js';\nimport type { MeshDht } from './mesh-dht.js';\nimport type { IMeshRelay } from './mesh-relay.js';\nimport type { IWebSocketLike } from './ws-signaling-client.js';\n\nconst WS_OPEN = 1;\n/** How long a candidate has to answer a probe. */\nconst DEFAULT_PROBE_TIMEOUT_MS = 1_500;\n/** How long one candidate source may look. */\nconst DEFAULT_SOURCE_TIMEOUT_MS = 1_500;\n/** After every candidate failed, the relay carries the pair's signals this long before looking again. */\nconst DEFAULT_RELAY_RECHECK_MS = 30_000;\n/** Candidates probed per lookup, across all sources. */\nconst MAX_PROBES = 8;\n/** Signals held for a pair while its way is being looked for. */\nconst MAX_QUEUED = 256;\n/** A direct path that has not carried an admission in this long is set aside. */\nconst DEFAULT_ADMISSION_TIMEOUT_MS = 20_000;\n/** A way that keeps failing is set aside for at most this many recheck periods at a time. */\nconst MAX_SET_ASIDE_FACTOR = 16;\n/** Ways whose failures a pair remembers; the oldest is forgotten first. */\nconst MAX_REMEMBERED_FAILURES = 32;\n\n/** Announces this device to its peers somewhere they look. */\nexport interface IMeshAdvertiser {\n advertise(routes: readonly IMeshPeerRoute[], port: number): Promise<void>;\n close(): void;\n}\n\nexport interface IDiscoveringMeshRelayOptions {\n /**\n * The self-hosted relay: the last resort. Absent with no `signaling` either: a pair no candidate\n * reaches cannot be signaled.\n */\n readonly relay?: IMeshRelay;\n /** Public signaling carriers, tried in order before `relay` when no candidate answers. */\n readonly signaling?: readonly IMeshRelay[];\n /** This device's direct signaling endpoint. */\n readonly listener: IMeshLanListener;\n /** Where to look for a peer's endpoint, in order. */\n readonly sources: readonly IMeshCandidateSource[];\n /** Remembers candidates that carried an admitted connection. */\n readonly cache?: IMeshAddressCache;\n /** Told the peers and this device's endpoint port, to announce this device; closed with this relay. */\n readonly advertisers?: readonly IMeshAdvertiser[];\n /** Test seam: open a socket to a candidate. */\n readonly connect?: (candidate: IMeshCandidate) => IWebSocketLike;\n readonly probeTimeoutMs?: number;\n readonly sourceTimeoutMs?: number;\n readonly relayRecheckMs?: number;\n /** How long a direct path may carry signals without an admission before it is set aside. */\n readonly admissionTimeoutMs?: number;\n readonly onError?: (error: Error) => void;\n readonly now?: () => number;\n}\n\ntype TPath =\n | { readonly kind: 'unresolved' }\n | { readonly kind: 'resolving'; readonly queue: unknown[] }\n | {\n readonly kind: 'direct';\n readonly socket: IWebSocketLike;\n readonly candidate: IMeshCandidate;\n /** Running from the first attempt since the last admission until the next admission. */\n deadline?: ReturnType<typeof setTimeout>;\n }\n | {\n readonly kind: 'relay';\n readonly until: number;\n readonly carrier: IMeshRelay;\n /** As a direct path's, unless this is the last carrier. */\n deadline?: ReturnType<typeof setTimeout>;\n };\n\n/** Ways set aside because they carried no admission, and how often each has failed in a row. */\nclass SetAside<K> {\n private readonly until = new Map<K, number>();\n private readonly failures = new Map<K, number>();\n\n /** Whether `key` is set aside at `now`. */\n public has(key: K, now: number): boolean {\n const until = this.until.get(key);\n if (until === undefined) return false;\n if (until > now) return true;\n this.until.delete(key);\n return false;\n }\n\n /** `key` failed again: set aside twice as long as the last time, within the bound. */\n public fail(key: K, now: number, periodMs: number): void {\n const failures = (this.failures.get(key) ?? 0) + 1;\n this.failures.delete(key);\n this.failures.set(key, failures);\n for (const oldest of this.failures.keys()) {\n if (this.failures.size <= MAX_REMEMBERED_FAILURES) break;\n this.failures.delete(oldest);\n this.until.delete(oldest);\n }\n const factor = Math.min(2 ** (failures - 1), MAX_SET_ASIDE_FACTOR);\n this.until.set(key, now + periodMs * factor);\n }\n\n /** An admission: every way starts over. */\n public clear(): void {\n this.until.clear();\n this.failures.clear();\n }\n}\n\n/**\n * Whether a signal can only precede an attempt's admission: a `hello`, an `offer` or an `answer`.\n * ICE candidates keep trickling after it.\n */\nfunction precedesAdmission(data: unknown): boolean {\n if (typeof data !== 'object' || data === null) return false;\n const kind = (data as { kind?: unknown }).kind;\n return kind === 'hello' || kind === 'offer' || kind === 'answer';\n}\n\ninterface IRouteState {\n route: IMeshPeerRoute;\n /** The topic this device's signals to the peer go to on the local network, this epoch. */\n lanOut: string;\n path: TPath;\n /** Candidates (`host:port`) set aside, because they carried no admission. */\n readonly avoid: SetAside<string>;\n /** Carriers set aside, because they carried no admission. */\n readonly avoidCarriers: SetAside<IMeshRelay>;\n}\n\nfunction candidateKey(candidate: IMeshCandidate): string {\n return `${candidate.host}:${candidate.port}`;\n}\n\nfunction randomNonce(): string {\n const bytes = new Uint8Array(16);\n globalThis.crypto.getRandomValues(bytes);\n return Buffer.from(bytes).toString('base64url');\n}\n\n/** A pairwise tag as a LAN topic. */\nfunction toLanTopic(tag: Uint8Array): string {\n return Buffer.from(tag).toString('base64url');\n}\n\nfunction candidateUrl(candidate: IMeshCandidate): string {\n const host = candidate.host.includes(':') ? `[${candidate.host}]` : candidate.host;\n return `ws://${host}:${candidate.port}`;\n}\n\nfunction parse(\n raw: unknown,\n): { type?: unknown; to?: unknown; nonce?: unknown; proof?: unknown } | undefined {\n try {\n const frame: unknown = JSON.parse(String(raw));\n return typeof frame === 'object' && frame !== null\n ? (frame as Record<string, unknown>)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport class DiscoveringMeshRelay implements IMeshRelay {\n private readonly routes = new Map<string, IRouteState>();\n private readonly byDevice = new Map<string, IRouteState>();\n /** LAN topic → the relay inbox topic the node knows the pair by. */\n private lanIn = new Map<string, string>();\n private readonly messages = new Set<(topic: string, data: unknown) => void>();\n private readonly absents = new Set<(topic: string) => void>();\n private readonly unsubscribes: (() => void)[] = [];\n private readonly abort = new AbortController();\n private sync: Promise<void> = Promise.resolve();\n /** Peer declarations not yet in force; signals sent meanwhile wait for them, in order. */\n private pendingPeers = 0;\n private waiting: { readonly topic: string; readonly data: unknown }[] = [];\n private timer?: ReturnType<typeof setTimeout>;\n private closed = false;\n\n public constructor(private readonly options: IDiscoveringMeshRelayOptions) {\n for (const carrier of this.carriers()) {\n this.unsubscribes.push(\n carrier.onMessage((topic, data) => this.deliver(topic, data)),\n carrier.onAbsent((topic) => this.absent(topic)),\n );\n }\n this.unsubscribes.push(\n options.listener.onMessage((lanTopic, data) => {\n const topic = this.lanIn.get(lanTopic);\n if (topic !== undefined) this.deliver(topic, data);\n }),\n );\n }\n\n private now(): number {\n return (this.options.now ?? Date.now)();\n }\n\n /** Where a pair's signals go when no candidate answers, in order. */\n private carriers(): IMeshRelay[] {\n return [\n ...(this.options.signaling ?? []),\n ...(this.options.relay !== undefined ? [this.options.relay] : []),\n ];\n }\n\n private deliver(topic: string, data: unknown): void {\n if (this.closed) return;\n for (const handler of this.messages) handler(topic, data);\n }\n\n private absent(topic: string): void {\n if (this.closed) return;\n for (const handler of this.absents) handler(topic);\n }\n\n public declarePresence(topics: readonly string[]): void {\n for (const carrier of this.carriers()) carrier.declarePresence(topics);\n }\n\n public declarePeers(peers: readonly IMeshPeerRoute[]): void {\n for (const carrier of this.carriers()) carrier.declarePeers?.(peers);\n this.pendingPeers += 1;\n this.sync = this.sync\n .then(() => this.applyPeers(peers))\n .catch((error: unknown) => {\n this.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n })\n .finally(() => {\n this.pendingPeers -= 1;\n if (this.pendingPeers > 0) return;\n for (const { topic, data } of this.waiting.splice(0)) this.send(topic, data);\n });\n }\n\n /** Recompute the pairs' LAN topics for the current epoch and hold them. */\n private async applyPeers(peers: readonly IMeshPeerRoute[]): Promise<void> {\n if (this.closed) return;\n const epoch = rendezvousEpoch(this.now());\n const lanIn = new Map<string, string>();\n const next = new Map<string, IRouteState>();\n for (const route of peers) {\n const outbound = toLanTopic(await route.rendezvous.tag('lan-inbox', 'outbound', epoch));\n for (const tag of await route.rendezvous.lookupTags('lan-inbox', epoch)) {\n lanIn.set(toLanTopic(tag), route.inbound);\n }\n const held = this.routes.get(route.outbound);\n const state: IRouteState =\n held !== undefined && held.route.deviceId === route.deviceId\n ? held\n : {\n route,\n lanOut: outbound,\n path: { kind: 'unresolved' },\n avoid: new SetAside(),\n avoidCarriers: new SetAside(),\n };\n state.route = route;\n state.lanOut = outbound;\n next.set(route.outbound, state);\n }\n if (this.closed) return;\n for (const [topic, state] of this.routes) {\n if (next.get(topic) !== state) this.reset(state);\n }\n this.routes.clear();\n this.byDevice.clear();\n for (const [topic, state] of next) {\n this.routes.set(topic, state);\n this.byDevice.set(state.route.deviceId, state);\n }\n this.lanIn = lanIn;\n this.options.listener.setPresence([...lanIn.keys()]);\n this.cacheWrite(() => this.options.cache?.retain(peers.map((p) => p.deviceId)));\n await Promise.all(\n (this.options.advertisers ?? []).map((a) => a.advertise(peers, this.options.listener.port)),\n );\n if (this.closed) return;\n this.scheduleEpoch(epoch, peers);\n }\n\n private scheduleEpoch(epoch: number, peers: readonly IMeshPeerRoute[]): void {\n if (this.timer !== undefined) clearTimeout(this.timer);\n const delay = Math.max(0, (epoch + 1) * RENDEZVOUS_EPOCH_MS - this.now());\n this.timer = setTimeout(() => this.declarePeers(peers), delay);\n this.timer.unref?.();\n }\n\n public confirmPeer(deviceId: string): void {\n const state = this.byDevice.get(deviceId);\n const path = state?.path;\n if (state === undefined || (path?.kind !== 'direct' && path?.kind !== 'relay')) return;\n clearTimeout(path.deadline);\n path.deadline = undefined;\n if (path.kind === 'relay') {\n state.avoidCarriers.clear();\n return;\n }\n state.avoid.clear();\n this.cacheWrite(() => this.options.cache?.remember(deviceId, path.candidate));\n }\n\n /** A cache that cannot be written costs the next connection a lookup, nothing more. */\n private cacheWrite(write: () => void): void {\n try {\n write();\n } catch (error) {\n this.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n public send(topic: string, data: unknown): void {\n if (this.closed) return;\n if (this.pendingPeers > 0) {\n // The pair may be one being declared right now: route it once it is.\n if (this.waiting.length < MAX_QUEUED) this.waiting.push({ topic, data });\n else this.viaRelay(topic, data);\n return;\n }\n const state = this.routes.get(topic);\n if (state === undefined) {\n this.viaRelay(topic, data);\n return;\n }\n const path = state.path;\n if (path.kind === 'direct' && path.socket.readyState === WS_OPEN) {\n this.sendDirect(state, path, data);\n return;\n }\n if (path.kind === 'relay' && this.now() < path.until) {\n this.viaCarrier(state, path, data);\n return;\n }\n if (path.kind === 'resolving') {\n if (path.queue.length < MAX_QUEUED) path.queue.push(data);\n else this.viaRelay(topic, data);\n return;\n }\n if (path.kind === 'direct') this.reset(state);\n const queue = [data];\n state.path = { kind: 'resolving', queue };\n void this.resolve(state, queue);\n }\n\n private sendDirect(\n state: IRouteState,\n path: Extract<TPath, { kind: 'direct' }>,\n data: unknown,\n ): void {\n // Every attempt must reach admission over this endpoint in time, not only the first one.\n if (precedesAdmission(data)) this.armDeadline(state, path);\n path.socket.send(JSON.stringify({ type: 'message', to: lanAddressOf(state.lanOut), data }));\n }\n\n /** A signal for a topic that is no declared pair's: the last carrier, if there is one. */\n private viaRelay(topic: string, data: unknown): void {\n const carrier = this.carriers().at(-1);\n if (carrier === undefined) {\n queueMicrotask(() => this.absent(topic));\n return;\n }\n carrier.send(topic, data);\n }\n\n private viaCarrier(\n state: IRouteState,\n path: Extract<TPath, { kind: 'relay' }>,\n data: unknown,\n ): void {\n // Every attempt must reach admission over this carrier in time, unless it is the last one.\n if (precedesAdmission(data) && path.carrier !== this.carriers().at(-1)) {\n this.armDeadline(state, path);\n }\n path.carrier.send(state.route.outbound, data);\n }\n\n /** Try the sources in order; the first candidate that holds the pair's topic carries its signals. */\n private async resolve(state: IRouteState, queue: unknown[]): Promise<void> {\n const tried = new Set<string>();\n for (const source of this.options.sources) {\n if (this.closed || state.path.kind !== 'resolving') return;\n for (const candidate of await this.look(source, state.route)) {\n const key = candidateKey(candidate);\n if (tried.size >= MAX_PROBES) break;\n if (tried.has(key) || state.avoid.has(key, this.now())) continue;\n tried.add(key);\n const socket = await this.probe(candidate, state.lanOut);\n if (socket === undefined) continue;\n if (\n this.closed ||\n state.path.kind !== 'resolving' ||\n this.routes.get(state.route.outbound) !== state\n ) {\n socket.close();\n return;\n }\n const path = this.useDirect(state, socket, candidate);\n for (const data of queue) this.sendDirect(state, path, data);\n return;\n }\n }\n if (this.closed || state.path.kind !== 'resolving') return;\n this.fallBack(state);\n const path = state.path as TPath;\n for (const data of queue) {\n if (path.kind === 'relay') this.viaCarrier(state, path, data);\n else this.viaRelay(state.route.outbound, data);\n }\n }\n\n /**\n * The first carrier not set aside carries the pair's signals for a while (the last one when all\n * are). Without a carrier there is nothing to wait on: the next signal looks again, since the\n * peer's endpoint may simply not have been up yet.\n */\n private fallBack(state: IRouteState): void {\n const now = this.now();\n const carriers = this.carriers();\n const carrier = carriers.find((c) => !state.avoidCarriers.has(c, now)) ?? carriers.at(-1);\n state.path =\n carrier === undefined\n ? { kind: 'unresolved' }\n : { kind: 'relay', until: now + this.recheckMs(), carrier };\n }\n\n private recheckMs(): number {\n return this.options.relayRecheckMs ?? DEFAULT_RELAY_RECHECK_MS;\n }\n\n private async look(\n source: IMeshCandidateSource,\n route: IMeshPeerRoute,\n ): Promise<readonly IMeshCandidate[]> {\n const timeout = AbortSignal.timeout(\n source.timeoutMs ?? this.options.sourceTimeoutMs ?? DEFAULT_SOURCE_TIMEOUT_MS,\n );\n const signal = AbortSignal.any([timeout, this.abort.signal]);\n try {\n return await Promise.race([\n source.candidates(route, signal),\n new Promise<readonly IMeshCandidate[]>((resolve) =>\n signal.addEventListener('abort', () => resolve([]), { once: true }),\n ),\n ]);\n } catch {\n // allow-fallback: a source that fails has no candidates; the next source, then the relay, is tried\n return [];\n }\n }\n\n /** A socket to `candidate` once it proves it holds `lanTopic`; `undefined` otherwise. */\n private probe(candidate: IMeshCandidate, lanTopic: string): Promise<IWebSocketLike | undefined> {\n const to = lanAddressOf(lanTopic);\n const nonce = randomNonce();\n return new Promise((resolve) => {\n let socket: IWebSocketLike;\n try {\n socket = (\n this.options.connect ??\n ((c) => new WebSocket(candidateUrl(c), { maxPayload: 4096 }) as unknown as IWebSocketLike)\n )(candidate);\n } catch {\n resolve(undefined);\n return;\n }\n let settled = false;\n const settle = (ok: boolean): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (!ok) socket.close();\n resolve(ok ? socket : undefined);\n };\n const timer = setTimeout(\n () => settle(false),\n this.options.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS,\n );\n timer.unref?.();\n socket.on('open', () => socket.send(JSON.stringify({ type: 'probe', to, nonce })));\n socket.on('message', (raw) => {\n const frame = parse(raw);\n if (frame?.to !== to) return;\n if (frame.type === 'absent') {\n settle(false);\n return;\n }\n // Whatever else the frame says, only the proof decides: it is an HMAC over this probe's\n // own nonce, so an echoed, stale or forged frame fails it.\n settle(verifyLanPresenceProof(lanTopic, nonce, frame.proof));\n });\n socket.on('error', () => settle(false));\n socket.on('close', () => settle(false));\n });\n }\n\n /**\n * An endpoint or carrier that carries an attempt but no admission is set aside, so it cannot keep\n * the pair from the next way — whether it never forwarded, or forwarded one attempt and swallows\n * the next. One that fails again is set aside for longer, so it is not tried every recheck.\n */\n private armDeadline(\n state: IRouteState,\n path: Extract<TPath, { kind: 'direct' } | { kind: 'relay' }>,\n ): void {\n if (path.deadline !== undefined) return;\n path.deadline = setTimeout(() => {\n if (state.path !== path) return;\n if (path.kind === 'direct') {\n state.avoid.fail(candidateKey(path.candidate), this.now(), this.recheckMs());\n } else {\n state.avoidCarriers.fail(path.carrier, this.now(), this.recheckMs());\n }\n this.reset(state);\n this.fallBack(state);\n }, this.options.admissionTimeoutMs ?? DEFAULT_ADMISSION_TIMEOUT_MS);\n path.deadline.unref?.();\n }\n\n private useDirect(\n state: IRouteState,\n socket: IWebSocketLike,\n candidate: IMeshCandidate,\n ): Extract<TPath, { kind: 'direct' }> {\n const path: Extract<TPath, { kind: 'direct' }> = { kind: 'direct', socket, candidate };\n state.path = path;\n const drop = (): void => {\n if (state.path === path) this.reset(state);\n };\n socket.on('close', drop);\n socket.on('error', drop);\n // The endpoint no longer holds the pair's topic (the peer restarted, or it is someone else now).\n socket.on('message', (raw) => {\n const frame = parse(raw);\n if (frame?.type === 'absent' && frame.to === lanAddressOf(state.lanOut)) drop();\n });\n return path;\n }\n\n private reset(state: IRouteState): void {\n const path = state.path;\n state.path = { kind: 'unresolved' };\n if (path.kind === 'relay') clearTimeout(path.deadline);\n if (path.kind === 'direct') {\n clearTimeout(path.deadline);\n path.socket.close();\n }\n }\n\n public onMessage(handler: (topic: string, data: unknown) => void): () => void {\n this.messages.add(handler);\n return () => this.messages.delete(handler);\n }\n\n public onAbsent(handler: (topic: string) => void): () => void {\n this.absents.add(handler);\n return () => this.absents.delete(handler);\n }\n\n public close(): void {\n if (this.closed) return;\n this.closed = true;\n this.abort.abort();\n if (this.timer !== undefined) clearTimeout(this.timer);\n for (const unsubscribe of this.unsubscribes.splice(0)) unsubscribe();\n for (const state of this.routes.values()) this.reset(state);\n this.routes.clear();\n this.byDevice.clear();\n this.messages.clear();\n this.absents.clear();\n this.waiting = [];\n for (const advertiser of this.options.advertisers ?? []) advertiser.close();\n // allow-fallback: closing the endpoint is best effort once this relay is done with it\n void this.options.listener.close().catch(() => undefined);\n // The carriers it was given stay the caller's to close, as a relay handed to a node does.\n }\n}\n\nexport interface IStartLanMeshRelayOptions {\n /** The self-hosted relay: the last carrier. */\n readonly relay?: IMeshRelay;\n /**\n * Beyond the local network: public records to find peers by (closed with the relay), and public\n * signaling carriers tried before the self-hosted relay (the caller's to close).\n */\n readonly internet?: {\n readonly dht?: MeshDht;\n readonly signaling?: readonly IMeshRelay[];\n };\n /** Default: one that lives as long as the process. */\n readonly cache?: IMeshAddressCache;\n /** The address the direct signaling endpoint binds; default: every interface. */\n readonly host?: string;\n /** mDNS options, or `false` for none (the cache and the relay still work). */\n readonly mdns?: IMeshMdnsOptions | false;\n readonly onError?: (error: Error) => void;\n}\n\n/**\n * A {@link DiscoveringMeshRelay} over this device's direct signaling endpoint, looking for peers in\n * the address cache, with mDNS, then in public records, and signaling through the public carriers,\n * then the self-hosted relay, when no candidate answers.\n */\nexport async function startLanMeshRelay(\n options: IStartLanMeshRelayOptions = {},\n): Promise<DiscoveringMeshRelay> {\n const cache = options.cache ?? createInMemoryMeshAddressCache();\n const lastPort = cache.lastListenPort();\n const listener = await startMeshLanListener({\n ...(lastPort !== undefined ? { port: lastPort } : {}),\n ...(options.host !== undefined ? { host: options.host } : {}),\n });\n try {\n cache.rememberListenPort(listener.port);\n } catch (error) {\n // allow-fallback: a port that cannot be remembered only means peers look this device up again\n options.onError?.(error instanceof Error ? error : new Error(String(error)));\n }\n const mdns =\n options.mdns === false\n ? undefined\n : new MeshMdns({\n ...(options.onError !== undefined ? { onError: options.onError } : {}),\n ...options.mdns,\n });\n const dht = options.internet?.dht;\n const signaling = options.internet?.signaling ?? [];\n return new DiscoveringMeshRelay({\n ...(options.relay !== undefined ? { relay: options.relay } : {}),\n ...(signaling.length > 0 ? { signaling } : {}),\n listener,\n sources: [\n addressCacheSource(cache),\n ...(mdns !== undefined ? [mdns] : []),\n ...(dht !== undefined ? [dht] : []),\n ],\n cache,\n advertisers: [...(mdns !== undefined ? [mdns] : []), ...(dht !== undefined ? [dht] : [])],\n ...(options.onError !== undefined ? { onError: options.onError } : {}),\n });\n}\n","/**\n * Signed, encrypted rendezvous records as public infrastructure stores them: BEP 44 mutable items\n * (and their pkarr form) under a one-time key per pair, direction, epoch and purpose.\n *\n * Nothing here names a device, a user or the product. The key and salt rotate every epoch and differ\n * per direction; the value is the pair's AEAD ciphertext, padded to a fixed size so its length says\n * nothing about what it holds. Whoever stores or serves a record can check its signature, and only\n * the pair can open it. Isomorphic: WebCrypto only.\n */\nimport type {\n IPairRendezvous,\n TRendezvousDirection,\n TRendezvousRecordPurpose,\n TRendezvousTagPurpose,\n} from '@robota-sdk/agent-remote-pairing';\n\nimport type { IMeshCandidate } from './mesh-discovery.js';\n\n/** BEP 44: a value is less than 1000 bytes. */\nexport const BEP44_MAX_VALUE_BYTES = 999;\n/** BEP 44: a salt is at most 64 bytes. */\nconst BEP44_MAX_SALT_BYTES = 64;\n/** Plaintext size every connection-hints record is padded to. */\nexport const HINTS_PADDED_BYTES = 640;\n/** Plaintext size of every device-lists record (one chunk); the pkarr form must still fit. */\nexport const LIST_CHUNK_BYTES = 864;\n/** Chunks the device lists may take; a larger list is not published. */\nexport const MAX_LIST_CHUNKS = 8;\n/** Bytes of the version every chunk of one list carries. */\nconst LIST_VERSION_BYTES = 8;\nconst LIST_CHUNK_HEADER = 2 + LIST_VERSION_BYTES;\n/** Addresses one hints record carries. */\nexport const MAX_HINT_CANDIDATES = 8;\n/** Relay endpoints one hints record carries. */\nexport const MAX_HINT_RELAYS = 4;\nconst MAX_HOST_CHARS = 64;\n\nconst ED25519_PKCS8_PREFIX = Uint8Array.from([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,\n]);\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nfunction ab(view: Uint8Array): ArrayBuffer {\n return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength) as ArrayBuffer;\n}\n\nfunction base64UrlToBytes(value: string): Uint8Array {\n const binary = atob(value.replace(/-/g, '+').replace(/_/g, '/'));\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);\n return out;\n}\n\nexport function concatBytes(parts: readonly Uint8Array[]): Uint8Array {\n const out = new Uint8Array(parts.reduce((sum, p) => sum + p.length, 0));\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.length;\n }\n return out;\n}\n\nexport function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i += 1) diff |= a[i]! ^ b[i]!;\n return diff === 0;\n}\n\n/** A one-time Ed25519 key: the public half, and a signer. */\nexport interface IItemKey {\n readonly publicKey: Uint8Array;\n sign(message: Uint8Array): Promise<Uint8Array>;\n}\n\n/** The Ed25519 key whose private key is `seed`. */\nexport async function ed25519FromSeed(seed: Uint8Array): Promise<IItemKey> {\n if (seed.length !== 32) throw new Error('an Ed25519 seed is 32 bytes');\n const privateKey = await globalThis.crypto.subtle.importKey(\n 'pkcs8',\n ab(concatBytes([ED25519_PKCS8_PREFIX, seed])),\n { name: 'Ed25519' },\n true,\n ['sign'],\n );\n const jwk = await globalThis.crypto.subtle.exportKey('jwk', privateKey);\n if (typeof jwk.x !== 'string') throw new Error('Ed25519 public key unavailable');\n const publicKey = base64UrlToBytes(jwk.x);\n return {\n publicKey,\n sign: async (message) =>\n new Uint8Array(\n await globalThis.crypto.subtle.sign({ name: 'Ed25519' }, privateKey, ab(message)),\n ),\n };\n}\n\n/** Whether `signature` is `publicKey`'s Ed25519 signature of `message`. Never throws. */\nexport async function verifyEd25519(\n publicKey: Uint8Array,\n message: Uint8Array,\n signature: Uint8Array,\n): Promise<boolean> {\n if (publicKey.length !== 32 || signature.length !== 64) return false;\n try {\n const key = await globalThis.crypto.subtle.importKey(\n 'raw',\n ab(publicKey),\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n return await globalThis.crypto.subtle.verify(\n { name: 'Ed25519' },\n key,\n ab(signature),\n ab(message),\n );\n } catch {\n // allow-fallback: a key or signature that cannot be read verifies nothing\n return false;\n }\n}\n\n/** A BEP 44 mutable item, as signed. */\nexport interface IMutableItem {\n readonly k: Uint8Array;\n readonly salt?: Uint8Array;\n readonly seq: number;\n readonly v: Uint8Array;\n readonly sig: Uint8Array;\n}\n\n/** The bytes a BEP 44 signature covers: `[4:salt…]3:seqi…e1:v…` with `v` as a byte string. */\nexport function bep44SignedBytes(\n salt: Uint8Array | undefined,\n seq: number,\n v: Uint8Array,\n): Uint8Array {\n const parts: Uint8Array[] = [];\n if (salt !== undefined && salt.length > 0) {\n parts.push(encoder.encode(`4:salt${salt.length}:`), salt);\n }\n parts.push(encoder.encode(`3:seqi${seq}e1:v${v.length}:`), v);\n return concatBytes(parts);\n}\n\n/** Sign `v` as a mutable item under `key` and `salt`. */\nexport async function signMutableItem(\n key: IItemKey,\n salt: Uint8Array | undefined,\n seq: number,\n v: Uint8Array,\n): Promise<IMutableItem> {\n if (v.length > BEP44_MAX_VALUE_BYTES) throw new Error('record value exceeds 999 bytes');\n if (salt !== undefined && salt.length > BEP44_MAX_SALT_BYTES)\n throw new Error('salt exceeds 64 bytes');\n if (!Number.isSafeInteger(seq) || seq < 0) throw new Error('seq must be a non-negative integer');\n const sig = await key.sign(bep44SignedBytes(salt, seq, v));\n return { k: key.publicKey, ...(salt !== undefined ? { salt } : {}), seq, v, sig };\n}\n\n/** Whether `item` is signed by `publicKey` under exactly `salt`. Never throws. */\nexport async function verifyMutableItem(\n item: IMutableItem,\n publicKey: Uint8Array,\n salt: Uint8Array | undefined,\n): Promise<boolean> {\n if (!bytesEqual(item.k, publicKey)) return false;\n const itemSalt = item.salt ?? new Uint8Array(0);\n if (!bytesEqual(itemSalt, salt ?? new Uint8Array(0))) return false;\n if (item.v.length > BEP44_MAX_VALUE_BYTES) return false;\n if (!Number.isSafeInteger(item.seq) || item.seq < 0) return false;\n return verifyEd25519(publicKey, bep44SignedBytes(salt, item.seq, item.v), item.sig);\n}\n\n/** Where one pair's record of one purpose lives in one direction at one epoch. */\nexport interface IItemAddress {\n readonly key: IItemKey;\n readonly salt: Uint8Array;\n}\n\nconst SALT_PURPOSE: Record<Exclude<TRendezvousRecordPurpose, 'signal'>, TRendezvousTagPurpose> = {\n hints: 'bep44-salt',\n revocation: 'bep44-revocation-salt',\n};\n\nasync function sha256(parts: readonly Uint8Array[]): Promise<Uint8Array> {\n return new Uint8Array(await globalThis.crypto.subtle.digest('SHA-256', ab(concatBytes(parts))));\n}\n\n/**\n * The address of chunk `chunk` of `purpose`'s record in `direction` at `epoch`. Every chunk has a key\n * and salt of its own, so chunks never share an item, salted or not.\n */\nexport async function itemAddress(\n rendezvous: IPairRendezvous,\n purpose: Exclude<TRendezvousRecordPurpose, 'signal'>,\n direction: TRendezvousDirection,\n epoch: number,\n chunk = 0,\n): Promise<IItemAddress> {\n const [seed, salt] = await Promise.all([\n rendezvous.signingSeed(direction, epoch, purpose),\n rendezvous.tag(SALT_PURPOSE[purpose], direction, epoch),\n ]);\n if (chunk === 0) return { key: await ed25519FromSeed(seed), salt };\n const label = encoder.encode('chunk');\n const index = Uint8Array.of(chunk);\n const [chunkSeed, chunkSalt] = await Promise.all([\n sha256([label, seed, index]),\n sha256([label, salt, index]),\n ]);\n return { key: await ed25519FromSeed(chunkSeed), salt: chunkSalt };\n}\n\n/**\n * `value` as fixed-size chunks, each `[count, index, version, …JSON]` padded with spaces. Every chunk\n * of one value carries the same version, derived from the value, so chunks of two values are never\n * joined. Throws when it needs more than {@link MAX_LIST_CHUNKS}.\n */\nexport async function chunkJson(value: unknown): Promise<Uint8Array[]> {\n const bytes = encoder.encode(JSON.stringify(value));\n const version = (await sha256([bytes])).subarray(0, LIST_VERSION_BYTES);\n const room = LIST_CHUNK_BYTES - LIST_CHUNK_HEADER;\n const count = Math.max(1, Math.ceil(bytes.length / room));\n if (count > MAX_LIST_CHUNKS) throw new Error('device lists too large to publish as records');\n return Array.from({ length: count }, (_, index) => {\n const out = new Uint8Array(LIST_CHUNK_BYTES).fill(0x20);\n out[0] = count;\n out[1] = index;\n out.set(version, 2);\n out.set(bytes.subarray(index * room, (index + 1) * room), LIST_CHUNK_HEADER);\n return out;\n });\n}\n\n/** How many chunks the value has, from its first chunk; `undefined` when it is not one. */\nexport function chunkCount(first: Uint8Array): number | undefined {\n const count = first[0];\n if (first.length !== LIST_CHUNK_BYTES || first[1] !== 0) return undefined;\n return count !== undefined && count >= 1 && count <= MAX_LIST_CHUNKS ? count : undefined;\n}\n\n/**\n * The value of `chunks` in order; `undefined` when they are not all chunks of one value, as when a\n * read finds some chunks already replaced by a newer value's.\n */\nexport function joinChunks(chunks: readonly Uint8Array[]): unknown {\n const count = chunks.length;\n const version = chunks[0]?.subarray(2, LIST_CHUNK_HEADER);\n const parts: Uint8Array[] = [];\n for (const [index, chunk] of chunks.entries()) {\n if (chunk.length !== LIST_CHUNK_BYTES || chunk[0] !== count || chunk[1] !== index) {\n return undefined;\n }\n if (!sameBytes(chunk.subarray(2, LIST_CHUNK_HEADER), version)) return undefined;\n parts.push(chunk.subarray(LIST_CHUNK_HEADER));\n }\n return unpadJson(concatBytes(parts));\n}\n\nfunction sameBytes(a: Uint8Array, b: Uint8Array | undefined): boolean {\n return b !== undefined && a.length === b.length && a.every((byte, i) => byte === b[i]);\n}\n\n/** `data` padded with trailing spaces to exactly `size` bytes; throws when it does not fit. */\nexport function padJson(value: unknown, size: number): Uint8Array {\n const bytes = encoder.encode(JSON.stringify(value));\n if (bytes.length > size) throw new Error('record does not fit its fixed size');\n const out = new Uint8Array(size).fill(0x20);\n out.set(bytes);\n return out;\n}\n\n/** Parse padded JSON; `undefined` when it is not JSON. */\nexport function unpadJson(bytes: Uint8Array): unknown {\n try {\n return JSON.parse(decoder.decode(bytes)) as unknown;\n } catch {\n return undefined;\n }\n}\n\nfunction keptAddresses(addresses: readonly IMeshCandidate[], max: number): [string, number][] {\n const kept: [string, number][] = [];\n for (const c of addresses) {\n if (kept.length >= max) break;\n if (c.host.length === 0 || c.host.length > MAX_HOST_CHARS) continue;\n kept.push([c.host, c.port]);\n }\n return kept;\n}\n\n/**\n * Connection hints: where the peer's direct signaling endpoint might be reached and, when this\n * device runs a relay, where that relay listens. What does not fit the fixed size is left out,\n * signaling addresses first.\n */\nexport function encodeHints(\n candidates: readonly IMeshCandidate[],\n relays: readonly IMeshCandidate[] = [],\n): Uint8Array {\n const c = keptAddresses(candidates, MAX_HINT_CANDIDATES);\n const t = keptAddresses(relays, MAX_HINT_RELAYS);\n for (;;) {\n try {\n return padJson({ v: 1, c, ...(t.length > 0 ? { t } : {}) }, HINTS_PADDED_BYTES);\n } catch (error) {\n if (c.length > 0) c.pop();\n else if (t.length > 0) t.pop();\n else throw error;\n }\n }\n}\n\nconst HOST = /^[0-9A-Za-z.:%_-]{1,64}$/;\n\n/** The candidates of a hints record; hostile input, so anything malformed is dropped. */\nexport function decodeHints(bytes: Uint8Array): IMeshCandidate[] {\n return decodeHintAddresses(bytes, 'c', MAX_HINT_CANDIDATES);\n}\n\n/** The relay endpoints of a hints record, if the peer runs a relay; hostile input like the rest. */\nexport function decodeRelayHints(bytes: Uint8Array): IMeshCandidate[] {\n return decodeHintAddresses(bytes, 't', MAX_HINT_RELAYS);\n}\n\nfunction decodeHintAddresses(bytes: Uint8Array, field: 'c' | 't', max: number): IMeshCandidate[] {\n const value = unpadJson(bytes);\n if (typeof value !== 'object' || value === null) return [];\n const r = value as { v?: unknown; c?: unknown; t?: unknown };\n const list = r[field];\n if (r.v !== 1 || !Array.isArray(list)) return [];\n const out: IMeshCandidate[] = [];\n for (const entry of list.slice(0, max)) {\n if (!Array.isArray(entry) || entry.length !== 2) continue;\n const [host, port] = entry as [unknown, unknown];\n if (typeof host !== 'string' || !HOST.test(host)) continue;\n if (typeof port !== 'number' || !Number.isInteger(port) || port < 1 || port > 65_535) continue;\n out.push({ host, port });\n }\n return out;\n}\n\n/**\n * A publish delay in `[0, maxMs]`, so the records one device publishes for its pairs do not all\n * appear at once and cannot be linked by their timing.\n */\nexport function publishJitter(maxMs: number, random: () => number = Math.random): number {\n const r = random();\n const unit = Number.isFinite(r) ? Math.min(Math.max(r, 0), 1) : 0;\n return Math.floor(unit * Math.max(0, maxMs));\n}\n","/**\n * Finding a peer device beyond the local network through records on public infrastructure (the\n * Mainline DHT, or pkarr relays in front of it), and handing on the newest device lists the same way.\n *\n * For every peer this device publishes, under `dir(self → peer)` and the current epoch:\n * - its connection hints: the addresses of its direct signaling endpoint;\n * - the newest device revocation lists it holds, so a peer can learn of a revocation from any other\n * device of the pair set before it admits someone.\n *\n * Each record is signed by a one-time key of that pair, direction, epoch and purpose, stored under a\n * rotating salt, and sealed by the pair; its plaintext is padded to a fixed size. Publish times are\n * jittered per pair, so the records one device publishes do not appear together. A lookup reads\n * `dir(peer → self)` at the current epoch and the adjacent ones.\n *\n * What a lookup yields is a candidate or an unverified list, never trust: candidates only carry\n * signals, and lists are verified by the device handshake before anything uses them.\n */\nimport {\n rendezvousEpoch,\n type IDeviceRevocationList,\n type ISigningKeyRevocation,\n} from '@robota-sdk/agent-remote-pairing';\n\nimport {\n chunkCount,\n chunkJson,\n decodeHints,\n decodeRelayHints,\n encodeHints,\n itemAddress,\n joinChunks,\n publishJitter,\n} from './mesh-records.js';\n\nimport type { IMeshCandidate, IMeshCandidateSource, IMeshPeerRoute } from './mesh-discovery.js';\nimport type { IRendezvousItemStore } from './mesh-item-store.js';\n\n/** Longest delay before a pair's records are published, by default. */\nexport const DEFAULT_MAX_PUBLISH_JITTER_MS = 90_000;\n/** How long a DHT lookup may take, by default: iterative lookups take seconds. */\nconst DEFAULT_LOOKUP_TIMEOUT_MS = 6_000;\n/** Peers whose list records a freshness lookup reads. */\nconst MAX_LIST_PEERS = 16;\n/** Candidates of one list kind a freshness lookup returns. */\nconst MAX_LIST_CANDIDATES = 16;\n/** Candidates of one list kind taken from one peer's records. */\nconst MAX_LIST_CANDIDATES_PER_PEER = 2;\n/** How long \"no relay\" found for a peer holds before its records are looked up again. */\nconst NO_RELAY_RECHECK_MS = 2 * 60 * 1000;\n/** How long a background lookup of the lists may take. */\nconst PREFETCH_TIMEOUT_MS = 30_000;\n\n/** The device lists this device hands on to its peers. */\nexport interface IPublishedLists {\n readonly revocation: IDeviceRevocationList;\n readonly signingKeyRevocation?: ISigningKeyRevocation;\n}\n\n/** What a freshness lookup found, as received, newest first: each candidate is verified before use. */\nexport interface IFetchedLists {\n readonly revocation?: readonly unknown[];\n readonly signingKeyRevocation?: readonly unknown[];\n}\n\nexport interface IMeshDhtOptions {\n /** Where records go and come from; every one is written to and read from. */\n readonly stores: readonly IRendezvousItemStore[];\n /** This device's addresses to publish with its endpoint port. */\n readonly addresses: () => readonly string[];\n /** Where this device's relay listens, published to each paired device with its hints; none: no relay. */\n readonly relayEndpoints?: () => readonly IMeshCandidate[];\n /** The newest lists this device holds; absent or `undefined`: no list records are published. */\n readonly lists?: () => IPublishedLists | undefined;\n readonly maxPublishJitterMs?: number;\n /** How long a candidate lookup may take. */\n readonly lookupTimeoutMs?: number;\n readonly random?: () => number;\n readonly now?: () => number;\n /** A record could not be published; lookups still run. */\n readonly onError?: (error: Error) => void;\n}\n\ninterface IScheduled {\n readonly timer: ReturnType<typeof setTimeout>;\n readonly epoch: number;\n readonly port: number;\n}\n\nfunction seqOf(value: unknown): number {\n if (typeof value !== 'object' || value === null) return -1;\n const seq = (value as { seq?: unknown }).seq;\n return typeof seq === 'number' && Number.isSafeInteger(seq) ? seq : -1;\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport class MeshDht implements IMeshCandidateSource {\n public readonly timeoutMs: number;\n private readonly scheduled = new Map<string, IScheduled>();\n private routes: readonly IMeshPeerRoute[] = [];\n /** The lists found by the last lookup that finished, for a lookup that runs out of time. */\n private listsFound: IFetchedLists | undefined;\n private prefetchedEpoch?: number;\n /** The relays peers advertised, by device: the epoch and time they were looked up. */\n private readonly relaysFound = new Map<\n string,\n {\n readonly epoch: number;\n readonly checkedAt: number;\n readonly endpoints: readonly IMeshCandidate[];\n }\n >();\n /** Relay lookups running, by device. */\n private readonly relayLookups = new Map<string, Promise<void>>();\n private closed = false;\n\n public constructor(private readonly options: IMeshDhtOptions) {\n this.timeoutMs = options.lookupTimeoutMs ?? DEFAULT_LOOKUP_TIMEOUT_MS;\n }\n\n private now(): number {\n return (this.options.now ?? Date.now)();\n }\n\n /** The peer's endpoint addresses from its hints record, at this epoch or an adjacent one. */\n public async candidates(\n peer: IMeshPeerRoute,\n signal: AbortSignal,\n ): Promise<readonly IMeshCandidate[]> {\n const epoch = rendezvousEpoch(this.now());\n const found = await this.lookup(peer, 'hints', epoch, signal);\n // A lookup cut short says nothing about a relay.\n if (!signal.aborted) this.rememberRelays(peer, epoch, found);\n const out: IMeshCandidate[] = [];\n const seen = new Set<string>();\n // Newest epoch first: its addresses are the likeliest to be current.\n for (const record of found.sort((a, b) => b.epoch - a.epoch)) {\n for (const candidate of decodeHints(record.data)) {\n const key = `${candidate.host}:${candidate.port}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(candidate);\n }\n }\n return out;\n }\n\n /** The relay endpoints in the newest of `found`, the peer's hints records. */\n private rememberRelays(\n peer: IMeshPeerRoute,\n epoch: number,\n found: readonly { readonly epoch: number; readonly data: Uint8Array }[],\n ): void {\n const newest = [...found].sort((a, b) => b.epoch - a.epoch)[0];\n const endpoints = newest === undefined ? [] : decodeRelayHints(newest.data);\n this.relaysFound.set(peer.deviceId, { epoch, checkedAt: this.now(), endpoints });\n }\n\n /**\n * The relays `peers` advertise to this device, by device id. What a lookup found holds for the\n * epoch, and \"no relay\" for a short while, so connection attempts do not wait on lookups; a\n * lookup still running when `signal` ends goes on, and the next call has its answer. Only a\n * peer's own sealed hints record says where its relay is, so no one else learns it and no one\n * else can plant one.\n */\n public async relayAdverts(\n peers: readonly IMeshPeerRoute[],\n signal: AbortSignal,\n ): Promise<ReadonlyMap<string, readonly IMeshCandidate[]>> {\n const now = this.now();\n const epoch = rendezvousEpoch(now);\n const routes = peers.slice(0, MAX_LIST_PEERS);\n const running: Promise<void>[] = [];\n for (const route of routes) {\n const known = this.relaysFound.get(route.deviceId);\n const fresh =\n known !== undefined &&\n known.epoch === epoch &&\n (known.endpoints.length > 0 || now - known.checkedAt < NO_RELAY_RECHECK_MS);\n if (fresh) continue;\n running.push(this.lookUpRelay(route, epoch));\n }\n if (running.length > 0) {\n const ended = new Promise<void>((resolve) => {\n if (signal.aborted) resolve();\n signal.addEventListener('abort', () => resolve(), { once: true });\n });\n await Promise.race([Promise.all(running), ended]);\n }\n const current = new Set(peers.map((route) => route.deviceId));\n for (const deviceId of [...this.relaysFound.keys()]) {\n if (!current.has(deviceId)) this.relaysFound.delete(deviceId);\n }\n const out = new Map<string, readonly IMeshCandidate[]>();\n for (const route of routes) {\n const endpoints = this.relaysFound.get(route.deviceId)?.endpoints ?? [];\n if (endpoints.length > 0) out.set(route.deviceId, endpoints);\n }\n return out;\n }\n\n /** One lookup of a peer's relay at a time, bounded by the lookup timeout, not by any caller. */\n private lookUpRelay(route: IMeshPeerRoute, epoch: number): Promise<void> {\n const held = this.relayLookups.get(route.deviceId);\n if (held !== undefined) return held;\n const signal = AbortSignal.timeout(this.timeoutMs);\n const running = this.lookup(route, 'hints', epoch, signal)\n .then((found) => {\n if (!signal.aborted && !this.closed) this.rememberRelays(route, epoch, found);\n })\n .finally(() => this.relayLookups.delete(route.deviceId));\n this.relayLookups.set(route.deviceId, running);\n return running;\n }\n\n /** The records of `purpose` the peer published for this device, opened; empty when none. */\n private async lookup(\n peer: IMeshPeerRoute,\n purpose: 'hints' | 'revocation',\n epoch: number,\n signal: AbortSignal,\n ): Promise<{ readonly epoch: number; readonly data: Uint8Array }[]> {\n const reads = [epoch - 1, epoch, epoch + 1].flatMap((e) =>\n this.options.stores.map(async (store) => {\n const data = await this.read(store, peer, purpose, e, 0, signal);\n return data === undefined ? undefined : { epoch: e, data };\n }),\n );\n return (await Promise.all(reads)).filter((r) => r !== undefined);\n }\n\n /** One chunk of the peer's record, opened; `undefined` when it is not there or not the peer's. */\n private async read(\n store: IRendezvousItemStore,\n peer: IMeshPeerRoute,\n purpose: 'hints' | 'revocation',\n epoch: number,\n chunk: number,\n signal: AbortSignal,\n ): Promise<Uint8Array | undefined> {\n try {\n const address = await itemAddress(peer.rendezvous, purpose, 'inbound', epoch, chunk);\n const value = await store.get(address.key.publicKey, address.salt, signal);\n if (value === undefined) return undefined;\n // Sealed for exactly this pair, direction, purpose and epoch, or it is not the peer's.\n const opened = await peer.rendezvous.openRecord(value, epoch, purpose);\n return opened?.epoch === epoch ? opened.hints : undefined;\n } catch {\n // allow-fallback: a record that cannot be read is not there; the other stores and epochs count\n return undefined;\n }\n }\n\n /** The device lists the peer published for this device, whole; one per store and epoch found. */\n private async lookupLists(\n peer: IMeshPeerRoute,\n epoch: number,\n signal: AbortSignal,\n ): Promise<unknown[]> {\n const reads = [epoch - 1, epoch, epoch + 1].flatMap((e) =>\n this.options.stores.map(async (store) => {\n const first = await this.read(store, peer, 'revocation', e, 0, signal);\n const count = first === undefined ? undefined : chunkCount(first);\n if (first === undefined || count === undefined) return undefined;\n const rest = await Promise.all(\n Array.from({ length: count - 1 }, (_, i) =>\n this.read(store, peer, 'revocation', e, i + 1, signal),\n ),\n );\n if (rest.some((chunk) => chunk === undefined)) return undefined;\n return joinChunks([first, ...(rest as Uint8Array[])]);\n }),\n );\n return (await Promise.all(reads)).filter((value) => value !== undefined);\n }\n\n /**\n * Publish this device's records for `routes`, each pair after its own random delay. A pair already\n * waiting to publish for this epoch and port keeps its delay.\n */\n public advertise(routes: readonly IMeshPeerRoute[], port: number): Promise<void> {\n if (this.closed) return Promise.resolve();\n this.routes = routes;\n const epoch = rendezvousEpoch(this.now());\n const wanted = new Set(routes.map((r) => r.outbound));\n for (const [topic, scheduled] of this.scheduled) {\n if (!wanted.has(topic)) {\n clearTimeout(scheduled.timer);\n this.scheduled.delete(topic);\n }\n }\n if (this.prefetchedEpoch !== epoch) {\n // The freshness lookup has only seconds; what the peers published is read ahead of it.\n this.prefetchedEpoch = epoch;\n void this.findLists(AbortSignal.timeout(PREFETCH_TIMEOUT_MS));\n }\n const maxJitter = this.options.maxPublishJitterMs ?? DEFAULT_MAX_PUBLISH_JITTER_MS;\n for (const route of routes) {\n const held = this.scheduled.get(route.outbound);\n if (held !== undefined && held.epoch === epoch && held.port === port) continue;\n if (held !== undefined) clearTimeout(held.timer);\n const timer = setTimeout(\n () => {\n this.scheduled.delete(route.outbound);\n void this.publish(route, port);\n },\n publishJitter(maxJitter, this.options.random),\n );\n timer.unref?.();\n this.scheduled.set(route.outbound, { timer, epoch, port });\n }\n return Promise.resolve();\n }\n\n private async publish(route: IMeshPeerRoute, port: number): Promise<void> {\n if (this.closed) return;\n const now = this.now();\n const epoch = rendezvousEpoch(now);\n const records: { purpose: 'hints' | 'revocation'; chunk: number; plaintext: Uint8Array }[] = [];\n try {\n records.push({\n purpose: 'hints',\n chunk: 0,\n plaintext: encodeHints(\n this.options.addresses().map((host) => ({ host, port })),\n this.options.relayEndpoints?.() ?? [],\n ),\n });\n const lists = this.options.lists?.();\n if (lists !== undefined) {\n const chunks = await chunkJson({\n r: lists.revocation,\n ...(lists.signingKeyRevocation !== undefined ? { s: lists.signingKeyRevocation } : {}),\n });\n for (const [chunk, plaintext] of chunks.entries()) {\n records.push({ purpose: 'revocation', chunk, plaintext });\n }\n }\n } catch (error) {\n this.options.onError?.(asError(error));\n }\n for (const { purpose, chunk, plaintext } of records) {\n try {\n const sealed = await route.rendezvous.sealRecord(plaintext, epoch, purpose);\n const address = await itemAddress(route.rendezvous, purpose, 'outbound', epoch, chunk);\n const results = await Promise.allSettled(\n this.options.stores.map((store) => store.put(address, sealed, now)),\n );\n for (const result of results) {\n if (result.status === 'rejected') this.options.onError?.(asError(result.reason));\n }\n } catch (error) {\n this.options.onError?.(asError(error));\n }\n }\n }\n\n /**\n * The lists the peers published for this device, as received — candidates for the freshness\n * lookup before a remote admission, each verified there. Any paired device can publish anything\n * here, so candidates are returned rather than the one that claims to be newest, and each peer's\n * newest few come before any peer's next, so one peer's records cannot crowd out another's. When\n * `signal` ends first, what the last finished lookup found. `undefined`: none.\n */\n public async latestLists(signal: AbortSignal): Promise<IFetchedLists | undefined> {\n const ended = new Promise<undefined>((resolve) => {\n if (signal.aborted) resolve(undefined);\n signal.addEventListener('abort', () => resolve(undefined), { once: true });\n });\n const found = await Promise.race([this.findLists(signal), ended]);\n return found ?? this.listsFound;\n }\n\n private async findLists(signal: AbortSignal): Promise<IFetchedLists | undefined> {\n const epoch = rendezvousEpoch(this.now());\n const found = await Promise.all(\n this.routes.slice(0, MAX_LIST_PEERS).map((route) => this.lookupLists(route, epoch, signal)),\n );\n const revocation: unknown[][] = [];\n const signingKeyRevocation: unknown[][] = [];\n for (const values of found) {\n const r: unknown[] = [];\n const s: unknown[] = [];\n for (const value of values) {\n if (typeof value !== 'object' || value === null) continue;\n const lists = value as { r?: unknown; s?: unknown };\n if (lists.r !== undefined) r.push(lists.r);\n if (lists.s !== undefined) s.push(lists.s);\n }\n revocation.push(r);\n signingKeyRevocation.push(s);\n }\n const r = fairlyNewestFirst(revocation);\n const s = fairlyNewestFirst(signingKeyRevocation);\n const lists: IFetchedLists | undefined =\n r.length === 0 && s.length === 0\n ? undefined\n : {\n ...(r.length > 0 ? { revocation: r } : {}),\n ...(s.length > 0 ? { signingKeyRevocation: s } : {}),\n };\n if (!signal.aborted) this.listsFound = lists;\n return lists;\n }\n\n public close(): void {\n if (this.closed) return;\n this.closed = true;\n for (const { timer } of this.scheduled.values()) clearTimeout(timer);\n this.scheduled.clear();\n for (const store of this.options.stores) store.close();\n }\n}\n\n/** Candidates without repeats, in order. */\nfunction distinct(candidates: readonly unknown[]): unknown[] {\n const seen = new Set<string>();\n return candidates.filter((c) => {\n const key = JSON.stringify(c);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/** Distinct candidates, the one claiming the highest `seq` first. */\nfunction newestFirst(candidates: readonly unknown[]): unknown[] {\n return distinct(candidates).sort((a, b) => seqOf(b) - seqOf(a));\n}\n\n/**\n * Distinct candidates from every peer, bounded: a few per peer, and every peer's newest before any\n * peer's next, so the bound never cuts one peer's newest for another's many.\n */\nfunction fairlyNewestFirst(byPeer: readonly (readonly unknown[])[]): unknown[] {\n const ranked = byPeer.map((candidates) =>\n newestFirst(candidates).slice(0, MAX_LIST_CANDIDATES_PER_PEER),\n );\n const rounds: unknown[] = [];\n for (let rank = 0; rank < MAX_LIST_CANDIDATES_PER_PEER; rank += 1) {\n rounds.push(...newestFirst(ranked.flatMap((candidates) => candidates.slice(rank, rank + 1))));\n }\n return distinct(rounds).slice(0, MAX_LIST_CANDIDATES);\n}\n","/**\n * Where rendezvous records are published: a store of signed mutable items that anyone may run and\n * nobody is trusted to run honestly.\n *\n * A store checks every item it returns against the key and salt it was asked for, so a node or relay\n * that serves a tampered item, or one signed by another key, yields nothing. What an item holds is\n * the pair's ciphertext; opening it is the caller's business. Isomorphic: WebCrypto and `fetch`.\n */\nimport {\n BEP44_MAX_VALUE_BYTES,\n concatBytes,\n signMutableItem,\n verifyMutableItem,\n bep44SignedBytes,\n verifyEd25519,\n type IItemAddress,\n type IMutableItem,\n} from './mesh-records.js';\n\nexport interface IRendezvousItemStore {\n /** Publish `value` at `address`, as of `now` (ms); rejects when no holder took it. */\n put(address: IItemAddress, value: Uint8Array, now: number, signal?: AbortSignal): Promise<void>;\n /** The verified value at `publicKey` and `salt`; `undefined` when none verifies. Never throws. */\n get(\n publicKey: Uint8Array,\n salt: Uint8Array,\n signal: AbortSignal,\n ): Promise<Uint8Array | undefined>;\n close(): void;\n}\n\nfunction hex(bytes: Uint8Array): string {\n let out = '';\n for (const b of bytes) out += b.toString(16).padStart(2, '0');\n return out;\n}\n\n// ── In-process network, for tests and loopback ─────────────────────────────────────────────────\n\nexport interface IInMemoryItemNetwork {\n /** A store client attached to this network. */\n store(): IRendezvousItemStore;\n /** Every item the network holds, as stored. */\n items(): readonly IMutableItem[];\n /** Replace what the network serves (a hostile node): return the item to serve, or `undefined`. */\n serve(rewrite: (item: IMutableItem) => IMutableItem | undefined): void;\n}\n\n/**\n * An in-process item network with a DHT node's rules: it refuses an item whose signature does not\n * verify, and a sequence number lower than the one it holds.\n */\nexport function createInMemoryItemNetwork(): IInMemoryItemNetwork {\n const held = new Map<string, IMutableItem>();\n let rewrite: (item: IMutableItem) => IMutableItem | undefined = (item) => item;\n const target = (k: Uint8Array, salt: Uint8Array | undefined): string =>\n `${hex(k)}/${hex(salt ?? new Uint8Array(0))}`;\n return {\n store: () => ({\n async put(address, value, now) {\n const item = await signMutableItem(\n address.key,\n address.salt,\n Math.floor(now / 1000),\n value,\n );\n if (!(await verifyMutableItem(item, item.k, item.salt))) throw new Error('item refused');\n const at = target(item.k, item.salt);\n const current = held.get(at);\n if (current !== undefined && current.seq > item.seq)\n throw new Error('item refused: older seq');\n held.set(at, item);\n },\n async get(publicKey, salt) {\n await Promise.resolve();\n const stored = held.get(target(publicKey, salt));\n const served = stored === undefined ? undefined : rewrite(stored);\n if (served === undefined) return undefined;\n return (await verifyMutableItem(served, publicKey, salt)) ? served.v : undefined;\n },\n close: () => undefined,\n }),\n items: () => [...held.values()],\n serve: (next) => {\n rewrite = next;\n },\n };\n}\n\n// ── pkarr relays (HTTP), for clients that cannot reach the DHT ─────────────────────────────────\n\nconst Z_BASE32 = 'ybndrfg8ejkmcpqxot1uwisza345h769';\n\n/** z-base-32, as pkarr names keys. */\nexport function zBase32(bytes: Uint8Array): string {\n let out = '';\n let buffer = 0;\n let bits = 0;\n for (const byte of bytes) {\n buffer = (buffer << 8) | byte;\n bits += 8;\n while (bits >= 5) {\n out += Z_BASE32[(buffer >>> (bits - 5)) & 31];\n bits -= 5;\n }\n }\n if (bits > 0) out += Z_BASE32[(buffer << (5 - bits)) & 31];\n return out;\n}\n\nconst DNS_TYPE_TXT = 16;\nconst DNS_CLASS_IN = 1;\nconst TXT_TTL_S = 300;\nconst RECORD_LABEL = '_r';\n\nfunction u16(value: number): Uint8Array {\n return Uint8Array.from([(value >>> 8) & 0xff, value & 0xff]);\n}\n\n/**\n * The DNS packet a pkarr relay carries: one TXT answer holding `value`. pkarr relays accept only a\n * DNS packet as the value, and no salt, so the pkarr form of a record is the salt-less item under\n * the same one-time key, whose value is this packet.\n */\nexport function encodePkarrPacket(publicKey: Uint8Array, value: Uint8Array): Uint8Array {\n const labels = [RECORD_LABEL, zBase32(publicKey)];\n const name = concatBytes([\n ...labels.flatMap((l) => [Uint8Array.of(l.length), new TextEncoder().encode(l)]),\n Uint8Array.of(0),\n ]);\n const chunks: Uint8Array[] = [];\n for (let i = 0; i < value.length; i += 255) {\n const part = value.subarray(i, i + 255);\n chunks.push(Uint8Array.of(part.length), part);\n }\n if (value.length === 0) chunks.push(Uint8Array.of(0));\n const rdata = concatBytes(chunks);\n return concatBytes([\n // id 0, flags: response + authoritative, 0 questions, 1 answer, 0 authority, 0 additional\n Uint8Array.from([0, 0, 0x84, 0, 0, 0, 0, 1, 0, 0, 0, 0]),\n name,\n u16(DNS_TYPE_TXT),\n u16(DNS_CLASS_IN),\n Uint8Array.from([0, 0, (TXT_TTL_S >>> 8) & 0xff, TXT_TTL_S & 0xff]),\n u16(rdata.length),\n rdata,\n ]);\n}\n\n/** The value of the first TXT answer of a pkarr packet; `undefined` when there is none. */\nexport function decodePkarrPacket(packet: Uint8Array): Uint8Array | undefined {\n const read16 = (at: number): number => ((packet[at] ?? 0) << 8) | (packet[at + 1] ?? 0);\n if (packet.length < 12) return undefined;\n const questions = read16(4);\n const answers = read16(6);\n let at = 12;\n const skipName = (): boolean => {\n for (let hops = 0; hops < 128; hops += 1) {\n const len = packet[at];\n if (len === undefined) return false;\n if (len === 0) {\n at += 1;\n return true;\n }\n if ((len & 0xc0) === 0xc0) {\n at += 2;\n return true;\n }\n at += 1 + len;\n }\n return false;\n };\n for (let q = 0; q < questions; q += 1) {\n if (!skipName()) return undefined;\n at += 4;\n }\n for (let a = 0; a < answers; a += 1) {\n if (!skipName() || at + 10 > packet.length) return undefined;\n const type = read16(at);\n const rdLength = read16(at + 8);\n const start = at + 10;\n const end = start + rdLength;\n if (end > packet.length) return undefined;\n if (type === DNS_TYPE_TXT) {\n const parts: Uint8Array[] = [];\n let p = start;\n while (p < end) {\n const len = packet[p]!;\n if (p + 1 + len > end) return undefined;\n parts.push(packet.subarray(p + 1, p + 1 + len));\n p += 1 + len;\n }\n return concatBytes(parts);\n }\n at = end;\n }\n return undefined;\n}\n\nexport interface IPkarrRelayStoreOptions {\n /** Relay base URLs (`https://…`), each run by whoever the user chose; several operators. */\n readonly relays: readonly string[];\n /** Default: the global `fetch`. */\n readonly fetch?: typeof fetch;\n}\n\nconst PKARR_CONTENT_TYPE = 'application/pkarr.org/relays#payload';\nconst SIG_BYTES = 64;\nconst SEQ_BYTES = 8;\n\nfunction seqBytes(seq: number): Uint8Array {\n const out = new Uint8Array(SEQ_BYTES);\n new DataView(out.buffer).setBigUint64(0, BigInt(seq));\n return out;\n}\n\nfunction withoutTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url[end - 1] === '/') end -= 1;\n return url.slice(0, end);\n}\n\n/**\n * A store over several pkarr relays. A put goes to every relay and succeeds when one took it; a get\n * asks every relay and keeps the newest item that verifies. A relay is only a cache in front of the\n * DHT: it can withhold or serve stale items, never forge one.\n */\nexport function createPkarrRelayStore(options: IPkarrRelayStoreOptions): IRendezvousItemStore {\n const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n const urlOf = (relay: string, publicKey: Uint8Array): string =>\n `${withoutTrailingSlashes(relay)}/${zBase32(publicKey)}`;\n return {\n async put(address, value, now, signal) {\n const packet = encodePkarrPacket(address.key.publicKey, value);\n if (packet.length > BEP44_MAX_VALUE_BYTES)\n throw new Error('record too large for a pkarr relay');\n const seq = Math.floor(now) * 1000;\n const sig = await address.key.sign(bep44SignedBytes(undefined, seq, packet));\n const body = concatBytes([sig, seqBytes(seq), packet]);\n const results = await Promise.allSettled(\n options.relays.map(async (relay) => {\n const response = await doFetch(urlOf(relay, address.key.publicKey), {\n method: 'PUT',\n headers: { 'content-type': PKARR_CONTENT_TYPE },\n body: body.slice().buffer,\n ...(signal !== undefined ? { signal } : {}),\n });\n if (!response.ok) throw new Error(`pkarr relay refused (${response.status})`);\n }),\n );\n if (!results.some((r) => r.status === 'fulfilled')) {\n throw new Error('no pkarr relay took the record');\n }\n },\n async get(publicKey, _salt, signal) {\n const found = await Promise.all(\n options.relays.map(async (relay) => {\n try {\n const response = await doFetch(urlOf(relay, publicKey), { signal });\n if (!response.ok) return undefined;\n const body = new Uint8Array(await response.arrayBuffer());\n if (body.length <= SIG_BYTES + SEQ_BYTES || body.length > 1_200) return undefined;\n const sig = body.subarray(0, SIG_BYTES);\n const seq = Number(\n new DataView(body.buffer, body.byteOffset + SIG_BYTES, SEQ_BYTES).getBigUint64(0),\n );\n const packet = body.slice(SIG_BYTES + SEQ_BYTES);\n if (!Number.isSafeInteger(seq)) return undefined;\n if (!(await verifyEd25519(publicKey, bep44SignedBytes(undefined, seq, packet), sig))) {\n return undefined;\n }\n const value = decodePkarrPacket(packet);\n return value === undefined ? undefined : { seq, value };\n } catch {\n // allow-fallback: a relay that fails or times out has nothing; the others are asked too\n return undefined;\n }\n }),\n );\n let best: { seq: number; value: Uint8Array } | undefined;\n for (const entry of found)\n if (entry !== undefined && (best === undefined || entry.seq > best.seq)) best = entry;\n return best?.value;\n },\n close: () => undefined,\n };\n}\n","/**\n * Rendezvous records on the BitTorrent Mainline DHT, as BEP 44 mutable items, straight from this\n * process over UDP — no relay in between.\n *\n * DHT nodes are strangers: they see the one-time key, the rotating salt and ciphertext, and the\n * publisher's address, never who the record is for. Every item a lookup returns is checked here\n * against the key and salt asked for, whatever the nodes claimed to check.\n */\nimport { createHash, createPublicKey, verify as verifySignature } from 'node:crypto';\n\nimport {\n BEP44_MAX_VALUE_BYTES,\n concatBytes,\n signMutableItem,\n verifyMutableItem,\n type IMutableItem,\n} from './mesh-records.js';\n\nimport type { IRendezvousItemStore } from './mesh-item-store.js';\n\n/** A BEP 44 item as `bittorrent-dht` hands it over. */\ninterface IDhtGetResult {\n readonly v?: Uint8Array;\n readonly k?: Uint8Array;\n readonly sig?: Uint8Array;\n readonly seq?: number;\n readonly salt?: Uint8Array;\n}\n\n/** The slice of a `bittorrent-dht` instance this store drives. */\nexport interface IMainlineDht {\n put(\n item: {\n readonly k: Buffer;\n readonly salt: Buffer;\n readonly seq: number;\n readonly v: Buffer;\n readonly sig: Buffer;\n },\n callback: (error: Error | null, hash: Buffer, responses?: number) => void,\n ): void;\n get(\n target: Buffer,\n options: { readonly salt: Buffer; readonly cache: false },\n callback: (error: Error | null, result: IDhtGetResult | null) => void,\n ): void;\n on(event: 'error' | 'warning', listener: (error: Error) => void): void;\n destroy(callback?: () => void): void;\n}\n\nexport interface IMainlineDhtStoreOptions {\n /** Test seam: the DHT client; default a `bittorrent-dht` node on the public bootstrap nodes. */\n readonly createDht?: (verify: TDhtVerify) => IMainlineDht | Promise<IMainlineDht>;\n readonly onError?: (error: Error) => void;\n}\n\ntype TDhtVerify = (signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array) => boolean;\n\nconst ED25519_SPKI_PREFIX = Uint8Array.from([\n 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,\n]);\n\n/** Synchronous Ed25519 verification, as the DHT client asks for it. Never throws. */\nexport const verifyEd25519Sync: TDhtVerify = (signature, message, publicKey) => {\n if (publicKey.length !== 32 || signature.length !== 64) return false;\n try {\n const key = createPublicKey({\n key: Buffer.from(concatBytes([ED25519_SPKI_PREFIX, publicKey])),\n format: 'der',\n type: 'spki',\n });\n return verifySignature(null, message, key, signature);\n } catch {\n // allow-fallback: a key or signature that cannot be read verifies nothing\n return false;\n }\n};\n\n/** The DHT client package; it ships no types, so it is loaded by name and narrowed here. */\nconst DHT_MODULE: string = 'bittorrent-dht';\n\n/** A DHT node on the public bootstrap nodes. */\nasync function defaultDht(verify: TDhtVerify): Promise<IMainlineDht> {\n // eslint-disable-next-line no-restricted-syntax -- truly conditional: only a device that turns the DHT on loads it, and the package is ESM-only\n const module = (await import(DHT_MODULE)) as {\n default: new (options: { verify: TDhtVerify }) => IMainlineDht;\n };\n return new module.default({ verify });\n}\n\nfunction toBuffer(bytes: Uint8Array): Buffer {\n return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n}\n\nfunction aborted(signal: AbortSignal | undefined): Promise<never> | undefined {\n if (signal === undefined) return undefined;\n return new Promise((_resolve, reject) => {\n if (signal.aborted) reject(new Error('aborted'));\n signal.addEventListener('abort', () => reject(new Error('aborted')), { once: true });\n });\n}\n\n/**\n * A Mainline DHT store. The node joins the DHT when the store is created and stays for the life of\n * the store, since joining takes seconds a lookup should not wait on each time.\n */\nexport async function startMainlineDhtStore(\n options: IMainlineDhtStoreOptions = {},\n): Promise<IRendezvousItemStore> {\n const dht = await (options.createDht ?? defaultDht)(verifyEd25519Sync);\n const report = (error: Error): void => options.onError?.(error);\n dht.on('error', report);\n dht.on('warning', report);\n let closed = false;\n\n return {\n async put(address, value, now, signal) {\n if (closed) throw new Error('DHT store closed');\n if (value.length > BEP44_MAX_VALUE_BYTES) throw new Error('record value exceeds 999 bytes');\n const item = await signMutableItem(address.key, address.salt, Math.floor(now / 1000), value);\n const put = new Promise<void>((resolve, reject) => {\n dht.put(\n {\n k: toBuffer(item.k),\n salt: toBuffer(address.salt),\n seq: item.seq,\n v: toBuffer(item.v),\n sig: toBuffer(item.sig),\n },\n (error, _hash, responses) => {\n if (error !== null) reject(error);\n else if (responses === 0) reject(new Error('no DHT node took the record'));\n else resolve();\n },\n );\n });\n const abort = aborted(signal);\n await (abort === undefined ? put : Promise.race([put, abort]));\n },\n async get(publicKey, salt, signal) {\n if (closed || signal.aborted) return undefined;\n const target = createHash('sha1')\n .update(concatBytes([publicKey, salt]))\n .digest();\n const lookup = new Promise<IDhtGetResult | null>((resolve) => {\n dht.get(target, { salt: toBuffer(salt), cache: false }, (error, result) =>\n resolve(error === null ? result : null),\n );\n });\n try {\n const result = await Promise.race([lookup, aborted(signal)!]);\n if (result?.v === undefined || result.k === undefined || result.sig === undefined) {\n return undefined;\n }\n const item: IMutableItem = {\n k: new Uint8Array(result.k),\n salt,\n seq: result.seq ?? 0,\n v: new Uint8Array(result.v),\n sig: new Uint8Array(result.sig),\n };\n return (await verifyMutableItem(item, publicKey, salt)) ? item.v : undefined;\n } catch {\n // allow-fallback: an aborted or failed lookup found nothing; the next source is tried\n return undefined;\n }\n },\n close() {\n if (closed) return;\n closed = true;\n dht.destroy();\n },\n };\n}\n","/**\n * Live signaling between two devices over public Nostr relays, as ephemeral events (kinds\n * 20000–29999, which relays forward and do not store).\n *\n * Nostr is only a mailbox run by strangers, so nothing in an event tells them whose it is:\n * - the author key is a one-time key of the pair, the direction and the epoch;\n * - the kind is drawn from the ephemeral range per pair, direction and epoch, so no constant kind\n * marks the product;\n * - there are no tags, and the content is the pair's own AEAD ciphertext of the signal, padded to a\n * step so its length says little about what it carries.\n *\n * Events go to several relays of different operators. What arrives is checked (shape, signature,\n * author key and kind for that pair and epoch, age, duplicates) and opened before it reaches the\n * node, which decodes it as hostile input like any relay-delivered signal and admits a peer by the\n * device handshake alone. The draft WebRTC-signaling NIP is not used: the event format here is ours.\n */\nimport { rendezvousEpoch, RENDEZVOUS_EPOCH_MS } from '@robota-sdk/agent-remote-pairing';\nimport { SimplePool } from 'nostr-tools/pool';\nimport { finalizeEvent, getPublicKey, verifyEvent, type Event } from 'nostr-tools/pure';\n\nimport { padJson, unpadJson } from './mesh-records.js';\n\nimport type { IMeshPeerRoute } from './mesh-discovery.js';\nimport type { IMeshRelay } from './mesh-relay.js';\n\n/** A Nostr event as relays carry it. */\nexport interface INostrEvent {\n readonly id: string;\n readonly pubkey: string;\n readonly created_at: number;\n readonly kind: number;\n readonly tags: readonly (readonly string[])[];\n readonly content: string;\n readonly sig: string;\n}\n\nexport interface INostrFilter {\n readonly kinds: readonly number[];\n readonly authors: readonly string[];\n readonly since?: number;\n}\n\n/** The relays a device signals through. */\nexport interface INostrRelayPool {\n /** Send `event` to every relay; resolves `true` when at least one took it. Never rejects. */\n publish(event: INostrEvent): Promise<boolean>;\n /** Events matching `filter` from any relay, as received. */\n subscribe(filter: INostrFilter, onEvent: (event: unknown) => void): { close(): void };\n close(): void;\n}\n\nconst EPHEMERAL_KIND_BASE = 20_000;\nconst EPHEMERAL_KIND_SPAN = 10_000;\n/** Plaintext is padded up to a multiple of this. */\nexport const NOSTR_SIGNAL_PAD_STEP = 512;\n/** Largest signal plaintext sent; relays refuse large events. */\nconst MAX_SIGNAL_BYTES = 32 * 1024;\n/** Largest event content accepted. */\nconst MAX_CONTENT_CHARS = 48 * 1024;\n/** An event older or newer than this is not taken: a relay could replay it. */\nconst DEFAULT_MAX_EVENT_AGE_S = 120;\n/** Event ids remembered to drop the copies several relays deliver. */\nconst SEEN_IDS = 2048;\nconst HEX64 = /^[0-9a-f]{64}$/;\nconst HEX128 = /^[0-9a-f]{128}$/;\n\n/** The ephemeral kind of `tag`: a pair's kind for one direction and epoch. */\nexport function ephemeralKindOf(tag: Uint8Array): number {\n return EPHEMERAL_KIND_BASE + ((((tag[0] ?? 0) << 8) | (tag[1] ?? 0)) % EPHEMERAL_KIND_SPAN);\n}\n\nfunction toBase64(bytes: Uint8Array): string {\n let binary = '';\n for (const b of bytes) binary += String.fromCharCode(b);\n return btoa(binary);\n}\n\nfunction fromBase64(value: string): Uint8Array | undefined {\n try {\n const binary = atob(value);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);\n return out;\n } catch {\n return undefined;\n }\n}\n\n/** An event's fields, copied out of whatever arrived, or `undefined` when it is not an event. */\nfunction readEvent(value: unknown): Event | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const r = value as Record<string, unknown>;\n if (typeof r.id !== 'string' || !HEX64.test(r.id)) return undefined;\n if (typeof r.pubkey !== 'string' || !HEX64.test(r.pubkey)) return undefined;\n if (typeof r.sig !== 'string' || !HEX128.test(r.sig)) return undefined;\n if (typeof r.kind !== 'number' || !Number.isInteger(r.kind)) return undefined;\n if (typeof r.created_at !== 'number' || !Number.isInteger(r.created_at)) return undefined;\n if (typeof r.content !== 'string' || r.content.length > MAX_CONTENT_CHARS) return undefined;\n if (!Array.isArray(r.tags) || r.tags.length !== 0) return undefined;\n // A fresh object: nothing the sender attached (such as a cached verdict) comes along.\n return {\n id: r.id,\n pubkey: r.pubkey,\n sig: r.sig,\n kind: r.kind,\n created_at: r.created_at,\n content: r.content,\n tags: [],\n };\n}\n\ninterface IOutbound {\n readonly route: IMeshPeerRoute;\n readonly epoch: number;\n readonly secretKey: Uint8Array;\n readonly kind: number;\n}\n\ninterface IInbound {\n readonly route: IMeshPeerRoute;\n readonly epoch: number;\n readonly kind: number;\n}\n\nexport interface INostrMeshRelayOptions {\n readonly pool: INostrRelayPool;\n readonly maxEventAgeS?: number;\n readonly now?: () => number;\n readonly onError?: (error: Error) => void;\n}\n\n/**\n * A mesh relay over Nostr. It needs the peers (see {@link IMeshRelay.declarePeers}) to derive their\n * keys, and addresses a pair by its outbound topic. It reports a topic absent only when it knows no\n * pair by it: whether the peer is listening, a relay that forwards ephemeral events cannot tell.\n */\nexport class NostrMeshRelay implements IMeshRelay {\n private readonly messages = new Set<(topic: string, data: unknown) => void>();\n private readonly absents = new Set<(topic: string) => void>();\n private outbound = new Map<string, IOutbound>();\n private inbound = new Map<string, IInbound>();\n private subscription?: { close(): void };\n private chain: Promise<void> = Promise.resolve();\n private readonly seen = new Set<string>();\n private routes: readonly IMeshPeerRoute[] = [];\n private epoch?: number;\n private timer?: ReturnType<typeof setTimeout>;\n private closed = false;\n\n public constructor(private readonly options: INostrMeshRelayOptions) {}\n\n private now(): number {\n return (this.options.now ?? Date.now)();\n }\n\n private report(error: unknown): void {\n this.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n }\n\n /** Run `task` after everything queued before it, so keys are in place before a send uses them. */\n private enqueue(task: () => Promise<void> | void): void {\n this.chain = this.chain.then(task).catch((error: unknown) => this.report(error));\n }\n\n /** Resolves once every declaration, send and receipt queued so far has run. */\n public whenIdle(): Promise<void> {\n return this.chain;\n }\n\n public declarePresence(): void {\n // Nostr addresses a pair by its derived keys, not by the relay inbox topics.\n }\n\n public declarePeers(peers: readonly IMeshPeerRoute[]): void {\n this.enqueue(() => this.derive(peers));\n }\n\n /** The pairs' keys and kinds for this epoch, and a subscription for the peers' adjacent epochs. */\n private async derive(peers: readonly IMeshPeerRoute[]): Promise<void> {\n if (this.closed) return;\n this.routes = peers;\n const epoch = rendezvousEpoch(this.now());\n const outbound = new Map<string, IOutbound>();\n const inbound = new Map<string, IInbound>();\n for (const route of peers) {\n const secretKey = await route.rendezvous.signingSeed('outbound', epoch, 'signal');\n const kind = ephemeralKindOf(await route.rendezvous.tag('nostr-kind', 'outbound', epoch));\n outbound.set(route.outbound, { route, epoch, secretKey, kind });\n for (const e of [epoch - 1, epoch, epoch + 1]) {\n const seed = await route.rendezvous.signingSeed('inbound', e, 'signal');\n const peerKind = ephemeralKindOf(await route.rendezvous.tag('nostr-kind', 'inbound', e));\n inbound.set(getPublicKey(seed), { route, epoch: e, kind: peerKind });\n }\n }\n if (this.closed) return;\n this.outbound = outbound;\n this.inbound = inbound;\n this.epoch = epoch;\n this.subscription?.close();\n this.subscription =\n inbound.size === 0\n ? undefined\n : this.options.pool.subscribe(\n {\n kinds: [...new Set([...inbound.values()].map((i) => i.kind))],\n authors: [...inbound.keys()],\n since:\n Math.floor(this.now() / 1000) -\n (this.options.maxEventAgeS ?? DEFAULT_MAX_EVENT_AGE_S),\n },\n (event) => this.enqueue(() => this.receive(event)),\n );\n if (this.timer !== undefined) clearTimeout(this.timer);\n this.timer = setTimeout(\n () => this.declarePeers(this.routes),\n Math.max(0, (epoch + 1) * RENDEZVOUS_EPOCH_MS - this.now()),\n );\n this.timer.unref?.();\n }\n\n public send(topic: string, data: unknown): void {\n if (this.closed) return;\n this.enqueue(async () => {\n if (this.closed) return;\n // A send that outlived its epoch waits for the new keys.\n if (this.epoch !== undefined && rendezvousEpoch(this.now()) !== this.epoch) {\n await this.derive(this.routes);\n }\n const out = this.outbound.get(topic);\n if (out === undefined) {\n for (const handler of this.absents) handler(topic);\n return;\n }\n const json = new TextEncoder().encode(JSON.stringify(data)).length;\n if (json > MAX_SIGNAL_BYTES) throw new Error('signal too large for a Nostr event');\n const padded = padJson(data, Math.ceil(json / NOSTR_SIGNAL_PAD_STEP) * NOSTR_SIGNAL_PAD_STEP);\n const sealed = await out.route.rendezvous.sealRecord(padded, out.epoch, 'signal');\n const event = finalizeEvent(\n {\n kind: out.kind,\n created_at: Math.floor(this.now() / 1000),\n tags: [],\n content: toBase64(sealed),\n },\n out.secretKey,\n );\n if (!(await this.options.pool.publish(event))) {\n throw new Error('no Nostr relay took the signal');\n }\n });\n }\n\n private async receive(raw: unknown): Promise<void> {\n if (this.closed) return;\n const event = readEvent(raw);\n if (event === undefined || this.seen.has(event.id)) return;\n const from = this.inbound.get(event.pubkey);\n if (from === undefined || event.kind !== from.kind) return;\n const ageS = Math.abs(Math.floor(this.now() / 1000) - event.created_at);\n if (ageS > (this.options.maxEventAgeS ?? DEFAULT_MAX_EVENT_AGE_S)) return;\n if (!verifyEvent(event)) return;\n this.remember(event.id);\n const sealed = fromBase64(event.content);\n if (sealed === undefined) return;\n const opened = await from.route.rendezvous.openRecord(sealed, from.epoch, 'signal');\n if (opened?.epoch !== from.epoch) return;\n const data = unpadJson(opened.hints);\n if (data === undefined || this.closed) return;\n for (const handler of this.messages) handler(from.route.inbound, data);\n }\n\n private remember(id: string): void {\n this.seen.add(id);\n if (this.seen.size <= SEEN_IDS) return;\n const oldest = this.seen.values().next().value;\n if (oldest !== undefined) this.seen.delete(oldest);\n }\n\n public onMessage(handler: (topic: string, data: unknown) => void): () => void {\n this.messages.add(handler);\n return () => this.messages.delete(handler);\n }\n\n public onAbsent(handler: (topic: string) => void): () => void {\n this.absents.add(handler);\n return () => this.absents.delete(handler);\n }\n\n public close(): void {\n if (this.closed) return;\n this.closed = true;\n if (this.timer !== undefined) clearTimeout(this.timer);\n this.subscription?.close();\n this.messages.clear();\n this.absents.clear();\n this.options.pool.close();\n }\n}\n\n// ── In-process relays, for tests and loopback ──────────────────────────────────────────────────\n\nexport interface IInMemoryNostrHub {\n /** A client of every relay of this hub. */\n pool(): INostrRelayPool;\n /** Every event the hub forwarded, as received. */\n readonly events: readonly INostrEvent[];\n /** Deliver `event` as a relay would, to every matching subscription (a hostile relay's injection). */\n inject(event: unknown): void;\n}\n\nfunction matches(filter: INostrFilter, event: INostrEvent): boolean {\n return filter.kinds.includes(event.kind) && filter.authors.includes(event.pubkey);\n}\n\n/**\n * In-process relays with a relay's rules for ephemeral events: an event whose signature does not\n * verify is refused, and one that passes is forwarded to matching subscriptions and not stored.\n * Deliveries run on a microtask, as a network would never deliver synchronously.\n */\nexport function createInMemoryNostrHub(): IInMemoryNostrHub {\n const subscriptions = new Set<{ filter: INostrFilter; onEvent: (event: unknown) => void }>();\n const events: INostrEvent[] = [];\n const deliver = (event: unknown): void => {\n const copy: unknown = JSON.parse(JSON.stringify(event));\n queueMicrotask(() => {\n for (const sub of [...subscriptions]) {\n if (matches(sub.filter, copy as INostrEvent)) sub.onEvent(copy);\n }\n });\n };\n return {\n pool: () => {\n const mine = new Set<{ filter: INostrFilter; onEvent: (event: unknown) => void }>();\n return {\n publish: (event) => {\n const read = readEvent(event);\n if (read === undefined || !verifyEvent(read)) return Promise.resolve(false);\n events.push(JSON.parse(JSON.stringify(event)) as INostrEvent);\n deliver(event);\n return Promise.resolve(true);\n },\n subscribe: (filter, onEvent) => {\n const sub = { filter, onEvent };\n subscriptions.add(sub);\n mine.add(sub);\n return {\n close: () => {\n subscriptions.delete(sub);\n mine.delete(sub);\n },\n };\n },\n close: () => {\n for (const sub of mine) subscriptions.delete(sub);\n mine.clear();\n },\n };\n },\n events,\n inject: (event) => deliver(event),\n };\n}\n\n// ── Public relays, through nostr-tools ─────────────────────────────────────────────────────────\n\n/**\n * A pool over `relays` (`wss://…`, several operators) with `nostr-tools`, connecting on first use.\n * The relays see one-time keys, rotating kinds and ciphertext; they are trusted with nothing.\n */\nexport function createNostrRelayPool(relays: readonly string[]): INostrRelayPool {\n if (relays.length === 0) throw new Error('a Nostr relay pool needs at least one relay');\n const pool = new SimplePool();\n const urls = [...relays];\n return {\n publish: async (event) => {\n const results = await Promise.allSettled(pool.publish(urls, event as Event));\n return results.some((r) => r.status === 'fulfilled');\n },\n subscribe: (filter, onEvent) =>\n pool.subscribeMany(\n urls,\n {\n kinds: [...filter.kinds],\n authors: [...filter.authors],\n ...(filter.since !== undefined ? { since: filter.since } : {}),\n },\n { onevent: (event) => onEvent(event) },\n ),\n close: () => pool.destroy(),\n };\n}\n","/**\n * Default public infrastructure for finding and signaling a peer device beyond the local network.\n *\n * These are well-known relays run by different operators, chosen so that no single operator sees\n * every record or every signal. None of them is trusted: they carry signed ciphertext under\n * one-time keys, and a peer is admitted by the device handshake alone. They are defaults only —\n * settings replace them, and a self-hosted relay remains an option.\n */\n\n/** Nostr relays for live signaling, one per operator. */\nexport const DEFAULT_NOSTR_RELAYS: readonly string[] = [\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.primal.net',\n];\n\n/**\n * pkarr relays, for a client that cannot reach the Mainline DHT directly (a browser, or a network\n * that blocks UDP). Two operators: Synonym (pubky) and n0 (iroh).\n */\nexport const DEFAULT_PKARR_RELAYS: readonly string[] = [\n 'https://pkarr.pubky.app',\n 'https://pkarr.pubky.org',\n 'https://dns.iroh.link/pkarr',\n];\n","/**\n * The connection a device enrollment runs over: a new device dials the relay topic derived from its\n * enrollment code, an existing device listens there, and each gets a data channel bound to the DTLS\n * fingerprints of the connection — its own, and the one of the certificate the DTLS layer verified\n * for the peer, never SDP text.\n *\n * This file carries frames and implements no policy: what the channel says, and whether the peer is\n * believed, is the enrollment protocol's (`agent-remote-pairing`), which proves knowledge of the code\n * over these very fingerprints before anything else crosses. Until then a peer here is a stranger,\n * so the relay's signals are decoded as hostile input, a remote description must advertise exactly one\n * fingerprint, and a listener serves one attempt at a time.\n */\nimport {\n extractDtlsFingerprint,\n extractDtlsFingerprintAttribute,\n} from '@robota-sdk/agent-remote-pairing';\n\nimport { whenRemoteCertificateVerified } from './negotiated-certificate.js';\nimport { RtcPeer, type RtcChannel } from './rtc-peer.js';\n\nimport type { IDataChannelModule } from './datachannel-loader.js';\nimport type { IMeshRelay } from './mesh-relay.js';\nimport type { IIceServer } from './webrtc-transport-options.js';\n\nconst SIGNAL_VERSION = 1;\nconst CHANNEL_LABEL = 'agent-enroll';\nconst DEFAULT_CONNECT_TIMEOUT_MS = 20_000;\nconst MAX_CANDIDATES = 64;\n/** Frames kept for a consumer that has not subscribed yet; an enrollment says a handful. */\nconst MAX_EARLY_FRAMES = 16;\n/** Largest frame an enrollment channel carries: a grant holds a roster of certificates. */\nconst MAX_FRAME_CHARS = 256 * 1024;\nconst MAX_SDP_CHARS = 48 * 1024;\nconst MAX_CANDIDATE_CHARS = 2048;\nconst ATTEMPT_ID = /^[A-Za-z0-9_-]{22}$/;\n/** Time for a channel close to leave before the connection under it is closed. */\nconst PEER_CLOSE_GRACE_MS = 250;\n\n/** A data channel bound to one negotiated connection, carrying JSON frames. */\nexport interface IEnrollmentChannel {\n readonly localFingerprint: string;\n /** Of the certificate the DTLS layer verified. */\n readonly remoteFingerprint: string;\n /** Send one frame; a closed channel drops it (the peer learns of the close instead). */\n send(frame: unknown): void;\n /** Each inbound frame, JSON-decoded; text that is not JSON arrives as `undefined`. */\n onFrame(handler: (frame: unknown) => void): () => void;\n onClose(handler: () => void): () => void;\n close(): void;\n}\n\n/**\n * Why no channel came about. `absent`: nobody waits at the topic — the code is wrong, expired or\n * already used.\n */\nexport type TEnrollmentLinkFailure =\n 'absent' | 'timeout' | 'signaling' | 'channel-binding' | 'closed';\n\nexport class EnrollmentLinkError extends Error {\n readonly reason: TEnrollmentLinkFailure;\n\n constructor(reason: TEnrollmentLinkFailure, detail?: string) {\n super(`enrollment connection failed (${reason})${detail !== undefined ? `: ${detail}` : ''}`);\n this.name = 'EnrollmentLinkError';\n this.reason = reason;\n }\n}\n\nexport interface IEnrollmentRendezvous {\n readonly relay: IMeshRelay;\n /** The topic this side waits at. */\n readonly inbound: string;\n /** The topic the other side waits at. */\n readonly outbound: string;\n readonly iceServers?: readonly IIceServer[];\n /** Test seam: inject the `node-datachannel` module. */\n readonly loadDataChannel?: () => IDataChannelModule;\n /** From the first signal of an attempt to a bound, open channel (default 20 s). */\n readonly connectTimeoutMs?: number;\n}\n\ntype TSignal =\n | { readonly kind: 'offer' | 'answer'; readonly cid: string; readonly sdp: string }\n | {\n readonly kind: 'ice';\n readonly cid: string;\n readonly candidate: { readonly candidate: string; readonly mid: string };\n };\n\nfunction bounded(value: unknown, max: number): value is string {\n return typeof value === 'string' && value.length > 0 && value.length <= max;\n}\n\nfunction decodeSignal(value: unknown): TSignal | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const r = value as Record<string, unknown>;\n if (r['v'] !== SIGNAL_VERSION || typeof r['cid'] !== 'string' || !ATTEMPT_ID.test(r['cid'])) {\n return undefined;\n }\n const cid = r['cid'];\n if (r['kind'] === 'offer' || r['kind'] === 'answer') {\n return bounded(r['sdp'], MAX_SDP_CHARS) ? { kind: r['kind'], cid, sdp: r['sdp'] } : undefined;\n }\n if (r['kind'] === 'ice') {\n const c = r['candidate'];\n if (typeof c !== 'object' || c === null || Array.isArray(c)) return undefined;\n const cr = c as Record<string, unknown>;\n if (!bounded(cr['candidate'], MAX_CANDIDATE_CHARS) || !bounded(cr['mid'], 64)) return undefined;\n return { kind: 'ice', cid, candidate: { candidate: cr['candidate'], mid: cr['mid'] } };\n }\n return undefined;\n}\n\nfunction randomId(): string {\n const bytes = new Uint8Array(16);\n globalThis.crypto.getRandomValues(bytes);\n return Buffer.from(bytes).toString('base64url');\n}\n\n/** One connection attempt, in either role, until its channel is bound and open. */\nclass EnrollmentAttempt {\n private peer?: RtcPeer;\n private channel?: RtcChannel;\n private localFingerprint?: string;\n private remoteFingerprint?: string;\n private tookRemoteDescription = false;\n private sentDescription = false;\n private readonly localCandidates: TSignal[] = [];\n private remoteCandidates = 0;\n private chain: Promise<void> = Promise.resolve();\n private stopAwaiting?: () => void;\n private readonly timer: ReturnType<typeof setTimeout>;\n private done = false;\n private readonly early: string[] = [];\n private readonly frameHandlers = new Set<(frame: unknown) => void>();\n private readonly closeHandlers = new Set<() => void>();\n\n public constructor(\n private readonly role: 'offerer' | 'answerer',\n public readonly cid: string,\n private readonly options: IEnrollmentRendezvous,\n private readonly onBound: (channel: IEnrollmentChannel) => void,\n private readonly onFailed: (error: EnrollmentLinkError) => void,\n ) {\n this.timer = setTimeout(\n () => this.fail(new EnrollmentLinkError('timeout')),\n options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,\n );\n }\n\n public async offer(): Promise<void> {\n try {\n const peer = this.createPeer();\n this.adopt(peer.createDataChannel(CHANNEL_LABEL));\n const sdp = await peer.createOffer();\n if (this.done) return;\n this.localFingerprint = extractDtlsFingerprint(sdp);\n this.sendDescription({ kind: 'offer', cid: this.cid, sdp });\n } catch (error) {\n this.fail(new EnrollmentLinkError('signaling', describe(error)));\n }\n }\n\n public onSignal(signal: TSignal): void {\n if (this.done) return;\n this.chain = this.chain\n .then(() => this.apply(signal))\n .catch((error: unknown) => this.fail(new EnrollmentLinkError('signaling', describe(error))));\n }\n\n private async apply(signal: TSignal): Promise<void> {\n if (this.done) return;\n if (signal.kind === 'ice') {\n if (this.peer === undefined || ++this.remoteCandidates > MAX_CANDIDATES) return;\n this.peer.addRemoteCandidate(signal.candidate);\n return;\n }\n const expected = this.role === 'offerer' ? 'answer' : 'offer';\n // One remote description: the one fingerprint checked is the one the binding names.\n if (signal.kind !== expected || this.tookRemoteDescription) return;\n this.tookRemoteDescription = true;\n let algorithm: string;\n try {\n algorithm = extractDtlsFingerprintAttribute(signal.sdp).algorithm;\n } catch (error) {\n this.fail(new EnrollmentLinkError('channel-binding', describe(error)));\n return;\n }\n if (this.role === 'offerer') {\n const peer = this.peer;\n if (peer === undefined) return;\n peer.acceptAnswer(signal.sdp);\n this.awaitCertificate(peer, algorithm);\n return;\n }\n const peer = this.createPeer();\n const sdp = await peer.acceptOffer(signal.sdp);\n if (this.done) return;\n this.localFingerprint = extractDtlsFingerprint(sdp);\n this.sendDescription({ kind: 'answer', cid: this.cid, sdp });\n this.awaitCertificate(peer, algorithm);\n }\n\n private createPeer(): RtcPeer {\n // A certificate of its own per connection, so no stable identifier crosses the relay.\n const peer = new RtcPeer({\n ...(this.options.iceServers !== undefined ? { iceServers: this.options.iceServers } : {}),\n ...(this.options.loadDataChannel !== undefined\n ? { loadDataChannel: this.options.loadDataChannel }\n : {}),\n });\n this.peer = peer;\n peer.onLocalCandidate((candidate) => {\n if (this.done) return;\n const signal: TSignal = {\n kind: 'ice',\n cid: this.cid,\n candidate: { candidate: candidate.candidate, mid: candidate.mid },\n };\n if (this.sentDescription) this.send(signal);\n else if (this.localCandidates.length < MAX_CANDIDATES) this.localCandidates.push(signal);\n });\n peer.onStateChange((state) => {\n if (state === 'failed' || state === 'closed') {\n this.fail(new EnrollmentLinkError('closed', `connection ${state}`));\n }\n });\n peer.onDataChannel((channel) => {\n if (this.role === 'answerer' && this.channel === undefined && !this.done) this.adopt(channel);\n else channel.close();\n });\n return peer;\n }\n\n private send(signal: TSignal): void {\n this.options.relay.send(this.options.outbound, { v: SIGNAL_VERSION, ...signal });\n }\n\n private sendDescription(signal: TSignal): void {\n this.send(signal);\n this.sentDescription = true;\n for (const candidate of this.localCandidates.splice(0)) this.send(candidate);\n }\n\n private awaitCertificate(peer: RtcPeer, algorithm: string): void {\n this.stopAwaiting = whenRemoteCertificateVerified(\n peer,\n algorithm,\n (fingerprint) => {\n this.remoteFingerprint = fingerprint;\n this.maybeBound();\n },\n (reason) => this.fail(new EnrollmentLinkError('channel-binding', reason)),\n );\n }\n\n private adopt(channel: RtcChannel): void {\n this.channel = channel;\n // Subscribed at once: the implementation does not keep a frame that arrives before a listener.\n channel.onMessage((text) => this.inbound(text));\n channel.onStateChange((state) => {\n if (state === 'open') this.maybeBound();\n else if (state === 'closed') this.closed();\n });\n if (channel.readyState === 'open') this.maybeBound();\n }\n\n private inbound(text: string): void {\n if (text.length > MAX_FRAME_CHARS) {\n this.closeAll();\n return;\n }\n if (this.frameHandlers.size === 0) {\n if (this.early.length >= MAX_EARLY_FRAMES) {\n this.closeAll();\n return;\n }\n this.early.push(text);\n return;\n }\n this.deliver(text);\n }\n\n private deliver(text: string): void {\n let frame: unknown;\n try {\n frame = JSON.parse(text);\n } catch {\n frame = undefined;\n }\n for (const handler of this.frameHandlers) handler(frame);\n }\n\n private maybeBound(): void {\n const channel = this.channel;\n if (this.done || channel === undefined || channel.readyState !== 'open') return;\n const localFingerprint = this.localFingerprint;\n const remoteFingerprint = this.remoteFingerprint;\n if (localFingerprint === undefined || remoteFingerprint === undefined) return;\n this.done = true;\n clearTimeout(this.timer);\n this.stopAwaiting?.();\n this.onBound({\n localFingerprint,\n remoteFingerprint,\n send: (frame) => {\n try {\n channel.send(JSON.stringify(frame));\n } catch {\n // allow-fallback: a closed channel drops the frame; its close reaches both sides\n }\n },\n onFrame: (handler) => {\n this.frameHandlers.add(handler);\n const held = this.early.splice(0);\n if (held.length > 0) queueMicrotask(() => held.forEach((text) => this.deliver(text)));\n return () => this.frameHandlers.delete(handler);\n },\n onClose: (handler) => {\n if (channel.readyState === 'closed') {\n queueMicrotask(handler);\n return () => undefined;\n }\n this.closeHandlers.add(handler);\n return () => this.closeHandlers.delete(handler);\n },\n close: () => this.closeAll(),\n });\n }\n\n private closed(): void {\n if (!this.done) {\n this.fail(new EnrollmentLinkError('closed', 'data channel closed'));\n return;\n }\n this.closeAll();\n }\n\n private closeAll(): void {\n this.channel?.close();\n const peer = this.peer;\n if (peer !== undefined) setTimeout(() => peer.close(), PEER_CLOSE_GRACE_MS).unref?.();\n const handlers = [...this.closeHandlers];\n this.closeHandlers.clear();\n this.frameHandlers.clear();\n for (const handler of handlers) handler();\n }\n\n public fail(error: EnrollmentLinkError): void {\n if (this.done) return;\n this.done = true;\n clearTimeout(this.timer);\n this.stopAwaiting?.();\n this.channel?.close();\n this.peer?.close();\n this.onFailed(error);\n }\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * New device: connect to whoever waits at `outbound`. Resolves with the bound channel; rejects when\n * nobody waits there (`absent`), on timeout, or when the connection fails. The relay stays the\n * caller's.\n */\nexport function dialEnrollment(options: IEnrollmentRendezvous): Promise<IEnrollmentChannel> {\n return new Promise<IEnrollmentChannel>((resolve, reject) => {\n const unsubscribes: (() => void)[] = [];\n const finish = (): void => {\n for (const unsubscribe of unsubscribes.splice(0)) unsubscribe();\n };\n const attempt = new EnrollmentAttempt(\n 'offerer',\n randomId(),\n options,\n (channel) => {\n finish();\n resolve(channel);\n },\n (error) => {\n finish();\n reject(error);\n },\n );\n unsubscribes.push(\n options.relay.onMessage((topic, data) => {\n if (topic !== options.inbound) return;\n const signal = decodeSignal(data);\n if (signal !== undefined && signal.cid === attempt.cid) attempt.onSignal(signal);\n }),\n options.relay.onAbsent((topic) => {\n if (topic === options.outbound) attempt.fail(new EnrollmentLinkError('absent'));\n }),\n );\n options.relay.declarePresence([options.inbound]);\n void attempt.offer();\n });\n}\n\nexport interface IEnrollmentListener {\n /**\n * Stop taking new attempts. A channel already handed over stays open. The topic is held until the\n * caller closes the relay or declares other topics.\n */\n close(): void;\n}\n\n/**\n * Existing device: wait at `inbound` for a new device, one attempt at a time. Each bound channel goes\n * to `onChannel`; the next attempt is taken once that channel closes. The relay stays the caller's.\n */\nexport function listenForEnrollment(\n options: IEnrollmentRendezvous & {\n readonly onChannel: (channel: IEnrollmentChannel) => void;\n /** An attempt that ended before its channel was bound. */\n readonly onAttemptFailed?: (error: EnrollmentLinkError) => void;\n },\n): IEnrollmentListener {\n let active: EnrollmentAttempt | undefined;\n let closed = false;\n const unsubscribe = options.relay.onMessage((topic, data) => {\n if (closed || topic !== options.inbound) return;\n const signal = decodeSignal(data);\n if (signal === undefined) return;\n if (active !== undefined) {\n if (signal.cid === active.cid) active.onSignal(signal);\n return;\n }\n if (signal.kind !== 'offer') return;\n const attempt: EnrollmentAttempt = new EnrollmentAttempt(\n 'answerer',\n signal.cid,\n options,\n (channel) => {\n if (closed) {\n channel.close();\n return;\n }\n channel.onClose(() => {\n if (active === attempt) active = undefined;\n });\n options.onChannel(channel);\n },\n (error) => {\n if (active === attempt) active = undefined;\n options.onAttemptFailed?.(error);\n },\n );\n active = attempt;\n attempt.onSignal(signal);\n });\n options.relay.declarePresence([options.inbound]);\n return {\n close: () => {\n if (closed) return;\n closed = true;\n unsubscribe();\n // An attempt still connecting is dropped; a bound channel is its consumer's.\n active?.fail(new EnrollmentLinkError('closed', 'no longer listening'));\n },\n };\n}\n"],"mappings":"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"}