import { emitter, emitterWithChannels } from '@amatiasq/emitter'; import { Message, MessageData } from '../Message.js'; import { ClientId } from './ClientId.js'; import { ICMType, InternalClientMessage, ISMType } from './messaging.js'; import { parseJson } from './parseJson.js'; import { ResilientSocket, ResilientSocketOptions, } from './ResilientSocket.js'; /** * The client of the session layer: typed messages over a socket whose identity * survives reconnection. On reconnect it re-announces its `ClientId`, so the * server rebinds it to the same `SessionConnection` instead of treating it as a * new client. * * Was `ClientSocket`, and it depended on `@amatiasq/resilient-socket`; that is * now the sibling module `ResilientSocket`, which is the one edge this merge * removed from the `workspace:` graph. */ export class SessionSocket< ClientMessage extends Message, ServerMessage extends Message > { private clientId?: ClientId; private readonly socket; private readonly typeListeners = emitterWithChannels< ServerMessage['type'], ServerMessage['data'] >(); private readonly emitConnected = emitter(); readonly onConnected = this.emitConnected.subscribe; private readonly emitMessage = emitter(); readonly onMessage = this.emitMessage.subscribe; readonly onError; readonly onClose; constructor( public readonly uri: string, options: ResilientSocketOptions = {}, ) { this.socket = new ResilientSocket(uri, options); this.onError = this.socket.onError; this.onClose = this.socket.onClose; this.socket.onOpen((this._open = this._open.bind(this))); this.socket.onReconnect((this._reconnect = this._reconnect.bind(this))); this.socket.onMessage((this._message = this._message.bind(this))); } send( type: Type, data: MessageData, ) { this.socket.send(JSON.stringify({ type, data })); } onMessageType( type: Type, listener: (data: MessageData) => void, ) { this.typeListeners.subscribe(type, listener); } close() { this._sendInternal(ICMType.AMQ_DISCONNECT, this.clientId); } private _open() { this._sendInternal(ICMType.AMQ_CONNECT, undefined); } private _reconnect() { if (this.clientId) { this._sendInternal(ICMType.AMQ_RECONNECT, this.clientId); } else { this._sendInternal(ICMType.AMQ_CONNECT, undefined); } } private _message(event: MessageEvent) { const message = parseJson(event.data); if (message.type in ISMType) { if (message.type === ISMType.AMQ_CONNECTED) { this.clientId = message.data; this.emitConnected(message.data); } return; } this.emitMessage(message); this.typeListeners(message.type, message.data); } private _sendInternal( type: Type, data: MessageData, ) { this.socket.send(JSON.stringify({ type, data })); } }