import { io } from 'socket.io-client'; import type { Message, OpenThreadResult, MessageEvents, SocketLike } from './types'; export interface MessageSocketConfig { serviceUrl: string; token: string; autoConnect?: boolean; } /** * Framework-agnostic facade over the `/message` Socket.io namespace (ports the * support-sdk MessageClient). No socket.io types leak out; RPCs use emitWithAck. * On reconnect it re-opens the current thread so subscriptions resume with no * lost messages (the docs' disconnect/reconnect requirement). */ export class MessageSocket { private readonly socket: SocketLike; private currentThreadId: string | null = null; constructor(config: MessageSocketConfig, socket?: SocketLike) { this.socket = socket ?? (io(`${config.serviceUrl.replace(/\/$/, '')}/message`, { auth: { token: config.token }, transports: ['websocket'], autoConnect: config.autoConnect ?? true, }) as unknown as SocketLike); // Re-open the active thread after a reconnect. this.socket.on('connect', () => { if (this.currentThreadId) void this.socket.emitWithAck('open_thread', { threadId: this.currentThreadId }); }); } connect(): void { this.socket.connect(); } disconnect(): void { this.socket.disconnect(); } /** Subscribe to a server event; returns an unsubscribe fn. */ on(event: E, handler: MessageEvents[E]): () => void { const fn = handler as (...args: unknown[]) => void; this.socket.on(event, fn); return () => this.socket.off(event, fn); } async openThread(threadId?: string): Promise { const result = (await this.socket.emitWithAck('open_thread', { threadId })) as OpenThreadResult; this.currentThreadId = result.threadId; return result; } async sendMessage(threadId: string, content: string, opts?: { contentRef?: string }): Promise { return (await this.socket.emitWithAck('send_message', { threadId, content, contentRef: opts?.contentRef, })) as Message; } async markRead(threadId: string, interactionId: string): Promise<{ ok: boolean }> { return (await this.socket.emitWithAck('read', { threadId, interactionId })) as { ok: boolean }; } typing(threadId: string): void { this.socket.emit('typing', { threadId }); } }