Files
whatsappNucleo/app/composables/useRealtime.ts
josedario87 26f755926b
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 51s
Feature: debug buttons + SSE realtime updates
- Agregar boton debug en MessageBubble para ver objeto completo
- Agregar boton debug en ChatItem para ver objeto completo
- Crear useRealtime composable para conectar a SSE
- Agregar indicador de estado SSE en mensajes
- Agregar panel debug para ver ultimo evento SSE
- Auto-recargar chats/mensajes cuando llegan nuevos mensajes
2025-12-02 20:18:05 -06:00

131 lines
3.6 KiB
TypeScript

/**
* 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<EventSource | null>('sse', () => null)
const isConnected = useState('sseConnected', () => false)
const lastEvent = useState<{ type: string; data: any; timestamp: Date } | null>('sseLastEvent', () => null)
// Event listeners
const listeners = useState<Map<string, Set<Function>>>('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 = <K extends keyof RealtimeEvents>(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 = <K extends keyof RealtimeEvents>(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
}
}