|  |  |  | import React, { ChangeEvent } from 'react'; | 
					
						
							|  |  |  | import { QRCode } from 'react-qr-svg'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { SessionModal } from './session/SessionModal'; | 
					
						
							|  |  |  | import { SessionButton, SessionButtonColor } from './session/SessionButton'; | 
					
						
							|  |  |  | import { SessionSpinner } from './session/SessionSpinner'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface Props { | 
					
						
							|  |  |  |   onClose: any; | 
					
						
							|  |  |  |   pubKeyToUnpair: string | undefined; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface State { | 
					
						
							|  |  |  |   currentPubKey: string | undefined; | 
					
						
							|  |  |  |   accepted: boolean; | 
					
						
							|  |  |  |   pubKeyRequests: Array<any>; | 
					
						
							|  |  |  |   currentView: 'filterRequestView' | 'qrcodeView' | 'unpairDeviceView'; | 
					
						
							|  |  |  |   errors: any; | 
					
						
							|  |  |  |   loading: boolean; | 
					
						
							|  |  |  |   deviceAlias: string | undefined; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class DevicePairingDialog extends React.Component<Props, State> { | 
					
						
							|  |  |  |   constructor(props: any) { | 
					
						
							|  |  |  |     super(props); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.closeDialog = this.closeDialog.bind(this); | 
					
						
							|  |  |  |     this.onKeyUp = this.onKeyUp.bind(this); | 
					
						
							|  |  |  |     this.stopReceivingRequests = this.stopReceivingRequests.bind(this); | 
					
						
							|  |  |  |     this.startReceivingRequests = this.startReceivingRequests.bind(this); | 
					
						
							|  |  |  |     this.skipDevice = this.skipDevice.bind(this); | 
					
						
							|  |  |  |     this.allowDevice = this.allowDevice.bind(this); | 
					
						
							|  |  |  |     this.validateSecondaryDevice = this.validateSecondaryDevice.bind(this); | 
					
						
							|  |  |  |     this.handleUpdateDeviceAlias = this.handleUpdateDeviceAlias.bind(this); | 
					
						
							|  |  |  |     this.triggerUnpairDevice = this.triggerUnpairDevice.bind(this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.state = { | 
					
						
							|  |  |  |       currentPubKey: undefined, | 
					
						
							|  |  |  |       accepted: false, | 
					
						
							|  |  |  |       pubKeyRequests: Array(), | 
					
						
							|  |  |  |       currentView: props.pubKeyToUnpair ? 'unpairDeviceView' : 'qrcodeView', | 
					
						
							|  |  |  |       loading: false, | 
					
						
							|  |  |  |       errors: undefined, | 
					
						
							|  |  |  |       deviceAlias: 'Unnamed Device', | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public componentWillMount() { | 
					
						
							|  |  |  |     if (this.state.currentView === 'qrcodeView') { | 
					
						
							|  |  |  |       this.startReceivingRequests(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public componentWillUnmount() { | 
					
						
							|  |  |  |     this.closeDialog(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public renderErrors() { | 
					
						
							|  |  |  |     const { errors } = this.state; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <> | 
					
						
							|  |  |  |         {errors && ( | 
					
						
							|  |  |  |           <> | 
					
						
							|  |  |  |             <div className="spacer-xs" /> | 
					
						
							|  |  |  |             <div className="session-label danger">{errors}</div> | 
					
						
							|  |  |  |           </> | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  |       </> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public renderFilterRequestsView() { | 
					
						
							|  |  |  |     const { currentPubKey, accepted, deviceAlias } = this.state; | 
					
						
							|  |  |  |     let secretWords: undefined; | 
					
						
							|  |  |  |     if (currentPubKey) { | 
					
						
							|  |  |  |       secretWords = window.mnemonic.pubkey_to_secret_words(currentPubKey); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (accepted) { | 
					
						
							|  |  |  |       return ( | 
					
						
							|  |  |  |         <SessionModal | 
					
						
							|  |  |  |           title={window.i18n('provideDeviceAlias')} | 
					
						
							|  |  |  |           onOk={() => null} | 
					
						
							|  |  |  |           onClose={this.closeDialog} | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           <div className="session-modal__centered"> | 
					
						
							|  |  |  |             <div className="spacer-lg" /> | 
					
						
							|  |  |  |             {this.renderErrors()} | 
					
						
							|  |  |  |             <input | 
					
						
							|  |  |  |               type="text" | 
					
						
							|  |  |  |               onChange={this.handleUpdateDeviceAlias} | 
					
						
							|  |  |  |               value={deviceAlias} | 
					
						
							|  |  |  |               id={currentPubKey} | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |             <div className="session-modal__button-group"> | 
					
						
							|  |  |  |               <SessionButton | 
					
						
							|  |  |  |                 text={window.i18n('ok')} | 
					
						
							|  |  |  |                 onClick={this.validateSecondaryDevice} | 
					
						
							|  |  |  |                 disabled={!deviceAlias} | 
					
						
							|  |  |  |                 buttonColor={SessionButtonColor.Green} | 
					
						
							|  |  |  |               /> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |             <SessionSpinner loading={this.state.loading} /> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         </SessionModal> | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <SessionModal | 
					
						
							|  |  |  |         title={window.i18n('allowPairing')} | 
					
						
							|  |  |  |         onOk={() => null} | 
					
						
							|  |  |  |         onClose={this.closeDialog} | 
					
						
							|  |  |  |       > | 
					
						
							|  |  |  |         <div className="session-modal__centered"> | 
					
						
							|  |  |  |           <h4 className="device-pairing-dialog__desc"> | 
					
						
							|  |  |  |             {window.i18n('allowPairingWithDevice')} | 
					
						
							|  |  |  |           </h4> | 
					
						
							|  |  |  |           {this.renderErrors()} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           <div className="device-pairing-dialog__secret-words"> | 
					
						
							|  |  |  |             <label>{window.i18n('secretWords')}</label> | 
					
						
							|  |  |  |             <div className="subtle">{secretWords}</div> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           <div className="session-modal__button-group"> | 
					
						
							|  |  |  |             <SessionButton | 
					
						
							|  |  |  |               text={window.i18n('cancel')} | 
					
						
							|  |  |  |               onClick={this.skipDevice} | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |             <SessionButton | 
					
						
							|  |  |  |               text={window.i18n('allowPairing')} | 
					
						
							|  |  |  |               onClick={this.allowDevice} | 
					
						
							|  |  |  |               buttonColor={SessionButtonColor.Green} | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       </SessionModal> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public renderQrCodeView() { | 
					
						
							|  |  |  |     const requestReceived = this.hasReceivedRequests(); | 
					
						
							|  |  |  |     const title = window.i18n('pairingDevice'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <SessionModal title={title} onOk={() => null} onClose={this.closeDialog}> | 
					
						
							|  |  |  |         <div className="session-modal__centered"> | 
					
						
							|  |  |  |           {this.renderErrors()} | 
					
						
							|  |  |  |           <h4>{window.i18n('waitingForDeviceToRegister')}</h4> | 
					
						
							|  |  |  |           <small className="subtle">{window.i18n('pairNewDevicePrompt')}</small> | 
					
						
							|  |  |  |           <div className="spacer-lg" /> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           <div className="qr-image"> | 
					
						
							|  |  |  |             <QRCode | 
					
						
							|  |  |  |               value={window.textsecure.storage.user.getNumber()} | 
					
						
							|  |  |  |               level="L" | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           <div className="spacer-lg" /> | 
					
						
							|  |  |  |           <div className="session-modal__button-group__center"> | 
					
						
							|  |  |  |             {!requestReceived ? ( | 
					
						
							|  |  |  |               <SessionButton | 
					
						
							|  |  |  |                 text={window.i18n('cancel')} | 
					
						
							|  |  |  |                 onClick={this.closeDialog} | 
					
						
							|  |  |  |               /> | 
					
						
							|  |  |  |             ) : null} | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       </SessionModal> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public renderUnpairDeviceView() { | 
					
						
							|  |  |  |     const { pubKeyToUnpair } = this.props; | 
					
						
							|  |  |  |     const secretWords = window.mnemonic.pubkey_to_secret_words(pubKeyToUnpair); | 
					
						
							|  |  |  |     const conv = window.ConversationController.get(pubKeyToUnpair); | 
					
						
							|  |  |  |     let description; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (conv && conv.getNickname()) { | 
					
						
							|  |  |  |       description = `${conv.getNickname()}: ${window.shortenPubkey( | 
					
						
							|  |  |  |         pubKeyToUnpair | 
					
						
							|  |  |  |       )} ${secretWords}`;
 | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       description = `${window.shortenPubkey(pubKeyToUnpair)} ${secretWords}`; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <SessionModal | 
					
						
							|  |  |  |         title={window.i18n('unpairDevice')} | 
					
						
							|  |  |  |         onOk={() => null} | 
					
						
							|  |  |  |         onClose={this.closeDialog} | 
					
						
							|  |  |  |       > | 
					
						
							|  |  |  |         <div className="session-modal__centered"> | 
					
						
							|  |  |  |           {this.renderErrors()} | 
					
						
							|  |  |  |           <p className="session-modal__description"> | 
					
						
							|  |  |  |             {window.i18n('confirmUnpairingTitle')} | 
					
						
							|  |  |  |             <br /> | 
					
						
							|  |  |  |             <span className="subtle">{description}</span> | 
					
						
							|  |  |  |           </p> | 
					
						
							|  |  |  |           <div className="spacer-xs" /> | 
					
						
							|  |  |  |           <div className="session-modal__button-group"> | 
					
						
							|  |  |  |             <SessionButton | 
					
						
							|  |  |  |               text={window.i18n('cancel')} | 
					
						
							|  |  |  |               onClick={this.closeDialog} | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |             <SessionButton | 
					
						
							|  |  |  |               text={window.i18n('unpairDevice')} | 
					
						
							|  |  |  |               onClick={this.triggerUnpairDevice} | 
					
						
							|  |  |  |               buttonColor={SessionButtonColor.Danger} | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           <SessionSpinner loading={this.state.loading} /> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       </SessionModal> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public render() { | 
					
						
							|  |  |  |     const { currentView } = this.state; | 
					
						
							|  |  |  |     const renderQrCodeView = currentView === 'qrcodeView'; | 
					
						
							|  |  |  |     const renderFilterRequestView = currentView === 'filterRequestView'; | 
					
						
							|  |  |  |     const renderUnpairDeviceView = currentView === 'unpairDeviceView'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <> | 
					
						
							|  |  |  |         {renderQrCodeView && this.renderQrCodeView()} | 
					
						
							|  |  |  |         {renderFilterRequestView && this.renderFilterRequestsView()} | 
					
						
							|  |  |  |         {renderUnpairDeviceView && this.renderUnpairDeviceView()} | 
					
						
							|  |  |  |       </> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private reset() { | 
					
						
							|  |  |  |     this.setState({ | 
					
						
							|  |  |  |       currentPubKey: undefined, | 
					
						
							|  |  |  |       accepted: false, | 
					
						
							|  |  |  |       pubKeyRequests: Array(), | 
					
						
							|  |  |  |       currentView: 'filterRequestView', | 
					
						
							|  |  |  |       deviceAlias: 'Unnamed Device', | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private startReceivingRequests() { | 
					
						
							|  |  |  |     this.reset(); | 
					
						
							|  |  |  |     window.Whisper.events.on( | 
					
						
							|  |  |  |       'devicePairingRequestReceived', | 
					
						
							|  |  |  |       (pubKey: string) => { | 
					
						
							|  |  |  |         this.requestReceived(pubKey); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     this.setState({ currentView: 'qrcodeView' }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private stopReceivingRequests() { | 
					
						
							|  |  |  |     this.setState({ currentView: 'filterRequestView' }); | 
					
						
							|  |  |  |     window.Whisper.events.off('devicePairingRequestReceived'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private requestReceived(secondaryDevicePubKey: string | EventHandlerNonNull) { | 
					
						
							|  |  |  |     // FIFO: push at the front of the array with unshift()
 | 
					
						
							|  |  |  |     this.state.pubKeyRequests.unshift(secondaryDevicePubKey); | 
					
						
							|  |  |  |     if (!this.state.currentPubKey) { | 
					
						
							|  |  |  |       this.nextPubKey(); | 
					
						
							|  |  |  |       this.stopReceivingRequests(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private allowDevice() { | 
					
						
							|  |  |  |     this.setState({ | 
					
						
							|  |  |  |       accepted: true, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private transmissionCB(errors: any) { | 
					
						
							|  |  |  |     if (!errors) { | 
					
						
							|  |  |  |       this.setState({ | 
					
						
							|  |  |  |         errors: null, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       this.closeDialog(); | 
					
						
							|  |  |  |       window.pushToast({ | 
					
						
							|  |  |  |         title: window.i18n('devicePairedSuccessfully'), | 
					
						
							|  |  |  |         type: 'success', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       const conv = window.ConversationController.get(this.state.currentPubKey); | 
					
						
							|  |  |  |       if (conv) { | 
					
						
							|  |  |  |         conv.setNickname(this.state.deviceAlias); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.setState({ | 
					
						
							|  |  |  |       errors: errors, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private skipDevice() { | 
					
						
							|  |  |  |     window.Whisper.events.trigger( | 
					
						
							|  |  |  |       'devicePairingRequestRejected', | 
					
						
							|  |  |  |       this.state.currentPubKey | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.closeDialog(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private nextPubKey() { | 
					
						
							|  |  |  |     // FIFO: pop at the back of the array using pop()
 | 
					
						
							|  |  |  |     this.setState({ | 
					
						
							|  |  |  |       currentPubKey: this.state.pubKeyRequests.pop(), | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private onKeyUp(event: any) { | 
					
						
							|  |  |  |     switch (event.key) { | 
					
						
							|  |  |  |       case 'Esc': | 
					
						
							|  |  |  |       case 'Escape': | 
					
						
							|  |  |  |         this.closeDialog(); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private validateSecondaryDevice() { | 
					
						
							|  |  |  |     this.setState({ loading: true }); | 
					
						
							|  |  |  |     window.Whisper.events.trigger( | 
					
						
							|  |  |  |       'devicePairingRequestAccepted', | 
					
						
							|  |  |  |       this.state.currentPubKey, | 
					
						
							|  |  |  |       (errors: any) => { | 
					
						
							|  |  |  |         this.transmissionCB(errors); | 
					
						
							|  |  |  |         window.Whisper.events.trigger('refreshLinkedDeviceList'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private hasReceivedRequests() { | 
					
						
							|  |  |  |     return this.state.currentPubKey || this.state.pubKeyRequests.length > 0; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private closeDialog() { | 
					
						
							|  |  |  |     window.removeEventListener('keyup', this.onKeyUp); | 
					
						
							|  |  |  |     this.stopReceivingRequests(); | 
					
						
							|  |  |  |     window.Whisper.events.off('devicePairingRequestReceived'); | 
					
						
							|  |  |  |     if (this.state.currentPubKey && !this.state.accepted) { | 
					
						
							|  |  |  |       window.Whisper.events.trigger( | 
					
						
							|  |  |  |         'devicePairingRequestRejected', | 
					
						
							|  |  |  |         this.state.currentPubKey | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.props.onClose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private handleUpdateDeviceAlias(value: ChangeEvent<HTMLInputElement>) { | 
					
						
							|  |  |  |     const trimmed = value.target.value.trim(); | 
					
						
							|  |  |  |     if (!!trimmed) { | 
					
						
							|  |  |  |       this.setState({ deviceAlias: trimmed }); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       this.setState({ deviceAlias: undefined }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private triggerUnpairDevice() { | 
					
						
							|  |  |  |     const deviceUnpaired = () => { | 
					
						
							|  |  |  |       window.pushToast({ | 
					
						
							|  |  |  |         title: window.i18n('deviceUnpaired'), | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       this.closeDialog(); | 
					
						
							|  |  |  |       this.setState({ loading: false }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     this.setState({ loading: true }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.Whisper.events.trigger( | 
					
						
							|  |  |  |       'deviceUnpairingRequested', | 
					
						
							|  |  |  |       this.props.pubKeyToUnpair, | 
					
						
							|  |  |  |       deviceUnpaired | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |