Browse Source

Add support for reload on network/account change, refactoring

develop
Ezerous 4 years ago
parent
commit
e47661b94e
  1. 2
      package.json
  2. 9
      src/Drizzle.js
  3. 4
      src/DrizzleContract.js
  4. 5
      src/accounts/accountsActions.js
  5. 9
      src/accounts/accountsMiddleware.js
  6. 4
      src/accounts/accountsReducer.js
  7. 6
      src/accounts/accountsSaga.js
  8. 8
      src/contracts/constants.js
  9. 2
      src/contracts/contractsReducer.js
  10. 8
      src/contracts/contractsSaga.js
  11. 2
      src/defaultOptions.js
  12. 0
      src/drizzle/drizzleActions.js
  13. 28
      src/drizzle/drizzleMiddleware.js
  14. 0
      src/drizzle/drizzleStatusReducer.js
  15. 8
      src/drizzle/drizzleStatusSaga.js
  16. 10
      src/generateStore.js
  17. 18
      src/index.js
  18. 20
      src/reducer.js
  19. 11
      src/root/rootMiddleware.js
  20. 20
      src/root/rootReducer.js
  21. 13
      src/root/rootSaga.js
  22. 11
      src/rootSaga.js
  23. 21
      src/web3/web3Actions.js
  24. 18
      src/web3/web3Middleware.js
  25. 11
      src/web3/web3Reducer.js
  26. 25
      src/web3/web3Saga.js

2
package.json

