/** * Socket.IO 客户端封装(动态加载 socket.io-client)。 */ export type SocketIoStatus = "connecting" | "open" | "closed" | "error"; export type SocketIoSession = { emit(event: string, args: unknown[]): void; close(): void; }; export type SocketIoOptions = { url: string; /** 附加选项 JSON:{ path?, transports?, auth?, ... } */ optionsJson?: string; /** 监听事件列表;空数组 = onAny 全收 */ listenEvents: string[]; onEvent(event: string, args: unknown[]): void; onStatus?(s: SocketIoStatus, detail?: string): void; onError?(msg: string): void; }; type IoSocket = { emit(ev: string, ...args: unknown[]): void; on(ev: string, cb: (...args: unknown[]) => void): void; onAny(cb: (ev: string, ...args: unknown[]) => void): void; disconnect(): void; }; export function connectSocketIo(opts: SocketIoOptions): SocketIoSession { let socket: IoSocket | null = null; let disposed = false; void (async () => { try { opts.onStatus?.("connecting"); const { io } = await import("socket.io-client"); const extra: Record = {}; if (opts.optionsJson?.trim()) { try { Object.assign(extra, JSON.parse(opts.optionsJson)); } catch { opts.onError?.("连接选项不是合法 JSON,已忽略"); } } const s: IoSocket = io(opts.url, { transports: ["websocket", "polling"], reconnection: false, ...extra, }) as unknown as IoSocket; socket = s; s.on("connect", () => opts.onStatus?.("open")); s.on("disconnect", (...args: unknown[]) => opts.onStatus?.("closed", String(args[0] ?? "")), ); s.on("connect_error", (...args: unknown[]) => { const err = args[0]; const msg = err instanceof Error ? err.message : String(err); opts.onError?.(msg); opts.onStatus?.("error", msg); }); const events = opts.listenEvents.map((e) => e.trim()).filter(Boolean); if (events.length === 0 || events.includes("*")) { s.onAny((ev, ...args) => opts.onEvent(ev, args)); } else { for (const ev of events) { s.on(ev, (...args: unknown[]) => opts.onEvent(ev, args)); } } if (disposed) s.disconnect(); } catch (e) { opts.onError?.(`socket.io-client 加载/连接失败:${e instanceof Error ? e.message : String(e)}`); opts.onStatus?.("error"); } })(); return { emit(event, args) { if (!socket) { opts.onError?.("Socket.IO 尚未连接"); return; } socket.emit(event, ...args); }, close() { disposed = true; socket?.disconnect(); opts.onStatus?.("closed"); }, }; }