diff --git a/lib/tunnelbroker/tunnelbroker-context.js b/lib/tunnelbroker/tunnelbroker-context.js --- a/lib/tunnelbroker/tunnelbroker-context.js +++ b/lib/tunnelbroker/tunnelbroker-context.js @@ -4,6 +4,8 @@ import * as React from 'react'; import uuid from 'uuid'; +import { tunnelbrokerHeartbeatTimout } from '../shared/timeouts.js'; +import type { Heartbeat } from '../types/tunnelbroker/heartbeat-types.js'; import type { MessageReceiveConfirmation } from '../types/tunnelbroker/message-receive-confirmation-types.js'; import type { MessageSentStatus } from '../types/tunnelbroker/message-to-device-request-status-types.js'; import type { MessageToDeviceRequest } from '../types/tunnelbroker/message-to-device-request-types.js'; @@ -49,6 +51,22 @@ const listeners = React.useRef>(new Set()); const socket = React.useRef(null); const promises = React.useRef({}); + const heartbeatTimeoutID = React.useRef(); + + const stopHeartbeatTimeout = React.useCallback(() => { + if (heartbeatTimeoutID.current) { + clearTimeout(heartbeatTimeoutID.current); + heartbeatTimeoutID.current = null; + } + }, []); + + const resetHeartbeatTimeout = React.useCallback(() => { + stopHeartbeatTimeout(); + heartbeatTimeoutID.current = setTimeout(() => { + socket.current?.close(); + setConnected(false); + }, tunnelbrokerHeartbeatTimout); + }, [stopHeartbeatTimeout]); React.useEffect(() => { if (connected || !initMessage) { @@ -63,6 +81,7 @@ tunnelbrokerSocket.onclose = () => { setConnected(false); + stopHeartbeatTimeout(); console.error('Connection to Tunnelbroker closed'); }; tunnelbrokerSocket.onerror = e => { @@ -81,6 +100,8 @@ } const message: TunnelbrokerMessage = rawMessage; + resetHeartbeatTimeout(); + for (const listener of listeners.current) { listener(message); } @@ -124,11 +145,22 @@ console.log('Tunnelbroker recorded InvalidRequest'); } } + } else if (message.type === tunnelbrokerMessageTypes.HEARTBEAT) { + const heartbeat: Heartbeat = { + type: tunnelbrokerMessageTypes.HEARTBEAT, + }; + socket.current?.send(JSON.stringify(heartbeat)); } }; socket.current = tunnelbrokerSocket; - }, [connected, initMessage, openSocket]); + }, [ + connected, + initMessage, + openSocket, + resetHeartbeatTimeout, + stopHeartbeatTimeout, + ]); const sendMessage: (message: ClientMessageToDevice) => Promise = React.useCallback(