Skip to content
This repository has been archived by the owner on Sep 3, 2019. It is now read-only.

Commit

Permalink
display unexpected thrown errors in UI (#146)
Browse files Browse the repository at this point in the history
* Issue145 try/catch for login, acctLogin errors(invalid mnemonic, privateKey, privateKeyHash),  display the error on the ui
* code clean - spacing, and convert history.push to routerStore.push for consistency
  • Loading branch information
soshochang committed Sep 5, 2018
1 parent 61d32f4 commit 3aaeb88
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
@@ -1,7 +1,7 @@
language: node_js

node_js:
- "9.3.0"
- "10.9.0"

env:
- CXX=g++-4.8
Expand Down
32 changes: 21 additions & 11 deletions src/background/controllers/accountController.ts
@@ -1,5 +1,6 @@
import { isEmpty, find, cloneDeep } from 'lodash';
import { Wallet as QtumWallet } from 'qtumjs-wallet';
import assert from 'assert';

import QryptoController from '.';
import IController from './iController';
Expand Down Expand Up @@ -69,8 +70,12 @@ export default class AccountController extends IController {
* Initial login with the master password and routing to the correct account login page.
*/
public login = async (password: string) => {
this.main.crypto.generateAppSaltIfNecessary();
this.main.crypto.derivePasswordHash(password);
try {
this.main.crypto.generateAppSaltIfNecessary();
this.main.crypto.derivePasswordHash(password);
} catch (err) {
this.displayErrorOnPopup(err);
}
}

public finishLogin = async () => {
Expand Down Expand Up @@ -124,10 +129,10 @@ export default class AccountController extends IController {
* @param mnemonic The mnemonic to derive the wallet from.
*/
public importMnemonic = async (accountName: string, mnemonic: string) => {
if (!mnemonic) {
throw Error('invalid mnemonic'); }

try {
// Non-empty mnemonic is already validated in the popup ui
assert(mnemonic, 'invalid mnemonic');

const network = this.main.network.network;
const wallet = network.fromMnemonic(mnemonic);
const privateKeyHash = this.getPrivateKeyHash(wallet);
Expand All @@ -141,8 +146,8 @@ export default class AccountController extends IController {

await this.addAccountAndLogin(accountName, privateKeyHash, wallet);
} catch (e) {
// TODO - Create error handling on ui side
console.log(e);
this.displayErrorOnPopup(e);
}
}

Expand All @@ -152,10 +157,10 @@ export default class AccountController extends IController {
* @param privateKey The private key to derive the wallet from.
*/
public importPrivateKey = async (accountName: string, privateKey: string) => {
if (!privateKey) {
throw Error('invalid privateKey'); }

try {
// Non-empty privateKey is already validated in the popup ui
assert(privateKey, 'invalid privateKey');

// recover wallet and privateKeyHash
const network = this.main.network.network;
const wallet = network.fromWIF(privateKey);
Expand All @@ -171,6 +176,7 @@ export default class AccountController extends IController {
await this.addAccountAndLogin(accountName, privateKeyHash, wallet);
} catch (e) {
console.log(e);
this.displayErrorOnPopup(e);
}
}

Expand Down Expand Up @@ -271,8 +277,7 @@ export default class AccountController extends IController {
* @param privateKeyHash The private key hash to recover the wallet from.
*/
private recoverFromPrivateKeyHash(privateKeyHash: string): QtumWallet {
if (!privateKeyHash) {
throw Error('invalid privateKeyHash'); }
assert(privateKeyHash, 'invalid privateKeyHash');

const network = this.main.network.network;
return network.fromEncryptedPrivateKey(
Expand Down Expand Up @@ -308,6 +313,7 @@ export default class AccountController extends IController {
return true;
} catch (err) {
console.log(err);
this.displayErrorOnPopup(err);
return false;
}
}
Expand Down Expand Up @@ -366,6 +372,10 @@ export default class AccountController extends IController {
}
}

private displayErrorOnPopup = (err: Error) => {
chrome.runtime.sendMessage({ type: MESSAGE_TYPE.UNEXPECTED_ERROR, error: err.message });
}

private handleMessage = (request: any, _: chrome.runtime.MessageSender, sendResponse: (response: any) => void) => {
switch (request.type) {
case MESSAGE_TYPE.LOGIN:
Expand Down
5 changes: 3 additions & 2 deletions src/background/controllers/cryptoController.ts
Expand Up @@ -64,7 +64,7 @@ export default class CryptoController extends IController {
/*
* Derives the password hash with the password input.
*/
public derivePasswordHash = async (password: string) => {
public derivePasswordHash = (password: string) => {
if (!this.appSalt) {
throw Error('appSalt should not be empty');
}
Expand All @@ -81,7 +81,8 @@ export default class CryptoController extends IController {
sww.postMessage({
password,
salt: this.appSalt,
scryptParams: CryptoController.SCRYPT_PARAMS_PW });
scryptParams: CryptoController.SCRYPT_PARAMS_PW,
});

sww.onmessage = (e) => {
if (e.data.err) {
Expand Down
4 changes: 2 additions & 2 deletions src/background/workers/scryptworker.js
Expand Up @@ -7,8 +7,8 @@ onmessage = (e) => {
const saltBuffer = Buffer.from(salt);
const { N, r, p } = e.data.scryptParams;
const derivedKey = scrypt(password, saltBuffer, N, r, p, 64);
postMessage({derivedKey});
postMessage({ derivedKey });
} catch (err) {
postMessage({err});
postMessage({ err });
}
}
1 change: 1 addition & 0 deletions src/constants.ts
Expand Up @@ -71,6 +71,7 @@ export enum MESSAGE_TYPE {
VALIDATE_WALLET_NAME = 'VALIDATE_WALLET_NAME',
GET_QRC_TOKEN_DETAILS = 'GET_QRC_TOKEN_DETAILS',
QRC_TOKEN_DETAILS_RETURN = 'QRC_TOKEN_DETAILS_RETURN',
UNEXPECTED_ERROR = 'UNEXPECTED_ERROR',
}

export enum RESPONSE_TYPE {
Expand Down
54 changes: 19 additions & 35 deletions src/popup/MainContainer.tsx
Expand Up @@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';
import { Router, Route, Switch } from 'react-router-dom';
import { SynchronizedHistory } from 'mobx-react-router';
import { Button, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@material-ui/core';

import Loading from './components/Loading';
import Login from './pages/Login';
Expand All @@ -28,7 +29,7 @@ interface IProps {
@observer
export default class MainContainer extends Component<IProps, {}> {
public componentDidMount() {
chrome.runtime.onMessage.addListener(this.handleMessage);
this.props.store!.mainContainerStore.init();
}

public componentWillUnmount() {
Expand Down Expand Up @@ -57,41 +58,24 @@ export default class MainContainer extends Component<IProps, {}> {
<Route exact path="/add-token" component={AddToken} />
</Switch>
</Router>
<UnexpectedErrorDialog />
</div>
);
}

private handleMessage = (request: any) => {
const { history, store: { loginStore, importStore } }: any = this.props;
switch (request.type) {
case MESSAGE_TYPE.ROUTE_LOGIN:
history.push('/login');
break;

case MESSAGE_TYPE.ACCOUNT_LOGIN_SUCCESS:
history.push('/home');
break;

case MESSAGE_TYPE.LOGIN_FAILURE:
loginStore.invalidPassword = true;
history.push('/login');
break;

case MESSAGE_TYPE.LOGIN_SUCCESS_WITH_ACCOUNTS:
history.push('/account-login');
break;

case MESSAGE_TYPE.LOGIN_SUCCESS_NO_ACCOUNTS:
history.push('/create-wallet');
break;

case MESSAGE_TYPE.IMPORT_MNEMONIC_PRKEY_FAILURE:
importStore.importMnemonicPrKeyFailed = true;
history.goBack();
break;

default:
break;
}
}
}

const UnexpectedErrorDialog: React.SFC<any> = inject('store')(observer(({ store: { mainContainerStore } }) => (
<Dialog
disableBackdropClick
open={!!mainContainerStore.unexpectedError}
onClose={() => mainContainerStore.unexpectedError = undefined}
>
<DialogTitle>Unexpected Error</DialogTitle>
<DialogContent>
<DialogContentText>{ mainContainerStore.unexpectedError }</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => mainContainerStore.unexpectedError = undefined} color="primary">Close</Button>
</DialogActions>
</Dialog>
)));
15 changes: 2 additions & 13 deletions src/popup/pages/SaveMnemonic/index.tsx
Expand Up @@ -6,7 +6,6 @@ import cx from 'classnames';
import styles from './styles';
import NavBar from '../../components/NavBar';
import AppStore from '../../stores/AppStore';
import { MESSAGE_TYPE } from '../../../constants';
const strings = require('../../localization/locales/en_US.json');

interface IProps {
Expand Down Expand Up @@ -38,7 +37,7 @@ class SaveMnemonic extends Component<WithStyles & IProps, {}> {
fullWidth
variant="contained"
color="primary"
onClick={() => this.createWallet(false)}
onClick={() => saveMnemonicStore.createWallet(false)}
>
I Copied It Somewhere Safe
</Button>
Expand All @@ -47,24 +46,14 @@ class SaveMnemonic extends Component<WithStyles & IProps, {}> {
fullWidth
variant="contained"
color="primary"
onClick={() => this.createWallet(true)}
onClick={() => saveMnemonicStore.createWallet(true)}
>
Save To File
</Button>
</div>
</div>
);
}

private createWallet = (saveFile: boolean) => {
const { history, store: { saveMnemonicStore } }: any = this.props;
history.push('/loading');
chrome.runtime.sendMessage({
type: saveFile ? MESSAGE_TYPE.SAVE_TO_FILE : MESSAGE_TYPE.IMPORT_MNEMONIC,
accountName: saveMnemonicStore.walletName,
mnemonicPrivateKey: saveMnemonicStore.mnemonic,
});
}
}

export default withStyles(styles)(SaveMnemonic);
5 changes: 4 additions & 1 deletion src/popup/stores/AppStore.ts
Expand Up @@ -11,6 +11,7 @@ import SettingsStore from './SettingsStore';
import AccountDetailStore from './AccountDetailStore';
import SendStore from './SendStore';
import AddTokenStore from './AddTokenStore';
import MainContainerStore from './MainContainerStore';

export default class AppStore {
public routerStore: RouterStore;
Expand All @@ -25,20 +26,22 @@ export default class AppStore {
public accountDetailStore: AccountDetailStore;
public sendStore: SendStore;
public addTokenStore: AddTokenStore;
public mainContainerStore: MainContainerStore;

constructor() {
this.routerStore = new RouterStore();
this.sessionStore = new SessionStore();
this.navBarStore = new NavBarStore(this);
this.loginStore = new LoginStore(this);
this.createWalletStore = new CreateWalletStore(this);
this.saveMnemonicStore = new SaveMnemonicStore();
this.saveMnemonicStore = new SaveMnemonicStore(this);
this.accountLoginStore = new AccountLoginStore(this);
this.importStore = new ImportStore(this);
this.settingsStore = new SettingsStore();
this.accountDetailStore = new AccountDetailStore(this);
this.sendStore = new SendStore(this);
this.addTokenStore = new AddTokenStore(this);
this.mainContainerStore = new MainContainerStore(this);
}
}

Expand Down
61 changes: 61 additions & 0 deletions src/popup/stores/MainContainerStore.ts
@@ -0,0 +1,61 @@
import { action, observable } from 'mobx';

import AppStore from './AppStore';
import { MESSAGE_TYPE } from '../../constants';

export default class MainContainerStore {
@observable public unexpectedError?: string = undefined;

private app: AppStore;

constructor(app: AppStore) {
this.app = app;
}

@action
public init = () => {
chrome.runtime.onMessage.addListener(this.handleMessage);
}

@action
private handleMessage = (request: any) => {
const { loginStore, importStore, routerStore }: any = this.app;
switch (request.type) {
case MESSAGE_TYPE.ROUTE_LOGIN:
routerStore.push('/login');
break;

case MESSAGE_TYPE.ACCOUNT_LOGIN_SUCCESS:
routerStore.push('/home');
break;

case MESSAGE_TYPE.LOGIN_FAILURE:
loginStore.invalidPassword = true;
routerStore.push('/login');
break;

case MESSAGE_TYPE.LOGIN_SUCCESS_WITH_ACCOUNTS:
routerStore.push('/account-login');
break;

case MESSAGE_TYPE.LOGIN_SUCCESS_NO_ACCOUNTS:
routerStore.push('/create-wallet');
break;

case MESSAGE_TYPE.IMPORT_MNEMONIC_PRKEY_FAILURE:
importStore.importMnemonicPrKeyFailed = true;
routerStore.goBack();
break;

case MESSAGE_TYPE.UNEXPECTED_ERROR:
if (routerStore.location.pathname === '/loading') {
routerStore.goBack();
}
this.unexpectedError = request.error;
break;

default:
break;
}
}
}
18 changes: 18 additions & 0 deletions src/popup/stores/SaveMnemonicStore.ts
@@ -1,6 +1,9 @@
import { observable, action } from 'mobx';
import bip39 from 'bip39';

import AppStore from './AppStore';
import { MESSAGE_TYPE } from '../../constants';

const INIT_VALUES = {
mnemonic: '',
walletName: '',
Expand All @@ -10,11 +13,26 @@ export default class SaveMnemonicStore {
@observable public mnemonic: string = INIT_VALUES.mnemonic;
public walletName: string = INIT_VALUES.walletName;

private app: AppStore;

constructor(app: AppStore) {
this.app = app;
}

@action
public generateMnemonic = () => {
this.mnemonic = bip39.generateMnemonic();
}

@action
public reset = () => Object.assign(this, INIT_VALUES)

public createWallet = (saveFile: boolean) => {
this.app.routerStore.push('/loading');
chrome.runtime.sendMessage({
type: saveFile ? MESSAGE_TYPE.SAVE_TO_FILE : MESSAGE_TYPE.IMPORT_MNEMONIC,
accountName: this.walletName,
mnemonicPrivateKey: this.mnemonic,
});
}
}

0 comments on commit 3aaeb88

Please sign in to comment.