Skip to content

Commit

Permalink
POC: Configure two Web Wallet applications (Root Document & Main Proc…
Browse files Browse the repository at this point in the history
…ess) to coexist.

The Root Document handles wallet synchronization for both applications.
  • Loading branch information
Empowerful committed Mar 14, 2019
1 parent a708ee6 commit c55bf21
Show file tree
Hide file tree
Showing 41 changed files with 1,666 additions and 327 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"@ledgerhq/hw-app-btc": "4.30.0",
"@ledgerhq/hw-app-str": "4.26.0-beta.ebeb3540",
"@ledgerhq/hw-transport-u2f": "4.31.0",
"@nodeguy/channel": "0.6.4",
"awesome-phonenumber": "2.2.6",
"base-64": "0.1.0",
"bignumber.js": "8.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const logger = (state = INITIAL_STATE, action) => {

switch (type) {
case AT.LOG_ERROR_MSG: {
console.error(payload)
return insert(0, createLog('ERROR', payload), state)
}
case AT.LOG_INFO_MSG: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const FORWARD = `FORWARD`
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { FORWARD } from './actionTypes'

export default ({ forward, types }) => () => next => action => {
if (types.has(action.type)) {
forward(action)
} else if (action.type === FORWARD) {
forward(action.payload)
}

return next(action)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import autoDisconnection from './autoDisconnection'
import forwardActions from './forwardActions'
import webSocketBch from './webSocketBch'
import webSocketBtc from './webSocketBtc'
import webSocketEth from './webSocketEth'
Expand All @@ -7,6 +8,7 @@ import streamingXlm from './streamingXlm'

export {
autoDisconnection,
forwardActions,
streamingXlm,
webSocketBch,
webSocketBtc,
Expand Down
288 changes: 184 additions & 104 deletions main-process/packages/blockchain-wallet-v4-frontend/src/store/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { AUTHENTICATE, LOGOUT } from '../data/auth/actionTypes'
import Channel from '@nodeguy/channel'
import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { persistStore, persistCombineReducers } from 'redux-persist'
import { RealmConnection, Multiplexed } from '../../../web-microkernel/src'
import storage from 'redux-persist/lib/storage'
import getStoredStateMigrateV4 from 'redux-persist/lib/integration/getStoredStateMigrateV4'
import { createHashHistory } from 'history'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import { head } from 'ramda'
import { clone, head, omit } from 'ramda'
import Bitcoin from 'bitcoinjs-lib'
import BitcoinCash from 'bitcoinforksjs-lib'

Expand All @@ -20,6 +23,7 @@ import { serializer } from 'blockchain-wallet-v4/src/types'
import { actions, rootSaga, rootReducer, selectors } from 'data'
import {
autoDisconnection,
forwardActions,
streamingXlm,
webSocketBch,
webSocketBtc,
Expand All @@ -29,6 +33,7 @@ import {

const devToolsConfig = {
maxAge: 1000,
name: `Main Process`,
serialize: serializer,
actionsBlacklist: [
// '@@redux-form/INITIALIZE',
Expand All @@ -43,118 +48,193 @@ const devToolsConfig = {
]
}

const configureStore = () => {
const configureStore = async () => {
const history = createHashHistory()
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(devToolsConfig)
: compose
const walletPath = 'wallet.payload'
const kvStorePath = 'wallet.kvstore'
const isAuthenticated = selectors.auth.isAuthenticated

return fetch('/Resources/wallet-options-v4.json')
.then(res => res.json())
.then(options => {
const apiKey = '1770d5d9-bcea-4d28-ad21-6cbd5be018a8'
// TODO: deprecate when wallet-options-v4 is updated on prod
const socketUrl = head(options.domains.webSocket.split('/inv'))
const horizonUrl = options.domains.horizon
const btcSocket = new Socket({
options,
url: `${socketUrl}/inv`
})
const bchSocket = new Socket({
options,
url: `${socketUrl}/bch/inv`
})
const ethSocket = new Socket({
options,
url: `${socketUrl}/eth/inv`
})
const ratesSocket = new ApiSocket({
options,
url: `${socketUrl}/nabu-gateway/markets/quotes`,
maxReconnects: 3
})
const xlmStreamingService = new HorizonStreamingService({
url: horizonUrl
})
const getAuthCredentials = () =>
selectors.modules.profile.getAuthCredentials(store.getState())
const reauthenticate = () =>
store.dispatch(actions.modules.profile.signIn())
const networks = {
btc: Bitcoin.networks[options.platforms.web.btc.config.network],
bch: BitcoinCash.networks[options.platforms.web.btc.config.network],
bsv: BitcoinCash.networks[options.platforms.web.btc.config.network],
eth: options.platforms.web.eth.config.network,
xlm: options.platforms.web.xlm.config.network
}
const api = createWalletApi({
options,
apiKey,
getAuthCredentials,
reauthenticate,
networks
})
const persistWhitelist = ['session', 'preferences', 'cache']

// TODO: remove getStoredStateMigrateV4 someday (at least a year from now)
const store = createStore(
connectRouter(history)(
persistCombineReducers(
{
getStoredState: getStoredStateMigrateV4({
whitelist: persistWhitelist
}),
key: 'root',
storage,
whitelist: persistWhitelist
},
rootReducer
)
),
composeEnhancers(
applyMiddleware(
sagaMiddleware,
routerMiddleware(history),
coreMiddleware.kvStore({ isAuthenticated, api, kvStorePath }),
webSocketBtc(btcSocket),
webSocketBch(bchSocket),
webSocketEth(ethSocket),
streamingXlm(xlmStreamingService, api),
webSocketRates(ratesSocket),
coreMiddleware.walletSync({ isAuthenticated, api, walletPath }),
autoDisconnection()
)
)
const options = await (await fetch(
'/Resources/wallet-options-v4.json'
)).json()

const apiKey = '1770d5d9-bcea-4d28-ad21-6cbd5be018a8'
// TODO: deprecate when wallet-options-v4 is updated on prod
const socketUrl = head(options.domains.webSocket.split('/inv'))
const horizonUrl = options.domains.horizon
const btcSocket = new Socket({
options,
url: `${socketUrl}/inv`
})
const bchSocket = new Socket({
options,
url: `${socketUrl}/bch/inv`
})
const ethSocket = new Socket({
options,
url: `${socketUrl}/eth/inv`
})
const ratesSocket = new ApiSocket({
options,
url: `${socketUrl}/nabu-gateway/markets/quotes`,
maxReconnects: 3
})
const xlmStreamingService = new HorizonStreamingService({
url: horizonUrl
})

// The store isn't available by the time we want to export its dispatch method
// so use a channel to hold pending actions.
const actionsChannel = Channel()

const rootDocument = await RealmConnection({
exports: { dispatch: actionsChannel.push },
input: Multiplexed({ realm: window, tag: `realms` }),
output: Multiplexed({ realm: window.parent, tag: `realms` }),
outputOrigin: options.domains.rootDocument
})

rootDocument.addEventListener(`error`, event => {
const plainError = pick(
[`message`, `filename`, `lineno`, `colno`, `error`],
event
)

store.dispatch(
actions.logs.logErrorMessage(`store`, `realm connection`, plainError)
)
})

const getAuthCredentials = () =>
selectors.modules.profile.getAuthCredentials(store.getState())
const reauthenticate = () => store.dispatch(actions.modules.profile.signIn())
const networks = {
btc: Bitcoin.networks[options.platforms.web.btc.config.network],
bch: BitcoinCash.networks[options.platforms.web.btc.config.network],
bsv: BitcoinCash.networks[options.platforms.web.btc.config.network],
eth: options.platforms.web.eth.config.network,
xlm: options.platforms.web.xlm.config.network
}

const axiosAdapter = async config => {
try {
// `transformRequest`, `transformResponse`, `validateStatus` are all
// performed locally so there's no need to call them in the root document.

const sanitizedConfig = omit(
[`adapter`, `transformRequest`, `transformResponse`, `validateStatus`],
config
)
const persistor = persistStore(store, null)

sagaMiddleware.run(rootSaga, {
api,
bchSocket,
btcSocket,
ethSocket,
ratesSocket,
networks,
options
})

// expose globals here
window.createTestXlmAccounts = () => {
store.dispatch(actions.core.data.xlm.createTestAccounts())
}

store.dispatch(actions.goals.defineGoals())

return {
store,
history,
persistor
}
})

const immutableResponse = await rootDocument.imports.axios(
sanitizedConfig
)

// Return a shallow clone because Axios wants to mutate it.
return { ...immutableResponse }
} catch (immutableException) {
// Create a mutable deep clone of the exception because Axios wants to
// mutate it.

const plainObject = { ...immutableException }

const mutableException = Object.assign(
Error(immutableException.message),
clone(plainObject)
)

throw mutableException
}
}

const api = createWalletApi({
axiosAdapter,
options,
apiKey,
getAuthCredentials,
reauthenticate,
networks
})

const persistWhitelist = ['session', 'preferences', 'cache']

// Forward the following action types to the root document.
//
// AUTHENTICATE: Enable the root document to synchronize the wallet.
// LOGOUT: Tell the root document to reload itself when we do.
const forwardActionTypes = new Set([AUTHENTICATE, LOGOUT])

// TODO: remove getStoredStateMigrateV4 someday (at least a year from now)
const store = createStore(
connectRouter(history)(
persistCombineReducers(
{
getStoredState: getStoredStateMigrateV4({
whitelist: persistWhitelist
}),
key: 'root',
storage,
whitelist: persistWhitelist
},
rootReducer
)
),
composeEnhancers(
applyMiddleware(
sagaMiddleware,
routerMiddleware(history),
coreMiddleware.kvStore({ isAuthenticated, api, kvStorePath }),
webSocketBtc(btcSocket),
webSocketBch(bchSocket),
webSocketEth(ethSocket),
streamingXlm(xlmStreamingService, api),
webSocketRates(ratesSocket),

coreMiddleware.walletSync({
isAuthenticated,
rootDocumentDispatch: rootDocument.imports.dispatch
}),

autoDisconnection(),

forwardActions({
forward: rootDocument.imports.dispatch,
types: forwardActionTypes
})
)
)
)
const persistor = persistStore(store, null)

sagaMiddleware.run(rootSaga, {
api,
bchSocket,
btcSocket,
ethSocket,
ratesSocket,
networks,
options
})

// Now that we have a store, dispatch pending and future actions from the
// channel.
actionsChannel.forEach(store.dispatch)

// expose globals here
window.createTestXlmAccounts = () => {
store.dispatch(actions.core.data.xlm.createTestAccounts())
}

store.dispatch(actions.goals.defineGoals())

return {
store,
history,
persistor
}
}

export default configureStore
Loading

0 comments on commit c55bf21

Please sign in to comment.