import { emitter } from '@amatiasq/emitter'; import { ResilientSocket, ResilientSocketOptions, } from './ResilientSocket.js'; /** * Typed JSON over a reconnecting socket: `TIn` is what arrives, `TOut` is what * you send. * * This used to carry its own copy of the reconnection logic — the same * constants, the same doubling backoff, its own `new WebSocket` — and no message * queue. It is now what it always meant to be: a serialisation layer on top of * `ResilientSocket`, which owns reconnection for both of them. One * implementation to test, and the queue comes for free. * * A message that is not valid JSON is dropped with a warning rather than thrown: * a peer sending garbage should not take down the listener. That is the one * behaviour that differs from `SessionSocket`, which lets the parse error out. */ export class JsonSocket { private readonly socket: ResilientSocket; private readonly emitMessage = emitter(); readonly onMessage = this.emitMessage.subscribe; readonly onOpen; readonly onReconnect; readonly onError; readonly onClose; get isConnected() { return this.socket.isConnected; } get uri() { return this.socket.uri; } constructor(uri: string, options: ResilientSocketOptions = {}) { this.socket = new ResilientSocket(uri, options); this.onOpen = this.socket.onOpen; this.onError = this.socket.onError; this.onClose = this.socket.onClose; this.onReconnect = this.socket.onReconnect; this.socket.onMessage(event => this.processMessage(event)); } send(value: TOut) { this.socket.send(JSON.stringify(value)); } close() { this.socket.close(); } private processMessage(event: MessageEvent) { let message: TIn; try { message = JSON.parse(event.data) as TIn; } catch (error) { console.warn('Invalid JSON:', event.data); return; } this.emitMessage(message); } }