import { io } from 'socket.io-client'; import type { ChatSocket } from './chat-contract'; let socket: ChatSocket | null = null; export function getSocket(): ChatSocket { if (!socket) { // socket.io-client 4.8.3's `io()` factory declaration always returns the // default unparameterized Socket (it accepts no // generics), so this one cast is the unavoidable boundary between that and the // typed `/chat` contract. Every other call site uses the resulting ChatSocket // with no further assertions. socket = io('/chat', { withCredentials: true, autoConnect: false, transports: ['websocket', 'polling'], }) as unknown as ChatSocket; // A transient `disconnect` (network blip, server restart) must NOT null // the singleton: socket.io-client auto-reconnects this same instance, // and its listeners stay registered across that reconnect. Nulling here // previously orphaned those listeners on the next getSocket() call by // handing back a brand-new, unconnected instance. Only destroySocket() // (an explicit, intentional teardown) may reset the singleton. } return socket; } /** Tear down the singleton socket and reset the reference. */ export function destroySocket(): void { if (socket) { socket.offAny(); socket.disconnect(); socket = null; } }