edge-core-js
Version:
Edge account & wallet management library
214 lines (193 loc) • 6.3 kB
JavaScript
function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }import { makeSyncClient } from 'edge-sync-client'
import { createStore } from 'redux'
import { attachPixie, filterPixie, } from 'redux-pixies'
import { emit } from 'yaob'
import { validateServer } from '../util/validateServer'
import { CLIENT_FILE_NAME, clientFile } from './context/client-file'
import { INFO_CACHE_FILE_NAME, infoCacheFile } from './context/info-cache-file'
import { filterLogs, makeLog } from './log/log'
import { loadAirbitzStashes } from './login/airbitz-stashes'
import { loadStashes } from './login/login-stash'
import { watchPlugins } from './plugins/plugins-actions'
import { rootPixie, } from './root-pixie'
import { defaultLogSettings, reducer, } from './root-reducer'
let allContexts = []
const enhancer =
typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION__ != null
? window.__REDUX_DEVTOOLS_EXTENSION__({ name: 'core' })
: undefined
/**
* Creates the root object for the entire core state machine.
* This core object contains the `io` object, context options,
* Redux store, and tree of background workers.
*/
export async function makeContext(
ios,
logBackend,
opts
) {
const { io } = ios
const {
airbitzSupport = false,
apiSecret,
appId = '',
authServer,
deviceDescription = null,
changeServer,
hideKeys = false,
infoServer,
loginServer,
plugins: pluginsInit = {},
skipBlockHeight = false,
syncServer
} = opts
let { apiKey } = opts
if (apiKey == null || apiKey === '') {
apiKey = '4248c1bf41e53b840a5fdb2c872dd3ade525e66d'
}
const authServers = toServerArray(authServer, [
'https://login1.edge.app',
'https://login2.edge.app'
])
const changeServers = toServerArray(changeServer, ['wss://change1.edge.app'])
const infoServers = toServerArray(infoServer, [
'https://info1.edge.app',
'https://info2.edge.app'
])
const loginServers = toServerArray(
loginServer,
authServers.map(server => server.replace(/\/api$/, ''))
)
const syncServers = toServerArray(syncServer, [
'https://sync-us1.edge.app',
'https://sync-us2.edge.app',
'https://sync-us3.edge.app',
'https://sync-us4.edge.app',
'https://sync-us5.edge.app',
'https://sync-us6.edge.app',
'https://sync-eu.edge.app'
])
const logSettings = { ...defaultLogSettings, ...opts.logSettings }
changeServers.map(server => validateServer(server))
infoServers.map(server => validateServer(server))
loginServers.map(server => validateServer(server))
syncServers.map(server => validateServer(server))
// Create a redux store:
const redux = createStore(reducer, enhancer)
// Create a log wrapper, using the settings from redux:
logBackend = filterLogs(logBackend, () => {
const state = redux.getState()
return state.ready ? state.logSettings : logSettings
})
const log = makeLog(logBackend, 'edge-core')
// Load the login stashes from disk:
let [clientInfo, infoCache = {}, stashes] = await Promise.all([
clientFile.load(io.disklet, CLIENT_FILE_NAME),
infoCacheFile.load(io.disklet, INFO_CACHE_FILE_NAME),
loadStashes(io.disklet, log)
])
// Load legacy stashes from disk
if (airbitzSupport) {
// Edge will write modern files to disk at login time,
// but it won't delete the legacy Airbitz data.
// Once this happens, we need to ignore the legacy files
// and just use the new files:
const avoidUsernames = new Set()
for (const { username } of stashes) {
if (username != null) avoidUsernames.add(username)
}
const airbitzStashes = await loadAirbitzStashes(io, avoidUsernames)
stashes.push(...airbitzStashes)
}
// Save the initial client info if the client is new:
if (clientInfo == null) {
clientInfo = {
clientId: io.random(16),
duressEnabled: false,
loginWaitTimestamps: {}
}
await clientFile.save(io.disklet, CLIENT_FILE_NAME, clientInfo)
}
// Write everything to redux:
redux.dispatch({
type: 'INIT',
payload: {
apiKey,
apiSecret,
appId,
changeServers,
loginServers,
infoCache,
infoServers,
syncServers,
clientInfo,
deviceDescription,
hideKeys,
logSettings,
pluginsInit,
skipBlockHeight,
stashes
}
})
// Subscribe to new plugins:
const closePlugins = watchPlugins(
ios,
infoCache,
logBackend,
pluginsInit,
redux.dispatch
)
// Create sync client:
const syncClient = makeSyncClient({
log,
fetch: (uri, opts) => io.fetch(uri, { ...opts, corsBypass: 'never' }),
edgeServers: { infoServers, syncServers }
})
// Start the pixie tree:
const mirror = { output: {} }
const closePixie = attachPixie(
redux,
filterPixie(
rootPixie,
(props) => ({
...props,
close() {
closePixie()
closePlugins()
redux.dispatch({ type: 'CLOSE' })
},
io,
log,
logBackend,
onError: error => {
if (_optionalChain([mirror, 'access', _ => _.output, 'access', _2 => _2.context, 'optionalAccess', _3 => _3.api]) != null) {
emit(mirror.output.context.api, 'error', error)
}
},
syncClient
})
),
error => log.error(error),
output => (mirror.output = output)
)
const out = mirror.output.context.api
allContexts.push(out)
return out
}
function toServerArray(
server,
fallback
) {
return typeof server === 'string'
? [server]
: server != null && server.length > 0
? server
: fallback
}
/**
* We use this for unit testing, to kill all core contexts.
*/
export function closeEdge() {
for (const context of allContexts) context.close().catch(() => {})
allContexts = []
}