session-desktop/ts/components/DevicePairingDialog.tsx

395 lines
11 KiB
TypeScript
Raw Normal View History

import React, { ChangeEvent } from 'react';
import { QRCode } from 'react-qr-svg';
2019-12-23 07:40:48 +01:00
import { SessionModal } from './session/SessionModal';
2020-01-20 03:03:50 +01:00
import { SessionButton, SessionButtonColor } from './session/SessionButton';
import { SessionSpinner } from './session/SessionSpinner';
2019-12-23 07:40:48 +01:00
interface Props {
2019-12-27 07:55:28 +01:00
onClose: any;
2020-01-20 03:03:50 +01:00
pubKeyToUnpair: string | undefined;
2019-12-23 07:40:48 +01:00
}
interface State {
2020-01-20 00:27:16 +01:00
currentPubKey: string | undefined;
2019-12-27 07:55:28 +01:00
accepted: boolean;
pubKeyRequests: Array<any>;
2020-01-20 03:03:50 +01:00
currentView: 'filterRequestView' | 'qrcodeView' | 'unpairDeviceView';
errors: any;
loading: boolean;
2020-01-20 00:27:16 +01:00
deviceAlias: string | undefined;
2019-12-23 07:40:48 +01:00
}
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);
2020-01-20 03:03:50 +01:00
this.triggerUnpairDevice = this.triggerUnpairDevice.bind(this);
2019-12-23 07:40:48 +01:00
this.state = {
2020-01-20 00:27:16 +01:00
currentPubKey: undefined,
2019-12-27 07:55:28 +01:00
accepted: false,
pubKeyRequests: Array(),
2020-01-20 03:03:50 +01:00
currentView: props.pubKeyToUnpair ? 'unpairDeviceView' : 'qrcodeView',
loading: false,
errors: undefined,
2020-01-20 00:27:16 +01:00
deviceAlias: 'Unnamed Device',
2019-12-23 07:40:48 +01:00
};
}
public componentWillMount() {
2020-01-20 03:03:50 +01:00
if (this.state.currentView === 'qrcodeView') {
this.startReceivingRequests();
}
2019-12-23 07:40:48 +01:00
}
public componentWillUnmount() {
this.closeDialog();
}
2019-12-23 07:40:48 +01:00
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;
2020-01-20 03:03:50 +01:00
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">
{this.renderErrors()}
2020-01-20 03:03:50 +01:00
<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}
/>
</div>
<SessionSpinner loading={this.state.loading} />
</div>
</SessionModal>
);
}
2020-01-03 06:54:27 +01:00
return (
<SessionModal
title={window.i18n('allowPairingWithDevice')}
onOk={() => null}
onClose={this.closeDialog}
>
<div className="session-modal__centered">
{this.renderErrors()}
<label>{window.i18n('secretWords')}</label>
<div className="text-subtle">{secretWords}</div>
<div className="session-modal__button-group">
<SessionButton
text={window.i18n('skip')}
onClick={this.skipDevice}
/>
<SessionButton
text={window.i18n('allowPairing')}
onClick={this.allowDevice}
/>
</div>
</div>
</SessionModal>
);
}
public renderQrCodeView() {
2020-01-07 00:19:44 +01:00
const theme = window.Events.getThemeSetting();
const requestReceived = this.hasReceivedRequests();
const title = window.i18n('pairingDevice');
2020-01-07 00:19:44 +01:00
// Foreground equivalent to .session-modal background color
const bgColor = 'rgba(0, 0, 0, 0)';
2020-01-07 00:29:47 +01:00
const fgColor = theme === 'dark' ? '#FFFFFF' : '#1B1B1B';
2020-01-07 00:19:44 +01:00
2019-12-23 07:40:48 +01:00
return (
<SessionModal title={title} onOk={() => null} onClose={this.closeDialog}>
<div className="session-modal__centered">
{this.renderErrors()}
<h4>{window.i18n('waitingForDeviceToRegister')}</h4>
<small className="text-subtle">
{window.i18n('pairNewDevicePrompt')}
</small>
<div className="spacer-lg" />
<div id="qr">
<QRCode
value={window.textsecure.storage.user.getNumber()}
bgColor={bgColor}
fgColor={fgColor}
level="L"
/>
</div>
<div className="spacer-lg" />
<div className="session-modal__button-group__center">
{!requestReceived ? (
<SessionButton
text={window.i18n('cancel')}
onClick={this.closeDialog}
/>
2019-12-27 07:55:28 +01:00
) : (
<div className="session-modal__button-group">
<SessionButton
text={window.i18n('filterReceivedRequests')}
onClick={this.stopReceivingRequests}
/>
</div>
2019-12-27 07:55:28 +01:00
)}
</div>
</div>
</SessionModal>
2019-12-23 07:40:48 +01:00
);
}
2020-01-20 03:03:50 +01:00
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()}
2020-01-20 03:03:50 +01:00
<p className="session-modal__description">
{window.i18n('confirmUnpairingTitle')}
<br />
<span className="text-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>
</div>
</SessionModal>
);
}
public render() {
const { currentView } = this.state;
const renderQrCodeView = currentView === 'qrcodeView';
const renderFilterRequestView = currentView === 'filterRequestView';
2020-01-20 03:03:50 +01:00
const renderUnpairDeviceView = currentView === 'unpairDeviceView';
2019-12-27 07:55:28 +01:00
return (
<>
{renderQrCodeView && this.renderQrCodeView()}
{renderFilterRequestView && this.renderFilterRequestsView()}
2020-01-20 03:03:50 +01:00
{renderUnpairDeviceView && this.renderUnpairDeviceView()}
</>
);
2019-12-23 07:40:48 +01:00
}
private reset() {
this.setState({
2020-01-20 00:27:16 +01:00
currentPubKey: undefined,
accepted: false,
pubKeyRequests: Array(),
currentView: 'filterRequestView',
2020-01-20 00:27:16 +01:00
deviceAlias: 'Unnamed Device',
});
}
private startReceivingRequests() {
this.reset();
window.Whisper.events.on(
'devicePairingRequestReceived',
(pubKey: string) => {
this.requestReceived(pubKey);
}
);
this.setState({ currentView: 'qrcodeView' });
}
2019-12-23 07:40:48 +01:00
private stopReceivingRequests() {
this.setState({ currentView: 'filterRequestView' });
window.Whisper.events.off('devicePairingRequestReceived');
}
2020-01-03 06:54:27 +01:00
private requestReceived(secondaryDevicePubKey: string | EventHandlerNonNull) {
// FIFO: push at the front of the array with unshift()
this.state.pubKeyRequests.unshift(secondaryDevicePubKey);
window.pushToast({
title: window.i18n('gotPairingRequest'),
description: `${window.shortenPubkey(
secondaryDevicePubKey
)} ${window.i18n(
'showPairingWordsTitle'
)}: ${window.mnemonic.pubkey_to_secret_words(secondaryDevicePubKey)}`,
});
if (!this.state.currentPubKey) {
this.nextPubKey();
}
}
private allowDevice() {
this.setState({
accepted: true,
});
}
private transmissionCB(errors: any) {
if (!errors) {
this.setState({
errors: null,
});
this.closeDialog();
window.pushToast({
title: window.i18n('devicePairedSuccessfully'),
});
2019-12-27 07:55:28 +01:00
const conv = window.ConversationController.get(this.state.currentPubKey);
if (conv) {
conv.setNickname(this.state.deviceAlias);
2019-12-27 07:55:28 +01:00
}
return;
2019-12-23 07:40:48 +01:00
}
2019-12-27 07:55:28 +01:00
this.setState({
errors: errors,
});
2019-12-23 07:40:48 +01:00
}
private skipDevice() {
window.Whisper.events.trigger(
'devicePairingRequestRejected',
this.state.currentPubKey
);
const hasNext = this.state.pubKeyRequests.length > 0;
this.nextPubKey();
if (!hasNext) {
this.startReceivingRequests();
}
this.setState({
currentView: hasNext ? 'filterRequestView' : 'qrcodeView',
});
}
private nextPubKey() {
// FIFO: pop at the back of the array using pop()
this.setState({
currentPubKey: this.state.pubKeyRequests.pop(),
});
}
2019-12-23 07:40:48 +01:00
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);
2020-01-20 03:03:50 +01:00
window.Whisper.events.trigger('refreshLinkedDeviceList');
return true;
}
);
}
private hasReceivedRequests() {
return this.state.currentPubKey || this.state.pubKeyRequests.length > 0;
}
2019-12-23 07:40:48 +01:00
private closeDialog() {
window.removeEventListener('keyup', this.onKeyUp);
2019-12-27 07:55:28 +01:00
this.stopReceivingRequests();
window.Whisper.events.off('devicePairingRequestReceived');
if (this.state.currentPubKey && !this.state.accepted) {
window.Whisper.events.trigger(
'devicePairingRequestRejected',
this.state.currentPubKey
);
}
2019-12-23 07:40:48 +01:00
this.props.onClose();
}
private handleUpdateDeviceAlias(value: ChangeEvent<HTMLInputElement>) {
const trimmed = value.target.value.trim();
if (!!trimmed) {
this.setState({ deviceAlias: trimmed });
} else {
2020-01-20 00:27:16 +01:00
this.setState({ deviceAlias: undefined });
}
}
2020-01-20 03:03:50 +01:00
private triggerUnpairDevice() {
window.Whisper.events.trigger(
'deviceUnpairingRequested',
this.props.pubKeyToUnpair
);
window.pushToast({
title: window.i18n('deviceUnpaired'),
});
this.closeDialog();
}
2019-12-27 07:55:28 +01:00
}