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.
This commit is contained in:
William O'Beirne 2018-11-06 13:42:45 -05:00 committed by GitHub
parent 79773ea4da
commit 0670eaa054
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 134 additions and 41 deletions

View File

@ -48,9 +48,12 @@ class SignIn extends React.Component<Props> {
</Button>
</div>
<p className="SignIn-back">
Want to use a different identity? <a onClick={this.props.reset}>Click here</a>.
</p>
{/*
Temporarily only supporting web3, so there are no other identites
<p className="SignIn-back">
Want to use a different identity? <a onClick={this.props.reset}>Click here</a>.
</p>
*/}
</div>
);
}

View File

@ -101,9 +101,12 @@ class SignUp extends React.Component<Props, State> {
</Form>
</div>
<p className="SignUp-back">
Want to use a different identity? <a onClick={this.props.reset}>Click here</a>.
</p>
{/*
Temporarily only supporting web3, so there are no other identites
<p className="SignUp-back">
Want to use a different identity? <a onClick={this.props.reset}>Click here</a>.
</p>
*/}
</div>
);
}

View File

@ -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,
};

View File

@ -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<Props> {
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<Props> {
}
render() {
const { isWeb3Locked } = this.props;
const {
isEnablingWeb3,
accountsLoading,
web3EnableError,
accountsError,
} = this.props;
const isLoading = isEnablingWeb3 || accountsLoading;
const error = web3EnableError || accountsError;
return (
<div className="Web3Provider">
<img className="Web3Provider-logo" src={MetamaskIcon} />
<p className="Web3Provider-description">
Make sure you have MetaMask or another web3 provider installed and unlocked,
then click below.
</p>
{isWeb3Locked && (
<Alert
showIcon
type="error"
message="It looks like MetaMask is locked"
style={{ margin: '1rem auto' }}
/>
{isLoading ? (
<Spin tip="Connecting..." />
) : (
<>
<img className="Web3Provider-logo" src={MetamaskIcon} />
<p className="Web3Provider-description">
Make sure you have MetaMask or another web3 provider installed and unlocked,
then click below.
</p>
{error && (
<Alert
showIcon
type="error"
message={error}
style={{ margin: '1rem auto' }}
/>
)}
<Button type="primary" size="large" onClick={this.props.enableWeb3}>
Connect to Web3
</Button>
</>
)}
<Button type="primary" size="large" onClick={this.connect}>
Connect to Web3
</Button>
</div>
);
}
private connect = () => {
this.props.setWeb3();
this.props.setAccounts();
};
}
export default connect<StateProps, DispatchProps, OwnProps, AppState>(
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);

View File

@ -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%);

View File

@ -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<any>, getState: GetState) => {

View File

@ -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,

View File

@ -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<any>(setAccounts()), put<any>(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<any>(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);
}

View File

@ -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);

View File

@ -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',

View File

@ -32,3 +32,12 @@ export function web3ErrorToString(err: Web3ErrorResponse): string {
.slice(-1)[0]
.trim();
}
export function safeEnable(): Promise<void> {
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();
}