10) { // Modo autenticado con token $userId = 'token_' . substr(md5($token), 0, 8); $authenticated = true; } } // Si aún no está autenticado, usar modo global if (!$authenticated) { // Modo global: recibir eventos broadcast a todos $userId = 'global'; error_log('SSE: Conexión en modo global (sin autenticación específica)'); } // IMPORTANTE: Enviar evento connected ANTES de hacer cualquier otra cosa error_log("SSE: Enviando evento connected (userId: {$userId})"); sendSSEEvent('connected', [ 'timestamp' => time(), 'user_id' => $userId, 'mode' => $authenticated ? 'authenticated' : 'global', 'status' => 'ready' ]); // Ahora sí, conectar a BD $db = Database::getInstance(); // Obtener último timestamp conocido por el cliente $lastEventId = $_SERVER['HTTP_LAST_EVENT_ID'] ?? null; $lastCheck = time(); $connectionStart = time(); $maxConnectionTime = 300; // 5 minutos máximo // Loop principal - revisar eventos cada 2 segundos while (true) { // Verificar tiempo máximo de conexión (evitar conexiones eternas) if (time() - $connectionStart > $maxConnectionTime) { error_log("SSE: Conexión alcanzó tiempo máximo ({$maxConnectionTime}s), cerrando..."); sendSSEEvent('timeout', ['message' => 'Conexión reiniciándose', 'reconnect' => true]); break; } // Verificar si la conexión sigue activa if (connection_aborted()) { error_log("SSE: Cliente desconectado (userId: {$userId})"); break; } // Leer eventos pendientes del archivo temporal $pendingEvents = readPendingEvents($userId); foreach ($pendingEvents as $event) { sendSSEEvent( $event['type'] ?? 'message', $event['data'] ?? [], $event['id'] ?? null ); } // Revisar nuevos mensajes en BD cada 3 segundos if (time() - $lastCheck >= 3) { // Verificar si hay nuevas conversaciones desde la última revisión $recentConversations = $db->fetchAll( "SELECT DISTINCT c.user_id, u.phone_number, u.name, MAX(c.created_at) as last_message_time, COUNT(*) as message_count FROM conversations c INNER JOIN users u ON c.user_id = u.id WHERE c.created_at >= DATE_SUB(NOW(), INTERVAL 10 SECOND) GROUP BY c.user_id ORDER BY last_message_time DESC LIMIT 5" ); foreach ($recentConversations as $conv) { sendSSEEvent('new_conversation', [ 'user_id' => $conv['user_id'], 'phone_number' => $conv['phone_number'], 'name' => $conv['name'], 'message_count' => $conv['message_count'], 'timestamp' => $conv['last_message_time'] ]); } // Verificar notificaciones no leídas try { $notifications = $db->fetchAll( "SELECT id, user_id, type, message, data, is_read, created_at FROM notifications WHERE is_read = 0 ORDER BY created_at DESC LIMIT 10" ); if ($notifications && count($notifications) > 0) { foreach ($notifications as $notification) { sendSSEEvent('notification', $notification); } } } catch (Exception $e) { error_log('SSE: Error leyendo notificaciones: ' . $e->getMessage()); } $lastCheck = time(); } // Enviar heartbeat cada 30 segundos para mantener conexión viva if (time() % 30 === 0) { sendSSEEvent('heartbeat', ['timestamp' => time()]); } // Esperar 2 segundos antes de la siguiente verificación sleep(2); } } catch (Exception $e) { error_log("SSE Error: " . $e->getMessage()); error_log("SSE Error trace: " . $e->getTraceAsString()); sendSSEEvent('error', [ 'message' => $e->getMessage(), 'code' => $e->getCode() ]); }