326 lines
9.7 KiB
JavaScript
326 lines
9.7 KiB
JavaScript
// @flow
|
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
import fs from 'fs';
|
|
import { promisify } from 'util';
|
|
import React, { PureComponent } from 'react';
|
|
import styled from 'styled-components';
|
|
import electron from 'electron';
|
|
import isDev from 'electron-is-dev';
|
|
import dateFns from 'date-fns';
|
|
import eres from 'eres';
|
|
|
|
import { Button } from '../components/button';
|
|
import { ConfirmDialogComponent } from '../components/confirm-dialog';
|
|
import { TextComponent } from '../components/text';
|
|
import { InputComponent } from '../components/input';
|
|
import { InputLabelComponent } from '../components/input-label';
|
|
import { RowComponent } from '../components/row';
|
|
import { Clipboard } from '../components/clipboard';
|
|
|
|
import rpc from '../../services/api';
|
|
|
|
const HOME_DIR = electron.remote.app.getPath('home');
|
|
|
|
const Wrapper = styled.div`
|
|
margin-top: ${props => props.theme.layoutContentPaddingTop};
|
|
`;
|
|
|
|
const ModalContent = styled.div`
|
|
padding: 20px;
|
|
width: 100%;
|
|
max-height: 600px;
|
|
overflow-y: auto;
|
|
|
|
p {
|
|
word-break: break-all;
|
|
}
|
|
`;
|
|
|
|
const Btn = styled(Button)`
|
|
margin-bottom: 10px;
|
|
`;
|
|
|
|
const ClipboardButton = styled(Clipboard)`
|
|
width: 50px;
|
|
border-radius: ${props => props.theme.boxBorderRadius};
|
|
height: 45px;
|
|
margin-left: 5px;
|
|
`;
|
|
|
|
const SettingsWrapper = styled.div`
|
|
margin-bottom: 45px;
|
|
min-width: 200px;
|
|
width: 37%;
|
|
`;
|
|
|
|
const SettingsTitle = styled(TextComponent)`
|
|
text-transform: uppercase;
|
|
color: ${props => props.theme.colors.transactionsDate};
|
|
font-size: ${props => `${props.theme.fontSize.regular * 0.9}em`};
|
|
font-weight: ${props => props.theme.fontWeight.bold};
|
|
margin-bottom: 5px;
|
|
`;
|
|
|
|
const SettingsContent = styled(TextComponent)`
|
|
margin-bottom: 20px;
|
|
margin-top: 10px;
|
|
`;
|
|
|
|
type Key = {
|
|
zAddress: string,
|
|
key: string,
|
|
};
|
|
|
|
type Props = {
|
|
addresses: string[],
|
|
};
|
|
type State = {
|
|
viewKeys: Key[],
|
|
privateKeys: Key[],
|
|
importedPrivateKeys: string,
|
|
successExportViewKeys: boolean,
|
|
successExportPrivateKeys: boolean,
|
|
successImportPrivateKeys: boolean,
|
|
isLoading: boolean,
|
|
error: string | null,
|
|
};
|
|
|
|
export class SettingsView extends PureComponent<Props, State> {
|
|
state = {
|
|
viewKeys: [],
|
|
privateKeys: [],
|
|
importedPrivateKeys: '',
|
|
isLoading: false,
|
|
successExportViewKeys: false,
|
|
successExportPrivateKeys: false,
|
|
successImportPrivateKeys: false,
|
|
error: null,
|
|
};
|
|
|
|
exportViewKeys = () => {
|
|
const { addresses } = this.props;
|
|
|
|
const zAddresses = addresses.filter(addr => addr.startsWith('z'));
|
|
|
|
this.setState({ isLoading: true });
|
|
|
|
Promise.all(
|
|
zAddresses.map(async (zAddr) => {
|
|
const viewKey = await rpc.z_exportviewingkey(zAddr);
|
|
return { zAddress: zAddr, key: viewKey };
|
|
}),
|
|
).then((viewKeys) => {
|
|
this.setState({
|
|
viewKeys,
|
|
successExportViewKeys: true,
|
|
isLoading: false,
|
|
});
|
|
});
|
|
};
|
|
|
|
exportPrivateKeys = () => {
|
|
const { addresses } = this.props;
|
|
|
|
const zAddresses = addresses.filter(addr => addr.startsWith('z'));
|
|
|
|
this.setState({ isLoading: true });
|
|
|
|
Promise.all(
|
|
zAddresses.map(async (zAddr) => {
|
|
const privateKey = await rpc.z_exportkey(zAddr);
|
|
return { zAddress: zAddr, key: privateKey };
|
|
}),
|
|
).then((privateKeys) => {
|
|
this.setState({
|
|
privateKeys,
|
|
successExportPrivateKeys: true,
|
|
isLoading: false,
|
|
});
|
|
});
|
|
};
|
|
|
|
importPrivateKeys = () => {
|
|
const { importedPrivateKeys } = this.state;
|
|
|
|
if (!importedPrivateKeys) return;
|
|
|
|
const keys = importedPrivateKeys
|
|
.split('\n')
|
|
.map(key => key.trim())
|
|
.filter(key => !!key);
|
|
|
|
this.setState({ isLoading: true, error: null });
|
|
|
|
Promise.all(keys.map(key => rpc.z_importkey(key)))
|
|
.then(() => {
|
|
this.setState({
|
|
successImportPrivateKeys: true,
|
|
isLoading: false,
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
this.setState({ isLoading: false, error: error.message });
|
|
});
|
|
};
|
|
|
|
backupWalletDat = async () => {
|
|
const backupFileName = `zcash-wallet-backup-${dateFns.format(
|
|
new Date(),
|
|
'YYYY-MM-DD-mm-ss',
|
|
)}.dat`;
|
|
|
|
electron.remote.dialog.showSaveDialog(
|
|
undefined,
|
|
{ defaultPath: backupFileName },
|
|
async (pathToSave) => {
|
|
if (!pathToSave) return;
|
|
|
|
const zcashDir = isDev ? `${HOME_DIR}/.zcash/testnet3` : HOME_DIR;
|
|
const walletDatPath = `${zcashDir}/wallet.dat`;
|
|
|
|
const [cannotAccess] = await eres(promisify(fs.access)(walletDatPath));
|
|
|
|
/* eslint-disable no-alert */
|
|
|
|
if (cannotAccess) {
|
|
alert("Couldn't backup the wallet.dat file. You need to back it up manually.");
|
|
}
|
|
|
|
const [error] = await eres(promisify(fs.copyFile)(walletDatPath, pathToSave));
|
|
|
|
if (error) {
|
|
alert("Couldn't backup the wallet.dat file. You need to back it up manually.");
|
|
}
|
|
},
|
|
);
|
|
};
|
|
|
|
render = () => {
|
|
const {
|
|
viewKeys,
|
|
privateKeys,
|
|
importedPrivateKeys,
|
|
successExportViewKeys,
|
|
successExportPrivateKeys,
|
|
successImportPrivateKeys,
|
|
isLoading,
|
|
error,
|
|
} = this.state;
|
|
|
|
return (
|
|
<Wrapper>
|
|
<ConfirmDialogComponent
|
|
title='Export view keys'
|
|
renderTrigger={toggleVisibility => (
|
|
<SettingsWrapper>
|
|
<SettingsTitle value='Export view keys' />
|
|
<SettingsContent value='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.' />
|
|
<Btn label='Export view keys' onClick={toggleVisibility} />
|
|
</SettingsWrapper>
|
|
)}
|
|
onConfirm={this.exportViewKeys}
|
|
showButtons={!successExportViewKeys}
|
|
width={750}
|
|
>
|
|
{() => (
|
|
<ModalContent>
|
|
{successExportViewKeys ? (
|
|
viewKeys.map(({ zAddress, key }) => (
|
|
<>
|
|
<InputLabelComponent value={zAddress} />
|
|
<RowComponent alignItems='center'>
|
|
<InputComponent
|
|
value={key}
|
|
onFocus={(event) => {
|
|
event.currentTarget.select();
|
|
}}
|
|
/>
|
|
<ClipboardButton text={key} />
|
|
</RowComponent>
|
|
</>
|
|
))
|
|
) : (
|
|
<TextComponent value='Ut id vulputate arcu. Curabitur mattis aliquam magna sollicitudin vulputate. Morbi tempus bibendum porttitor. Quisque dictum ac ipsum a luctus. Donec et lacus ac erat consectetur molestie a id erat.' />
|
|
)}
|
|
</ModalContent>
|
|
)}
|
|
</ConfirmDialogComponent>
|
|
|
|
<ConfirmDialogComponent
|
|
title='Export private keys'
|
|
renderTrigger={toggleVisibility => (
|
|
<SettingsWrapper>
|
|
<SettingsTitle value='Export private keys' />
|
|
<SettingsContent value='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.' />
|
|
<Btn label='Export private keys' onClick={toggleVisibility} />
|
|
</SettingsWrapper>
|
|
)}
|
|
onConfirm={this.exportPrivateKeys}
|
|
showButtons={!successExportPrivateKeys}
|
|
width={750}
|
|
>
|
|
{() => (
|
|
<ModalContent>
|
|
{successExportPrivateKeys ? (
|
|
privateKeys.map(({ zAddress, key }) => (
|
|
<>
|
|
<InputLabelComponent value={zAddress} />
|
|
<RowComponent alignItems='center'>
|
|
<InputComponent
|
|
value={key}
|
|
onFocus={(event) => {
|
|
event.currentTarget.select();
|
|
}}
|
|
/>
|
|
<ClipboardButton text={key} />
|
|
</RowComponent>
|
|
</>
|
|
))
|
|
) : (
|
|
<TextComponent value='Ut id vulputate arcu. Curabitur mattis aliquam magna sollicitudin vulputate. Morbi tempus bibendum porttitor. Quisque dictum ac ipsum a luctus. Donec et lacus ac erat consectetur molestie a id erat.' />
|
|
)}
|
|
</ModalContent>
|
|
)}
|
|
</ConfirmDialogComponent>
|
|
|
|
<ConfirmDialogComponent
|
|
title='Import private keys'
|
|
renderTrigger={toggleVisibility => (
|
|
<SettingsWrapper>
|
|
<SettingsTitle value='Import private keys' />
|
|
<SettingsContent value='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.' />
|
|
<Btn label='Import private keys' onClick={toggleVisibility} />
|
|
</SettingsWrapper>
|
|
)}
|
|
onConfirm={this.importPrivateKeys}
|
|
showButtons={!successImportPrivateKeys}
|
|
width={900}
|
|
isLoading={isLoading}
|
|
>
|
|
{() => (
|
|
<ModalContent>
|
|
<InputLabelComponent value='Please paste your private keys here, one per line. The keys will be imported into your zcashd node' />
|
|
<InputComponent
|
|
value={importedPrivateKeys}
|
|
onChange={value => this.setState({ importedPrivateKeys: value })}
|
|
inputType='textarea'
|
|
rows={10}
|
|
/>
|
|
{successImportPrivateKeys && (
|
|
<TextComponent value='Private keys imported in your node' align='center' />
|
|
)}
|
|
{error && <TextComponent value={error} align='center' />}
|
|
</ModalContent>
|
|
)}
|
|
</ConfirmDialogComponent>
|
|
|
|
<SettingsWrapper>
|
|
<SettingsTitle value='Backup Wallet' />
|
|
<SettingsContent value='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.' />
|
|
<Btn label='Backup wallet.dat' onClick={this.backupWalletDat} />
|
|
</SettingsWrapper>
|
|
</Wrapper>
|
|
);
|
|
};
|
|
}
|