From 835b81a94c1ae7182a6bde5fcd429e299aa59f1b Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Fri, 8 Jun 2018 12:49:43 -0500 Subject: [PATCH 01/25] Set custom dPath for address verification on ledger (#1918) * set custom dPath for address verification on ledger * trigger mycryptobuild --- common/components/WalletDecrypt/components/LedgerNano.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/components/WalletDecrypt/components/LedgerNano.tsx b/common/components/WalletDecrypt/components/LedgerNano.tsx index bc46bb63..3f19202e 100644 --- a/common/components/WalletDecrypt/components/LedgerNano.tsx +++ b/common/components/WalletDecrypt/components/LedgerNano.tsx @@ -125,6 +125,9 @@ class LedgerNanoSDecryptClass extends PureComponent { private handlePathChange = (dPath: DPath) => { this.handleConnect(dPath); + this.setState({ + dPath + }); }; private handleConnect = (dPath: DPath) => { From bab240039736a322aae408da009924b09435452c Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Fri, 8 Jun 2018 19:14:38 -0400 Subject: [PATCH 02/25] Add system requirements to README.md (#1922) --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d48875ee..cbc79bfb 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,19 @@ * **Just looking to download?** Grab our [latest release](https://github.com/MyCryptoHQ/MyCrypto/releases). * **Looking for the old site?** Check out [https://legacy.mycrypto.com](https://legacy.mycrypto.com) or the source at [MyCryptoHQ/mycrypto.com](https://github.com/MyCryptoHQ/mycrypto.com) +## Requirements + +* Node 8.9.4* +* NPM >= 5 +* Python 2.7.X** + +*Higher versions should work fun, but may cause inconsistencies. It's suggested you run 8.9.4 using `nvm`. +
+**Python 3 is **not** supported, since our dependencies use `node-gyp`. + ## Running the App -This codebase targets Node 8.9.4 (LTS). After `npm install`ing all dependencies (You may be required to install additional system dependencies, due to some node modules relying on them) you can run various commands depending on what you want to do: +After `npm install`ing all dependencies you can run various commands depending on what you want to do: #### Development From 5ba62ccd45f170f6078928e01433990b83d1b171 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Mon, 11 Jun 2018 17:22:25 -0400 Subject: [PATCH 03/25] Hex prefix & checksum paper wallet address. (#1926) --- common/components/PaperWallet/index.tsx | 4 +++- common/components/ui/Identicon.tsx | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/components/PaperWallet/index.tsx b/common/components/PaperWallet/index.tsx index db23352f..1d4c7b85 100644 --- a/common/components/PaperWallet/index.tsx +++ b/common/components/PaperWallet/index.tsx @@ -1,5 +1,6 @@ import { Identicon, QRCode } from 'components/ui'; import React from 'react'; +import { addHexPrefix, toChecksumAddress } from 'ethereumjs-util'; import ethLogo from 'assets/images/logo-ethereum-1.png'; import notesBg from 'assets/images/notes-bg.png'; @@ -96,7 +97,8 @@ interface Props { export default class PaperWallet extends React.Component { public render() { - const { privateKey, address } = this.props; + const { privateKey } = this.props; + const address = toChecksumAddress(addHexPrefix(this.props.address)); return (
diff --git a/common/components/ui/Identicon.tsx b/common/components/ui/Identicon.tsx index c7733f7e..fc4de891 100644 --- a/common/components/ui/Identicon.tsx +++ b/common/components/ui/Identicon.tsx @@ -11,7 +11,6 @@ interface Props { export default function Identicon(props: Props) { const size = props.size || '4rem'; const { address, className = '' } = props; - // FIXME breaks on failed checksums const identiconDataUrl = isValidETHAddress(address) ? makeBlockie(address) : ''; return ( // Use inline styles for printable wallets From d75cd5ccdb7cc6ec8c23553b5e2fa7337576537c Mon Sep 17 00:00:00 2001 From: Danny Skubak Date: Mon, 11 Jun 2018 17:46:03 -0400 Subject: [PATCH 04/25] Bug Fix for #1863 (#1929) * use RedirectWithQuery wrapper instead of Redirect * remove unused var --- common/Root.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/Root.tsx b/common/Root.tsx index 59dba1a1..e1d6fac3 100644 --- a/common/Root.tsx +++ b/common/Root.tsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import { Provider, connect } from 'react-redux'; -import { withRouter, Switch, Redirect, HashRouter, Route, BrowserRouter } from 'react-router-dom'; +import { withRouter, Switch, HashRouter, Route, BrowserRouter } from 'react-router-dom'; // Components import Contracts from 'containers/Tabs/Contracts'; import ENS from 'containers/Tabs/ENS'; @@ -80,7 +80,6 @@ class RootClass extends Component { const routes = ( - @@ -90,6 +89,7 @@ class RootClass extends Component { + From c21d520422aa806d1671b23bf9d5cfd440d6177e Mon Sep 17 00:00:00 2001 From: Danny Skubak Date: Mon, 11 Jun 2018 17:53:49 -0400 Subject: [PATCH 05/25] Bug Fix for #1813 (#1927) * move transaction reset to componentWillMount * revert initialState change --- common/components/TXMetaDataPanel/TXMetaDataPanel.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx b/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx index da1bfff3..942119f8 100644 --- a/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx +++ b/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx @@ -68,9 +68,14 @@ class TXMetaDataPanel extends React.Component { sliderState: (this.props as DefaultProps).initialState }; - public componentDidMount() { + public componentWillMount() { if (!this.props.offline) { this.props.resetTransactionRequested(); + } + } + + public componentDidMount() { + if (!this.props.offline) { this.props.fetchCCRates([this.props.network.unit]); this.props.getNonceRequested(); } From 59d2b73fad4c7ba13e5a44a098fe097110e590a8 Mon Sep 17 00:00:00 2001 From: HenryNguyen5 Date: Mon, 11 Jun 2018 18:43:39 -0400 Subject: [PATCH 06/25] Revamp Offline Status Checker (#1917) * Revamp app status to be event listener based * Update lockfile * Update snapshot * Show invalid only if .has-blurred * revert yarn.lock changes * Clean up input classes and types (#1925) * Show invalid for Nonce Field when empty (#1930) --- common/Root.tsx | 4 - common/actions/config/actionCreators.ts | 7 - common/actions/config/actionTypes.ts | 6 - common/actions/config/constants.ts | 2 +- common/components/NonceField.tsx | 1 + common/components/ui/Input.scss | 4 +- common/components/ui/Input.tsx | 35 +++-- common/sagas/config/node.ts | 130 ++++++++++-------- .../sass/styles/overrides/react-select.scss | 9 +- .../config/__snapshots__/config.spec.ts.snap | 2 +- spec/reducers/config/config.spec.ts | 86 +----------- 11 files changed, 106 insertions(+), 180 deletions(-) diff --git a/common/Root.tsx b/common/Root.tsx index e1d6fac3..fed42b3f 100644 --- a/common/Root.tsx +++ b/common/Root.tsx @@ -19,7 +19,6 @@ import OnboardModal from 'containers/OnboardModal'; import WelcomeModal from 'components/WelcomeModal'; import NewAppReleaseModal from 'components/NewAppReleaseModal'; import { Store } from 'redux'; -import { pollOfflineStatus, TPollOfflineStatus } from 'actions/config'; import { AppState } from 'reducers'; import { RouteNotFound } from 'components/RouteNotFound'; import { RedirectWithQuery } from 'components/RedirectWithQuery'; @@ -36,7 +35,6 @@ interface StateProps { } interface DispatchProps { - pollOfflineStatus: TPollOfflineStatus; setUnitMeta: TSetUnitMeta; } @@ -52,7 +50,6 @@ class RootClass extends Component { }; public componentDidMount() { - this.props.pollOfflineStatus(); this.props.setUnitMeta(this.props.networkUnit); this.addBodyClasses(); } @@ -190,6 +187,5 @@ const mapStateToProps = (state: AppState) => { }; export default connect(mapStateToProps, { - pollOfflineStatus, setUnitMeta })(RootClass); diff --git a/common/actions/config/actionCreators.ts b/common/actions/config/actionCreators.ts index c1e6c26a..2b8bf434 100644 --- a/common/actions/config/actionCreators.ts +++ b/common/actions/config/actionCreators.ts @@ -28,13 +28,6 @@ export function changeLanguage(sign: string): interfaces.ChangeLanguageAction { }; } -export type TPollOfflineStatus = typeof pollOfflineStatus; -export function pollOfflineStatus(): interfaces.PollOfflineStatus { - return { - type: TypeKeys.CONFIG_POLL_OFFLINE_STATUS - }; -} - export type TChangeNodeRequested = typeof changeNodeRequested; export function changeNodeRequested(payload: string): interfaces.ChangeNodeRequestedAction { return { diff --git a/common/actions/config/actionTypes.ts b/common/actions/config/actionTypes.ts index bd2c11a1..426a6321 100644 --- a/common/actions/config/actionTypes.ts +++ b/common/actions/config/actionTypes.ts @@ -20,11 +20,6 @@ export interface ChangeLanguageAction { payload: string; } -/*** Poll offline status ***/ -export interface PollOfflineStatus { - type: TypeKeys.CONFIG_POLL_OFFLINE_STATUS; -} - /*** Change Node Requested ***/ export interface ChangeNodeRequestedAction { type: TypeKeys.CONFIG_CHANGE_NODE_REQUESTED; @@ -120,7 +115,6 @@ export type MetaAction = | SetOnlineAction | SetOfflineAction | ToggleAutoGasLimitAction - | PollOfflineStatus | SetLatestBlockAction; /*** Union Type ***/ diff --git a/common/actions/config/constants.ts b/common/actions/config/constants.ts index 7a720c6c..66a4fb95 100644 --- a/common/actions/config/constants.ts +++ b/common/actions/config/constants.ts @@ -6,7 +6,7 @@ export enum TypeKeys { CONFIG_TOGGLE_OFFLINE = 'CONFIG_TOGGLE_OFFLINE', CONFIG_TOGGLE_AUTO_GAS_LIMIT = 'CONFIG_TOGGLE_AUTO_GAS_LIMIT', - CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS', + CONFIG_SET_LATEST_BLOCK = 'CONFIG_SET_LATEST_BLOCK', CONFIG_NODE_WEB3_SET = 'CONFIG_NODE_WEB3_SET', diff --git a/common/components/NonceField.tsx b/common/components/NonceField.tsx index ccffae9b..416304a8 100644 --- a/common/components/NonceField.tsx +++ b/common/components/NonceField.tsx @@ -50,6 +50,7 @@ class NonceField extends React.Component { readOnly={readOnly} onChange={onChange} disabled={noncePending} + showInvalidWithoutValue={true} /> {noncePending ? (
diff --git a/common/components/ui/Input.scss b/common/components/ui/Input.scss index 27c63028..b7a84038 100644 --- a/common/components/ui/Input.scss +++ b/common/components/ui/Input.scss @@ -81,11 +81,11 @@ color: $input-color-placeholder; } &:not([disabled]):not([readonly]) { - &.invalid.has-blurred.has-value { + &.invalid { border-color: $brand-danger; box-shadow: inset 0px 0px 0px 1px $brand-danger; } - &.valid.has-value { + &.valid { border-color: #8dd17b; box-shadow: inset 0px 0px 0px 1px #8dd17b; } diff --git a/common/components/ui/Input.tsx b/common/components/ui/Input.tsx index d4293ba1..34e31c0d 100644 --- a/common/components/ui/Input.tsx +++ b/common/components/ui/Input.tsx @@ -3,7 +3,10 @@ import classnames from 'classnames'; import './Input.scss'; interface OwnProps extends HTMLProps { + isValid?: boolean; showInvalidBeforeBlur?: boolean; + showInvalidWithoutValue?: boolean; + showValidAsPlain?: boolean; setInnerRef?(ref: HTMLInputElement | null): void; } @@ -16,12 +19,9 @@ interface State { isStateless: boolean; } -interface OwnProps extends HTMLProps { - isValid: boolean; - showValidAsPlain?: boolean; -} +type Props = OwnProps & HTMLProps; -class Input extends React.Component { +class Input extends React.Component { public state: State = { hasBlurred: false, isStateless: true @@ -31,18 +31,29 @@ class Input extends React.Component { const { setInnerRef, showInvalidBeforeBlur, + showInvalidWithoutValue, showValidAsPlain, isValid, ...htmlProps } = this.props; + const { hasBlurred, isStateless } = this.state; const hasValue = !!this.props.value && this.props.value.toString().length > 0; - const classname = classnames( - this.props.className, - 'input-group-input', - this.state.isStateless ? '' : isValid ? (showValidAsPlain ? '' : '') : `invalid`, - (showInvalidBeforeBlur || this.state.hasBlurred) && 'has-blurred', - hasValue && 'has-value' - ); + + // Currently we don't ever highlight valid, so go empty string instead + let validClass = isValid ? '' : 'invalid'; + if (isStateless) { + validClass = ''; + } + if (!hasValue && !showInvalidWithoutValue) { + validClass = ''; + } else if (!hasBlurred && !showInvalidBeforeBlur) { + validClass = ''; + } + if (!hasValue && showInvalidWithoutValue) { + validClass = 'invalid'; + } + + const classname = classnames('input-group-input', this.props.className, validClass); return ( { + const getShepherdStatus = () => ({ + pending: getShepherdPending(), + isOnline: !getShepherdOffline() + }); - const restoreNotif = showNotification( - 'success', - 'Your connection to the network has been restored!', - 3000 - ); - const lostNetworkNotif = showNotification( - 'danger', - `You’ve lost your connection to the network, check your internet - connection or try changing networks from the dropdown at the - top right of the page.`, - Infinity - ); - const offlineNotif = showNotification( - 'info', - 'You are currently offline. Some features will be unavailable.', - 5000 + const { online, offline, lostNetworkNotif, offlineNotif, restoreNotif } = bindActionCreators( + { + offline: setOffline, + online: setOnline, + restoreNotif: () => + showNotification('success', 'Your connection to the network has been restored!', 3000), + lostNetworkNotif: () => + showNotification( + 'danger', + `You’ve lost your connection to the network, check your internet +connection or try changing networks from the dropdown at the +top right of the page.`, + Infinity + ), + + offlineNotif: () => + showNotification( + 'info', + 'You are currently offline. Some features will be unavailable.', + 5000 + ) + }, + store.dispatch ); - while (true) { - yield call(delay, 2500); + const getAppOnline = () => !getOffline(store.getState()); - const pending: ReturnType = yield call(getShepherdPending); - if (pending) { - continue; + /** + * @description Repeatedly polls itself to check for online state conflict occurs, implemented in recursive style for flexible polling times + * as network requests take a variable amount of time. + * + * Whenever an app online state conflict occurs, it resolves the conflict with the following priority: + * * If shepherd is online but app is offline -> do a ping request via shepherd provider, with the result of the ping being the set app state + * * If shepherd is offline but app is online -> set app to offline as it wont be able to make requests anyway + */ + async function detectOnlineStateConflict() { + const shepherdStatus = getShepherdStatus(); + const appOffline = getAppOnline(); + const onlineStateConflict = shepherdStatus.isOnline !== appOffline; + + if (shepherdStatus.pending || !onlineStateConflict) { + return setTimeout(detectOnlineStateConflict, 1000); } - const isOffline: boolean = yield select(getOffline); - const balancerOffline = yield call(getShepherdOffline); - - if (!balancerOffline && isOffline) { - // If we were able to ping but redux says we're offline, mark online - yield put(restoreNotif); - yield put(setOnline()); - } else if (balancerOffline && !isOffline) { - // If we were unable to ping but redux says we're online, mark offline - // If they had been online, show an error. - // If they hadn't been online, just inform them with a warning. - yield put(setOffline()); - if (hasCheckedOnline) { - yield put(lostNetworkNotif); - } else { - yield put(offlineNotif); + // if app reports online but shepherd offline, then set app offline + if (appOffline && !shepherdStatus.isOnline) { + lostNetworkNotif(); + offline(); + } else if (!appOffline && shepherdStatus.isOnline) { + // if app reports offline but shepherd reports online + // send a request to shepherd provider to see if we can still send out requests + const success = await shepherdProvider.ping().catch(() => false); + if (success) { + restoreNotif(); + online(); } } - hasCheckedOnline = true; + detectOnlineStateConflict(); } -} + detectOnlineStateConflict(); -// Fork our recurring API call, watch for the need to cancel. -export function* handlePollOfflineStatus(): SagaIterator { - const pollOfflineStatusTask = yield fork(pollOfflineStatus); - yield take('CONFIG_STOP_POLL_OFFLINE_STATE'); - yield cancel(pollOfflineStatusTask); -} + window.addEventListener('offline', () => { + const previouslyOnline = getAppOnline(); + + // if browser reports as offline and we were previously online + // then set offline without checking balancer state + if (!navigator.onLine && previouslyOnline) { + offlineNotif(); + offline(); + } + }); +}); // @HACK For now we reload the app when doing a language swap to force non-connected // data to reload. Also the use of timeout to avoid using additional actions for now. @@ -283,7 +296,6 @@ export const node = [ takeEvery(TypeKeys.CONFIG_CHANGE_NODE_REQUESTED, handleChangeNodeRequested), takeEvery(TypeKeys.CONFIG_CHANGE_NODE_FORCE, handleNodeChangeForce), takeEvery(TypeKeys.CONFIG_CHANGE_NETWORK_REQUESTED, handleChangeNetworkRequested), - takeLatest(TypeKeys.CONFIG_POLL_OFFLINE_STATUS, handlePollOfflineStatus), takeEvery(TypeKeys.CONFIG_LANGUAGE_CHANGE, reload), takeEvery(TypeKeys.CONFIG_ADD_CUSTOM_NODE, handleAddCustomNode), takeEvery(TypeKeys.CONFIG_REMOVE_CUSTOM_NODE, handleRemoveCustomNode) diff --git a/common/sass/styles/overrides/react-select.scss b/common/sass/styles/overrides/react-select.scss index 91278fbd..e83025b2 100644 --- a/common/sass/styles/overrides/react-select.scss +++ b/common/sass/styles/overrides/react-select.scss @@ -65,10 +65,11 @@ &-menu { max-height: 10.0625rem; } - &.invalid.has-blurred { - border-color: $brand-danger; - box-shadow: inset 0px 0px 0px 1px $brand-danger; - } + // Selects should never have invalid input + // &.invalid { + // border-color: $brand-danger; + // box-shadow: inset 0px 0px 0px 1px $brand-danger; + // } &.is-focused { border-color: #4295bc; box-shadow: inset 0px 0px 0px 1px #4295bc; diff --git a/spec/reducers/config/__snapshots__/config.spec.ts.snap b/spec/reducers/config/__snapshots__/config.spec.ts.snap index 5ddb4aa6..d649e139 100644 --- a/spec/reducers/config/__snapshots__/config.spec.ts.snap +++ b/spec/reducers/config/__snapshots__/config.spec.ts.snap @@ -5,7 +5,7 @@ Object { "@@redux-saga/IO": true, "SELECT": Object { "args": Array [ - "ELLA", + "CLO", ], "selector": [Function], }, diff --git a/spec/reducers/config/config.spec.ts b/spec/reducers/config/config.spec.ts index eba96681..03691223 100644 --- a/spec/reducers/config/config.spec.ts +++ b/spec/reducers/config/config.spec.ts @@ -1,10 +1,8 @@ import { configuredStore } from 'store'; import { delay, SagaIterator } from 'redux-saga'; -import { call, cancel, fork, put, take, select, apply } from 'redux-saga/effects'; -import { cloneableGenerator, createMockTask } from 'redux-saga/utils'; +import { call, fork, put, take, select, apply } from 'redux-saga/effects'; +import { cloneableGenerator } from 'redux-saga/utils'; import { - setOffline, - setOnline, changeNodeSucceeded, changeNodeRequested, changeNodeFailed, @@ -16,8 +14,6 @@ import { } from 'actions/config'; import { handleChangeNodeRequested, - handlePollOfflineStatus, - pollOfflineStatus, handleNewNetwork, handleChangeNodeRequestedOneTime } from 'sagas/config/node'; @@ -39,88 +35,10 @@ import { selectedNodeExpectedState } from './nodes/selectedNode.spec'; import { customNodesExpectedState, firstCustomNode } from './nodes/customNodes.spec'; import { unsetWeb3Node, unsetWeb3NodeOnWalletEvent } from 'sagas/config/web3'; import { shepherd } from 'mycrypto-shepherd'; -import { getShepherdOffline, getShepherdPending } from 'libs/nodes'; // init module configuredStore.getState(); -describe('pollOfflineStatus*', () => { - const restoreNotif = 'Your connection to the network has been restored!'; - - const lostNetworkNotif = `You’ve lost your connection to the network, check your internet - connection or try changing networks from the dropdown at the - top right of the page.`; - - const offlineNotif = 'You are currently offline. Some features will be unavailable.'; - - const offlineOnFirstTimeCase = pollOfflineStatus(); - it('should delay by 2.5 seconds', () => { - expect(offlineOnFirstTimeCase.next().value).toEqual(call(delay, 2500)); - }); - - it('should skip if a node change is pending', () => { - expect(offlineOnFirstTimeCase.next().value).toEqual(call(getShepherdPending)); - expect(offlineOnFirstTimeCase.next(true).value).toEqual(call(delay, 2500)); - expect(offlineOnFirstTimeCase.next().value).toEqual(call(getShepherdPending)); - }); - - it('should select offline', () => { - expect(offlineOnFirstTimeCase.next(false).value).toEqual(select(getOffline)); - }); - - it('should select shepherd"s offline', () => { - expect(offlineOnFirstTimeCase.next(false).value).toEqual(call(getShepherdOffline)); - }); - - // .PUT.action.payload.msg is used because the action creator uses an random ID, cant to a showNotif comparision - it('should put a different notif if online for the first time ', () => { - expect(offlineOnFirstTimeCase.next(true).value).toEqual(put(setOffline())); - expect((offlineOnFirstTimeCase.next().value as any).PUT.action.payload.msg).toEqual( - offlineNotif - ); - }); - - it('should loop around then go back online, putting a restore msg', () => { - expect(offlineOnFirstTimeCase.next().value).toEqual(call(delay, 2500)); - expect(offlineOnFirstTimeCase.next().value).toEqual(call(getShepherdPending)); - expect(offlineOnFirstTimeCase.next(false).value).toEqual(select(getOffline)); - expect(offlineOnFirstTimeCase.next(true).value).toEqual(call(getShepherdOffline)); - expect((offlineOnFirstTimeCase.next().value as any).PUT.action.payload.msg).toEqual( - restoreNotif - ); - expect(offlineOnFirstTimeCase.next(false).value).toEqual(put(setOnline())); - }); - - it('should put a generic lost connection notif on every time afterwards', () => { - expect(offlineOnFirstTimeCase.next().value).toEqual(call(delay, 2500)); - expect(offlineOnFirstTimeCase.next().value).toEqual(call(getShepherdPending)); - expect(offlineOnFirstTimeCase.next(false).value).toEqual(select(getOffline)); - expect(offlineOnFirstTimeCase.next(false).value).toEqual(call(getShepherdOffline)); - expect(offlineOnFirstTimeCase.next(true).value).toEqual(put(setOffline())); - expect((offlineOnFirstTimeCase.next().value as any).PUT.action.payload.msg).toEqual( - lostNetworkNotif - ); - }); -}); - -describe('handlePollOfflineStatus*', () => { - const gen = handlePollOfflineStatus(); - const mockTask = createMockTask(); - - it('should fork pollOffineStatus', () => { - const expectedForkYield = fork(pollOfflineStatus); - expect(gen.next().value).toEqual(expectedForkYield); - }); - - it('should take CONFIG_STOP_POLL_OFFLINE_STATE', () => { - expect(gen.next(mockTask).value).toEqual(take('CONFIG_STOP_POLL_OFFLINE_STATE')); - }); - - it('should cancel pollOfflineStatus', () => { - expect(gen.next().value).toEqual(cancel(mockTask)); - }); -}); - describe('handleChangeNodeRequested*', () => { let originalRandom: any; From 237c826141e45c9ecc9dabcd83864c13e5e972ac Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Mon, 11 Jun 2018 17:54:36 -0500 Subject: [PATCH 07/25] Show (AUTO) in Node Selection (#1931) --- common/selectors/config/nodes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/selectors/config/nodes.ts b/common/selectors/config/nodes.ts index ef34d42e..8189b153 100644 --- a/common/selectors/config/nodes.ts +++ b/common/selectors/config/nodes.ts @@ -118,7 +118,7 @@ export function getSelectedNodeLabel(state: AppState): INodeLabel { ); if (networkNodes.length > 1) { - info = `${networkNodes.length} Nodes`; + info = 'AUTO'; } else { info = networkNodes[0].service; } From 256a8fdf5b21cd0cbcb146b8a3f12f5473bc83ee Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Tue, 12 Jun 2018 10:51:58 -0500 Subject: [PATCH 08/25] Update ReadMe instructions with yarn requirement (#1933) * Update ReadMe instructions to stress yarn requirements * Change shell commands to reflect yarn usage --- README.md | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index cbc79bfb..1d94e430 100644 --- a/README.md +++ b/README.md @@ -7,48 +7,50 @@ ## Requirements -* Node 8.9.4* -* NPM >= 5 -* Python 2.7.X** +* Node 8.9.4\* +* Yarn >= 1.7.0\*\* +* Python 2.7.X\*\*\* -*Higher versions should work fun, but may cause inconsistencies. It's suggested you run 8.9.4 using `nvm`. +\*Higher versions should work fun, but may cause inconsistencies. It's suggested you run 8.9.4 using `nvm`.
-**Python 3 is **not** supported, since our dependencies use `node-gyp`. +**npm is NOT supported for package management. MyCrypto uses yarn.lock to ensure sub-dependency versions are pinned, so yarn is required to install node_modules +
+\***Python 3 is **not** supported, since our dependencies use `node-gyp`. ## Running the App -After `npm install`ing all dependencies you can run various commands depending on what you want to do: +After `yarn`ing all dependencies you can run various commands depending on what you want to do: #### Development ```bash # run app in dev mode in browser, rebuild on file changes -npm run dev +yarn dev ``` ```bash # run app in dev mode in electron, rebuild on file changes -npm run dev:electron +yarn dev:electron ``` #### Build Releases ```bash # builds the production server app -npm run build +yarn build ``` ```bash # builds the downloadable version of the site -npm run build:downloadable +yarn build:downloadable ``` ```bash # builds the electron apps -npm run build:electron +yarn build:electron # builds only one OS's electron app -npm run build:electron:(osx|linux|windows) +yarn build:electron:(osx|linux|windows) ``` All of these builds are output to a folder in `dist/`. @@ -57,26 +59,26 @@ All of these builds are output to a folder in `dist/`. ```bash # run unit tests with Jest -npm run test +yarn test ``` #### Integration Tests: ```bash # run integration tests with Jest -npm run test:int +yarn test:int ``` #### Dev (HTTPS): Some parts of the site, such as the Ledger wallet, require an HTTPS environment to work. To develop on HTTPS, do the following: -1. Create your own SSL Certificate (Heroku has a [nice guide here](https://devcenter.heroku.com/articles/ssl-certificate-self)) -2. Move the `.key` and `.crt` files into `webpack_config/server.*` -3. Run the following command: +1. Create your own SSL Certificate (Heroku has a [nice guide here](https://devcenter.heroku.com/articles/ssl-certificate-self)) +2. Move the `.key` and `.crt` files into `webpack_config/server.*` +3. Run the following command: ```bash -npm run dev:https +yarn dev:https ``` #### Address Derivation Checker: @@ -89,20 +91,20 @@ To test for correct address derivation, the address derivation checker uses mult ##### The derivation checker utility assumes that you have: -1. Docker installed/available -2. [dternyak/eth-priv-to-addr](https://hub.docker.com/r/dternyak/eth-priv-to-addr/) pulled from DockerHub +1. Docker installed/available +2. [dternyak/eth-priv-to-addr](https://hub.docker.com/r/dternyak/eth-priv-to-addr/) pulled from DockerHub ##### Docker setup instructions: -1. Install docker (on macOS, [Docker for Mac](https://docs.docker.com/docker-for-mac/) is suggested) -2. `docker pull dternyak/eth-priv-to-addr` +1. Install docker (on macOS, [Docker for Mac](https://docs.docker.com/docker-for-mac/) is suggested) +2. `docker pull dternyak/eth-priv-to-addr` ##### Run Derivation Checker The derivation checker utility runs as part of the integration test suite. ```bash -npm run test:int +yarn test:int ``` ## Folder structure: @@ -144,4 +146,3 @@ npm run test:int Cross browser testing and debugging provided by the very lovely team at BrowserStack. - From 74cb8de16e9898935ce346c8b9b8b5491a8f6c10 Mon Sep 17 00:00:00 2001 From: HenryNguyen5 Date: Tue, 12 Jun 2018 13:01:14 -0400 Subject: [PATCH 09/25] Custom node defaults (#1829) * Add eth-exists dep * Add elementary support for custom nodes * Update eth-exists references --- .../CustomNodeModal/CustomNodeModal.tsx | 71 ++- package.json | 1 + yarn.lock | 475 +++++++++--------- 3 files changed, 317 insertions(+), 230 deletions(-) diff --git a/common/components/CustomNodeModal/CustomNodeModal.tsx b/common/components/CustomNodeModal/CustomNodeModal.tsx index 883f8679..2c15d5d0 100644 --- a/common/components/CustomNodeModal/CustomNodeModal.tsx +++ b/common/components/CustomNodeModal/CustomNodeModal.tsx @@ -13,6 +13,7 @@ import { } from 'selectors/config'; import { Input, Dropdown } from 'components/ui'; import './CustomNodeModal.scss'; +import { exists, SuccessConfig, FailConfig } from 'mycrypto-eth-exists'; const CUSTOM = { label: 'Custom', value: 'custom' }; @@ -42,12 +43,13 @@ interface State { hasAuth: boolean; username: string; password: string; + defaultNodes: ((SuccessConfig | FailConfig) & { display: string; index: number })[]; } type Props = OwnProps & StateProps & DispatchProps; class CustomNodeModal extends React.Component { - public INITIAL_STATE = { + public INITIAL_STATE: State = { name: '', url: '', network: Object.keys(this.props.staticNetworks)[0], @@ -56,10 +58,19 @@ class CustomNodeModal extends React.Component { customNetworkChainId: '', hasAuth: false, username: '', - password: '' + password: '', + defaultNodes: [] }; + public state: State = this.INITIAL_STATE; + private timer: number | null; + + constructor(props: Props) { + super(props); + this.pollForDefaultNodes(); + } + public componentDidUpdate(prevProps: Props) { // Reset state when modal opens if (!prevProps.isOpen && prevProps.isOpen !== this.props.isOpen) { @@ -67,6 +78,13 @@ class CustomNodeModal extends React.Component { } } + public componentWillUnmount() { + if (this.timer) { + window.clearInterval(this.timer); + } + this.timer = null; + } + public render() { const { customNetworks, handleClose, staticNetworks, isOpen } = this.props; const { network, customNetworkChainId } = this.state; @@ -114,6 +132,8 @@ class CustomNodeModal extends React.Component {
)} + {this.renderDefaultNodeDropdown()} +