UNPKG

edge-core-js

Version:

Edge account & wallet management library

335 lines (289 loc) 12.2 kB
function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } 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 { combinePixies, mapPixie, } from 'redux-pixies' import { matchJson } from '../../util/match-json' import { connectChangeServer } from './change-server-connection' import { walletPixie } from './wallet/currency-wallet-pixie' export const currency = combinePixies({ wallets: mapPixie( walletPixie, (props) => props.state.currency.currencyWalletIds, (props, walletId) => ({ ...props, walletId, walletState: props.state.currency.wallets[walletId], walletOutput: props.output.currency.wallets[walletId] }) ), pluginUpdater(input) { let lastInfo return async () => { const { infoCache, plugins } = input.props.state // Bail out quickly if nothing has changed: if (lastInfo === infoCache) return // Update plugins after the first run: if (lastInfo != null) { for (const pluginId of Object.keys(plugins.currency)) { const plugin = plugins.currency[pluginId] const newPayload = _nullishCoalesce(_optionalChain([infoCache, 'access', _ => _.corePlugins, 'optionalAccess', _2 => _2[pluginId]]), () => ( {})) const oldPayload = _nullishCoalesce(_optionalChain([lastInfo, 'access', _3 => _3.corePlugins, 'optionalAccess', _4 => _4[pluginId]]), () => ( {})) if ( plugin.updateInfoPayload != null && !matchJson(oldPayload, newPayload) ) { await plugin.updateInfoPayload(newPayload) } } } lastInfo = infoCache } }, changeServiceManager(input) { let lastWallets return async () => { const { wallets } = input.props.state.currency // Memoize this function using the wallet state: if (wallets === lastWallets) { return } else { lastWallets = wallets } let { changeService, changeServiceConnected = false } = _nullishCoalesce(input.props.output.currency.changeServiceManager, () => ( {})) // The viable wallets that support change server subscriptions const supportedWallets = Object.entries(wallets).filter(([, wallet]) => wallet.changeServiceSubscriptions.some( subscription => subscription.status !== 'avoiding' ) ) // Connect the socket if we have 1 or more supported wallets: if (changeService == null && supportedWallets.length > 0) { const url = input.props.state.changeServers[0] changeService = connectChangeServer(url, { handleChange([pluginId, address, checkpoint]) { const wallets = Object.entries(input.props.state.currency.wallets) const filteredWallets = wallets.filter(([, wallet]) => { return ( wallet.currencyInfo.pluginId === pluginId && wallet.changeServiceSubscriptions.some( subscription => subscription.address === address ) ) }) for (const [walletId, wallet] of filteredWallets) { const subscriptions = wallet.changeServiceSubscriptions .filter( subscription => subscription.address === address && subscription.status === 'listening' ) .map(subscription => ({ ...subscription, status: 'syncing' , checkpoint })) input.props.dispatch({ type: 'CURRENCY_ENGINE_UPDATE_CHANGE_SERVICE_SUBSCRIPTIONS', payload: { walletId, subscriptions } }) } }, handleConnect() { const wallets = Object.entries(input.props.state.currency.wallets) // Reset to subscribing status for all reconnecting wallets: for (const [walletId, wallet] of wallets) { const subscriptions = wallet.changeServiceSubscriptions .filter(subscription => subscription.status === 'reconnecting') .map(subscription => ({ ...subscription, status: 'subscribing' })) input.props.dispatch({ type: 'CURRENCY_ENGINE_UPDATE_CHANGE_SERVICE_SUBSCRIPTIONS', payload: { walletId, subscriptions } }) } // Start subscribing for all supported wallets: input.onOutput({ changeService, changeServiceConnected: true }) }, handleDisconnect() { const wallets = Object.entries(input.props.state.currency.wallets) // Reset to reconnecting status for all supported wallets: for (const [walletId, wallet] of wallets) { const subscriptions = wallet.changeServiceSubscriptions .filter(subscription => subscription.status !== 'avoiding') .map(subscription => ({ ...subscription, status: 'reconnecting' })) input.props.dispatch({ type: 'CURRENCY_ENGINE_UPDATE_CHANGE_SERVICE_SUBSCRIPTIONS', payload: { walletId, subscriptions } }) } input.onOutput({ changeService, changeServiceConnected: false }) }, handleSubLost([pluginId, address]) { const wallets = Object.entries(input.props.state.currency.wallets) const filteredWallets = wallets.filter( ([, wallet]) => // Wallet must be for the same plugin, because those wallets, wallet.currencyInfo.pluginId === pluginId && // Wallet must have a subscription for the address: wallet.changeServiceSubscriptions.some( subscription => subscription.address === address ) && // Wallet subscription not be avoiding the change service // because those wallets shouldn't be affected by this event: wallet.changeServiceSubscriptions.some( subscription => subscription.status !== 'avoiding' ) ) // Set status back to subscribing for all filtered wallets: for (const [walletId, wallet] of filteredWallets) { const subscriptions = wallet.changeServiceSubscriptions .filter(subscription => subscription.status !== 'avoiding') .map(subscription => ({ ...subscription, status: 'subscribing' })) input.props.dispatch({ type: 'CURRENCY_ENGINE_UPDATE_CHANGE_SERVICE_SUBSCRIPTIONS', payload: { walletId, subscriptions } }) } } }) input.onOutput({ changeService, changeServiceConnected: false }) } // Disconnect the socket if we have 0 supported wallets: if (changeService != null && supportedWallets.length === 0) { changeService.close() changeService = undefined input.onOutput({ changeService, changeServiceConnected: false }) } // Subscribe wallets to the change service: if (_optionalChain([changeService, 'optionalAccess', _5 => _5.connected]) === true && changeServiceConnected) { const filteredWallets = supportedWallets.filter(([, wallet]) => wallet.changeServiceSubscriptions.some( subscription => subscription.status === 'subscribing' || subscription.status === 'resubscribing' ) ) const indexToWalletId = [] const batches = [] for (const [walletId, wallet] of filteredWallets) { if (wallet.paused) continue // Build the subscribe parameters: const params = wallet.changeServiceSubscriptions.map( (subscription) => [ wallet.currencyInfo.pluginId, subscription.address, subscription.checkpoint ] ) if (params.length === 0) continue let subscribeParams = batches[batches.length - 1] if ( subscribeParams == null || subscribeParams.length + params.length > 100 ) { batches.push([]) subscribeParams = batches[batches.length - 1] } subscribeParams.push(...params) for (let i = 0; i < params.length; i++) { indexToWalletId[indexToWalletId.length] = { walletId, wallet } } } // Subscribe to the change service: const results = [] for (const subscribeParams of batches) { const r = await changeService .subscribe(subscribeParams) .catch(err => { input.props.log(`Failed to subscribe: ${String(err)}`) return [0] }) results.push(...r) } const subscriptionUpdates = new Map() for (let i = 0; i < results.length; i++) { const result = results[i] const { walletId, wallet } = indexToWalletId[i] // Determine the new status of the subscription to all addresses // for the wallet: let status switch (result) { // Change server does not support this wallet plugin: case -1: // Avoid the change service: status = 'avoiding' break // Change server does support this wallet plugin, but failed to // subscribe to the address: case 0: // Try subscribing again later: status = 'resubscribing' break // Change server does support this wallet plugin, and there are no // changes for the address: case 1: // Start syncing the wallet once for initial syncNetwork call: status = 'syncing' break // Change server does support this wallet plugin, and there are // changes for the address: case 2: // Start syncing the wallet: status = 'syncing' break } // Update the status for the subscription: const subscriptions = wallet.changeServiceSubscriptions .filter( subscription => subscription.status === 'subscribing' || subscription.status === 'resubscribing' ) .map(subscription => ({ ...subscription, status })) subscriptionUpdates.set(walletId, subscriptions) } // TODO: Remove the Array.from() once we drop ES5 targeting. This is // need to avoid iterables because looping over iterables are broken // for ES5 targets. const entries = Array.from(subscriptionUpdates.entries()) for (const [walletId, subscriptions] of entries) { input.props.dispatch({ type: 'CURRENCY_ENGINE_UPDATE_CHANGE_SERVICE_SUBSCRIPTIONS', payload: { walletId, subscriptions } }) } } } } })