123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- // Copyright 2017-2020 @polkadot/app-accounts authors & contributors
- // This software may be modified and distributed under the terms
- // of the Apache-2.0 license. See the LICENSE file for details.
- import { KeyringPair$Json } from '@polkadot/keyring/types';
- import { ActionStatus } from '@polkadot/react-components/Status/types';
- import { ModalProps } from '../../types';
- import React, { useCallback, useState } from 'react';
- import { AddressRow, Button, InputAddress, InputFile, Modal, Password } from '@polkadot/react-components';
- import { isObject, u8aToString } from '@polkadot/util';
- import keyring from '@polkadot/ui-keyring';
- import { isPasswordValid } from '@polkadot/joy-utils/functions/accounts';
- import { useTranslation } from '../../translate';
- interface Props extends ModalProps {
- className?: string;
- onClose: () => void;
- onStatusChange: (status: ActionStatus) => void;
- }
- interface FileState {
- address: string | null;
- isFileValid: boolean;
- json: KeyringPair$Json | null;
- }
- interface PassState {
- isPassValid: boolean;
- password: string;
- }
- const acceptedFormats = ['application/json', 'text/plain'].join(', ');
- function parseFile (file: Uint8Array): FileState {
- try {
- const json = JSON.parse(u8aToString(file)) as KeyringPair$Json;
- const publicKey = keyring.decodeAddress(json.address, true);
- const address = keyring.encodeAddress(publicKey);
- const isFileValid = publicKey.length === 32 && !!json.encoded && isObject(json.meta) && (
- Array.isArray(json.encoding.content)
- ? json.encoding.content[0] === 'pkcs8'
- : json.encoding.content === 'pkcs8'
- );
- return { address, isFileValid, json };
- } catch (error) {
- console.error(error);
- }
- return { address: null, isFileValid: false, json: null };
- }
- function Import ({ className = '', onClose, onStatusChange }: Props): React.ReactElement<Props> {
- const { t } = useTranslation();
- const [isBusy, setIsBusy] = useState(false);
- const [{ address, isFileValid, json }, setFile] = useState<FileState>({ address: null, isFileValid: false, json: null });
- const [{ isPassValid, password }, setPass] = useState<PassState>({ isPassValid: true, password: '' });
- const _onChangeFile = useCallback(
- (file: Uint8Array) => setFile(parseFile(file)),
- []
- );
- const _onChangePass = useCallback(
- (password: string) => setPass({ isPassValid: isPasswordValid(password), password }),
- []
- );
- const _onSave = useCallback(
- (): void => {
- if (!json) {
- return;
- }
- setIsBusy(true);
- setTimeout((): void => {
- const status: Partial<ActionStatus> = { action: 'restore' };
- try {
- const pair = keyring.restoreAccount(json, password);
- const { address } = pair;
- status.status = pair ? 'success' : 'error';
- status.account = address;
- status.message = t<string>('account restored');
- InputAddress.setLastValue('account', address);
- } catch (error) {
- setPass((state: PassState) => ({ ...state, isPassValid: false }));
- status.status = 'error';
- status.message = (error as Error).message;
- console.error(error);
- }
- setIsBusy(false);
- onStatusChange(status as ActionStatus);
- if (status.status !== 'error') {
- onClose();
- }
- }, 0);
- },
- [json, onClose, onStatusChange, password, t]
- );
- return (
- <Modal
- className={className}
- header={t<string>('Add via backup file')}
- size='large'
- >
- <Modal.Content>
- <Modal.Columns>
- <Modal.Column>
- <AddressRow
- defaultName={(isFileValid && json?.meta.name as string) || null}
- noDefaultNameOpacity
- value={isFileValid && address ? address : null}
- />
- </Modal.Column>
- </Modal.Columns>
- <Modal.Columns>
- <Modal.Column>
- <InputFile
- accept={acceptedFormats}
- className='full'
- help={t<string>('Select the JSON key file that was downloaded when you created the account. This JSON file contains your private key encrypted with your password.')}
- isError={!isFileValid}
- label={t<string>('backup file')}
- onChange={_onChangeFile}
- withLabel
- />
- </Modal.Column>
- <Modal.Column>
- <p>{t<string>('Supply a backed-up JSON file, encrypted with your account-specific password.')}</p>
- </Modal.Column>
- </Modal.Columns>
- <Modal.Columns>
- <Modal.Column>
- <Password
- autoFocus
- className='full'
- help={t<string>('Type the password chosen at the account creation. It was used to encrypt your account\'s private key in the backup file.')}
- isError={!isPassValid}
- label={t<string>('password')}
- onChange={_onChangePass}
- onEnter={_onSave}
- value={password}
- />
- </Modal.Column>
- <Modal.Column>
- <p>{t<string>('The password previously used to encrypt this account.')}</p>
- </Modal.Column>
- </Modal.Columns>
- </Modal.Content>
- <Modal.Actions onCancel={onClose}>
- <Button
- icon='sync'
- isBusy={isBusy}
- isDisabled={!isFileValid || !isPassValid}
- label={t<string>('Restore')}
- onClick={_onSave}
- />
- </Modal.Actions>
- </Modal>
- );
- }
- export default React.memo(Import);
|