From dfdf4a6551efb672e627b17a1a7b0aeb81b53941 Mon Sep 17 00:00:00 2001 From: Lennart Grahl Date: Wed, 18 Dec 2019 10:15:52 +0100 Subject: [PATCH] Wait briefly for a broadcast message before attempting to connect (#943) --- src/partials/welcome.ts | 69 ++++++++++++++++++++++++++++++++--------- tsconfig.json | 7 +++-- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/partials/welcome.ts b/src/partials/welcome.ts index 552bfa4ec..598c98ca2 100644 --- a/src/partials/welcome.ts +++ b/src/partials/welcome.ts @@ -44,6 +44,7 @@ import GlobalConnectionState = threema.GlobalConnectionState; import DisconnectReason = threema.DisconnectReason; class WelcomeController { + private static BROADCAST_DELAY = 100; private static REDIRECT_DELAY = 500; // Angular services @@ -262,9 +263,6 @@ class WelcomeController { resume: false, }); - // Set up the broadcast channel that checks whether we're already connected in another tab - this.setupBroadcastChannel(this.webClientService.salty.keyStore.publicKeyHex); - // Initialize QR code params this.$scope.$watch(() => this.password, () => { const payload = this.webClientService.buildQrCodePayload(this.password.length > 0); @@ -272,8 +270,26 @@ class WelcomeController { this.passwordStrength = scorePassword(this.password); }); - // Start webclient - this.start(); + // Set up the broadcast channel that checks whether we're already connected in another tab + this.setupBroadcastChannel(this.webClientService.salty.keyStore.publicKeyHex, 0) + .then((result) => { + this.$scope.$apply(() => { + switch (result) { + case 'already_open': + this.log.warn('Session already connected in another tab or window'); + break; + case 'no_answer': + this.start(); + break; + } + }); + }) + .catch((error) => { + this.$scope.$apply(() => { + this.log.warn('Unable to set up broadcast channel:', error); + this.start(); + }); + }); } /** @@ -301,24 +317,43 @@ class WelcomeController { const keyStore = new saltyrtcClient.KeyStore(decrypted.ownSecretKey); // Set up the broadcast channel that checks whether we're already connected in another tab - this.setupBroadcastChannel(keyStore.publicKeyHex); - - // Reconnect - this.reconnect(keyStore, decrypted); + this.setupBroadcastChannel(keyStore.publicKeyHex, WelcomeController.BROADCAST_DELAY) + .then((result) => { + this.$scope.$apply(() => { + switch (result) { + case 'already_open': + this.log.warn('Session already connected in another tab or window'); + break; + case 'no_answer': + this.log.debug('No broadcast received indicating that a session is already open'); + this.reconnect(keyStore, decrypted); + break; + } + }); + }) + .catch((error) => { + this.$scope.$apply(() => { + this.log.warn('Unable to set up broadcast channel:', error); + this.reconnect(keyStore, decrypted); + }); + }); } /** * Set up a `BroadcastChannel` to check if there are other tabs running on - * the same session. + * the same session. Resolves when either an `already_connected` message has + * been received or a timeout of `delayMs` has been elapsed. * * The `publicKeyHex` parameter is the hex-encoded public key of the keystore * used to establish the SaltyRTC connection. */ - private setupBroadcastChannel(publicKeyHex: string) { + private setupBroadcastChannel(publicKeyHex: string, delayMs: number): Future<'already_open' | 'no_answer'> { + const future: Future<'already_open' | 'no_answer'> = new Future(); + + // Check for broadcast support in the browser if (!('BroadcastChannel' in this.$window)) { - // No BroadcastChannel support in this browser - this.log.warn('BroadcastChannel not supported in this browser'); - return; + future.reject('BroadcastChannel not supported in this browser'); + return future; } // Config constants @@ -351,7 +386,7 @@ class WelcomeController { // Another tab notified us that the session we're trying to connect to // is already active. if (message.key === publicKeyHex && this.stateService.connectionBuildupState !== 'done') { - this.log.error('Session already connected in another tab or window'); + future.resolve('already_open'); this.timeoutService.register(() => { this.stateService.updateConnectionBuildupState('already_connected'); this.stateService.state = GlobalConnectionState.Error; @@ -370,6 +405,10 @@ class WelcomeController { type: TYPE_PUBLIC_KEY, key: publicKeyHex, })); + + // Resolve after the specified delay without an `already_connected` response + setTimeout(() => future.resolve('no_answer'), delayMs); + return future; } /** diff --git a/tsconfig.json b/tsconfig.json index fa7cd420b..185d66241 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,9 @@ { "compilerOptions": { - "target": "ES2017", - "module": "esNext", - "moduleResolution": "node", + "lib": ["DOM", "ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Node", "removeComments": true }, "exclude": [