diff --git a/WebApp/src/lib/app/controller.js b/WebApp/src/lib/app/controller.js index 3835a6e..5411f6b 100644 --- a/WebApp/src/lib/app/controller.js +++ b/WebApp/src/lib/app/controller.js @@ -72,6 +72,7 @@ const DEFAULT_CAMERA_CONSTRAINTS = { height: { ideal: 360, max: 540 }, frameRate: { ideal: 15, max: 24 } }; +const SOCKET_HEARTBEAT_INTERVAL_MS = 10_000; const rtcConfig = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] @@ -81,6 +82,7 @@ let initialized = false; let initPromise = null; let socket = null; let pollInterval = null; +let socketHeartbeatInterval = null; let localCameraStream = null; let activeMediaRecorder = null; let activeRecordingChunks = []; @@ -1414,10 +1416,34 @@ const handleCameraStreamRequest = async ({ streamId, requesterDeviceId }) => { addActivity('Stream', 'Accepted stream request and started WebRTC offer'); }; +const stopSocketHeartbeat = () => { + if (socketHeartbeatInterval) { + clearInterval(socketHeartbeatInterval); + socketHeartbeatInterval = null; + } +}; + +const emitSocketHeartbeat = () => { + if (!socket?.connected) { + return; + } + + socket.emit('heartbeat'); +}; + +const startSocketHeartbeat = () => { + stopSocketHeartbeat(); + emitSocketHeartbeat(); + socketHeartbeatInterval = setInterval(() => { + emitSocketHeartbeat(); + }, SOCKET_HEARTBEAT_INTERVAL_MS); +}; + const connectSocket = () => { const { deviceToken } = getAppState(); if (!deviceToken) return; + stopSocketHeartbeat(); if (socket) socket.disconnect(); socket = io(getBackendUrl(), { auth: { token: deviceToken }, @@ -1425,6 +1451,7 @@ const connectSocket = () => { }); socket.on('connect', () => { + startSocketHeartbeat(); setAppState({ socketConnected: true }); addActivity('System', 'Connected to realtime server'); if (getAppState().device?.role === 'camera') { @@ -1434,6 +1461,7 @@ const connectSocket = () => { }); socket.on('disconnect', () => { + stopSocketHeartbeat(); setAppState({ socketConnected: false }); void stopLocalRecording(); teardownPeerConnection(); @@ -1443,6 +1471,7 @@ const connectSocket = () => { socket.on('connect_error', (error) => { const message = error?.message || 'Realtime connection failed'; + stopSocketHeartbeat(); setAppState({ socketConnected: false }); addActivity('System', `Realtime connection failed: ${message}`); @@ -1675,6 +1704,7 @@ const startPolling = () => { const cleanupConnectionState = async () => { stopPolling(); + stopSocketHeartbeat(); await stopLocalRecording(); teardownPeerConnection(); stopCameraPreview();