import type { PacketChannel } from './channels'; import type { Envelope } from './protocol'; type EnvelopeHandler = (envelope: Envelope) => void; type CloseHandler = (reason?: string) => void; class LoopbackPacketChannel implements PacketChannel { readonly mode = 'loopback' as const; private peer: LoopbackPacketChannel | null = null; private closed = false; private readonly envelopeHandlers = new Set(); private readonly closeHandlers = new Set(); constructor(readonly peerId: string) {} link(peer: LoopbackPacketChannel): void { this.peer = peer; } send(envelope: Envelope): void { if (this.closed) throw new Error(`Loopback channel ${this.peerId} is closed`); if (!this.peer || this.peer.closed) throw new Error(`Loopback peer for ${this.peerId} is closed`); queueMicrotask(() => this.peer?.emitEnvelope(envelope)); } 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 { if (this.closed) return; this.closed = true; this.emitClose(reason); const peer = this.peer; this.peer = null; peer?.close(reason); } private emitEnvelope(envelope: Envelope): void { if (this.closed) return; for (const handler of this.envelopeHandlers) handler(envelope); } private emitClose(reason?: string): void { for (const handler of this.closeHandlers) handler(reason); } } export function createLoopbackPair( aPeerId = 'loopback-a', bPeerId = 'loopback-b', ): readonly [PacketChannel, PacketChannel] { const a = new LoopbackPacketChannel(aPeerId); const b = new LoopbackPacketChannel(bPeerId); a.link(b); b.link(a); return [a, b]; }