|  |  |  | /* global Whisper, i18n, getAccountManager, $, textsecure, QRCode */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* eslint-disable more/no-then */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // eslint-disable-next-line func-names
 | 
					
						
							|  |  |  | (function() { | 
					
						
							|  |  |  |   'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   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; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Whisper.InstallView = Whisper.View.extend({ | 
					
						
							|  |  |  |     templateName: 'link-flow-template', | 
					
						
							|  |  |  |     className: 'main full-screen-flow', | 
					
						
							|  |  |  |     events: { | 
					
						
							|  |  |  |       'click .try-again': 'connect', | 
					
						
							|  |  |  |       'click .finish': 'finishLinking', | 
					
						
							|  |  |  |       // the actual next step happens in confirmNumber() on submit form #link-phone
 | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     initialize(options = {}) { | 
					
						
							|  |  |  |       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 = | 
					
						
							|  |  |  |         Whisper.Registration.everDone() || options.hasExistingData; | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     render_attributes() { | 
					
						
							|  |  |  |       let errorMessage; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       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 === 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: 'Something went wrong!', | 
					
						
							|  |  |  |           errorMessage, | 
					
						
							|  |  |  |           errorButton: 'Try again', | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       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(); | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     connect() { | 
					
						
							|  |  |  |       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.$('#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(); | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     finishLinking() { | 
					
						
							|  |  |  |       // We use a form so we get submit-on-enter behavior
 | 
					
						
							|  |  |  |       this.$('#link-phone').submit(); | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     confirmNumber() { | 
					
						
							|  |  |  |       const tsp = textsecure.storage.protocol; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       window.removeSetupMenuItems(); | 
					
						
							|  |  |  |       this.selectStep(Steps.ENTER_NAME); | 
					
						
							|  |  |  |       this.setDeviceNameDefault(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return new Promise(resolve => { | 
					
						
							|  |  |  |         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; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           this.selectStep(Steps.PROGRESS_BAR); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           const finish = () => 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 | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |             finish(); | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | })(); |