Import log instead of using it off of window

This commit is contained in:
Josh Perez 2021-09-17 14:27:53 -04:00 committed by GitHub
parent 8eb0dd3116
commit 65ddf0a9e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
155 changed files with 3654 additions and 3433 deletions

View file

@ -1,201 +0,0 @@
// Copyright 2015-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global i18n, Whisper, $ */
// eslint-disable-next-line func-names
(function () {
window.Whisper = window.Whisper || {};
// This enum-like object describes the load state of `DebugLogView`. It's designed to be
// unidirectional; `NotStarted` → `Started` → `LogsFetchedButNotInTextarea`, etc.
const LoadState = {
NotStarted: 0,
Started: 1,
LogsFetchedButNotInTextarea: 2,
PuttingLogsInTextarea: 3,
LogsInTextarea: 4,
};
Whisper.LoadingFullLogsToast = Whisper.ToastView.extend({
render_attributes() {
return { toastMessage: i18n('loading') };
},
});
Whisper.LinkedCopiedToast = Whisper.ToastView.extend({
render_attributes() {
return { toastMessage: i18n('debugLogLinkCopied') };
},
});
Whisper.DebugLogLinkView = Whisper.View.extend({
template: () => $('#debug-log-link').html(),
initialize(options) {
this.url = options.url;
},
events: {
'click .copy': 'copy',
},
render_attributes() {
return {
url: this.url,
reportIssue: i18n('reportIssue'),
debugLogCopy: i18n('debugLogCopy'),
debugLogCopyAlt: i18n('debugLogCopyAlt'),
};
},
copy(e) {
e.preventDefault();
window.copyText(e.currentTarget.href);
Whisper.ToastView.show(Whisper.LinkedCopiedToast, document.body);
},
});
/**
* The bulk of the logic in this view involves grabbing the logs from disk and putting
* them in a `<textarea>`. The first part isn't instant but is reasonably fast; setting
* the textarea's `value` takes a long time.
*
* After loading the logs into memory, we only put a small number of lines into the
* textarea. If the user clicks or scrolls the textarea, we pull the full logs, which
* can cause the system to lock up for a bit.
*
* Ideally, we'd only show a sampling of the logs and allow the user to download and
* edit them in their own editor. This is mostly a stopgap solution.
*/
Whisper.DebugLogView = Whisper.View.extend({
template: () => $('#debug-log').html(),
className: 'debug-log modal',
initialize() {
this.render();
this.textarea = this.$('.textarea').get(0);
if (!this.textarea) {
throw new Error('textarea not found');
}
this.textarea.setAttribute('readonly', '');
this.loadState = LoadState.NotStarted;
this.putFullLogsInTextareaPlease = false;
this.fetchLogs();
},
events: {
'click .textarea': 'putFullLogsInTextarea',
'scroll .textarea': 'putFullLogsInTextarea',
'wheel .textarea': 'putFullLogsInTextarea',
'click .submit': 'submit',
'click .close': 'close',
},
render_attributes: {
title: i18n('submitDebugLog'),
cancel: i18n('cancel'),
submit: i18n('submit'),
close: i18n('gotIt'),
debugLogExplanation: i18n('debugLogExplanation'),
},
async fetchLogs() {
if (this.loadState !== LoadState.NotStarted) {
return;
}
this.loadState = LoadState.Started;
this.textarea.value = i18n('loading');
this.$('.submit').attr('disabled', 'disabled');
this.logText = await window.log.fetch();
this.loadState = LoadState.LogsFetchedButNotInTextarea;
// This number is somewhat arbitrary; we want to show enough that it's clear that
// we need to scroll, but not so many that things get slow.
const linesToShow = Math.ceil(Math.min(window.innerHeight, 2000) / 5);
this.textarea.value = this.logText
.split(/\n/g, linesToShow)
.concat(['', i18n('loading')])
.join('\n');
this.$('.submit').removeAttr('disabled');
if (this.putFullLogsInTextareaPlease) {
this.putFullLogsInTextarea();
}
},
putFullLogsInTextarea() {
switch (this.loadState) {
case LoadState.NotStarted:
case LoadState.Started:
this.putFullLogsInTextareaPlease = true;
break;
case LoadState.LogsInTextarea:
case LoadState.PuttingLogsInTextarea:
break;
case LoadState.LogsFetchedButNotInTextarea:
if (!this.logText) {
throw new Error('Expected log text to be present');
}
this.loadState = LoadState.PuttingLogsInTextarea;
Whisper.ToastView.show(Whisper.LoadingFullLogsToast, document.body);
setTimeout(() => {
this.textarea.value = this.logText;
this.textarea.removeAttribute('readonly');
this.loadState = LoadState.LogsInTextarea;
}, 0);
break;
default:
// When we can, we should make this throw a `missingCaseError`.
break;
}
},
close() {
window.closeDebugLog();
},
async submit(e) {
e.preventDefault();
let text;
switch (this.loadState) {
case LoadState.NotStarted:
case LoadState.Started:
return;
case LoadState.LogsFetchedButNotInTextarea:
text = this.logText;
break;
case LoadState.LogsInTextarea:
text = this.textarea.value;
break;
default:
// When we can, we should make this throw a `missingCaseError`.
return;
}
if (text.length === 0) {
return;
}
this.$('.buttons, .textarea').remove();
this.$('.result').addClass('loading');
try {
const publishedLogURL = await window.log.publish(
text,
window.getVersion()
);
const view = new Whisper.DebugLogLinkView({
url: publishedLogURL,
el: this.$('.result'),
});
this.$('.loading').removeClass('loading');
view.render();
this.$('.link').focus().select();
} catch (error) {
window.log.error(
'DebugLogView error:',
error && error.stack ? error.stack : error
);
this.$('.loading').removeClass('loading');
this.$('.result').text(i18n('debugLogError'));
}
},
});
})();

