diff --git a/lib/shared/timeouts.js b/lib/shared/timeouts.js --- a/lib/shared/timeouts.js +++ b/lib/shared/timeouts.js @@ -41,6 +41,9 @@ // as unhealthy and chooses to close the socket. export const tunnelbrokerHeartbeatTimeout = 9000; // in milliseconds +// Client-side timeout duration for certain Tunnelbroker WebSocket requests. +export const tunnelbrokerRequestTimeout = 10000; // in milliseconds + // This controls how long the client waits before trying to reconnect a // disconnected Tunnelbroker socket. export const clientTunnelbrokerSocketReconnectDelay = 3000; 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 @@ -12,7 +12,10 @@ import { tunnnelbrokerURL } from '../facts/tunnelbroker.js'; import { DMOpsQueueHandler } from '../shared/dm-ops/dm-ops-queue-handler.react.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; -import { tunnelbrokerHeartbeatTimeout } from '../shared/timeouts.js'; +import { + tunnelbrokerHeartbeatTimeout, + tunnelbrokerRequestTimeout, +} from '../shared/timeouts.js'; import { isWebPlatform } from '../types/device-types.js'; import type { MessageSentStatus } from '../types/tunnelbroker/device-to-tunnelbroker-request-status-types.js'; import type { MessageToDeviceRequest } from '../types/tunnelbroker/message-to-device-request-types.js'; @@ -35,6 +38,7 @@ import { getConfig } from '../utils/config.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; import { useSelector } from '../utils/redux-utils.js'; +import sleep from '../utils/sleep.js'; export type TunnelbrokerClientMessageToDevice = { +deviceID: string, @@ -321,7 +325,7 @@ const sendMessage: (request: DeviceToTunnelbrokerRequest) => Promise = React.useCallback( request => { - return new Promise((resolve, reject) => { + const resultPromise: Promise = new Promise((resolve, reject) => { const socketActive = socketState.connected && socket.current; if (!shouldBeClosed && !socketActive) { throw new Error('Tunnelbroker not connected'); @@ -336,6 +340,13 @@ secondaryTunnelbrokerConnection?.sendMessage(request); } }); + + const timeoutPromise: Promise = (async () => { + await sleep(tunnelbrokerRequestTimeout); + throw new Error('Tunnelbroker request timed out'); + })(); + + return Promise.race([resultPromise, timeoutPromise]); }, [socketState, secondaryTunnelbrokerConnection, shouldBeClosed], );