/ common / components / ParityQrSigner.tsx
ParityQrSigner.tsx
  1  import React from 'react';
  2  import classnames from 'classnames';
  3  import QrSigner from '@parity/qr-signer';
  4  
  5  import translate from 'translations';
  6  import { Spinner } from 'components/ui';
  7  import './ParityQrSigner.scss';
  8  
  9  interface State {
 10    webcamError: null | React.ReactElement<any>;
 11    isLoading: boolean;
 12  }
 13  
 14  interface ScanProps {
 15    scan: true;
 16    onScan(data: string): void;
 17  }
 18  
 19  interface ShowProps {
 20    scan: false;
 21    account: string;
 22    data?: string;
 23    rlp?: string;
 24  }
 25  
 26  interface SharedProps {
 27    size?: number;
 28  }
 29  
 30  type Props = (ScanProps | ShowProps) & SharedProps;
 31  
 32  export default class ParityQrSigner extends React.PureComponent<Props, State> {
 33    public state: State = {
 34      webcamError: null,
 35      isLoading: true
 36    };
 37  
 38    public componentDidMount() {
 39      this.checkForWebcam();
 40      if (navigator.mediaDevices) {
 41        navigator.mediaDevices.addEventListener('devicechange', this.checkForWebcam);
 42      }
 43    }
 44  
 45    public componentWillUnmount() {
 46      if (navigator.mediaDevices && navigator.mediaDevices.ondevicechange) {
 47        navigator.mediaDevices.removeEventListener('devicechange', this.checkForWebcam);
 48      }
 49    }
 50  
 51    public checkForWebcam = async () => {
 52      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
 53        try {
 54          await navigator.mediaDevices.getUserMedia({ video: true });
 55          this.setState({
 56            webcamError: null,
 57            isLoading: false
 58          });
 59        } catch (e) {
 60          const err = e as DOMException;
 61          let errorMessage;
 62          switch (err.name) {
 63            case 'NotAllowedError':
 64            case 'SecurityError':
 65              errorMessage = translate('ADD_PARITY_ERROR_DISABLED');
 66              break;
 67            case 'NotFoundError':
 68            case 'OverconstrainedError':
 69              errorMessage = translate('ADD_PARITY_ERROR_NO_CAM');
 70              break;
 71            default:
 72              errorMessage = translate('ADD_PARITY_ERROR_UNKNOWN');
 73          }
 74          this.setState({
 75            webcamError: errorMessage,
 76            isLoading: false
 77          });
 78        }
 79      }
 80    };
 81  
 82    public render() {
 83      const { webcamError, isLoading } = this.state;
 84      const size = this.props.size || 300;
 85  
 86      return (
 87        <div
 88          className={classnames({
 89            ParityQrSigner: true,
 90            'is-disabled': !!webcamError || isLoading
 91          })}
 92          style={{
 93            width: size,
 94            height: size
 95          }}
 96        >
 97          {isLoading ? (
 98            <div className="ParityQrSigner-loader">
 99              <Spinner size="x3" light={true} />
100            </div>
101          ) : webcamError ? (
102            <div className="ParityQrSigner-error">
103              <i className="ParityQrSigner-error-icon fa fa-exclamation-circle" />
104              {webcamError}
105            </div>
106          ) : (
107            <QrSigner {...this.props} size={size} />
108          )}
109        </div>
110      );
111    }
112  }