View file

@ -1,242 +0,0 @@
// Copyright 2014-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global
ConversationController,
i18n,
Whisper,
Signal,
$
*/
// eslint-disable-next-line func-names
(function () {
window.Whisper = window.Whisper || {};
Whisper.StickerPackInstallFailedToast = Whisper.ToastView.extend({
render_attributes() {
return { toastMessage: i18n('stickers--toast--InstallFailed') };
},
});
Whisper.ConversationStack = Whisper.View.extend({
className: 'conversation-stack',
lastConversation: null,
open(conversation, messageId) {
const id = `conversation-${conversation.cid}`;
if (id !== this.el.lastChild.id) {
const view = new Whisper.ConversationView({
model: conversation,
});
this.listenTo(conversation, 'unload', () =>
this.onUnload(conversation)
);
view.$el.appendTo(this.el);
if (this.lastConversation && this.lastConversation !== conversation) {
this.lastConversation.trigger(
'unload',
'opened another conversation'
);
this.stopListening(this.lastConversation);
this.lastConversation = null;
}
this.lastConversation = conversation;
conversation.trigger('opened', messageId);
} else if (messageId) {
conversation.trigger('scroll-to-message', messageId);
}
// Make sure poppers are positioned properly
window.dispatchEvent(new Event('resize'));
},
unload() {
const { lastConversation } = this;
if (!lastConversation) {
return;
}
lastConversation.trigger('unload', 'force unload requested');
},
onUnload(conversation) {
if (this.lastConversation === conversation) {
this.stopListening(this.lastConversation);
this.lastConversation = null;
}
},
});
Whisper.AppLoadingScreen = Whisper.View.extend({
template: () => $('#app-loading-screen').html(),
className: 'app-loading-screen',
updateProgress(count) {
if (count > 0) {
const message = i18n('loadingMessages', [count.toString()]);
this.$('.message').text(message);
}
},
render_attributes: {
message: i18n('loading'),
},
});
Whisper.InboxView = Whisper.View.extend({
template: () => $('#two-column').html(),
className: 'inbox index',
initialize(options = {}) {
this.ready = false;
this.render();
this.conversation_stack = new Whisper.ConversationStack({
el: this.$('.conversation-stack'),
model: { window: options.window },
});
this.renderWhatsNew();
Whisper.events.on('refreshConversation', ({ oldId, newId }) => {
const convo = this.conversation_stack.lastConversation;
if (convo && convo.get('id') === oldId) {
this.conversation_stack.open(newId);
}
});
// Close current opened conversation to reload the group information once
// linked.
Whisper.events.on('setupAsNewDevice', () => {
this.conversation_stack.unload();
});
window.Whisper.events.on('showConversation', async (id, messageId) => {
const conversation = await ConversationController.getOrCreateAndWait(
id,
'private'
);
conversation.setMarkedUnread(false);
const { openConversationExternal } = window.reduxActions.conversations;
if (openConversationExternal) {
openConversationExternal(conversation.id, messageId);
}
this.conversation_stack.open(conversation, messageId);
this.focusConversation();
});
window.Whisper.events.on('loadingProgress', count => {
const view = this.appLoadingScreen;
if (view) {
view.updateProgress(count);
}
});
if (!options.initialLoadComplete) {
this.appLoadingScreen = new Whisper.AppLoadingScreen();
this.appLoadingScreen.render();
this.appLoadingScreen.$el.prependTo(this.el);
this.startConnectionListener();
} else {
this.setupLeftPane();
}
Whisper.events.on('pack-install-failed', () => {
const toast = new Whisper.StickerPackInstallFailedToast();
toast.$el.appendTo(this.$el);
toast.render();
});
},
render_attributes: {
welcomeToSignal: i18n('welcomeToSignal'),
// TODO DESKTOP-1451: add back the selectAContact message
selectAContact: '',
},
events: {
click: 'onClick',
},
renderWhatsNew() {
if (this.whatsNewView) {
return;
}
this.whatsNewView = new Whisper.ReactWrapperView({
Component: window.Signal.Components.WhatsNew,
props: {
i18n: window.i18n,
},
});
this.$('.whats-new-placeholder').append(this.whatsNewView.el);
},
setupLeftPane() {
if (this.leftPaneView) {
return;
}
this.leftPaneView = new Whisper.ReactWrapperView({
className: 'left-pane-wrapper',
JSX: Signal.State.Roots.createLeftPane(window.reduxStore),
});
this.$('.left-pane-placeholder').append(this.leftPaneView.el);
},
startConnectionListener() {
this.interval = setInterval(() => {
const status = window.getSocketStatus();
switch (status) {
case 'CONNECTING':
break;
case 'OPEN':
clearInterval(this.interval);
// if we've connected, we can wait for real empty event
this.interval = null;
break;
case 'CLOSING':
case 'CLOSED':
clearInterval(this.interval);
this.interval = null;
// if we failed to connect, we pretend we got an empty event
this.onEmpty();
break;
default:
window.log.warn(
`startConnectionListener: Found unexpected socket status ${status}; calling onEmpty() manually.`
);
this.onEmpty();
break;
}
}, 1000);
},
onEmpty() {
this.setupLeftPane();
const view = this.appLoadingScreen;
if (view) {
this.appLoadingScreen = null;
view.remove();
const searchInput = document.querySelector(
'.module-main-header__search__input'
);
if (searchInput && searchInput.focus) {
searchInput.focus();
}
}
},
focusConversation(e) {
if (e && this.$(e.target).closest('.placeholder').length) {
return;
}
this.$('#header, .gutter').addClass('inactive');
this.$('.conversation-stack').removeClass('inactive');
},
closeRecording(e) {
if (e && this.$(e.target).closest('.capture-audio').length > 0) {
return;
}
this.$('.conversation:first .recorder').trigger('close');
},
onClick(e) {
this.closeRecording(e);
},
});
})();