@ -1,6 +1,6 @@
{
"name": "@ezerous/drizzle",
"version": "0.2.1",
"version": "0.3.0",
"description": "A reactive data-store for web3 and smart contracts.",
"license": "MIT",
"author": "Ezerous <ezerous@gmail.com>",

9
src/Drizzle.js

@ -3,7 +3,7 @@ import defaultOptions from './defaultOptions'
import merge from './mergeOptions'
import DrizzleContract from './DrizzleContract'
import * as ContractActions from './contracts/constants'
import * as DrizzleActions from './drizzleStatus/drizzleActions'
import * as DrizzleActions from './drizzle/drizzleActions'
// Load as promise so that async Drizzle initialization can still resolve
const isEnvReadyPromise = new Promise((resolve) => {
@ -81,11 +81,8 @@ class Drizzle {
events
)
if (this.contracts[drizzleContract.contractName]) {
throw new Error(
`Contract already exists: ${drizzleContract.contractName}`
)
}
if (this.contracts[drizzleContract.contractName])
console.warn(`Adding an already existing contract: ${drizzleContract.contractName}.\nIgnore this if upon drizzle reinitialization.`);
this.store.dispatch({ type: ContractActions.CONTRACT_INITIALIZING, contractConfig })

4
src/DrizzleContract.js

@ -38,14 +38,14 @@ class DrizzleContract {
if (typeof event === 'object') {
store.dispatch({
type: ContractActions.LISTEN_FOR_EVENT,
type: ContractActions.CONTRACT_EVENT_LISTEN,
contract: this,
eventName: event.eventName,
eventOptions: event.eventOptions
})
} else {
store.dispatch({
type: ContractActions.LISTEN_FOR_EVENT,
type: ContractActions.CONTRACT_EVENT_LISTEN,
contract: this,
eventName: event
})

5
src/accounts/accountsActions.js

@ -1,11 +1,12 @@
export const ACCOUNTS_FETCHING = 'ACCOUNTS_FETCHING'
export const ACCOUNTS_FETCHED = 'ACCOUNTS_FETCHED'
export const ACCOUNTS_CHANGED = 'ACCOUNTS_CHANGED'
export const ACCOUNTS_FAILED = 'ACCOUNTS_FAILED'
export const ACCOUNTS_LISTENING = 'ACCOUNTS_LISTENING'
export function accountsFetched (accounts) {
export function accountsChanged (accounts) {
return {
type: ACCOUNTS_FETCHED,
type: ACCOUNTS_CHANGED,
accounts
}
}

9
src/accounts/accountsMiddleware.js

@ -1,5 +1,5 @@
import { WEB3_INITIALIZED } from '../web3/web3Actions'
import { accountsFetched, accountsListening } from './accountsActions'
import { networkIdChanged, WEB3_INITIALIZED } from '../web3/web3Actions'
import { accountsChanged, accountsFetched, accountsListening } from './accountsActions'
export const accountsMiddleware = web3 => store => next => action => {
const { type } = action
@ -13,7 +13,10 @@ export const accountsMiddleware = web3 => store => next => action => {
// For some reason accounts here are returned with lowercase letters, so we need to patch them
let patchedAccounts = Array.from(accounts);
patchedAccounts.forEach((account, i) => patchedAccounts[i] = web3.utils.toChecksumAddress(account));
store.dispatch(accountsFetched(patchedAccounts));
const storedAccounts = store.getState().accounts;
if(storedAccounts[0] && (patchedAccounts[0] !== storedAccounts[0]))
store.dispatch(accountsChanged(patchedAccounts));
});
store.dispatch(accountsListening());
}

4
src/accounts/accountsReducer.js

@ -1,4 +1,4 @@
import { ACCOUNTS_FETCHED, ACCOUNTS_FETCHING } from './accountsActions'
import { ACCOUNTS_CHANGED, ACCOUNTS_FETCHED, ACCOUNTS_FETCHING } from './accountsActions'
const initialState = {}
@ -7,7 +7,7 @@ const accountsReducer = (state = initialState, action) => {
return state
}
if (action.type === ACCOUNTS_FETCHED) {
if (action.type === ACCOUNTS_FETCHED || action.type === ACCOUNTS_CHANGED) {
return Object.assign({}, state, action.accounts)
}

6
src/accounts/accountsSaga.js

@ -9,10 +9,10 @@ export function * getAccounts (action) {
try {
yield put({ type: ACCOUNTS_FETCHING });
const accounts = yield call(web3.eth.getAccounts)
const accounts = yield call(web3.eth.getAccounts);
if (!accounts)
throw 'No accounts found!'
if (!accounts.length)
throw new Error('No accounts found!');
yield put({ type: ACCOUNTS_FETCHED, accounts })
} catch (error) {

8
src/contracts/constants.js

@ -1,7 +1,7 @@
export const EVENT_FIRED = 'EVENT_FIRED'
export const EVENT_CHANGED = 'EVENT_CHANGED'
export const EVENT_ERROR = 'EVENT_ERROR'
export const LISTEN_FOR_EVENT = 'LISTEN_FOR_EVENT'
export const CONTRACT_EVENT_FIRED = 'CONTRACT_EVENT_FIRED'
export const CONTRACT_EVENT_CHANGED = 'CONTRACT_EVENT_CHANGED'
export const CONTRACT_EVENT_ERROR = 'CONTRACT_EVENT_ERROR'
export const CONTRACT_EVENT_LISTEN = 'CONTRACT_EVENT_LISTEN'
export const CONTRACT_INITIALIZING = 'CONTRACT_INITIALIZING'
export const CONTRACT_INITIALIZED = 'CONTRACT_INITIALIZED'
export const CONTRACT_NOT_DEPLOYED = 'CONTRACT_NOT_DEPLOYED'

2
src/contracts/contractsReducer.js

@ -123,7 +123,7 @@ const contractsReducer = (state = initialState, action) => {
* Contract Events
*/
if (action.type === ContractActions.EVENT_FIRED) {
if (action.type === ContractActions.CONTRACT_EVENT_FIRED) {
return {
...state,
[action.name]: {

8
src/contracts/contractsSaga.js

@ -16,13 +16,13 @@ export function createContractEventChannel ({
return eventChannel(emit => {
const eventListener = contract.events[eventName](eventOptions)
.on('data', event => {
emit({ type: ContractActions.EVENT_FIRED, name, event })
emit({ type: ContractActions.CONTRACT_EVENT_FIRED, name, event })
})
.on('changed', event => {
emit({ type: ContractActions.EVENT_CHANGED, name, event })
emit({ type: ContractActions.CONTRACT_EVENT_CHANGED, name, event })
})
.on('error', error => {
emit({ type: ContractActions.EVENT_ERROR, name, error })
emit({ type: ContractActions.CONTRACT_EVENT_ERROR, name, error })
emit(END)
})
@ -287,7 +287,7 @@ function * contractsSaga () {
yield takeEvery(ContractActions.SEND_CONTRACT_TX, callSendContractTx)
yield takeEvery(ContractActions.CALL_CONTRACT_FN, callCallContractFn)
yield takeEvery(ContractActions.CONTRACT_SYNCING, callSyncContract)
yield takeEvery(ContractActions.LISTEN_FOR_EVENT, callListenForContractEvent)
yield takeEvery(ContractActions.CONTRACT_EVENT_LISTEN, callListenForContractEvent)
}
export default contractsSaga

2
src/defaultOptions.js

@ -9,6 +9,8 @@ const defaultOptions = {
contracts: [],
events: {},
syncAlways: false,
reloadWindowOnNetworkChange: false,
reloadWindowOnAccountChange: false,
networkWhitelist: []
}

0
src/drizzleStatus/drizzleActions.js → src/drizzle/drizzleActions.js

28
src/drizzle-middleware.js → src/drizzle/drizzleMiddleware.js

@ -1,6 +1,7 @@
import * as DrizzleActions from './drizzleStatus/drizzleActions'
import * as ContractActions from './contracts/constants'
import { ACCOUNTS_FETCHED } from './accounts/accountsActions'
import * as DrizzleActions from './drizzleActions'
import * as ContractActions from '../contracts/constants'
import { ACCOUNTS_CHANGED, ACCOUNTS_FETCHED } from '../accounts/accountsActions'
import { WEB3_NETWORK_CHANGED } from '../web3/web3Actions'
export const drizzleMiddleware = drizzleInstance => store => next => action => {
const { type } = action
@ -9,6 +10,23 @@ export const drizzleMiddleware = drizzleInstance => store => next => action => {
drizzleInstance = action.drizzle
}
// If network changes, drizzle should reinitialize
if (type === WEB3_NETWORK_CHANGED) {
// Hard reinitialization
if(drizzleInstance.options.reloadWindowOnNetworkChange)
window.location.reload();
else{ // Soft reinitialization
store.dispatch({
type: DrizzleActions.DRIZZLE_INITIALIZING,
drizzle: drizzleInstance,
options: drizzleInstance.options
})
}
}
if (type === ACCOUNTS_CHANGED && drizzleInstance.options.reloadWindowOnAccountChange)
window.location.reload();
if (
type === ACCOUNTS_FETCHED &&
drizzleInstance &&
@ -40,8 +58,8 @@ export const drizzleMiddleware = drizzleInstance => store => next => action => {
}
store.dispatch(notificationAction)
// Don't propogate current action
return
// Don't propagate current action
return;
}
}
return next(action)

0
src/drizzleStatus/drizzleStatusReducer.js → src/drizzle/drizzleStatusReducer.js

8
src/drizzleStatus/drizzleStatusSaga.js → src/drizzle/drizzleStatusSaga.js

@ -1,13 +1,13 @@
import { call, put, takeLatest } from 'redux-saga/effects'
// Initialization Functions
import { getNetworkId, initializeWeb3 } from '../web3/web3Saga'
import { getNetworkInfo, initializeWeb3 } from '../web3/web3Saga'
import { getAccounts } from '../accounts/accountsSaga'
import { getAccountBalances } from '../accountBalances/accountBalancesSaga'
import * as DrizzleActions from './drizzleActions'
import * as BlocksActions from '../blocks/blockActions'
import { NETWORK_IDS, NETWORK_MISMATCH } from '../web3/web3Actions'
import { NETWORK_IDS, WEB3_NETWORK_MISMATCH } from '../web3/web3Actions'
import { CONTRACT_NOT_DEPLOYED } from '../contracts/constants'
import { isContractDeployed } from '../contracts/contractsSaga'
@ -23,14 +23,14 @@ export function * initializeDrizzle (action) {
// further web3 interaction, and note web3 will be undefined
//
if (web3) {
const networkId = yield call(getNetworkId, { web3 })
const networkId = yield call(getNetworkInfo, { web3 })
// Check whether network is allowed
const networkWhitelist = options.networkWhitelist
if (networkWhitelist.length &&
networkId !== NETWORK_IDS.ganache &&
!networkWhitelist.includes(networkId)) {
yield put({ type: NETWORK_MISMATCH, networkId })
yield put({ type: WEB3_NETWORK_MISMATCH, networkId })
} else {
// Get initial accounts list and balances.
yield call(getAccounts, { web3 })

10
src/generateStore.js

@ -1,10 +1,10 @@
import { all, fork } from 'redux-saga/effects'
import { applyMiddleware, combineReducers, compose, createStore } from 'redux'
import createSagaMiddleware from 'redux-saga'
import drizzleSagas from './rootSaga'
import drizzleReducers from './reducer'
import drizzleSagas from './root/rootSaga'
import drizzleReducers from './root/rootReducer'
import { generateContractsInitialState } from './contractStateUtils'
import drizzleMW from './drizzle-middleware'
import drizzleMiddlewares from './root/rootMiddleware'
const composeSagas = sagas =>
function * () {
@ -28,7 +28,7 @@ export function generateStore ({
drizzleOptions,
appReducers = {},
appSagas = [],
appMiddlewares = [],
appMiddlewares = drizzleMiddlewares,
disableReduxDevTools = false,
...options
}) {
@ -55,7 +55,7 @@ export function generateStore ({
}
const sagaMiddleware = createSagaMiddleware()
const allMiddlewares = [...appMiddlewares, sagaMiddleware, drizzleMW]
const allMiddlewares = [...appMiddlewares, sagaMiddleware]
const allReducers = { ...drizzleReducers, ...appReducers }
const store = createStore(

18
src/index.js

@ -7,23 +7,13 @@ import * as EventActions from './contracts/constants'
import * as AccountActions from './accounts/accountsActions'
// Reducers
import drizzleReducers from './reducer'
import drizzleReducers from './root/rootReducer'
// Middleware
import drizzleMiddleware from './drizzle-middleware'
import accountsMiddleware from './accounts/accountsMiddleware'
import accountBalancesMiddleware from './accountBalances/accountBalancesMiddleware'
import web3Middleware from './web3/web3Middleware'
// Middlewares
import drizzleMiddlewares from './root/rootMiddleware'
// Sagas
import drizzleSagas from './rootSaga'
const drizzleMiddlewares = [
drizzleMiddleware,
accountsMiddleware,
accountBalancesMiddleware,
web3Middleware
]
import drizzleSagas from './root/rootSaga'
const drizzleActions = {
account: AccountActions,

20
src/reducer.js

@ -1,20 +0,0 @@
import accountsReducer from './accounts/accountsReducer'
import accountBalancesReducer from './accountBalances/accountBalancesReducer'
import blocksReducer from './blocks/blocksReducer'
import contractsReducer from './contracts/contractsReducer'
import drizzleStatusReducer from './drizzleStatus/drizzleStatusReducer'
import transactionsReducer from './transactions/transactionsReducer'
import transactionStackReducer from './transactions/transactionStackReducer'
import web3Reducer from './web3/web3Reducer'
// All our reducers
export default {
accounts: accountsReducer,
accountBalances: accountBalancesReducer,
contracts: contractsReducer,
currentBlock: blocksReducer,
drizzleStatus: drizzleStatusReducer,
transactions: transactionsReducer,
transactionStack: transactionStackReducer,
web3: web3Reducer
}

11
src/root/rootMiddleware.js

@ -0,0 +1,11 @@
import drizzleMiddleware from '../drizzle/drizzleMiddleware'
import accountsMiddleware from '../accounts/accountsMiddleware'
import accountBalancesMiddleware from '../accountBalances/accountBalancesMiddleware'
import web3Middleware from '../web3/web3Middleware'
export default [
drizzleMiddleware,
accountsMiddleware,
accountBalancesMiddleware,
web3Middleware
]

20
src/root/rootReducer.js

@ -0,0 +1,20 @@
import accountsReducer from '../accounts/accountsReducer'
import accountBalancesReducer from '../accountBalances/accountBalancesReducer'
import blocksReducer from '../blocks/blocksReducer'
import contractsReducer from '../contracts/contractsReducer'
import drizzleStatusReducer from '../drizzle/drizzleStatusReducer'
import transactionsReducer from '../transactions/transactionsReducer'
import transactionStackReducer from '../transactions/transactionStackReducer'
import web3Reducer from '../web3/web3Reducer'
// All our reducers
export default {
accounts: accountsReducer,
accountBalances: accountBalancesReducer,
contracts: contractsReducer,
currentBlock: blocksReducer,
drizzleStatus: drizzleStatusReducer,
transactions: transactionsReducer,
transactionStack: transactionStackReducer,
web3: web3Reducer
}

13
src/root/rootSaga.js

@ -0,0 +1,13 @@
import accountBalancesSaga from '../accountBalances/accountBalancesSaga'
import blocksSaga from '../blocks/blocksSaga'
import contractsSaga from '../contracts/contractsSaga'
import drizzleStatusSaga from '../drizzle/drizzleStatusSaga'
import web3Saga from '../web3/web3Saga'
export default [
accountBalancesSaga,
blocksSaga,
contractsSaga,
drizzleStatusSaga,
web3Saga
]

11
src/rootSaga.js

@ -1,11 +0,0 @@
import accountBalancesSaga from './accountBalances/accountBalancesSaga'
import blocksSaga from './blocks/blocksSaga'
import contractsSaga from './contracts/contractsSaga'
import drizzleStatusSaga from './drizzleStatus/drizzleStatusSaga'
export default [
accountBalancesSaga,
blocksSaga,
contractsSaga,
drizzleStatusSaga
]

21
src/web3/web3Actions.js

@ -3,10 +3,11 @@ export const WEB3_INITIALIZED = 'WEB3_INITIALIZED'
export const WEB3_FAILED = 'WEB3_FAILED'
export const WEB3_USER_DENIED = 'WEB3_USER_DENIED'
export const NETWORK_ID_FETCHED = 'NETWORK_ID_FETCHED'
export const NETWORK_ID_CHANGED = 'NETWORK_ID_CHANGED'
export const NETWORK_ID_FAILED = 'NETWORK_ID_FAILED'
export const NETWORK_MISMATCH = 'NETWORK_MISMATCH'
export const WEB3_NETWORK_FETCHING = 'WEB3_NETWORK_FETCHING'
export const WEB3_NETWORK_FETCHED = 'WEB3_NETWORK_FETCHED'
export const WEB3_NETWORK_CHANGED = 'WEB3_NETWORK_CHANGED'
export const WEB3_NETWORK_FAILED = 'WEB3_NETWORK_FAILED'
export const WEB3_NETWORK_MISMATCH = 'WEB3_NETWORK_MISMATCH'
export const NETWORK_IDS = {
mainnet: 1,
@ -17,9 +18,15 @@ export const NETWORK_IDS = {
ganache: 5777
}
export function networkIdChanged (web3, networkId) {
export function networkInfoFetching (web3) {
return {
type: NETWORK_ID_CHANGED,
networkId
type: WEB3_NETWORK_FETCHING,
web3
}
}
export function networkChanged () {
return {
type: WEB3_NETWORK_CHANGED
}
}

18
src/web3/web3Middleware.js

@ -1,18 +1,18 @@
import { networkIdChanged, WEB3_INITIALIZED } from './web3Actions'
import { networkChanged, networkInfoFetching, WEB3_INITIALIZED } from './web3Actions'
export const web3Middleware = () => store => next => action => {
export const web3Middleware = web3 => store => next => action => {
const { type } = action
if (type === WEB3_INITIALIZED) {
if(!window.ethereum)
console.warn('No Metamask detected, not subscribed to network changes!')
else {
window.ethereum.on('networkChanged', (networkId) => {
// Warning: 'networkChanged' is deprecated (EIP-1193)
const storedNetworkId = store.getState().web3.networkId;
if(storedNetworkId && (networkId !== storedNetworkId)){
store.dispatch(networkIdChanged(networkId)); // Just to be typical
window.location.reload();
web3 = action.web3;
window.ethereum.on('chainChanged', (chainId) => {
const storedNetworkId = store.getState().web3.chainId;
if(storedNetworkId && (chainId !== storedNetworkId)){
store.dispatch(networkChanged());
store.dispatch(networkInfoFetching(web3));
}
});
}
@ -20,5 +20,5 @@ export const web3Middleware = () => store => next => action => {
return next(action)
}
const initializedMiddleware = web3Middleware()
const initializedMiddleware = web3Middleware(undefined)
export default initializedMiddleware

11
src/web3/web3Reducer.js

@ -33,21 +33,22 @@ const web3Reducer = (state = initialState, action) => {
}
}
if (action.type === Action.NETWORK_ID_FETCHED
|| action.type === Action.NETWORK_ID_CHANGED) {
if (action.type === Action.WEB3_NETWORK_FETCHED) {
return {
...state,
networkId: action.networkId
networkId: action.networkInfo.networkId,
chainId: action.networkInfo.chainId,
nodeInfo: action.networkInfo.nodeInfo
}
}
if (action.type === Action.NETWORK_ID_FAILED) {
if (action.type === Action.WEB3_NETWORK_FAILED) {
return {
...state,
networkId: action.networkId
}
}
if (action.type === Action.NETWORK_MISMATCH) {
if (action.type === Action.WEB3_NETWORK_MISMATCH) {
return {
...state,
networkMismatch: true

25
src/web3/web3Saga.js

@ -1,4 +1,4 @@
import { call, put } from 'redux-saga/effects'
import { call, put, takeLatest } from 'redux-saga/effects'
import * as Action from './web3Actions'
const Web3 = require('web3');
@ -66,19 +66,30 @@ export function * initializeWeb3 (options) {
}
/*
* Fetch Network ID
* Fetch Network Information
*/
export function * getNetworkId ({ web3 }) {
export function * getNetworkInfo ({ web3 }) {
try {
const networkId = yield call(web3.eth.net.getId);
const chainId = yield call(web3.eth.getChainId);
const nodeInfo = yield call(web3.eth.getNodeInfo);
yield put({ type: Action.NETWORK_ID_FETCHED, networkId });
const networkInfo = { networkId, chainId, nodeInfo };
return networkId
yield put({ type: Action.WEB3_NETWORK_FETCHED, networkInfo });
return networkInfo;
} catch (error) {
yield put({ type: Action.NETWORK_ID_FAILED, error });
yield put({ type: Action.WEB3_NETWORK_FAILED, error });
console.error('Error fetching network ID:');
console.error('Error fetching network information:');
console.error(error);
}
}
function * web3Saga () {
yield takeLatest(Action.WEB3_NETWORK_FETCHING, getNetworkInfo);
}
export default web3Saga;

Loading…
Cancel
Save