fetch('SELECT created_at FROM conversations WHERE user_id = :uid ORDER BY created_at DESC LIMIT 1', ['uid' => $userId]); $last = $r && isset($r['created_at']) ? $r['created_at'] : date('Y-m-d H:i:s', time() - 1); } catch (Exception $e) { $last = date('Y-m-d H:i:s', time() - 1); } } // Try Redis pub/sub first (if available) $usedRedis = false; if (extension_loaded('redis')) { try { $redis = new Redis(); // connect using defaults; expect redis on localhost:6379 $redis->connect('127.0.0.1'); // subscribe in a non-blocking manner via pub/sub loop $chan = "user_{$userId}"; // Use raw subscribe loop with callback $usedRedis = true; $redis->subscribe([$chan], function ($redis, $chanName, $msg) use (&$last) { // msg expected JSON string with {type:'new_message', user_id, message_id, created_at} $payload = $msg; if (is_array($msg)) $payload = json_encode($msg); echo "event: new_message\n"; echo "data: {$payload}\n\n"; @ob_flush(); @flush(); }); } catch (Exception $e) { error_log('stream_user_messages.php - redis subscribe failed: ' . $e->getMessage()); $usedRedis = false; } } // Fallback: polling loop (also used if redis is not available or subscription ended) $start = time(); $timeout = 55; // keep connection alive for ~55s (EventSource will reconnect) while (!connection_aborted()) { // Query for messages strictly newer than last timestamp try { $rows = $db->fetchAll( 'SELECT id, created_at FROM conversations WHERE user_id = :uid AND created_at > :since ORDER BY created_at ASC', ['uid' => $userId, 'since' => $last] ); } catch (Exception $e) { error_log('stream_user_messages.php - DB poll failed: ' . $e->getMessage()); $rows = []; } if (!empty($rows)) { foreach ($rows as $r) { $d = json_encode(['type' => 'new_message', 'user_id' => $userId, 'message_id' => (int)$r['id'], 'created_at' => $r['created_at']]); echo "event: new_message\n"; echo "data: {$d}\n\n"; @ob_flush(); @flush(); $last = $r['created_at']; } } // Periodic ping to keep connection alive echo "event: ping\n"; echo "data: {\"now\": \"" . date('Y-m-d H:i:s') . "\"}\n\n"; @ob_flush(); @flush(); // Break/refresh connection after timeout so client reconnects (avoids very long running PHP requests) if ((time() - $start) > $timeout) break; sleep(2); } // Final flush echo "event: close\n"; echo "data: {}\n\n"; @ob_flush(); @flush(); exit; } catch (Exception $e) { error_log('stream_user_messages.php - fatal: ' . $e->getMessage()); http_response_code(500); echo "data: {\"error\": \"Internal server error\"}\n\n"; exit; }