Co-Authored-By: Claude Haiku 4.5 <[email protected]>
37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
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 <ListenEvents, EmitEvents>
|
|
// 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;
|
|
}
|
|
}
|