This commit is contained in:
2025-11-08 17:36:52 +01:00
parent 4b196c1278
commit acb5e0a209
2 changed files with 34 additions and 35 deletions

View File

@@ -301,41 +301,42 @@ async def event_generator(request: Request) -> AsyncGenerator[str, None]:
try: try:
await pubsub.subscribe(redis_channel) await pubsub.subscribe(redis_channel)
logger.info(f"SSE client connected, subscribed to {redis_channel}")
# Create heartbeat task # Create heartbeat tracking
last_heartbeat = asyncio.get_event_loop().time() last_heartbeat = asyncio.get_event_loop().time()
heartbeat_interval = 25
while True: while True:
# Check if client disconnected # Check if client disconnected
if await request.is_disconnected(): if await request.is_disconnected():
logger.info("SSE client disconnected")
break break
# Get message with timeout for heartbeat # Try to get message (non-blocking)
try: message = await pubsub.get_message(ignore_subscribe_messages=True, timeout=0.1)
message = await asyncio.wait_for(
pubsub.get_message(ignore_subscribe_messages=True),
timeout=1.0
)
if message and message["type"] == "message": # Handle actual data messages
# Send data event if message and message["type"] == "message":
data = message["data"] data = message["data"]
yield f"event: message\ndata: {data}\n\n" logger.debug(f"Sending SSE message: {data[:100]}...")
last_heartbeat = asyncio.get_event_loop().time() yield f"event: message\ndata: {data}\n\n"
last_heartbeat = asyncio.get_event_loop().time()
except asyncio.TimeoutError: else:
pass # No message, sleep a bit to avoid busy loop
await asyncio.sleep(0.1)
# Send heartbeat every 25 seconds # Send heartbeat every 25 seconds
current_time = asyncio.get_event_loop().time() current_time = asyncio.get_event_loop().time()
if current_time - last_heartbeat >= 25: if current_time - last_heartbeat >= heartbeat_interval:
yield "event: ping\ndata: heartbeat\n\n" yield "event: ping\ndata: heartbeat\n\n"
last_heartbeat = current_time last_heartbeat = current_time
finally: finally:
await pubsub.unsubscribe(redis_channel) await pubsub.unsubscribe(redis_channel)
await pubsub.close() await pubsub.aclose()
await redis_client.close() await redis_client.aclose()
logger.info("SSE connection closed")
@app.get("/realtime") @app.get("/realtime")

View File

@@ -855,6 +855,12 @@
// Update device state // Update device state
if (data.type === 'state' && data.device_id && data.payload) { if (data.type === 'state' && data.device_id && data.payload) {
const card = document.querySelector(`[data-device-id="${data.device_id}"]`);
if (!card) {
console.warn(`No card found for device ${data.device_id}`);
return;
}
// Check if it's a light // Check if it's a light
if (data.payload.power !== undefined) { if (data.payload.power !== undefined) {
currentState[data.device_id] = data.payload.power; currentState[data.device_id] = data.payload.power;
@@ -930,11 +936,13 @@
document.addEventListener('visibilitychange', () => { document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') { if (document.visibilityState === 'visible') {
console.log('Page visible, checking SSE connection...'); console.log('Page visible, checking SSE connection...');
// Force reconnect if connection is dead // Only reconnect if connection is actually dead (CLOSED = 2)
if (!eventSource || eventSource.readyState === EventSource.CLOSED || eventSource.readyState === EventSource.CONNECTING) { if (!eventSource || eventSource.readyState === EventSource.CLOSED) {
console.log('SSE connection dead or connecting, forcing reconnect...'); console.log('SSE connection dead, forcing reconnect...');
reconnectAttempts = 0; // Reset for immediate reconnect reconnectAttempts = 0; // Reset for immediate reconnect
connectSSE(); connectSSE();
} else {
console.log('SSE connection OK, readyState:', eventSource.readyState);
} }
} }
}); });
@@ -942,26 +950,16 @@
// Safari/iOS specific: Reconnect on page focus // Safari/iOS specific: Reconnect on page focus
window.addEventListener('focus', () => { window.addEventListener('focus', () => {
console.log('Window focused, checking SSE connection...'); console.log('Window focused, checking SSE connection...');
// Only reconnect if connection is actually dead (CLOSED = 2)
if (!eventSource || eventSource.readyState === EventSource.CLOSED) { if (!eventSource || eventSource.readyState === EventSource.CLOSED) {
console.log('SSE connection dead, forcing reconnect...'); console.log('SSE connection dead, forcing reconnect...');
reconnectAttempts = 0; // Reset for immediate reconnect reconnectAttempts = 0; // Reset for immediate reconnect
connectSSE(); connectSSE();
} else {
console.log('SSE connection OK, readyState:', eventSource.readyState);
} }
}); });
// Safari/iOS specific: Keep connection alive with periodic check
setInterval(() => {
if (!eventSource || eventSource.readyState === EventSource.CLOSED) {
console.log('Periodic check: SSE connection dead, reconnecting...');
reconnectAttempts = 0;
connectSSE();
} else if (eventSource.readyState === EventSource.CONNECTING) {
console.log('Periodic check: SSE still connecting, readyState:', eventSource.readyState);
} else {
console.log('Periodic check: SSE connection alive, readyState:', eventSource.readyState);
}
}, 10000); // Check every 10 seconds
// Initialize // Initialize
connectSSE(); connectSSE();