Selaa lähdekoodia

Apply "device unreachable" dialog to reconnecting logic

An attempt to re-establish a connection may fail for different
reasons than pushes. For example, WebRTC could be blocked by a plugin
or the WebSocket connection fails.

If the connection cannot be established after 3 tries, the "device
unreachable" dialog will be shown.
Lennart Grahl 6 vuotta sitten
vanhempi
commit
3da5fdf752
4 muutettua tiedostoa jossa 72 lisäystä ja 36 poistoa
  1. 22 12
      src/controllers/status.ts
  2. 11 3
      src/partials/messenger.ts
  3. 2 3
      src/services/state.ts
  4. 37 18
      src/services/webclient.ts

+ 22 - 12
src/controllers/status.ts

@@ -120,21 +120,16 @@ export class StatusController {
                 if (oldValue === 'ok' && isWebrtc) {
                     this.scheduleStatusBar();
                 }
-                if (this.stateService.wasConnected) {
-                    this.webClientService.clearIsTypingFlags();
-                }
-                if (this.stateService.wasConnected && isRelayedData) {
+                this.webClientService.clearIsTypingFlags();
+                if (isRelayedData) {
                     this.reconnectIos();
                 }
                 break;
             case 'error':
-                if (this.stateService.wasConnected && isWebrtc) {
-                    if (oldValue === 'ok') {
-                        this.scheduleStatusBar();
-                    }
+                if (isWebrtc) {
                     this.reconnectAndroid();
                 }
-                if (this.stateService.wasConnected && isRelayedData) {
+                if (this.stateService.attempt === 0 && isRelayedData) {
                     this.reconnectIos();
                 }
                 break;
@@ -166,7 +161,12 @@ export class StatusController {
      * Attempt to reconnect an Android device after a connection loss.
      */
     private reconnectAndroid(): void {
-        this.$log.warn(this.logTag, 'Connection lost (Android). Attempting to reconnect...');
+        this.$log.warn(this.logTag, `Connection lost (Android). Reconnect attempt #${this.stateService.attempt + 1}`);
+
+        // Show expanded status bar (if on 'messenger')
+        if (this.$state.includes('messenger')) {
+            this.scheduleStatusBar();
+        }
 
         // Get original keys
         const originalKeyStore = this.webClientService.salty.keyStore;
@@ -183,7 +183,16 @@ export class StatusController {
             peerTrustedKey: originalPeerPermanentKeyBytes,
             resume: true,
         });
-        this.webClientService.start()
+
+        // Show device unreachable dialog if maximum attempts exceeded
+        // Note: This will not be shown on 'welcome'
+        const pause = this.stateService.attempt >= WebClientService.MAX_CONNECT_ATTEMPTS;
+        if (pause) {
+            this.webClientService.showDeviceUnreachableDialog();
+        }
+
+        // Start
+        this.webClientService.start(pause)
             .then(
                 () => { /* ignored */ },
                 (error) => {
@@ -199,13 +208,14 @@ export class StatusController {
                 // Hide expanded status bar
                 this.collapseStatusBar();
             });
+        ++this.stateService.attempt;
     }
 
     /**
      * Attempt to reconnect an iOS device after a connection loss.
      */
     private reconnectIos(): void {
-        this.$log.info(this.logTag, 'Connection lost (iOS). Attempting to reconnect...');
+        this.$log.info(this.logTag, `Connection lost (iOS). Reconnect attempt #${++this.stateService.attempt}`);
 
         // Get original keys
         const originalKeyStore = this.webClientService.salty.keyStore;

+ 11 - 3
src/partials/messenger.ts

@@ -131,27 +131,35 @@ class SendFileController extends DialogController {
  * Handle device unreachable
  */
 export class DeviceUnreachableController extends DialogController {
-    public static readonly $inject = ['$rootScope', '$window', '$mdDialog', 'CONFIG', 'WebClientService'];
+    public static readonly $inject = [
+        '$rootScope', '$window', '$mdDialog',
+        'CONFIG', 'StateService', 'WebClientService',
+    ];
     private readonly $rootScope: any;
     private readonly $window: ng.IWindowService;
+    private readonly stateService: StateService;
     private readonly webClientService: WebClientService;
     public retrying: boolean = false;
     public progress: number = 0;
 
     constructor(
         $rootScope: any, $window: ng.IWindowService, $mdDialog: ng.material.IDialogService,
-        CONFIG: threema.Config, webClientService: WebClientService,
+        CONFIG: threema.Config, stateService: StateService, webClientService: WebClientService,
     ) {
         super($mdDialog, CONFIG);
         this.$rootScope = $rootScope;
         this.$window = $window;
+        this.stateService = stateService;
         this.webClientService = webClientService;
     }
 
     /**
      * Retry wakeup of the device via a push session.
      */
-    public async retry(): Promise<any> {
+    public async retry(): Promise<void> {
+        // Reset attempt counter
+        this.stateService.attempt = 0;
+
         // Schedule sending a push
         const [expectedPeriodMaxMs, pushSessionPromise] = this.webClientService.sendPush();
 

+ 2 - 3
src/services/state.ts

@@ -52,7 +52,7 @@ export class StateService {
     // Global connection state
     private stage: Stage;
     private _state: threema.GlobalConnectionState;
-    public wasConnected: boolean;
+    public attempt: number = 0;
 
     // Unread messages
     private _unreadCount: number = 0;
@@ -132,7 +132,7 @@ export class StateService {
                     break;
                 case TaskConnectionState.Connected:
                     this.state = GlobalConnectionState.Ok;
-                    this.wasConnected = true;
+                    this.attempt = 0;
                     break;
                 case TaskConnectionState.Disconnected:
                     this.state = GlobalConnectionState.Error;
@@ -271,7 +271,6 @@ export class StateService {
         this.taskConnectionState = TaskConnectionState.New;
         this.stage = Stage.Signaling;
         this.state = GlobalConnectionState.Error;
-        this.wasConnected = false;
         this.connectionBuildupState = connectionBuildupState;
         this.progress = 0;
         this.unreadCount = 0;

+ 37 - 18
src/services/webclient.ts

@@ -54,6 +54,7 @@ import {SequenceNumber} from '../protocol/sequence_number';
 import InitializationStep = threema.InitializationStep;
 import ContactReceiverFeature = threema.ContactReceiverFeature;
 import DisconnectReason = threema.DisconnectReason;
+import PushSessionConfig = threema.PushSessionConfig;
 
 /**
  * Payload of a connectionInfo message.
@@ -77,6 +78,7 @@ const fakeConnectionId = Uint8Array.from([
  * This service handles everything related to the communication with the peer.
  */
 export class WebClientService {
+    public static readonly MAX_CONNECT_ATTEMPTS = 3;
     private static CHUNK_SIZE = 64 * 1024;
     private static SEQUENCE_NUMBER_MIN = 0;
     private static SEQUENCE_NUMBER_MAX = (2 ** 32) - 1;
@@ -218,10 +220,11 @@ export class WebClientService {
     public alerts: threema.Alert[] = [];
 
     // Push
-    private readonly pushExpectedPeriodMaxMs: number = PushSession.expectedPeriodMaxMs();
     private pushToken: string = null;
     private pushTokenType: threema.PushTokenType = null;
     private pushSession: PushSession | null = null;
+    private readonly pushSessionConfig: PushSessionConfig;
+    private readonly pushSessionExpectedPeriodMaxMs: number;
     private pushPromise: Promise<any> | null = null;
     private deviceUnreachableDialog: ng.IPromise<any> | null = null;
 
@@ -317,6 +320,11 @@ export class WebClientService {
         // State
         this.stateService = stateService;
 
+        // Push session configuration
+        this.pushSessionConfig = PushSession.defaultConfig;
+        this.pushSessionConfig.triesMax = WebClientService.MAX_CONNECT_ATTEMPTS;
+        this.pushSessionExpectedPeriodMaxMs = PushSession.expectedPeriodMaxMs(this.pushSessionConfig);
+
         // Other properties
         this.container = container;
         this.trustedKeyStore = trustedKeyStore;
@@ -1035,7 +1043,7 @@ export class WebClientService {
     public sendPush(): [number, Promise<void>] {
         // Create new session
         if (this.pushSession === null) {
-            this.pushSession = this.pushService.createSession(this.salty.permanentKeyBytes);
+            this.pushSession = this.pushService.createSession(this.salty.permanentKeyBytes, this.pushSessionConfig);
 
             // Start and handle success/error
             this.pushPromise = this.pushSession.start()
@@ -1046,17 +1054,7 @@ export class WebClientService {
 
                     // Handle error
                     if (error instanceof TimeoutError) {
-                        // Show device unreachable dialog (if we were already
-                        // connected and if not already visible).
-                        if (this.$state.includes('messenger') && this.deviceUnreachableDialog === null) {
-                            this.deviceUnreachableDialog = this.$mdDialog.show({
-                                controller: DeviceUnreachableController,
-                                controllerAs: 'ctrl',
-                                templateUrl: 'partials/dialog.device-unreachable.html',
-                                parent: angular.element(document.body),
-                                escapeToClose: false,
-                            }).finally(() => this.deviceUnreachableDialog = null);
-                        }
+                        this.showDeviceUnreachableDialog();
                     } else {
                         this.failSession();
                     }
@@ -1071,7 +1069,7 @@ export class WebClientService {
         }
 
         // Retrieve the expected maximum period
-        return [this.pushExpectedPeriodMaxMs, this.pushPromise];
+        return [this.pushSessionExpectedPeriodMaxMs, this.pushPromise];
     }
 
     /**
@@ -1092,6 +1090,25 @@ export class WebClientService {
         }
     }
 
+    /**
+     * Show the *device unreachable* dialog.
+     */
+    public showDeviceUnreachableDialog(): void {
+        // Show device unreachable dialog (if we were already
+        // connected and if not already visible).
+        if (this.pushService.isAvailable() && this.$state.includes('messenger')
+            && this.deviceUnreachableDialog === null) {
+            this.deviceUnreachableDialog = this.$mdDialog.show({
+                controller: DeviceUnreachableController,
+                controllerAs: 'ctrl',
+                templateUrl: 'partials/dialog.device-unreachable.html',
+                parent: angular.element(document.body),
+                escapeToClose: false,
+            })
+                .finally(() => this.deviceUnreachableDialog = null);
+        }
+    }
+
     /**
      * Start the webclient service.
      * Return a promise that resolves once connected.
@@ -1112,10 +1129,12 @@ export class WebClientService {
         this.salty.connect();
 
         // If push service is available, notify app
-        if (skipPush === true) {
-            this.$log.debug(this.logTag, 'start(): Skipping push notification');
-        } else if (this.pushService.isAvailable()) {
-            this.sendPush();
+        if (this.pushService.isAvailable()) {
+            if (skipPush === true) {
+                this.$log.debug(this.logTag, 'start(): Skipping push notification');
+            } else {
+                this.sendPush();
+            }
         } else if (this.trustedKeyStore.hasTrustedKey()) {
             this.$log.debug(this.logTag, 'Push service not available');
             this.stateService.updateConnectionBuildupState('manual_start');