diff --git a/ts/test-electron/WebsocketResources_test.ts b/ts/test-electron/WebsocketResources_test.ts index 63a7e66c1..06f469ee1 100644 --- a/ts/test-electron/WebsocketResources_test.ts +++ b/ts/test-electron/WebsocketResources_test.ts @@ -225,6 +225,20 @@ describe('WebSocket-Resource', () => { this.clock.next(); }); + it('optionally disconnects if suspended', function thisNeeded1(done) { + const socket = new FakeSocket(); + + sinon.stub(socket, 'close').callsFake(() => done()); + + new WebSocketResource(socket as WebSocket, { + keepalive: true, + }); + + // Just skip one hour immediately + this.clock.setSystemTime(NOW + 3600 * 1000); + this.clock.next(); + }); + it('allows resetting the keepalive timer', function thisNeeded2(done) { const startTime = Date.now(); diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 5fdb6f456..c736d1b30 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -336,9 +336,18 @@ async function _connectSocket( reject(translatedError); }); - client.on('connectFailed', error => { + client.on('connectFailed', e => { clearTimeout(timer); - reject(error); + + reject( + makeHTTPError( + '_connectSocket connectFailed', + 0, + {}, + e.toString(), + stack + ) + ); }); }); } diff --git a/ts/textsecure/WebsocketResources.ts b/ts/textsecure/WebsocketResources.ts index d83524abd..3961d6f13 100644 --- a/ts/textsecure/WebsocketResources.ts +++ b/ts/textsecure/WebsocketResources.ts @@ -34,6 +34,8 @@ import { typedArrayToArrayBuffer as toArrayBuffer } from '../Crypto'; import EventTarget from './EventTarget'; +import { isOlderThan } from '../util/timestamp'; + class Request { verb: string; @@ -264,6 +266,10 @@ type KeepAliveOptionsType = { disconnect?: boolean; }; +const KEEPALIVE_INTERVAL_MS = 55000; // 55 seconds + 5 seconds for closing the +// socket above. +const MAX_KEEPALIVE_INTERVAL_MS = 300 * 1000; // 5 minutes + class KeepAlive { private keepAliveTimer: NodeJS.Timeout | undefined; @@ -275,6 +281,8 @@ class KeepAlive { private wsr: WebSocketResource; + private lastAliveAt: number = Date.now(); + constructor( websocketResource: WebSocketResource, opts: KeepAliveOptionsType = {} @@ -295,9 +303,19 @@ class KeepAlive { public send(): void { this.clearTimers(); + if (isOlderThan(this.lastAliveAt, MAX_KEEPALIVE_INTERVAL_MS)) { + window.log.info('WebSocketResources: disconnecting due to stale state'); + this.wsr.close( + 3001, + `Last keepalive request was too far in the past: ${this.lastAliveAt}` + ); + return; + } + if (this.disconnect) { // automatically disconnect if server doesn't ack this.disconnectTimer = setTimeout(() => { + window.log.info('WebSocketResources: disconnecting due to no response'); this.clearTimers(); this.wsr.close(3001, 'No response to keepalive request'); @@ -315,9 +333,11 @@ class KeepAlive { } public reset(): void { + this.lastAliveAt = Date.now(); + this.clearTimers(); - this.keepAliveTimer = setTimeout(() => this.send(), 55000); + this.keepAliveTimer = setTimeout(() => this.send(), KEEPALIVE_INTERVAL_MS); } private clearTimers(): void {