Skip to content
Permalink
Browse files

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.
  • Loading branch information...
lgrahl committed May 2, 2019
1 parent 3d237a1 commit 3ccd7a8421d50fefefc1e6cffeb1fc71979315f4
Showing with 71 additions and 38 deletions.
  1. +22 −12 src/controllers/status.ts
  2. +10 −2 src/partials/messenger.ts
  3. +2 −6 src/services/state.ts
  4. +37 −18 src/services/webclient.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;
@@ -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> {
// Reset attempt counter
this.stateService.attempt = 0;

// Schedule sending a push
const [expectedPeriodMaxMs, pushSessionPromise] = this.webClientService.sendPush();

@@ -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;
@@ -94,9 +94,6 @@ export class StateService {
case 'ws-connecting':
case 'server-handshake':
this.state = GlobalConnectionState.Warning;
// This is a hack to allow for reconnecting when we
// never went into the 'connected' state.
this.wasConnected = true;
break;
case 'peer-handshake':
this.state = GlobalConnectionState.Warning;
@@ -137,7 +134,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;
@@ -276,7 +273,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;
@@ -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<any>] {
// 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');

0 comments on commit 3ccd7a8

Please sign in to comment.
You can’t perform that action at this time.