/** * Composable for real-time updates via Server-Sent Events */ export interface RealtimeEvents { 'message.received': (data: { instanceId: string; message: any }) => void 'message.sent': (data: { instanceId: string; message: any }) => void 'message.status': (data: { instanceId: string; messageId: string; status: string }) => void 'instance.status': (data: { instanceId: string; status: string; phoneNumber?: string }) => void 'instance.qr': (data: { instanceId: string; qr: string; qrDataUrl: string }) => void } export const useRealtime = () => { const eventSource = useState('sse', () => null) const isConnected = useState('sseConnected', () => false) const lastEvent = useState<{ type: string; data: any; timestamp: Date } | null>('sseLastEvent', () => null) // Event listeners const listeners = useState>>('sseListeners', () => new Map()) const connect = () => { // Only run on client if (import.meta.server) return // Already connected if (eventSource.value?.readyState === EventSource.OPEN) return console.log('[SSE] Connecting to /api/events/stream...') const es = new EventSource('/api/events/stream') es.onopen = () => { console.log('[SSE] Connected') isConnected.value = true } es.onerror = (error) => { console.error('[SSE] Error:', error) isConnected.value = false // Reconnect after 5 seconds setTimeout(() => { if (eventSource.value?.readyState === EventSource.CLOSED) { connect() } }, 5000) } // Listen for specific event types const eventTypes = [ 'message.received', 'message.sent', 'message.status', 'instance.status', 'instance.qr', 'instance.pairing' ] eventTypes.forEach(eventType => { es.addEventListener(eventType, (e: MessageEvent) => { try { const data = JSON.parse(e.data) console.log(`[SSE] ${eventType}:`, data) // Update last event for debugging lastEvent.value = { type: eventType, data, timestamp: new Date() } // Call registered listeners const eventListeners = listeners.value.get(eventType) if (eventListeners) { eventListeners.forEach(listener => listener(data)) } } catch (err) { console.error(`[SSE] Error parsing ${eventType}:`, err) } }) }) // Generic message handler for debugging es.onmessage = (e) => { console.log('[SSE] Generic message:', e.data) } eventSource.value = es } const disconnect = () => { if (eventSource.value) { eventSource.value.close() eventSource.value = null isConnected.value = false console.log('[SSE] Disconnected') } } const on = (event: K, handler: RealtimeEvents[K]) => { if (!listeners.value.has(event)) { listeners.value.set(event, new Set()) } listeners.value.get(event)!.add(handler as Function) // Return cleanup function return () => { listeners.value.get(event)?.delete(handler as Function) } } const off = (event: K, handler: RealtimeEvents[K]) => { listeners.value.get(event)?.delete(handler as Function) } // Auto-connect on mount, disconnect on unmount onMounted(() => { connect() }) onUnmounted(() => { // Don't disconnect on unmount as other components may still need it // disconnect() }) return { isConnected, lastEvent, connect, disconnect, on, off } }