 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() {
            // Start subscribing for all supported wallets:
            input.onOutput({ changeService, changeServiceConnected: true })
          },
          handleDisconnect() {
            const wallets = Object.entries(input.props.state.currency.wallets)
            // Reset to subscribing status for all supported wallets:
            for (const [walletId, wallet] of wallets) {
              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 })
          },
          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'
          )
        )
        for (const [walletId, wallet] of filteredWallets) {
          // Build the subscribe parameters:
          const subscribeParams =
            wallet.changeServiceSubscriptions.map(subscription => [
              wallet.currencyInfo.pluginId,
              subscription.address,
              subscription.checkpoint
            ])
          if (subscribeParams.length === 0) continue

          // Subscribe to the change service:
          const results = await changeService
            .subscribe(subscribeParams)
            .catch(err => {
              input.props.log(`Failed to subscribe: ${String(err)}`)
              return [0] 
            })

          // Determine the new status of the subscription to all addresses
          // for the wallet:
          let status = 'listening'
          // If any of the subscriptions not supported:
          if (results.some(result => result === -1)) {
            // The engine wants ECS, but the server does not support it, so
            // avoid the change service:
            status = 'avoiding'
          }
          // If any of the subscriptions failed:
          if (results.some(result => result === 0)) {
            // The engine wants ECS, but the server failed, so try again later:
            status = 'subscribing'
          }
          // If any of the subscriptions have changes present:
          if (results.some(result => result === 2)) {
            // Start syncing the wallet:
            status = 'syncing'
          }

          const subscriptions = wallet.changeServiceSubscriptions
            .filter(subscription => subscription.status === 'subscribing')
            .map(subscription => ({
              ...subscription,
              status
            }))
          input.props.dispatch({
            type: 'CURRENCY_ENGINE_UPDATE_CHANGE_SERVICE_SUBSCRIPTIONS',
            payload: {
              walletId,
              subscriptions
            }
          })
        }
      }
    }
  }
})