View file

@ -1,238 +0,0 @@
// Copyright 2015-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global Whisper, i18n, getAccountManager, $, textsecure, QRCode */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function () {
window.Whisper = window.Whisper || {};
const 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 = {}) {
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 = i18n('installTryAgain');
let errorSecondButton = null;
if (this.error) {
if (
this.error.name === 'HTTPError' &&
this.error.code === TOO_MANY_DEVICES
) {
errorMessage = i18n('installTooManyDevices');
} else if (
this.error.name === 'HTTPError' &&
this.error.code === TOO_OLD
) {
errorMessage = i18n('installTooOld');
errorButton = i18n('upgrade');
errorSecondButton = i18n('quit');
} else if (
this.error.name === 'HTTPError' &&
this.error.code === CONNECTION_ERROR
) {
errorMessage = i18n('installConnectionFailed');
} else if (this.error.message === 'websocket closed') {
// AccountManager.registerSecondDevice uses this specific
// 'websocket closed' error message
errorMessage = i18n('installConnectionFailed');
}
return {
isError: true,
errorHeader: i18n('installErrorHeader'),
errorMessage,
errorButton,
errorSecondButton,
};
}
return {
isStep3: this.step === Steps.SCAN_QR_CODE,
linkYourPhone: i18n('linkYourPhone'),
signalSettings: i18n('signalSettings'),
linkedDevices: i18n('linkedDevices'),
androidFinalStep: i18n('plusButton'),
appleFinalStep: i18n('linkNewDevice'),
isStep4: this.step === Steps.ENTER_NAME,
chooseName: i18n('chooseDeviceName'),
finishLinkingPhoneButton: i18n('finishLinkingPhone'),
isStep5: this.step === Steps.PROGRESS_BAR,
syncing: i18n('initialSync'),
};
},
selectStep(step) {
this.step = step;
this.render();
},
shutdown() {
window.shutdown();
},
connect() {
if (
this.error &&
this.error.name === 'HTTPError' &&
this.error.code === TOO_OLD
) {
window.location = '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 = getAccountManager();
accountManager
.registerSecondDevice(
this.setProvisioningUrl.bind(this),
this.confirmNumber.bind(this)
)
.catch(this.handleDisconnect.bind(this));
},
handleDisconnect(error) {
window.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.name !== '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) {
if ($('#qr').length === 0) {
window.log.error('Did not find #qr element in the DOM!');
return;
}
this.clearQR();
this.$('#qr .container').hide();
this.qr = new QRCode(this.$('#qr')[0]).makeCode(url);
this.$('#qr').removeAttr('title');
this.$('#qr').addClass('ready');
},
setDeviceNameDefault() {
const deviceName = textsecure.storage.user.getDeviceName();
this.$(DEVICE_NAME_SELECTOR).val(deviceName || window.getHostName());
this.$(DEVICE_NAME_SELECTOR).focus();
},
confirmNumber() {
const tsp = textsecure.storage.protocol;
window.removeSetupMenuItems();
this.selectStep(Steps.ENTER_NAME);
this.setDeviceNameDefault();
return new Promise(resolve => {
const onDeviceName = name => {
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();
}
return tsp.removeAllData().then(finish, error => {
window.log.error(
'confirmNumber: error clearing database',
error && error.stack ? error.stack : error
);
return finish();
});
};
if (window.CI) {
onDeviceName(window.CI.deviceName);
return;
}
// eslint-disable-next-line consistent-return
this.$('#link-phone').submit(e => {
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 null;
}
onDeviceName(name);
});
});
},
});
})();

