ghost-keychain/packages/extension-ui/src/Popup/RestoreJson.tsx
Jaco Greeff f4fc547c7e
Close the no-accounts import (JSON/seed) hole (#388)
* Close the no-accounts import (JSON/seed) hole

* Use accounts (not hierarchy)
2020-07-11 11:25:27 +02:00

126 lines
3.5 KiB
TypeScript

// Copyright 2019-2020 @polkadot/extension-ui 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 React, { useCallback, useContext, useEffect, useState } from 'react';
import { AccountContext, ActionContext, InputWithLabel, InputFileWithLabel, Button, Address } from '../components';
import { u8aToString } from '@polkadot/util';
import styled from 'styled-components';
import { jsonRestore, jsonVerifyPassword, jsonVerifyFile } from '../messaging';
import { Header } from '../partials';
interface FileState {
address: string | null;
isFileValid: boolean;
json: KeyringPair$Json | null;
}
// FIXME We want to display the decodeError
interface PassState {
decodeError?: string | null;
isPassValid: boolean;
password: string;
}
const acceptedFormats = ['application/json', 'text/plain'].join(', ');
async function parseFile (file: Uint8Array): Promise<FileState> {
try {
const json = JSON.parse(u8aToString(file)) as KeyringPair$Json;
const isFileValid = await jsonVerifyFile(json);
const address = json.address;
return { address, isFileValid, json };
} catch (error) {
console.error(error);
}
return { address: null, isFileValid: false, json: null };
}
export default function Upload (): React.ReactElement {
const { accounts } = useContext(AccountContext);
const onAction = useContext(ActionContext);
const [{ address, isFileValid, json }, setJson] = useState<FileState>({ address: null, isFileValid: false, json: null });
const [{ isPassValid, password }, setPass] = useState<PassState>({ isPassValid: false, password: '' });
useEffect((): void => {
!accounts.length && onAction();
}, [accounts, onAction]);
const _onChangePass = useCallback(
(password: string): void => {
jsonVerifyPassword(password)
.then((isPassValid) => setPass({ isPassValid, password }))
.catch(console.error);
}, []
);
const _onChangeFile = useCallback(
(file: Uint8Array): void => {
parseFile(file)
.then(setJson)
.catch(console.error);
}, []
);
const _onRestore = useCallback(
(): void => {
if (!json || !password) {
return;
}
jsonRestore(json, password)
.then(({ error }): void => {
if (error) {
setPass(({ password }) => ({ decodeError: error, isPassValid: false, password }));
} else {
onAction('/');
}
})
.catch(console.error);
},
[json, onAction, password]
);
return (
<>
<HeaderWithSmallerMargin
showBackArrow
text='Restore from JSON'
/>
<div>
<Address
address={(isFileValid && address) || null}
name={(isFileValid && json?.meta.name as string) || null}
/>
<InputFileWithLabel
accept={acceptedFormats}
isError={!isFileValid}
label={'backup file'}
onChange={_onChangeFile}
withLabel
/>
<InputWithLabel
isError={!isPassValid}
label='Password for this file'
onChange={_onChangePass}
type='password'
/>
<Button
isDisabled={!isFileValid || !isPassValid}
onClick={_onRestore}
>
Restore
</Button>
</div>
</>
);
}
const HeaderWithSmallerMargin = styled(Header)`
margin-bottom: 15px;
`;