import { io, Socket } from 'socket.io-client' const SOCKET_URL = (() => { const url = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8008' try { return new URL(url).origin } catch { return url } })() type Listener = (data: any) => void const HEARTBEAT_INTERVAL_MS = 30_000 class SocketService { private socket: Socket | null = null private token: string | null = null private listeners = new Map>() private heartbeatTimer: ReturnType | null = null // ── Conexão ──────────────────────────────────────────────────────────────── connect(token?: string) { if (token) this.token = token if (this.socket?.connected) return this.socket = io(SOCKET_URL, { auth: { token: this.token }, transports: ['websocket', 'polling'], reconnectionAttempts: 10, reconnectionDelay: 2000, }) this.socket.on('connect', () => { this._emit('connected', { id: this.socket?.id }) this._startHeartbeat() }) this.socket.on('disconnect', (reason) => { this._stopHeartbeat() this._emit('disconnected', { reason }) }) this.socket.on('connect_error', (err) => { console.warn('[Socket] connect_error:', err.message) }) this.socket.onAny((event, data) => { console.log('[Socket Event Received]', event, data) this._emit(event, data) if (event !== '*') this._emit('*', { event, data }) }) } disconnect() { this._stopHeartbeat() this.socket?.disconnect() this.socket = null this.token = null } // ── Emit para o servidor ─────────────────────────────────────────────────── emit(event: string, ...args: any[]) { this.socket?.emit(event, ...args) } joinInstance(instanceId: string) { if (this.socket?.connected) { this.socket.emit('join:instance', instanceId) } else { // Aguarda conexão e então entra na sala const onConnect = () => { this.socket?.emit('join:instance', instanceId) this.off('connected', onConnect) } this.on('connected', onConnect) } } joinChat(chatId: string) { if (this.socket?.connected) { this.socket.emit('join:chat', chatId) } else { const onConnect = () => { this.socket?.emit('join:chat', chatId) this.off('connected', onConnect) } this.on('connected', onConnect) } } leaveChat(chatId: string) { this.socket?.emit('leave:chat', chatId) } // ── Listeners ───────────────────────────────────────────────────────────── on(event: string, cb: Listener) { if (!this.listeners.has(event)) this.listeners.set(event, new Set()) this.listeners.get(event)!.add(cb) } off(event: string, cb: Listener) { this.listeners.get(event)?.delete(cb) } // ── Estado ──────────────────────────────────────────────────────────────── get connected() { return this.socket?.connected ?? false } getSocket() { return this.socket } // ── Privado ─────────────────────────────────────────────────────────────── private _startHeartbeat() { this._stopHeartbeat() this.heartbeatTimer = setInterval(() => { if (this.socket?.connected) this.socket.emit('user:heartbeat') }, HEARTBEAT_INTERVAL_MS) } private _stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer) this.heartbeatTimer = null } } private _emit(event: string, data: any) { this.listeners.get(event)?.forEach((cb) => cb(data)) } } export const socketService = new SocketService()