@exodus/headless 2.0.0-alpha.1 → 2.0.0-alpha.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ import { combine, compute } from '@exodus/atoms'
2
+ import { uniq } from 'lodash'
3
+
4
+ const getNetworks = (assetNames, assets) =>
5
+ uniq(
6
+ assetNames
7
+ .map((assetName) => assets[assetName]?.baseAsset.name)
8
+ .filter((assetName) => !!assetName && !assets[assetName].isCombined)
9
+ )
10
+
11
+ const createBaseAssetNamesToMonitorAtom = ({
12
+ assetsModule,
13
+ enabledAssetsAtom,
14
+ restoreAtom,
15
+ availableAssetNamesAtom,
16
+ }) => {
17
+ const selector = ({ isRestore, enabledAssets, availableAssetNames }) => {
18
+ const assetNames = isRestore ? availableAssetNames : Object.keys(enabledAssets)
19
+ return getNetworks(assetNames, assetsModule.getAssets())
20
+ }
21
+
22
+ return compute({
23
+ atom: combine({
24
+ isRestore: restoreAtom,
25
+ enabledAssets: enabledAssetsAtom,
26
+ availableAssetNames: availableAssetNamesAtom,
27
+ }),
28
+ selector,
29
+ })
30
+ }
31
+
32
+ // eslint-disable-next-line @exodus/export-default/named
33
+ export default {
34
+ id: 'baseAssetNamesToMonitorAtom',
35
+ type: 'atom',
36
+ factory: createBaseAssetNamesToMonitorAtom,
37
+ dependencies: ['assetsModule', 'availableAssetNamesAtom', 'enabledAssetsAtom', 'restoreAtom'],
38
+ }
@@ -0,0 +1,39 @@
1
+ export const atomsToAttach = [
2
+ 'abTestingAtom',
3
+ 'apyRatesAtom',
4
+ 'availableAssetNamesAtom',
5
+ 'balancesAtom',
6
+ 'connectedOriginsAtom',
7
+ 'cryptoNewsAtom',
8
+ 'currencyAtom',
9
+ 'enabledWalletAccountsAtom',
10
+ 'featureFlagsAtom',
11
+ 'geolocationAtom',
12
+ 'kycAtom',
13
+ 'languageAtom',
14
+ 'nftsConfigAtom',
15
+ 'personalNotesAtom',
16
+ 'referralsAtom',
17
+ 'topMoversAtom',
18
+ 'walletAccountsAtom',
19
+ 'restoringAssetsAtom',
20
+ ]
21
+
22
+ export const LifecycleHook = Object.freeze({
23
+ Lock: 'lock',
24
+ Unlock: 'unlock',
25
+ Clear: 'clear',
26
+ Import: 'import',
27
+ Migrate: 'migrate',
28
+ Start: 'start',
29
+ Stop: 'stop',
30
+ Restart: 'restart',
31
+ Load: 'load',
32
+ Unload: 'unload',
33
+ Create: 'create',
34
+ Backup: 'backup',
35
+ Restore: 'restore',
36
+ RestoreCompleted: 'restore-completed',
37
+ AssetsSynced: 'assets-synced',
38
+ ChangePassphrase: 'change-passphrase',
39
+ })
@@ -1,15 +1,5 @@
1
- import { createInMemoryAtom } from '@exodus/atoms'
2
- import { withType } from './utils'
1
+ import baseAssetNamesToMonitorAtomDefinition from '../atoms/base-asset-names-to-monitor'
3
2
 
4
- const createAtomDependencies = () =>
5
- [
6
- {
7
- definition: {
8
- id: 'lockedAtom',
9
- factory: () => createInMemoryAtom({ defaultValue: true }),
10
- dependencies: [],
11
- },
12
- },
13
- ].map(withType('atom'))
3
+ const createAtomDependencies = () => [{ definition: baseAssetNamesToMonitorAtomDefinition }]
14
4
 
15
5
  export default createAtomDependencies
@@ -3,10 +3,11 @@
3
3
 
4
4
  import assert from 'minimalistic-assert'
5
5
 
6
- import createConfigDependencies from './configs'
7
6
  import createAdapterDependencies from './adapters'
