import type { PacketChannel } from './channels'; import type { SignalingRelayCoordinator } from './cloudflare/coordinator'; import type { Envelope } from './protocol'; type EnvelopeHandler = (envelope: Envelope) => void; type CloseHandler = (reason?: string) => void; export class CloudflareRelayPacketChannel implements PacketChannel { readonly mode = 'relay' as const; private readonly envelopeHandlers = new Set(); private readonly closeHandlers = new Set(); private opened = false; constructor( readonly peerId: string, private readonly roomId: string, private readonly targetPeerId: string, private readonly coordinator: SignalingRelayCoordinator, ) { coordinator.onEnvelope(peerId, (signal) => { if (signal.kind !== 'relay-data') return; for (const handler of this.envelopeHandlers) handler(signal.envelope); }); } send(envelope: Envelope): void { if (!this.opened) { const opened = this.coordinator.forward({ kind: 'relay-open', roomId: this.roomId, target: this.targetPeerId, from: this.peerId, }); if (opened?.kind === 'error') throw new Error(opened.message); this.opened = true; } const result = this.coordinator.forward({ kind: 'relay-data', roomId: this.roomId, target: this.targetPeerId, from: this.peerId, envelope, }); if (result?.kind === 'error') throw new Error(result.message); } onEnvelope(handler: EnvelopeHandler): () => void { this.envelopeHandlers.add(handler); return () => this.envelopeHandlers.delete(handler); } onClose(handler: CloseHandler): () => void { this.closeHandlers.add(handler); return () => this.closeHandlers.delete(handler); } close(reason?: string): void { for (const handler of this.closeHandlers) handler(reason); } }