import React from 'react'; import { SessionModal } from './SessionModal'; import { SessionButton } from './SessionButton'; interface Props { onClose: any; } interface State { error: string; loadingPassword: boolean; loadingSeed: boolean; seed: string; hasPassword: boolean | null; passwordHash: string; passwordValid: boolean; } export class SessionSeedModal extends React.Component<Props, State> { constructor(props: any) { super(props); this.state = { error: '', loadingPassword: true, loadingSeed: true, seed: '', hasPassword: null, passwordHash: '', passwordValid: false, }; this.copySeed = this.copySeed.bind(this); this.getSeed = this.getSeed.bind(this); this.confirmPassword = this.confirmPassword.bind(this); this.checkHasPassword = this.checkHasPassword.bind(this); this.onEnter = this.onEnter.bind(this); } public componentDidMount() { setTimeout(() => $('#seed-input-password').focus(), 100); } public render() { const i18n = window.i18n; this.checkHasPassword(); this.getSeed().ignore(); const { onClose } = this.props; const { hasPassword, passwordValid } = this.state; const loading = this.state.loadingPassword || this.state.loadingSeed; return ( <> {!loading && ( <SessionModal title={i18n('showSeed')} onOk={() => null} onClose={onClose} > <div className="spacer-sm" /> {hasPassword && !passwordValid ? ( <>{this.renderPasswordView()}</> ) : ( <>{this.renderSeedView()}</> )} </SessionModal> )} </> ); } private renderPasswordView() { const maxPasswordLen = 64; const error = this.state.error; const i18n = window.i18n; const { onClose } = this.props; return ( <> <p>{i18n('showSeedPasswordRequest')}</p> <input type="password" id="seed-input-password" placeholder={i18n('password')} onKeyUp={this.onEnter} maxLength={maxPasswordLen} /> {error && ( <> <div className="spacer-xs" /> <div className="session-label danger">{error}</div> </> )} <div className="spacer-lg" /> <div className="session-modal__button-group"> <SessionButton text={i18n('confirm')} onClick={this.confirmPassword} /> <SessionButton text={i18n('cancel')} onClick={onClose} /> </div> </> ); } private renderSeedView() { const i18n = window.i18n; const { onClose } = this.props; return ( <> <div className="session-modal__centered text-center"> <p className="session-modal__description"> {i18n('seedSavePromptMain')} <br /> <span className="subtle">{i18n('seedSavePromptAlt')}</span> </p> <div className="spacer-xs" /> <i className="session-modal__text-highlight">{this.state.seed}</i> </div> <div className="spacer-lg" /> <div className="session-modal__button-group"> <SessionButton text={i18n('ok')} onClick={onClose} /> <SessionButton text={i18n('copySeed')} onClick={() => { this.copySeed(this.state.seed); }} /> </div> </> ); } private confirmPassword() { const passwordHash = this.state.passwordHash; const passwordValue = $('#seed-input-password').val(); const isPasswordValid = window.passwordUtil.matchesHash( passwordValue, passwordHash ); if (!passwordValue) { this.setState({ error: window.i18n('noGivenPassword'), }); return false; } if (passwordHash && !isPasswordValid) { this.setState({ error: window.i18n('invalidPassword'), }); return false; } this.setState({ passwordValid: true, error: '', }); window.removeEventListener('keyup', this.onEnter); return true; } private checkHasPassword() { if (!this.state.loadingPassword) { return; } const hashPromise = window.Signal.Data.getPasswordHash(); hashPromise.then((hash: any) => { this.setState({ hasPassword: !!hash, passwordHash: hash, loadingPassword: false, }); }); } private async getSeed() { if (this.state.seed) { return false; } const manager = await window.getAccountManager(); const seed = manager.getCurrentMnemonic(); this.setState({ seed, loadingSeed: false, }); return true; } private copySeed(seed: string) { window.clipboard.writeText(seed); window.pushToast({ title: window.i18n('copiedMnemonic'), type: 'success', id: 'copySeedToast', }); } private onEnter(event: any) { if (event.key === 'Enter') { this.confirmPassword(); } } }