import { emitter, emitterWithChannels } from '@amatiasq/emitter'; import { Message, MessageData } from '../Message.js'; import { SocketTransport } from '../transport.js'; import { ClientId } from './ClientId.js'; import { ICMType, InternalServerMessage, ISMType } from './messaging.js'; import { parseJson } from './parseJson.js'; /** * One client's session on the server, identified by `ClientId` and outliving the * socket underneath it: when that client reconnects, `SessionServer` binds the * new socket to this same instance and every listener stays attached. * * Was `ServerSocket`. It talked to a `ws` socket directly (`on`/`off`/`send`); * it now talks to a `SocketTransport`, which is what removes `ws` from this * package. */ export class SessionConnection< ServerMessage extends Message, ClientMessage extends Message > { private transport?: SocketTransport | null; private unbindListeners: (() => void)[] = []; private readonly typeListeners = emitterWithChannels< ClientMessage['type'], ClientMessage['data'] >(); private readonly emitMessage = emitter(); readonly onMessage = this.emitMessage.subscribe; private readonly emitDestroy = emitter(); readonly onDestroy = this.emitDestroy.subscribe; constructor(readonly id: ClientId) { this._onClose = this._onClose.bind(this); this._onMessage = this._onMessage.bind(this); } bindTo(transport: SocketTransport) { this._onClose(); this.transport = transport; this.unbindListeners = [ transport.onClose(this._onClose), transport.onMessage(this._onMessage), ]; transport.send( JSON.stringify({ type: ISMType.AMQ_CONNECTED, data: this.id, } as InternalServerMessage), ); } send( type: Type, data: MessageData, ) { this.transport!.send(JSON.stringify({ type, data })); } onMessageType( type: Type, listener: (data: MessageData) => void, ) { this.typeListeners.subscribe(type, listener); } destroy() { this.emitDestroy(); } private _onMessage(payload: string) { const msg = parseJson(payload) as ClientMessage; if (msg.type === ICMType.AMQ_DISCONNECT) { this._onClose(); this.destroy(); return; } this.emitMessage(msg); this.typeListeners(msg.type, msg.data); } private _onClose() { for (const unbind of this.unbindListeners) unbind(); this.unbindListeners = []; this.transport = null; } }