2018-11-23 15:23:41 -08:00
// @flow
2019-02-04 20:41:45 -08:00
2019-01-12 19:03:33 -08:00
/* eslint-disable import/no-extraneous-dependencies */
import fs from 'fs' ;
2019-02-18 18:20:25 -08:00
import os from 'os' ;
import path from 'path' ;
2019-01-12 19:03:33 -08:00
import { promisify } from 'util' ;
2019-01-12 10:02:34 -08:00
import React , { PureComponent } from 'react' ;
import styled from 'styled-components' ;
2019-01-12 19:03:33 -08:00
import electron from 'electron' ;
import isDev from 'electron-is-dev' ;
import dateFns from 'date-fns' ;
import eres from 'eres' ;
2018-11-23 15:23:41 -08:00
2019-01-12 10:02:34 -08:00
import { Button } from '../components/button' ;
import { ConfirmDialogComponent } from '../components/confirm-dialog' ;
import { TextComponent } from '../components/text' ;
import { InputComponent } from '../components/input' ;
2019-01-12 11:30:44 -08:00
import { InputLabelComponent } from '../components/input-label' ;
import { RowComponent } from '../components/row' ;
import { Clipboard } from '../components/clipboard' ;
2019-02-08 21:02:03 -08:00
import { SelectComponent } from '../components/select' ;
2018-11-23 15:23:41 -08:00
2019-01-12 10:02:34 -08:00
import rpc from '../../services/api' ;
2019-02-10 09:52:34 -08:00
import { DARK , LIGHT , THEME _MODE } from '../constants/themes' ;
2019-02-08 21:02:03 -08:00
import electronStore from '../../config/electron-store' ;
2019-02-18 07:45:34 -08:00
import { openExternal } from '../utils/open-external' ;
2019-01-12 10:02:34 -08:00
2019-02-18 18:10:42 -08:00
import type { MapDispatchToProps , MapStateToProps } from '../containers/settings' ;
2019-01-12 10:02:34 -08:00
2019-02-18 07:45:34 -08:00
const EXPORT _VIEW _KEYS _TITLE = 'Export View Keys' ;
const EXPORT _VIEW _KEYS _CONTENT = 'Viewing keys for shielded addresses allow for the disclosure of all transaction information to a preffered party. Anyone who holds these keys can see all shielded transaction details, but cannot spend coins as it is not a private key.' ;
const EXPORT _VIEW _KEYS _LEARN _MORE = 'https://z.cash/blog/viewing-keys-selective-disclosure' ;
const IMPORT _PRIV _KEYS _TITLE = 'Import Private Keys' ;
const IMPORT _PRIV _KEYS _CONTENT = 'Importing private keys will add the spendable coins to this wallet.' ;
const EXPORT _PRIV _KEYS _TITLE = 'Export Private Keys' ;
const EXPORT _PRIV _KEYS _CONTENT = 'Beware: exporting your private keys will allow anyone controlling them to spend your coins. Only perform this action on a trusted machine.' ;
const BACKUP _WALLET _TITLE = 'Backup Wallet' ;
const BACKUP _WALLET _CONTENT = 'It is recommended that you backup your wallet often.' ;
2019-01-12 10:02:34 -08:00
const Wrapper = styled . div `
2019-02-07 08:31:59 -08:00
margin - top : $ { props => props . theme . layoutContentPaddingTop } ;
2019-01-12 10:02:34 -08:00
` ;
const ModalContent = styled . div `
2019-02-17 19:56:50 -08:00
padding : 20 px 30 px ;
2019-01-12 10:02:34 -08:00
width : 100 % ;
2019-01-12 11:30:44 -08:00
max - height : 600 px ;
overflow - y : auto ;
p {
word - break : break - all ;
}
` ;
const Btn = styled ( Button ) `
margin - bottom : 10 px ;
2019-01-12 10:02:34 -08:00
` ;
2019-01-12 11:30:44 -08:00
const ClipboardButton = styled ( Clipboard ) `
width : 50 px ;
2019-02-07 08:31:59 -08:00
border - radius : $ { props => props . theme . boxBorderRadius } ;
2019-01-12 11:30:44 -08:00
height : 45 px ;
margin - left : 5 px ;
` ;
2019-01-23 21:04:15 -08:00
const SettingsWrapper = styled . div `
2019-02-18 07:45:34 -08:00
margin - bottom : 20 px ;
2019-01-23 07:07:08 -08:00
min - width : 200 px ;
2019-02-18 07:45:34 -08:00
width : 70 % ;
max - width : 600 px ;
min - width : 350 px ;
background : $ { props => props . theme . colors . settingsCardBg } ;
padding : 20 px 20 px 10 px 20 px ;
border : 1 px solid $ { props => props . theme . colors . inputBorder } ;
border - radius : $ { props => props . theme . boxBorderRadius } ;
` ;
const SettingsInnerWrapper = styled . div `
margin - bottom : 50 px ;
& : last - child {
margin - bottom : 0 ;
}
` ;
const LearnMore = styled . div `
cursor : pointer ;
text - transform : uppercase ;
font - size : 10 px ;
2019-02-18 08:20:47 -08:00
font - family : $ { props => props . theme . fontFamily } ; ;
2019-02-18 07:45:34 -08:00
letter - spacing : 1 px ;
color : $ { props => props . theme . colors . settingsLearnMore } ;
& : hover {
color : $ { props => props . theme . colors . settingsLearnMoreHovered } ; ;
}
}
2019-01-23 07:07:08 -08:00
` ;
2019-01-23 21:04:15 -08:00
const SettingsTitle = styled ( TextComponent ) `
text - transform : uppercase ;
2019-02-07 08:31:59 -08:00
color : $ { props => props . theme . colors . transactionsDate } ;
font - size : $ { props => ` ${ props . theme . fontSize . regular * 0.9 } em ` } ;
font - weight : $ { props => String ( props . theme . fontWeight . bold ) } ;
2019-01-23 21:04:15 -08:00
margin - bottom : 5 px ;
2019-01-23 07:07:08 -08:00
` ;
2019-01-23 21:04:15 -08:00
const SettingsContent = styled ( TextComponent ) `
2019-02-18 07:45:34 -08:00
margin - bottom : 30 px ;
margin - top : 15 px ;
font - weight : 300 ;
letter - spacing : 0.5 px ;
line - height : 1.4 ;
2019-01-23 07:07:08 -08:00
` ;
2019-02-08 21:02:03 -08:00
const ThemeSelectWrapper = styled . div `
margin - bottom : 20 px ;
2019-02-18 07:45:34 -08:00
width : 70 % ;
max - width : 600 px ;
min - width : 350 px ;
` ;
const SettingsActionWrapper = styled . div `
display : flex ;
flex - direction : row ;
align - items : center ;
justify - content : space - between ;
2019-02-08 21:02:03 -08:00
` ;
2019-01-12 11:30:44 -08:00
type Key = {
2019-01-12 10:02:34 -08:00
zAddress : string ,
key : string ,
} ;
2019-02-18 18:10:42 -08:00
type Props = MapDispatchToProps & MapStateToProps ;
2019-01-12 10:02:34 -08:00
type State = {
2019-01-12 11:30:44 -08:00
viewKeys : Key [ ] ,
privateKeys : Key [ ] ,
2019-01-12 18:18:59 -08:00
importedPrivateKeys : string ,
2019-01-12 10:02:34 -08:00
successExportViewKeys : boolean ,
2019-01-12 11:30:44 -08:00
successExportPrivateKeys : boolean ,
2019-01-12 18:18:59 -08:00
successImportPrivateKeys : boolean ,
isLoading : boolean ,
error : string | null ,
2019-01-12 10:02:34 -08:00
} ;
export class SettingsView extends PureComponent < Props , State > {
state = {
viewKeys : [ ] ,
2019-01-12 11:30:44 -08:00
privateKeys : [ ] ,
2019-01-12 18:18:59 -08:00
importedPrivateKeys : '' ,
2019-01-12 10:02:34 -08:00
isLoading : false ,
successExportViewKeys : false ,
2019-01-12 11:30:44 -08:00
successExportPrivateKeys : false ,
2019-01-12 18:18:59 -08:00
successImportPrivateKeys : false ,
error : null ,
2019-01-12 10:02:34 -08:00
} ;
2019-02-18 18:10:42 -08:00
componentDidMount ( ) {
const { loadAddresses } = this . props ;
loadAddresses ( ) ;
}
2019-02-18 18:20:25 -08:00
getWalletFolderPath = ( ) => {
const { app } = electron . remote ;
if ( os . platform ( ) === 'darwin' ) {
return path . join ( app . getPath ( 'appData' ) , 'Zcash' ) ;
}
if ( os . platform ( ) === 'linux' ) {
return path . join ( app . getPath ( 'home' ) , '.zcash' ) ;
}
return path . join ( app . getPath ( 'appData' ) , 'Zcash' ) ;
} ;
2019-01-12 10:02:34 -08:00
exportViewKeys = ( ) => {
const { addresses } = this . props ;
const zAddresses = addresses . filter ( addr => addr . startsWith ( 'z' ) ) ;
2019-01-12 11:30:44 -08:00
this . setState ( { isLoading : true } ) ;
2019-01-12 10:02:34 -08:00
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 ,
2019-01-12 11:30:44 -08:00
isLoading : false ,
2019-01-12 10:02:34 -08:00
} ) ;
} ) ;
} ;
2019-01-12 11:30:44 -08:00
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 ,
} ) ;
} ) ;
2019-01-12 10:02:34 -08:00
} ;
2019-01-12 18:18:59 -08:00
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 } ) ;
} ) ;
} ;
2019-01-12 19:03:33 -08:00
backupWalletDat = async ( ) => {
const backupFileName = ` zcash-wallet-backup- ${ dateFns . format (
new Date ( ) ,
'YYYY-MM-DD-mm-ss' ,
) } . dat ` ;
electron . remote . dialog . showSaveDialog (
2019-01-28 16:34:07 -08:00
undefined ,
2019-01-12 19:03:33 -08:00
{ defaultPath : backupFileName } ,
2019-02-06 19:06:48 -08:00
async ( pathToSave : string ) => {
2019-01-12 19:06:05 -08:00
if ( ! pathToSave ) return ;
2019-02-18 18:20:25 -08:00
const WALLET _DIR = this . getWalletFolderPath ( ) ;
const zcashDir = isDev ? path . join ( WALLET _DIR , 'testnet3' ) : WALLET _DIR ;
2019-01-12 19:06:05 -08:00
const walletDatPath = ` ${ zcashDir } /wallet.dat ` ;
const [ cannotAccess ] = await eres ( promisify ( fs . access ) ( walletDatPath ) ) ;
/* eslint-disable no-alert */
if ( cannotAccess ) {
2019-01-28 16:34:07 -08:00
alert ( "Couldn't backup the wallet.dat file. You need to back it up manually." ) ;
2019-02-18 18:20:25 -08:00
return ;
2019-01-12 19:06:05 -08:00
}
2019-01-28 16:34:07 -08:00
const [ error ] = await eres ( promisify ( fs . copyFile ) ( walletDatPath , pathToSave ) ) ;
2019-01-12 19:03:33 -08:00
if ( error ) {
2019-01-28 16:34:07 -08:00
alert ( "Couldn't backup the wallet.dat file. You need to back it up manually." ) ;
2019-01-12 19:03:33 -08:00
}
} ,
) ;
} ;
2019-01-12 10:02:34 -08:00
render = ( ) => {
const {
2019-01-12 11:30:44 -08:00
viewKeys ,
privateKeys ,
2019-01-12 18:18:59 -08:00
importedPrivateKeys ,
successExportViewKeys ,
2019-01-12 11:30:44 -08:00
successExportPrivateKeys ,
2019-01-12 18:18:59 -08:00
successImportPrivateKeys ,
isLoading ,
error ,
2019-01-12 10:02:34 -08:00
} = this . state ;
2019-02-10 09:52:34 -08:00
const themeOptions = [
{ label : 'Dark' , value : DARK } ,
{ label : 'Light' , value : LIGHT } ,
] ;
2019-01-12 10:02:34 -08:00
return (
< Wrapper >
2019-02-08 21:02:03 -08:00
< ThemeSelectWrapper >
< SettingsTitle value = 'Theme' / >
< SelectComponent
2019-02-10 09:52:34 -08:00
onChange = { newMode => electronStore . set ( THEME _MODE , newMode ) }
value = { electronStore . get ( THEME _MODE ) }
options = { themeOptions }
2019-02-08 21:02:03 -08:00
/ >
< / T h e m e S e l e c t W r a p p e r >
2019-01-12 11:30:44 -08:00
< ConfirmDialogComponent
2019-02-18 07:45:34 -08:00
title = { EXPORT _VIEW _KEYS _TITLE }
2019-01-12 10:02:34 -08:00
renderTrigger = { toggleVisibility => (
2019-01-23 21:04:15 -08:00
< SettingsWrapper >
2019-02-18 07:45:34 -08:00
< SettingsTitle value = { EXPORT _VIEW _KEYS _TITLE } / >
< SettingsContent value = { EXPORT _VIEW _KEYS _CONTENT } / >
< SettingsActionWrapper >
< Btn label = { EXPORT _VIEW _KEYS _TITLE } onClick = { toggleVisibility } / >
< LearnMore onClick = { ( ) => openExternal ( EXPORT _VIEW _KEYS _LEARN _MORE ) } >
Learn More
< / L e a r n M o r e >
< / S e t t i n g s A c t i o n W r a p p e r >
2019-01-23 21:04:15 -08:00
< / S e t t i n g s W r a p p e r >
2019-01-12 10:02:34 -08:00
) }
2019-01-12 11:30:44 -08:00
onConfirm = { this . exportViewKeys }
showButtons = { ! successExportViewKeys }
2019-02-17 19:56:50 -08:00
width = { 450 }
2019-01-12 10:02:34 -08:00
>
2019-01-21 13:42:20 -08:00
{ ( ) => (
< ModalContent >
{ successExportViewKeys ? (
viewKeys . map ( ( { zAddress , key } ) => (
< >
< InputLabelComponent value = { zAddress } / >
< RowComponent alignItems = 'center' >
< InputComponent
value = { key }
onFocus = { ( event ) => {
event . currentTarget . select ( ) ;
} }
/ >
< ClipboardButton text = { key } / >
< / R o w C o m p o n e n t >
< / >
) )
) : (
< 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.' / >
) }
< / M o d a l C o n t e n t >
) }
2019-01-12 11:30:44 -08:00
< / C o n f i r m D i a l o g C o m p o n e n t >
2019-02-18 07:45:34 -08:00
< SettingsWrapper >
< ConfirmDialogComponent
title = { EXPORT _PRIV _KEYS _TITLE }
renderTrigger = { toggleVisibility => (
< SettingsInnerWrapper >
< SettingsTitle value = { EXPORT _PRIV _KEYS _TITLE } / >
< SettingsContent value = { EXPORT _PRIV _KEYS _CONTENT } / >
< Btn label = { EXPORT _PRIV _KEYS _TITLE } onClick = { toggleVisibility } / >
< / S e t t i n g s I n n e r W r a p p e r >
) }
onConfirm = { this . exportPrivateKeys }
showButtons = { ! successExportPrivateKeys }
width = { 450 }
>
{ ( ) => (
< ModalContent >
{ successExportPrivateKeys ? (
privateKeys . map ( ( { zAddress , key } ) => (
< >
< InputLabelComponent value = { zAddress } / >
< RowComponent alignItems = 'center' >
< InputComponent
value = { key }
onFocus = { ( event ) => {
event . currentTarget . select ( ) ;
} }
/ >
< ClipboardButton text = { key } / >
< / R o w C o m p o n e n t >
< / >
) )
) : (
< 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.' / >
) }
< / M o d a l C o n t e n t >
) }
< / C o n f i r m D i a l o g C o m p o n e n t >
< ConfirmDialogComponent
title = { IMPORT _PRIV _KEYS _TITLE }
renderTrigger = { toggleVisibility => (
< SettingsInnerWrapper >
< SettingsTitle value = { IMPORT _PRIV _KEYS _TITLE } / >
< SettingsContent value = { IMPORT _PRIV _KEYS _CONTENT } / >
< Btn label = { IMPORT _PRIV _KEYS _TITLE } onClick = { toggleVisibility } / >
< / S e t t i n g s I n n e r W r a p p e r >
) }
onConfirm = { this . importPrivateKeys }
showButtons = { ! successImportPrivateKeys }
width = { 450 }
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' / > }
< / M o d a l C o n t e n t >
) }
< / C o n f i r m D i a l o g C o m p o n e n t >
< / S e t t i n g s W r a p p e r >
2019-01-12 19:03:33 -08:00
2019-01-23 21:04:15 -08:00
< SettingsWrapper >
2019-02-18 07:45:34 -08:00
< SettingsTitle value = { BACKUP _WALLET _TITLE } / >
< SettingsContent value = { BACKUP _WALLET _CONTENT } / >
< Btn label = { BACKUP _WALLET _TITLE } onClick = { this . backupWalletDat } / >
2019-01-23 21:04:15 -08:00
< / S e t t i n g s W r a p p e r >
2019-01-12 10:02:34 -08:00
< / W r a p p e r >
) ;
} ;
}