236 lines
6.7 KiB
TypeScript
236 lines
6.7 KiB
TypeScript
// Copyright 2015-2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import * as log from '../logging/log';
|
|
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
|
|
import { HTTPError } from '../textsecure/Errors';
|
|
|
|
window.Whisper = window.Whisper || {};
|
|
const { Whisper } = window;
|
|
|
|
enum Steps {
|
|
INSTALL_SIGNAL = 2,
|
|
SCAN_QR_CODE = 3,
|
|
ENTER_NAME = 4,
|
|
PROGRESS_BAR = 5,
|
|
TOO_MANY_DEVICES = 'TooManyDevices',
|
|
NETWORK_ERROR = 'NetworkError',
|
|
}
|
|
|
|
const DEVICE_NAME_SELECTOR = 'input.device-name';
|
|
const CONNECTION_ERROR = -1;
|
|
const TOO_MANY_DEVICES = 411;
|
|
const TOO_OLD = 409;
|
|
|
|
Whisper.InstallView = Whisper.View.extend({
|
|
template: () => $('#link-flow-template').html(),
|
|
className: 'main full-screen-flow',
|
|
events: {
|
|
'click .try-again': 'connect',
|
|
'click .second': 'shutdown',
|
|
// the actual next step happens in confirmNumber() on submit form #link-phone
|
|
},
|
|
initialize(options: { hasExistingData?: boolean } = {}) {
|
|
window.readyForUpdates();
|
|
|
|
this.selectStep(Steps.SCAN_QR_CODE);
|
|
this.connect();
|
|
this.on('disconnected', this.reconnect);
|
|
|
|
// Keep data around if it's a re-link, or the middle of a light import
|
|
this.shouldRetainData =
|
|
window.Signal.Util.Registration.everDone() || options.hasExistingData;
|
|
},
|
|
render_attributes() {
|
|
let errorMessage;
|
|
let errorButton = window.i18n('installTryAgain');
|
|
let errorSecondButton = null;
|
|
|
|
if (this.error) {
|
|
if (
|
|
this.error instanceof HTTPError &&
|
|
this.error.code === TOO_MANY_DEVICES
|
|
) {
|
|
errorMessage = window.i18n('installTooManyDevices');
|
|
} else if (
|
|
this.error instanceof HTTPError &&
|
|
this.error.code === TOO_OLD
|
|
) {
|
|
errorMessage = window.i18n('installTooOld');
|
|
errorButton = window.i18n('upgrade');
|
|
errorSecondButton = window.i18n('quit');
|
|
} else if (
|
|
this.error instanceof HTTPError &&
|
|
this.error.code === CONNECTION_ERROR
|
|
) {
|
|
errorMessage = window.i18n('installConnectionFailed');
|
|
} else if (this.error.message === 'websocket closed') {
|
|
// AccountManager.registerSecondDevice uses this specific
|
|
// 'websocket closed' error message
|
|
errorMessage = window.i18n('installConnectionFailed');
|
|
}
|
|
|
|
return {
|
|
isError: true,
|
|
errorHeader: window.i18n('installErrorHeader'),
|
|
errorMessage,
|
|
errorButton,
|
|
errorSecondButton,
|
|
};
|
|
}
|
|
|
|
return {
|
|
isStep3: this.step === Steps.SCAN_QR_CODE,
|
|
linkYourPhone: window.i18n('linkYourPhone'),
|
|
signalSettings: window.i18n('signalSettings'),
|
|
linkedDevices: window.i18n('linkedDevices'),
|
|
androidFinalStep: window.i18n('plusButton'),
|
|
appleFinalStep: window.i18n('linkNewDevice'),
|
|
|
|
isStep4: this.step === Steps.ENTER_NAME,
|
|
chooseName: window.i18n('chooseDeviceName'),
|
|
finishLinkingPhoneButton: window.i18n('finishLinkingPhone'),
|
|
|
|
isStep5: this.step === Steps.PROGRESS_BAR,
|
|
syncing: window.i18n('initialSync'),
|
|
};
|
|
},
|
|
selectStep(step: Steps) {
|
|
this.step = step;
|
|
this.render();
|
|
},
|
|
shutdown() {
|
|
window.shutdown();
|
|
},
|
|
async connect() {
|
|
if (this.error instanceof HTTPError && this.error.code === TOO_OLD) {
|
|
openLinkInWebBrowser('https://signal.org/download');
|
|
return;
|
|
}
|
|
|
|
this.error = null;
|
|
this.selectStep(Steps.SCAN_QR_CODE);
|
|
this.clearQR();
|
|
if (this.timeout) {
|
|
clearTimeout(this.timeout);
|
|
this.timeout = null;
|
|
}
|
|
|
|
const accountManager = window.getAccountManager();
|
|
|
|
try {
|
|
await accountManager.registerSecondDevice(
|
|
this.setProvisioningUrl.bind(this),
|
|
this.confirmNumber.bind(this)
|
|
);
|
|
} catch (err) {
|
|
this.handleDisconnect(err);
|
|
}
|
|
},
|
|
handleDisconnect(error: Error) {
|
|
log.error(
|
|
'provisioning failed',
|
|
error && error.stack ? error.stack : error
|
|
);
|
|
|
|
this.error = error;
|
|
this.render();
|
|
|
|
if (error.message === 'websocket closed') {
|
|
this.trigger('disconnected');
|
|
} else if (
|
|
!(error instanceof HTTPError) ||
|
|
(error.code !== CONNECTION_ERROR && error.code !== TOO_MANY_DEVICES)
|
|
) {
|
|
throw error;
|
|
}
|
|
},
|
|
reconnect() {
|
|
if (this.timeout) {
|
|
clearTimeout(this.timeout);
|
|
this.timeout = null;
|
|
}
|
|
this.timeout = setTimeout(this.connect.bind(this), 10000);
|
|
},
|
|
clearQR() {
|
|
this.$('#qr img').remove();
|
|
this.$('#qr canvas').remove();
|
|
this.$('#qr .container').show();
|
|
this.$('#qr').removeClass('ready');
|
|
},
|
|
setProvisioningUrl(url: string) {
|
|
if ($('#qr').length === 0) {
|
|
log.error('Did not find #qr element in the DOM!');
|
|
return;
|
|
}
|
|
|
|
this.clearQR();
|
|
this.$('#qr .container').hide();
|
|
this.qr = new window.QRCode(this.$('#qr')[0]).makeCode(url);
|
|
this.$('#qr').removeAttr('title');
|
|
this.$('#qr').addClass('ready');
|
|
this.$('#qr img').attr('alt', window.i18n('LinkScreen__scan-this-code'));
|
|
},
|
|
setDeviceNameDefault() {
|
|
const deviceName = window.textsecure.storage.user.getDeviceName();
|
|
|
|
this.$(DEVICE_NAME_SELECTOR).val(deviceName || window.getHostName());
|
|
this.$(DEVICE_NAME_SELECTOR).focus();
|
|
},
|
|
confirmNumber() {
|
|
window.removeSetupMenuItems();
|
|
this.selectStep(Steps.ENTER_NAME);
|
|
this.setDeviceNameDefault();
|
|
|
|
return new Promise(resolve => {
|
|
const onDeviceName = async (name: string) => {
|
|
this.selectStep(Steps.PROGRESS_BAR);
|
|
|
|
const finish = () => {
|
|
window.Signal.Util.postLinkExperience.start();
|
|
return resolve(name);
|
|
};
|
|
|
|
// Delete all data from database unless we're in the middle
|
|
// of a re-link, or we are finishing a light import. Without this,
|
|
// app restarts at certain times can cause weird things to happen,
|
|
// like data from a previous incomplete light import showing up
|
|
// after a new install.
|
|
if (this.shouldRetainData) {
|
|
return finish();
|
|
}
|
|
|
|
try {
|
|
await window.textsecure.storage.protocol.removeAllData();
|
|
} catch (error) {
|
|
log.error(
|
|
'confirmNumber: error clearing database',
|
|
error && error.stack ? error.stack : error
|
|
);
|
|
} finally {
|
|
finish();
|
|
}
|
|
};
|
|
|
|
if (window.CI) {
|
|
onDeviceName(window.CI.deviceName);
|
|
return;
|
|
}
|
|
|
|
// eslint-disable-next-line consistent-return
|
|
this.$('#link-phone').submit((e: SubmitEvent) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
let name = this.$(DEVICE_NAME_SELECTOR).val();
|
|
name = name.replace(/\0/g, ''); // strip unicode null
|
|
if (name.trim().length === 0) {
|
|
this.$(DEVICE_NAME_SELECTOR).focus();
|
|
return;
|
|
}
|
|
|
|
onDeviceName(name);
|
|
});
|
|
});
|
|
},
|
|
});
|