View file

@ -1,40 +0,0 @@
// Copyright 2015-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global libphonenumber, Whisper, $ */
// eslint-disable-next-line func-names
(function () {
window.Whisper = window.Whisper || {};
Whisper.PhoneInputView = Whisper.View.extend({
tagName: 'div',
className: 'phone-input',
template: () => $('#phone-number').html(),
initialize() {
this.$('input.number').intlTelInput();
},
events: {
change: 'validateNumber',
keydown: 'validateNumber',
},
validateNumber() {
const input = this.$('input.number');
const regionCode = this.$('li.active')
.attr('data-country-code')
.toUpperCase();
const number = input.val();
const parsedNumber = libphonenumber.util.parseNumber(number, regionCode);
if (parsedNumber.isValidNumber) {
this.$('.number-container').removeClass('invalid');
this.$('.number-container').addClass('valid');
} else {
this.$('.number-container').removeClass('valid');
}
input.trigger('validation');
return parsedNumber.e164;
},
});
})();

View file

@ -49,7 +49,7 @@
try {
cb();
} catch (error) {
window.log.error(
window.SignalWindow.log.error(
'ReactWrapperView.update error:',
error && error.stack ? error.stack : error
);

View file

@ -1,149 +0,0 @@
// Copyright 2016-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global $, Whisper, moment, WebAudioRecorder */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function () {
window.Whisper = window.Whisper || {};
Whisper.RecorderView = Whisper.View.extend({
className: 'recorder clearfix',
template: () => $('#recorder').html(),
initialize() {
this.startTime = Date.now();
this.interval = setInterval(this.updateTime.bind(this), 1000);
this.onSwitchAwayBound = this.onSwitchAway.bind(this);
$(window).on('blur', this.onSwitchAwayBound);
this.handleKeyDownBound = this.handleKeyDown.bind(this);
this.$el.on('keydown', this.handleKeyDownBound);
this.start();
},
events: {
'click .close': 'remove',
'click .finish': 'finish',
close: 'remove',
},
onSwitchAway() {
this.lostFocus = true;
this.recorder.finishRecording();
},
handleKeyDown(event) {
if (event.key === 'Escape') {
this.remove();
event.preventDefault();
event.stopPropagation();
}
},
updateTime() {
const duration = moment.duration(Date.now() - this.startTime, 'ms');
const minutes = `${Math.trunc(duration.asMinutes())}`;
let seconds = `${duration.seconds()}`;
if (seconds.length < 2) {
seconds = `0${seconds}`;
}
this.$('.time').text(`${minutes}:${seconds}`);
},
remove() {
// Note: the 'close' event can be triggered by InboxView, when the user clicks
// anywhere outside the recording pane.
if (this.recorder.isRecording()) {
this.recorder.cancelRecording();
}
// Reach in and terminate the web worker used by WebAudioRecorder, otherwise
// it gets leaked due to a reference cycle with its onmessage listener
this.recorder.worker.terminate();
this.recorder = null;
if (this.interval) {
clearInterval(this.interval);
}
this.interval = null;
if (this.source) {
this.source.disconnect();
}
this.source = null;
if (this.context) {
this.context.close().then(() => {
window.log.info('audio context closed');
});
}
this.context = null;
Whisper.View.prototype.remove.call(this);
this.trigger('closed');
$(window).off('blur', this.onSwitchAwayBound);
this.$el.off('keydown', this.handleKeyDownBound);
},
finish() {
this.clickedFinish = true;
this.recorder.finishRecording();
},
handleBlob(recorder, blob) {
if (blob && this.clickedFinish) {
this.trigger('send', blob);
} else if (blob) {
this.trigger('confirm', blob, this.lostFocus);
}
this.remove();
},
start() {
this.lostFocus = false;
this.clickedFinish = false;
this.context = new AudioContext();
this.input = this.context.createGain();
this.recorder = new WebAudioRecorder(this.input, {
encoding: 'mp3',
workerDir: 'js/', // must end with slash
});
this.recorder.onComplete = this.handleBlob.bind(this);
this.recorder.onError = this.onError.bind(this);
this.recorder.onTimeout = this.onTimeout.bind(this);
navigator.webkitGetUserMedia(
{ audio: true },
stream => {
this.source = this.context.createMediaStreamSource(stream);
this.source.connect(this.input);
},
this.onError.bind(this)
);
this.recorder.startRecording();
},
onTimeout() {
this.recorder.finishRecording();
},
onError(error) {
// Protect against out-of-band errors, which can happen if the user revokes media
// permissions after successfully accessing the microphone.
if (!this.recorder) {
return;
}
this.remove();
if (error && error.name === 'NotAllowedError') {
window.log.warn(
'RecorderView.onError: Microphone access is not allowed!'
);
window.showPermissionsPopup();
} else {
window.log.error(
'RecorderView.onError:',
error && error.stack ? error.stack : error
);
}
},
});
})();