8
7
  import createAtomDependencies from './atoms'
8
+ import createConfigDependencies from './configs'
9
9
  import createModuleDependencies from './modules'
10
+ import createPluginDependencies from './plugins'
10
11
  import { wrapConstant } from './utils'
11
12
 
12
13
  const adapterKeys = [
@@ -16,6 +17,9 @@ const adapterKeys = [
16
17
  'legacyPrivToPub',
17
18
  'seedStorage',
18
19
  'unsafeStorage',
20
+ 'fusion',
21
+ 'fetch',
22
+ 'freeze',
19
23
  ]
20
24
 
21
25
  const createDependencies = ({ adapters, config }) => {
@@ -33,11 +37,14 @@ const createDependencies = ({ adapters, config }) => {
33
37
 
34
38
  const adaptersTree = createAdapterDependencies({ adapters, config })
35
39
 
40
+ const plugins = createPluginDependencies()
41
+
36
42
  return []
37
43
  .concat(adaptersTree)
38
44
  .concat(configs)
39
45
  .concat(modules)
40
46
  .concat(atoms)
47
+ .concat(plugins)
41
48
  .concat(wrapConstant({ id: 'logger', type: 'module', value: logger }))
42
49
  }
43
50
 
@@ -1,12 +1,11 @@
1
- import { createKeychain } from '@exodus/keychain'
2
- import walletDefinition from '@exodus/wallet/module'
3
1
  import createKeyIdentifierProvider from '@exodus/key-identifier-provider'
4
2
  import walletCompatibilityModesDefinition from '@exodus/wallet-compatibility-modes/module'
5
3
 
6
4
  import createApplication from '../application'
5
+ import unlockEncryptedStorageDefinition from '../unlock-encrypted-storage'
7
6
  import { withType } from './utils'
8
7
 
9
- const createModuleDependencies = () =>
8
+ const createModuleDependencies = ({ config }) =>
10
9
  [
11
10
  {
12
11
  definition: {
@@ -22,19 +21,10 @@ const createModuleDependencies = () =>
22
21
  dependencies: [],
23
22
  },
24
23
  },
25
- {
26
- definition: {
27
- id: 'keychain',
28
- factory: createKeychain,
29
- dependencies: ['legacyPrivToPub'],
30
- },
31
- },
32
- {
33
- definition: walletDefinition,
34
- },
35
24
  {
36
25
  definition: walletCompatibilityModesDefinition,
37
26
  },
27
+ { definition: unlockEncryptedStorageDefinition },
38
28
  ].map(withType('module'))
39
29
 
40
30
  export default createModuleDependencies
@@ -0,0 +1,7 @@
1
+ import plugins from '../plugins'
2
+ import { withType } from './utils'
3
+
4
+ const createPluginDependencies = () =>
5
+ plugins.map((definition) => ({ definition })).map(withType('plugin'))
6
+
7
+ export default createPluginDependencies
@@ -2,10 +2,6 @@ export const wrapConstant = ({ id, type, value }) => ({
2
2
  definition: { id, type, factory: () => value },
3
3
  })
4
4
 
5
- export const insertIf = (module, enabled) => {
6
- return enabled ? [module] : []
7
- }
8
-
9
5
  export const withType =
10
6
  (type) =>
11
7
  ({ definition, ...rest }) => ({
package/src/index.js CHANGED
@@ -1,35 +1,115 @@
1
- import createIOC from './ioc'
1
+ import addressProvider from '@exodus/address-provider'
2
+ import availableAssets from '@exodus/available-assets'
3
+ import balances from '@exodus/balances'
4
+ import { pick } from '@exodus/basic-utils'
5
+ import blockchainMetadata from '@exodus/blockchain-metadata'
6
+ import enabledAssets from '@exodus/enabled-assets'
7
+ import featureFlags from '@exodus/feature-flags'
8
+ import fees from '@exodus/fee-monitors'
9
+ import geolocation from '@exodus/geolocation'
10
+ import keychain from '@exodus/keychain'
11
+ import locale from '@exodus/locale'
12
+ import pricing from '@exodus/pricing'
13
+ import rates from '@exodus/rates-monitor'
14
+ import remoteConfig from '@exodus/remote-config'
15
+ import restoreProgressTracker from '@exodus/restore-progress-tracker'
16
+ import wallet from '@exodus/wallet'
17
+ import walletAccounts from '@exodus/wallet-accounts'
18
+
2
19
  import createApi from './api'
20
+ import attachAtoms from './atoms/attach'
21
+ import { atomsToAttach } from './constants'
22
+ import createIOC from './ioc'
23
+ import attachPlugins from './plugins/attach'
3
24
 
4
25
  const createExodus = ({ adapters, config, port }) => {
5
26
  const ioc = createIOC({ adapters, config })
6
27
 
28
+ ioc.use(wallet())
29
+ ioc.use(keychain(config.keychain))
30
+ ioc.use(walletAccounts())
31
+ ioc.use(blockchainMetadata())
32
+ ioc.use(availableAssets())
33
+ ioc.use(enabledAssets())
34
+ ioc.use(balances())
35
+ ioc.use(remoteConfig())
36
+ ioc.use(geolocation())
37
+ ioc.use(pricing())
38
+ ioc.use(fees())
39
+ ioc.use(rates())
40
+ ioc.use(featureFlags())
41
+ ioc.use(locale())
42
+ ioc.use(addressProvider({ config: config.addressProvider }))
43
+ ioc.use(restoreProgressTracker())
44
+
45
+ ioc.register({ definition: { id: 'port', type: 'port', factory: () => port } })
46
+
7
47
  const resolve = () => {
8
48
  ioc.resolve()
9
49
 
10
- const { assetsModule } = ioc.getByType('adapter')
50
+ const { assetsModule, storage, fusion } = ioc.getByType('adapter')
51
+
52
+ const { application, wallet, unlockEncryptedStorage } = ioc.getByType('module')
53
+
54
+ application.on('start', (payload) => port.emit('start', payload))
55
+
56
+ application.on('load', (args) => port.emit('load', args))
57
+
58
+ application.on('create', async ({ hasPassphraseSet }) => {
59
+ const isLocked = await wallet.isLocked()
60
+
61
+ port.emit('create', {
62
+ walletExists: true,
63
+ hasPassphraseSet,
64
+ isLocked,
65
+ isBackedUp: false,
66
+ isRestoring: false,
67
+ })
68
+ })
69
+
70
+ application.on('unlock', () => port.emit('unlock'))
71
+
72
+ application.on('lock', () => port.emit('lock'))
11
73
 
12
- const { application } = ioc.getByType('module')
74
+ application.on('backup', () => port.emit('backup'))
13
75
 
14
- application.hook('start', () => {
15
- port.emit('start')
76
+ application.on('restore', () => port.emit('restore'))
77
+
78
+ application.on('restore-completed', () => {
79
+ port.emit('restore-completed')
16
80
  })
17
81
 
82
+ application.on('change-passphrase', () => port.emit('passphrase-changed'))
83
+
84
+ application.on('import', () => port.emit('import', { walletExists: true }))
85
+
18
86
  application.hook('unlock', async () => {
87
+ if (typeof storage.unlock === 'function') unlockEncryptedStorage(storage)
88
+
19
89
  await assetsModule.load()
20
90
  })
21
91
 
22
92
  application.hook('clear', async () => {
23
- await Promise.all([assetsModule.clear()])
93
+ await Promise.all([assetsModule.clear(), fusion.clearStorage()])
24
94
  })
25
95
 
26
- application.on('restart', () => {
27
- port.emit('restart')
96
+ application.on('clear', () => port.emit('clear'))
97
+
98
+ application.on('restart', (payload) => port.emit('restart', payload))
99
+
100
+ attachAtoms({
101
+ port,
102
+ application,
103
+ atoms: pick(ioc.getByType('atom'), atomsToAttach),
28
104
  })
29
105
 
30
- application.start()
106
+ attachPlugins({
107
+ application,
108
+ plugins: ioc.getByType('plugin'),
109
+ logger: ioc.get('createLogger')('attachPlugins'),
110
+ })
31
111
 
32
- return createApi({ ioc })
112
+ return createApi({ ioc, port })
33
113
  }
34
114
 
35
115
  return { ...ioc, resolve }
package/src/ioc.js CHANGED
@@ -1,23 +1,56 @@
1
1
  import createIocContainer from '@exodus/dependency-injection'
2
2
  import preprocess from '@exodus/dependency-preprocessors'
3
3
  import alias from '@exodus/dependency-preprocessors/src/preprocessors/alias'
4
+ import devModeAtoms from '@exodus/dependency-preprocessors/src/preprocessors/dev-mode-atoms'
4
5
  import logify from '@exodus/dependency-preprocessors/src/preprocessors/logify'
5
6
  import namespaceConfig from '@exodus/dependency-preprocessors/src/preprocessors/namespace-config'
7
+ import namespaceStorage from '@exodus/dependency-preprocessors/src/preprocessors/namespace-storage'
8
+ import optional from '@exodus/dependency-preprocessors/src/preprocessors/optional'
9
+ import readOnlyAtoms from '@exodus/dependency-preprocessors/src/preprocessors/read-only-atoms'
10
+ import assert from 'minimalistic-assert'
6
11
 
7
12
  import createDependencies from './dependencies'
8
13
 
9
14
  const createIOC = ({ adapters, config }) => {
10
15
  const { createLogger } = adapters
16
+ const { readOnlyAtoms: readOnlyAtomsConfig, devModeAtoms: devModeAtomsConfig } = config.ioc ?? {}
11
17
 
12
- const logger = createLogger('exodus:ioc')
13
- const dependencies = createDependencies({ adapters, config })
14
- const preprocessors = [logify({ createLogger }), namespaceConfig(), alias()]
15
- const definitions = preprocess({ dependencies, preprocessors })
16
- const ioc = createIocContainer({ logger })
18
+ const ioc = createIocContainer({ logger: createLogger('exodus:ioc') })
17
19
 
18
- ioc.registerMultiple(definitions)
20
+ const preprocessors = [
21
+ logify({ createLogger }),
22
+ namespaceConfig(),
23
+ alias(),
24
+ // NOTE: order matters, this should come after `alias`
25
+ namespaceStorage(),
26
+ readOnlyAtoms({
27
+ logger: createLogger('exodus:read-only-atoms'),
28
+ warn: true,
29
+ ...readOnlyAtomsConfig,
30
+ }),
31
+ optional(),
32
+ ...(devModeAtomsConfig ? [devModeAtoms(devModeAtomsConfig)] : []),
33
+ ]
19
34
 
20
- return ioc
35
+ const registerMultiple = (dependencies) => {
36
+ ioc.registerMultiple(preprocess({ dependencies, preprocessors }))
37
+ }
38
+
39
+ const register = (dependency) => {
40
+ registerMultiple([dependency])
41
+ }
42
+
43
+ const use = (module) => {
44
+ for (const { definition } of module.definitions) {
45
+ assert(definition.type, `ioc.use: "${definition.id}" is missing type field`)
46
+ }
47
+
48
+ registerMultiple(module.definitions)
49
+ }
50
+
51
+ registerMultiple(createDependencies({ adapters, config }))
52
+
53
+ return { ...ioc, register, registerMultiple, use }
21
54
  }
22
55
 
23
56
  export default createIOC
@@ -0,0 +1,36 @@
1
+ import { LifecycleHook } from '../constants'
2
+
3
+ const LIFECYCLE_METHOD_TO_HOOK_NAME = Object.fromEntries(
4
+ Object.entries(LifecycleHook).map(([pascalCaseName, hookName]) => [
5
+ `on${pascalCaseName}`,
6
+ hookName,
7
+ ])
8
+ )
9
+
10
+ const attachPlugins = ({ plugins, application, logger }) => {
11
+ const timeFn =
12
+ (fn, name) =>
13
+ async (...args) => {
14
+ const start = Date.now()
15
+ try {
16
+ return await fn(...args)
17
+ } finally {
18
+ logger.debug(name, `${Date.now() - start}ms`)
19
+ }
20
+ }
21
+
22
+ Object.entries(plugins).forEach(([name, lifecycleMethods]) => {
23
+ const entries = Object.entries(lifecycleMethods || {})
24
+
25
+ for (const [lifecycleMethod, fn] of entries) {
26
+ const hookName = LIFECYCLE_METHOD_TO_HOOK_NAME[lifecycleMethod]
27
+ if (hookName) {
28
+ application.hook(hookName, timeFn(fn, `plugin ${name}.${lifecycleMethod}`))
29
+ } else {
30
+ logger.error(`plugin "${name}" declares unsupported lifecycle method "${lifecycleMethod}"`)
31
+ }
32
+ }
33
+ })
34
+ }
35
+
36
+ export default attachPlugins
@@ -0,0 +1,5 @@
1
+ import autoEnableAssetsPlugin from '@exodus/auto-enable-assets-plugin'
2
+
3
+ import logLifecyclePlugin from './log-lifecycle'
4
+
5
+ export default [logLifecyclePlugin, autoEnableAssetsPlugin]
@@ -0,0 +1,26 @@
1
+ const logLifecycle = {
2
+ id: 'logLifecyclePlugin',
3
+ type: 'plugin',
4
+ factory: ({ logger }) => {
5
+ return {
6
+ onLock: () => logger.debug('onLock'),
7
+ onUnlock: () => logger.debug('onUnlock'),
8
+ onClear: () => logger.debug('onClear'),
9
+ onImport: () => logger.debug('onImport'),
10
+ onMigrate: () => logger.debug('onMigrate'),
11
+ onStart: () => logger.debug('onStart'),
12
+ onRestart: () => logger.debug('onRestart'),
13
+ onLoad: () => logger.debug('onLoad'),
14
+ onUnload: () => logger.debug('onUnload'),
15
+ onCreate: () => logger.debug('onCreate'),
16
+ onBackup: () => logger.debug('onBackup'),
17
+ onRestore: () => logger.debug('onRestore'),
18
+ onRestoreCompleted: () => logger.debug('onRestoreCompleted'),
19
+ onAssetsSynced: () => logger.debug('onAssetsSynced'),
20
+ onChangePassphrase: () => logger.debug('onChangePassphrase'),
21
+ }
22
+ },
23
+ dependencies: ['logger'],
24
+ }
25
+
26
+ export default logLifecycle
@@ -0,0 +1,25 @@
1
+ import { zipObject } from 'lodash'
2
+
3
+ const createReporting = ({ ioc }) => {
4
+ const getReports = async () => {
5
+ const nodes = ioc.getByType('report')
6
+ const reports = Object.values(nodes)
7
+
8
+ const resolvedReports = await Promise.allSettled(reports.map((report) => report.export()))
9
+
10
+ const namespaces = reports.map((report) => report.namespace)
11
+ const data = resolvedReports.map((outcome) =>
12
+ outcome.status === 'fulfilled' ? outcome.value : { error: outcome.reason }
13
+ )
14
+
15
+ return zipObject(namespaces, data)
16
+ }
17
+
18
+ return {
19
+ reporting: {
20
+ export: getReports,
21
+ },
22
+ }
23
+ }
24
+
25
+ export default createReporting
@@ -0,0 +1,21 @@
1
+ import { EXODUS_KEY_IDS } from '@exodus/keychain/module'
2
+
3
+ const createUnlockEncryptedStorage = ({ keychain }) => {
4
+ return async (encryptedStorage) => {
5
+ // should the key id be part of a `config` dep?
6
+ const sodiumEncryptor = keychain.createSodiumEncryptor(EXODUS_KEY_IDS.WALLET_INFO)
7
+
8
+ await encryptedStorage.unlock({
9
+ encrypt: (data) => sodiumEncryptor.encryptSecretBox({ data }),
10
+ decrypt: (data) => sodiumEncryptor.decryptSecretBox({ data }),
11
+ })
12
+ }
13
+ }
14
+
15
+ // eslint-disable-next-line @exodus/export-default/named
16
+ export default {
17
+ id: 'unlockEncryptedStorage',
18
+ type: 'module',
19
+ factory: createUnlockEncryptedStorage,
20
+ dependencies: ['keychain'],
21
+ }