From 0670eaa05419f18a07133484a1420d6d2662191f Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 6 Nov 2018 13:42:45 -0500 Subject: [PATCH] Web3 Only Auth + MetaMask future proofing (#187) * Setup ethereum.enable() flow, change login to only use web3. * Fix sign in icon to be white again. --- .../client/components/AuthFlow/SignIn.tsx | 9 ++- .../client/components/AuthFlow/SignUp.tsx | 9 ++- frontend/client/components/AuthFlow/index.tsx | 3 +- .../components/AuthFlow/providers/Web3.tsx | 76 ++++++++++++------- frontend/client/components/Header/style.less | 5 +- frontend/client/modules/web3/actions.ts | 4 + frontend/client/modules/web3/reducers.ts | 23 ++++++ frontend/client/modules/web3/sagas.ts | 28 ++++++- frontend/client/modules/web3/selectors.ts | 4 + frontend/client/modules/web3/types.ts | 5 ++ frontend/client/utils/web3.ts | 9 +++ 11 files changed, 134 insertions(+), 41 deletions(-) diff --git a/frontend/client/components/AuthFlow/SignIn.tsx b/frontend/client/components/AuthFlow/SignIn.tsx index a37cda7c..dee165a9 100644 --- a/frontend/client/components/AuthFlow/SignIn.tsx +++ b/frontend/client/components/AuthFlow/SignIn.tsx @@ -48,9 +48,12 @@ class SignIn extends React.Component { -

- Want to use a different identity? Click here. -

+ {/* + Temporarily only supporting web3, so there are no other identites +

+ Want to use a different identity? Click here. +

+ */} ); } diff --git a/frontend/client/components/AuthFlow/SignUp.tsx b/frontend/client/components/AuthFlow/SignUp.tsx index 9b290aed..09dba976 100644 --- a/frontend/client/components/AuthFlow/SignUp.tsx +++ b/frontend/client/components/AuthFlow/SignUp.tsx @@ -101,9 +101,12 @@ class SignUp extends React.Component { -

- Want to use a different identity? Click here. -

+ {/* + Temporarily only supporting web3, so there are no other identites +

+ Want to use a different identity? Click here. +

+ */} ); } diff --git a/frontend/client/components/AuthFlow/index.tsx b/frontend/client/components/AuthFlow/index.tsx index 9fabd9a5..1bee853d 100644 --- a/frontend/client/components/AuthFlow/index.tsx +++ b/frontend/client/components/AuthFlow/index.tsx @@ -28,7 +28,8 @@ interface State { } const DEFAULT_STATE: State = { - provider: null, + // Temporarily hardcode to web3, change to null when others are supported + provider: AUTH_PROVIDER.WEB3, address: null, }; diff --git a/frontend/client/components/AuthFlow/providers/Web3.tsx b/frontend/client/components/AuthFlow/providers/Web3.tsx index a29d0c01..f491854e 100644 --- a/frontend/client/components/AuthFlow/providers/Web3.tsx +++ b/frontend/client/components/AuthFlow/providers/Web3.tsx @@ -1,19 +1,21 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Button, Alert } from 'antd'; -import { web3Actions } from 'modules/web3'; +import { Button, Alert, Spin } from 'antd'; +import { enableWeb3 } from 'modules/web3/actions'; import { AppState } from 'store/reducers'; import MetamaskIcon from 'static/images/metamask.png'; import './Web3.less'; interface StateProps { accounts: AppState['web3']['accounts']; - isWeb3Locked: AppState['web3']['isWeb3Locked']; + isEnablingWeb3: AppState['web3']['isEnablingWeb3']; + accountsLoading: AppState['web3']['accountsLoading']; + web3EnableError: AppState['web3']['web3EnableError']; + accountsError: AppState['web3']['accountsError']; } interface DispatchProps { - setWeb3: typeof web3Actions['setWeb3']; - setAccounts: typeof web3Actions['setAccounts']; + enableWeb3: typeof enableWeb3; } interface OwnProps { @@ -23,6 +25,12 @@ interface OwnProps { type Props = StateProps & DispatchProps & OwnProps; class Web3Provider extends React.Component { + componentWillMount() { + if (!this.props.accounts || !this.props.accounts[0]) { + this.props.enableWeb3(); + } + } + componentDidUpdate() { const { accounts } = this.props; if (accounts && accounts[0]) { @@ -31,42 +39,52 @@ class Web3Provider extends React.Component { } render() { - const { isWeb3Locked } = this.props; + const { + isEnablingWeb3, + accountsLoading, + web3EnableError, + accountsError, + } = this.props; + const isLoading = isEnablingWeb3 || accountsLoading; + const error = web3EnableError || accountsError; return (
- -

- Make sure you have MetaMask or another web3 provider installed and unlocked, - then click below. -

- {isWeb3Locked && ( - + {isLoading ? ( + + ) : ( + <> + +

+ Make sure you have MetaMask or another web3 provider installed and unlocked, + then click below. +

+ {error && ( + + )} + + )} -
); } - - private connect = () => { - this.props.setWeb3(); - this.props.setAccounts(); - }; } export default connect( state => ({ accounts: state.web3.accounts, - isWeb3Locked: state.web3.isWeb3Locked, + isEnablingWeb3: state.web3.isEnablingWeb3, + accountsLoading: state.web3.accountsLoading, + web3EnableError: state.web3.web3EnableError, + accountsError: state.web3.accountsError, }), { - setWeb3: web3Actions.setWeb3, - setAccounts: web3Actions.setAccounts, + enableWeb3, }, )(Web3Provider); diff --git a/frontend/client/components/Header/style.less b/frontend/client/components/Header/style.less index 41f6bbc5..6b705a9b 100644 --- a/frontend/client/components/Header/style.less +++ b/frontend/client/components/Header/style.less @@ -14,10 +14,6 @@ text-shadow: none; box-shadow: 0 1px rgba(0, 0, 0, 0.1); - svg { - fill: #333; - } - &.is-transparent { position: absolute; background: transparent; @@ -58,6 +54,7 @@ height: 2rem; width: auto; transform: translateY(10%); + fill: #333; .is-transparent & { transform: scale(1.4) translateY(20%); diff --git a/frontend/client/modules/web3/actions.ts b/frontend/client/modules/web3/actions.ts index 3d86426f..ee2726c1 100644 --- a/frontend/client/modules/web3/actions.ts +++ b/frontend/client/modules/web3/actions.ts @@ -33,6 +33,10 @@ export function setWeb3() { }; } +export function enableWeb3() { + return { type: types.ENABLE_WEB3_PENDING }; +} + export type TSetContract = typeof setContract; export function setContract(json: any, deployedAddress?: string) { return (dispatch: Dispatch, getState: GetState) => { diff --git a/frontend/client/modules/web3/reducers.ts b/frontend/client/modules/web3/reducers.ts index 78e7443d..e2fc3041 100644 --- a/frontend/client/modules/web3/reducers.ts +++ b/frontend/client/modules/web3/reducers.ts @@ -11,6 +11,9 @@ export interface Web3State { isWrongNetwork: boolean; isWeb3Locked: boolean; + isEnablingWeb3: boolean; + web3EnableError: null | string; + contracts: Contract[]; contractsLoading: boolean; contractsError: null | string; @@ -39,6 +42,9 @@ export const INITIAL_STATE: Web3State = { isWrongNetwork: false, isWeb3Locked: false, + isEnablingWeb3: false, + web3EnableError: null, + contracts: [], contractsLoading: false, contractsError: null, @@ -96,6 +102,23 @@ export default (state = INITIAL_STATE, action: any): Web3State => { isMissingWeb3: true, }; + case types.ENABLE_WEB3_PENDING: + return { + ...state, + isEnablingWeb3: true, + }; + case types.ENABLE_WEB3_FULFILLED: + return { + ...state, + isEnablingWeb3: false, + }; + case types.ENABLE_WEB3_REJECTED: + return { + ...state, + isEnablingWeb3: false, + web3EnableError: action.payload, + }; + case types.CROWD_FUND_PENDING: return { ...state, diff --git a/frontend/client/modules/web3/sagas.ts b/frontend/client/modules/web3/sagas.ts index a6be3f54..6c683b10 100644 --- a/frontend/client/modules/web3/sagas.ts +++ b/frontend/client/modules/web3/sagas.ts @@ -1,6 +1,8 @@ import { SagaIterator } from 'redux-saga'; -import { put, all, fork, take } from 'redux-saga/effects'; +import { put, all, fork, take, takeLatest, select, call } from 'redux-saga/effects'; import { setWeb3, setAccounts, setContract } from './actions'; +import { selectWeb3 } from './selectors'; +import { safeEnable } from 'utils/web3'; import types from './types'; /* tslint:disable no-var-requires --- TODO: find a better way to import contract */ @@ -18,6 +20,30 @@ export function* bootstrapWeb3(): SagaIterator { yield all([put(setAccounts()), put(setContract(CrowdFundFactory))]); } +export function* handleEnableWeb3(): SagaIterator { + const web3 = yield select(selectWeb3); + + try { + if (!web3) { + const web3Action = yield take([types.WEB3_FULFILLED, types.WEB3_REJECTED]); + if (web3Action.type === types.WEB3_REJECTED) { + throw new Error('No web3 instance available'); + } + } + + yield call(safeEnable); + yield put(setAccounts()); + yield put({ type: types.ENABLE_WEB3_FULFILLED }); + } catch (err) { + yield put({ + type: types.ENABLE_WEB3_REJECTED, + payload: err.message || err.toString(), + error: true, + }); + } +} + export default function* authSaga(): SagaIterator { yield all([fork(bootstrapWeb3)]); + yield takeLatest(types.ENABLE_WEB3_PENDING, handleEnableWeb3); } diff --git a/frontend/client/modules/web3/selectors.ts b/frontend/client/modules/web3/selectors.ts index a67278d5..bf3c21e2 100644 --- a/frontend/client/modules/web3/selectors.ts +++ b/frontend/client/modules/web3/selectors.ts @@ -1,5 +1,9 @@ import { AppState } from 'store/reducers'; +export function selectWeb3(state: AppState) { + return state.web3.web3; +} + export function findContract(state: AppState, contractAddress: string) { const { contracts } = state.web3; return contracts.find(contract => contract._address === contractAddress); diff --git a/frontend/client/modules/web3/types.ts b/frontend/client/modules/web3/types.ts index eddc1e4d..552ebbfc 100644 --- a/frontend/client/modules/web3/types.ts +++ b/frontend/client/modules/web3/types.ts @@ -4,6 +4,11 @@ enum web3Types { WEB3_REJECTED = 'WEB3_REJECTED', WEB3_PENDING = 'WEB3_PENDING', + ENABLE_WEB3 = 'ENABLE_WEB3', + ENABLE_WEB3_PENDING = 'ENABLE_WEB3_PENDING', + ENABLE_WEB3_REJECTED = 'ENABLE_WEB3_REJECTED', + ENABLE_WEB3_FULFILLED = 'ENABLE_WEB3_FULFILLED', + CONTRACT = 'CONTRACT', CONTRACT_FULFILLED = 'CONTRACT_FULFILLED', CONTRACT_REJECTED = 'CONTRACT_REJECTED', diff --git a/frontend/client/utils/web3.ts b/frontend/client/utils/web3.ts index 35cd4e42..40595f5c 100644 --- a/frontend/client/utils/web3.ts +++ b/frontend/client/utils/web3.ts @@ -32,3 +32,12 @@ export function web3ErrorToString(err: Web3ErrorResponse): string { .slice(-1)[0] .trim(); } + +export function safeEnable(): Promise { + const w = typeof window === 'undefined' ? {} : (window as any); + if (!w.ethereum || !w.ethereum.enable) { + return Promise.reject('No web3 client installed'); + } + + return w.ethereum.enable(); +}