View file

@ -1,106 +0,0 @@
// Copyright 2017-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global Whisper, $, getAccountManager, textsecure */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function () {
window.Whisper = window.Whisper || {};
Whisper.StandaloneRegistrationView = Whisper.View.extend({
template: () => $('#standalone').html(),
className: 'full-screen-flow',
initialize() {
window.readyForUpdates();
this.accountManager = getAccountManager();
this.render();
const number = textsecure.storage.user.getNumber();
if (number) {
this.$('input.number').val(number);
}
this.phoneView = new Whisper.PhoneInputView({
el: this.$('#phone-number-input'),
});
this.$('#error').hide();
},
events: {
'validation input.number': 'onValidation',
'click #request-voice': 'requestVoice',
'click #request-sms': 'requestSMSVerification',
'change #code': 'onChangeCode',
'click #verifyCode': 'verifyCode',
},
verifyCode() {
const number = this.phoneView.validateNumber();
const verificationCode = $('#code').val().replace(/\D+/g, '');
this.accountManager
.registerSingleDevice(number, verificationCode)
.then(() => {
this.$el.trigger('openInbox');
})
.catch(this.log.bind(this));
},
log(s) {
window.log.info(s);
this.$('#status').text(s);
},
validateCode() {
const verificationCode = $('#code').val().replace(/\D/g, '');
if (verificationCode.length === 6) {
return verificationCode;
}
return null;
},
displayError(error) {
this.$('#error').hide().text(error).addClass('in').fadeIn();
},
onValidation() {
if (this.$('#number-container').hasClass('valid')) {
this.$('#request-sms, #request-voice').removeAttr('disabled');
} else {
this.$('#request-sms, #request-voice').prop('disabled', 'disabled');
}
},
onChangeCode() {
if (!this.validateCode()) {
this.$('#code').addClass('invalid');
} else {
this.$('#code').removeClass('invalid');
}
},
requestVoice() {
window.removeSetupMenuItems();
this.$('#error').hide();
const number = this.phoneView.validateNumber();
if (number) {
this.accountManager
.requestVoiceVerification(number)
.catch(this.displayError.bind(this));
this.$('#step2').addClass('in').fadeIn();
} else {
this.$('#number-container').addClass('invalid');
}
},
requestSMSVerification() {
window.removeSetupMenuItems();
$('#error').hide();
const number = this.phoneView.validateNumber();
if (number) {
this.accountManager
.requestSMSVerification(number)
.catch(this.displayError.bind(this));
this.$('#step2').addClass('in').fadeIn();
} else {
this.$('#number-container').addClass('invalid');
}
},
});
})();