@exodus/headless 1.1.0

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## 1.1.0 (2023-03-28)
7
+
8
+ ### Features
9
+
10
+ - add headless module ([#996](https://github.com/ExodusMovement/exodus-hydra/issues/996)) ([0ea954e](https://github.com/ExodusMovement/exodus-hydra/commit/0ea954e378f1afd1881ddcdde63546772157327e))
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # @exodus/headless
2
+
3
+ The headless Exodus wallet SDK
4
+
5
+ ## Quick Start
6
+
7
+ ```js
8
+ import createExodus from '@exodus/headless'
9
+ import Emitter from '@exodus/wild-emitter'
10
+
11
+ // 1. Create port. Acts as an event bus between headless wallet and client
12
+ const port = new Emitter()
13
+
14
+ // 2. Instantiate client-specific adapters and config. More details below
15
+ const adapters = {}
16
+ const config = {}
17
+
18
+ // 3. Create Exodus container
19
+ const container = createExodus({ port, adapters, config })
20
+
21
+ // 4. Register external modules. Does not support overriding modules at the moment.
22
+ container.register({
23
+ definition: {
24
+ id: 'remoteConfig',
25
+ factory: createRemoteConfig,
26
+ dependencies: ['config', 'fetch', 'logger'],
27
+ },
28
+ })
29
+
30
+ // 5. Resolve exodus instance
31
+ const exodus = container.resolve()
32
+
33
+ // 6. Use it!
34
+ await exodus.wallet.create({ passphrase: 'my-super-secure-passphrase' })
35
+ ```
36
+
37
+ ## Port
38
+
39
+ Event bus between headless wallet and client
40
+
41
+ | Method | Type | Description |
42
+ | -------------------- | ------------------------------------------ | ------------------------------------------ |
43
+ | subscribe | `({ type: string, payload: any }) => void` | Subscribe to receive events. |
44
+ | unsubscribe(handler) | `({ type: string, payload: any }) => void` | Unsubscribe handler from receiving events. |
45
+ | emit | `(type: string, payload: any) => void` | Emit an event. |
46
+
47
+ ## Adapters
48
+
49
+ Adapters are client-specific implementations of defined APIs needed for the wallet to function.
50
+
51
+ New adapters will be required by this module as we migrate more modules into it. Below are explained the ones that this module requires at the date.
52
+
53
+ ### assetsModule
54
+
55
+ `AssetsModule` implementation. Currently being injected as each of our clients have different implementations.
56
+
57
+ ### createLogger
58
+
59
+ A function that returns an instance of the Logger class with the given namespace.
60
+
61
+ | Parameter | Type | Description |
62
+ | --------- | -------- | ----------------------------- |
63
+ | namespace | `string` | The namespace for the logger. |
64
+
65
+ The returned Logger instance has the following methods:
66
+
67
+ | Method | Type | Description |
68
+ | ------ | -------------------------- | ----------------------------------- |
69
+ | trace | `(...args: any[]) => void` | Log a message with the trace level. |
70
+ | debug | `(...args: any[]) => void` | Log a message with the debug level. |
71
+ | log | `(...args: any[]) => void` | Log a message with the log level. |
72
+ | info | `(...args: any[]) => void` | Log a message with the info level. |
73
+ | warn | `(...args: any[]) => void` | Log a message with the warn level. |
74
+ | error | `(...args: any[]) => void` | Log a message with the error level. |
75
+
76
+ Each method takes any number of arguments, which will be logged as a message with the corresponding log level. The message will be prefixed with the logger's namespace.
77
+
78
+ ### legacyPrivToPub
79
+
80
+ Legacy private to public key transformation function.
81
+
82
+ An object where each key is an asset name and the value is a function that takes a `Buffer` and returns a `Buffer`.
83
+
84
+ ### unsafeStorage
85
+
86
+ Unsafe (raw text) storage instance. Used internally by modules to persist data in client underlying storage layer.
87
+
88
+ In most cases you probably want to use encrypted storage instead (not yet migrated).
89
+
90
+ Check [storage-spec](https://github.com/ExodusMovement/exodus-hydra/tree/master/modules/storage-spec) for more details about Storage API
91
+
92
+ ### seco
93
+
94
+ An object with methods to encrypt and decrypt strings.
95
+
96
+ | Method | Type | Description |
97
+ | ------------- | -------------------------------------------------------------- | --------------- |
98
+ | encryptString | `({ data: string, key: string, encoding?: string }) => string` | Encrypt string. |
99
+ | decryptString | `({ data: string, key: string, encoding?: string }) => string` | Decrypt string. |
100
+
101
+ ### passphraseCache
102
+
103
+ An instance of the PassphraseCache class.
104
+
105
+ | Method | Type | Description |
106
+ | ------------- | ------------------------------ | ------------------------------------------------------------ |
107
+ | set | `(passphrase: string) => void` | Set the passphrase with the given value. |
108
+ | get | `() => string` | Get the current passphrase value. |
109
+ | changeTtl | `(ttl: number) => void` | Change the time-to-live (TTL) value of the passphrase cache. |
110
+ | clear | `() => void` | Clear the passphrase cache. |
111
+ | scheduleClear | `() => void` | |
112
+
113
+ ## Config
114
+
115
+ An object with additional configuration options for the Exodus instance.
116
+
117
+ Currently not used.
118
+
119
+ ## Headless API
120
+
121
+ ## wallet
122
+
123
+ > Type: object
124
+
125
+ | Method | Type | Description |
126
+ | ------------------------ | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
127
+ | exists | `async () => boolean` | Checks if a wallet exists on the device. |
128
+ | create | `async ({ passphrase?: string }) => void` | Creates new wallet |
129
+ | import | `async ({ mnemonic: string, passphrase?: string }) => void` | Imports existing wallet |
130
+ | delete | `async ({ passphrase?: string }) => void` | Deletes wallet. Passphrase is mandatory if wallet was created with one |
131
+ | lock | `async () => void` | Locks wallet |
132
+ | unlock | `async ({ passphrase?: string }) => void` | Unlocks wallet. Passphrase is mandatory if wallet was created with one |
133
+ | isLocked | `async () => boolean` | Checks if wallet is locked |
134
+ | getMnemonic | `async () => void` | Sets wallet as backed up |
135
+ | changePassphrase | `async ({ currentPassphrase?: string. newPassphrase: string }) => void` | Change passphrase. Current passphrase is mandatory if wallet was created with one |
136
+ | restoreFromCurrentPhrase | `async ({ passphrase?: string }) => void` | Restore current wallet. Passphrase is mandatory if wallet was created with one |
137
+ | load | `async () => void` | Loads UI by rehydratating data through port (`BE Only`) |
138
+ | unload | `async () => void` | Unloads UI by rehydratating data through port (`BE Only`) |
139
+ | changeLockTimer | `async ({ ttl: number }) => void` | Change auto unlock ttl (`BE Only`) |
140
+
141
+ ## isMnemonicValid
142
+
143
+ > Type: function
144
+
145
+ | Argument | Type | Description |
146
+ | -------- | ---------- | ---------------------------- |
147
+ | mnemonic | `string` | Mnemonic to check validity. |
148
+ | wordlist | `string[]` | Words list to check against. |
149
+
150
+ **Returns**: `boolean`
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@exodus/headless",
3
+ "version": "1.1.0",
4
+ "description": "The headless Exodus wallet SDK",
5
+ "author": "Exodus Movement Inc",
6
+ "main": "src/index.js",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ExodusMovement/exodus-hydra.git"
10
+ },
11
+ "homepage": "https://github.com/ExodusMovement/exodus-hydra/tree/master/modules/headless",
12
+ "license": "UNLICENSED",
13
+ "bugs": {
14
+ "url": "https://github.com/ExodusMovement/exodus-hydra/issues?q=is%3Aissue+is%3Aopen+label%3Aheadless"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "README.md",
19
+ "CHANGELOG.md",
20
+ "!**/__tests__",
21
+ "!**/*.test.js"
22
+ ],
23
+ "scripts": {
24
+ "lint": "eslint . --ignore-path ../../.gitignore",
25
+ "lint:fix": "yarn lint --fix",
26
+ "test": "jest"
27
+ },
28
+ "dependencies": {
29
+ "@exodus/atoms": "^2.9.0",
30
+ "@exodus/dependency-injection": "^1.2.0",
31
+ "@exodus/dependency-preprocessors": "^2.0.2",
32
+ "@exodus/fetch": "^1.2.1",
33
+ "@exodus/key-identifier-provider": "^1.1.1",
34
+ "@exodus/keychain": "^3.2.1",
35
+ "@exodus/module": "^1.0.0",
36
+ "@exodus/wallet": "4.1.0",
37
+ "@exodus/wallet-compatibility-modes": "1.0.1",
38
+ "bip39": "2.6.0",
39
+ "lodash": "https://registry.yarnpkg.com/@exodus/lodash/-/lodash-4.17.21-exodus.2.tgz",
40
+ "minimalistic-assert": "^1.0.1"
41
+ },
42
+ "devDependencies": {
43
+ "@exodus/ethereum-lib": "^2.22.2",
44
+ "@exodus/solana-lib": "^1.3.11",
45
+ "@exodus/storage-memory": "^1.1.0",
46
+ "@exodus/wild-emitter": "^1.0.0",
47
+ "eslint": "^8.33.0",
48
+ "jest": "^29.1.2"
49
+ },
50
+ "gitHead": "e5126f0fa052ef08113c4eb66c450172dbfa3012"
51
+ }
package/src/api.js ADDED
@@ -0,0 +1,34 @@
1
+ import { validateMnemonic as isMnemonicValid } from 'bip39'
2
+
3
+ const createApi = ({ ioc }) => {
4
+ const { passphraseCache } = ioc.getByType('adapter')
5
+
6
+ const { application, wallet } = ioc.getByType('module')
7
+
8
+ return {
9
+ wallet: {
10
+ exists: () => wallet.exists(),
11
+ load: application.load,
12
+ unload: application.unload,
13
+ create: application.create,
14
+ lock: application.lock,
15
+ unlock: application.unlock,
16
+ import: application.import,
17
+ delete: application.delete,
18
+ getMnemonic: application.getMnemonic,
19
+ setBackedUp: application.setBackedUp,
20
+ changePassphrase: application.changePassphrase,
21
+ restoreFromCurrentPhrase: async ({ passphrase } = {}) => {
22
+ if (!passphrase) passphrase = await passphraseCache.get()
23
+ const mnemonic = await application.getMnemonic({ passphrase })
24
+
25
+ await application.import({ passphrase, mnemonic })
26
+ },
27
+ changeLockTimer: application.changeLockTimer,
28
+ isLocked: () => wallet.isLocked(),
29
+ },
30
+ isMnemonicValid,
31
+ }
32
+ }
33
+
34
+ export default createApi
@@ -0,0 +1,246 @@
1
+ /* eslint-disable unicorn/no-for-loop */
2
+
3
+ import ExodusModule from '@exodus/module'
4
+ import assert from 'minimalistic-assert'
5
+
6
+ // Because we are forced to restart wallet after deleting/importing, we need to store certain flags to persist state between executions
7
+
8
+ // Triggers deletetion logic when wallet starts.
9
+ // Set as true on delete method is called, and set to false when wallet is restarted
10
+ const DELETE_FLAG = 'deleteFlag'
11
+
12
+ // Triggers import logic when wallet starts. Used when overwriting existing wallet (not importing one from scratch)
13
+ // Set as true on import method is called, and set to false when wallet is restarted
14
+ const IMPORT_FLAG = 'importFlag'
15
+
16
+ // Triggers restore logic. We need to do it after any import, no matter if there was a previous wallet or not
17
+ // Set as true on import method is called, and set to false after restore is completed
18
+ const RESTORE_FLAG = 'restoreFlag'
19
+
20
+ const HOOKS = Object.freeze({
21
+ lock: 'lock',
22
+ unlock: 'unlock',
23
+ clear: 'clear',
24
+ import: 'import',
25
+ migrate: 'migrate',
26
+ start: 'start',
27
+ load: 'load',
28
+ unload: 'unload',
29
+ create: 'create',
30
+ backup: 'backup',
31
+ restore: 'restore',
32
+ 'restore-completed': 'restore-completed',
33
+ 'assets-synced': 'assets-synced',
34
+ 'change-passphrase': 'change-passphrase',
35
+ })
36
+
37
+ class Application extends ExodusModule {
38
+ #hooks = {}
39
+ #wallet = null
40
+ #storage = null
41
+ #passphraseCache = null
42
+ #applicationStarted = null
43
+ #resolveStart = null
44
+
45
+ constructor({ wallet, unsafeStorage, passphraseCache, logger }) {
46
+ super({ name: 'Application', logger })
47
+
48
+ this.#wallet = wallet
49
+ this.#passphraseCache = passphraseCache
50
+ this.#storage = unsafeStorage.namespace('flags')
51
+
52
+ this.#applicationStarted = new Promise((resolve) => (this.#resolveStart = resolve))
53
+ }
54
+
55
+ start = async () => {
56
+ const [deleteFlag, importFlag] = await this.#storage.batchGet([DELETE_FLAG, IMPORT_FLAG])
57
+
58
+ const isDeleting = !!deleteFlag
59
+ const isImporting = !!importFlag
60
+
61
+ if (isDeleting) await this.#wallet.clear()
62
+
63
+ if (isDeleting || isImporting) {
64
+ await this.#storage.batchDelete([DELETE_FLAG, IMPORT_FLAG])
65
+ await this.#passphraseCache.clear()
66
+ await this.fire(HOOKS.clear)
67
+ }
68
+
69
+ if (isImporting) await this.fire(HOOKS.import)
70
+
71
+ await this.fire(HOOKS.start)
72
+ await this.#autoUnlock()
73
+
74
+ const locked = await this.#wallet.isLocked()
75
+ this._logger.log('wallet is locked', locked)
76
+
77
+ this.#resolveStart()
78
+ }
79
+
80
+ load = async () => {
81
+ await this.#applicationStarted
82
+
83
+ const [walletExists, hasPassphraseSet, isLocked, isBackedUp] = await Promise.all([
84
+ this.#wallet.exists(),
85
+ this.#wallet.hasPassphraseSet(),
86
+ this.#wallet.isLocked(),
87
+ this.#wallet.isBackedUp(),
88
+ ])
89
+
90
+ await this.fire(HOOKS.load, { walletExists, hasPassphraseSet, isLocked, isBackedUp })
91
+ }
92
+
93
+ unload = async () => {
94
+ await this.#applicationStarted
95
+ await this.#passphraseCache.scheduleClear()
96
+ await this.fire(HOOKS.unload)
97
+ }
98
+
99
+ hook = (hookName, listener) => {
100
+ assert(HOOKS[hookName], `no such hook: ${hookName}`)
101
+
102
+ if (!this.#hooks[hookName]) {
103
+ this.#hooks[hookName] = []
104
+ }
105
+
106
+ this.#hooks[hookName].push(listener)
107
+ }
108
+
109
+ fire = async (hookName, params) => {
110
+ assert(HOOKS[hookName], `no such hook: ${hookName}`)
111
+ this._logger.debug('firing hooks', hookName)
112
+
113
+ const hooks = this.#hooks[hookName] || []
114
+
115
+ for (let i = 0; i < hooks.length; i++) {
116
+ await hooks[i](params)
117
+ }
118
+
119
+ this.emit(hookName, params)
120
+ }
121
+
122
+ create = async (opts) => {
123
+ this._logger.log('creating wallet')
124
+
125
+ await this.#applicationStarted
126
+ await this.#wallet.create(opts)
127
+
128
+ await this.fire(HOOKS.create, { hasPassphraseSet: !!opts?.passphrase })
129
+ }
130
+
131
+ import = async (opts) => {
132
+ this._logger.log('importing wallet')
133
+
134
+ await this.#storage.set(RESTORE_FLAG, true)
135
+
136
+ await this.#applicationStarted
137
+
138
+ if (await this.#wallet.exists()) {
139
+ await this.#wallet.import(opts)
140
+ await this.#storage.set(IMPORT_FLAG, true)
141
+
142
+ this.emit('restart')
143
+
144
+ return
145
+ }
146
+
147
+ await this.#wallet.import(opts)
148
+ await this.fire(HOOKS.import)
149
+
150
+ this._logger.log('wallet imported')
151
+ }
152
+
153
+ getMnemonic = async (opts) => this.#wallet.getMnemonic(opts)
154
+
155
+ setBackedUp = async () => {
156
+ await this.#wallet.setBackedUp()
157
+ await this.fire(HOOKS.backup)
158
+ }
159
+
160
+ lock = async (opts) => {
161
+ this._logger.log('locking')
162
+
163
+ await this.#applicationStarted
164
+ await this.#wallet.lock(opts)
165
+ await this.#passphraseCache.clear()
166
+ await this.fire(HOOKS.lock)
167
+
168
+ this._logger.log('locked')
169
+ }
170
+
171
+ #restoreIfNeeded = async () => {
172
+ const isRestoring = await this.isRestoring()
173
+
174
+ if (isRestoring) {
175
+ await this.fire(HOOKS.restore)
176
+ await this.#storage.delete(RESTORE_FLAG)
177
+ await this.fire(HOOKS['restore-completed'])
178
+ }
179
+
180
+ this.fire(HOOKS['assets-synced'])
181
+ }
182
+
183
+ #autoUnlock = async () => {
184
+ const walletLocked = await this.#wallet.isLocked()
185
+ const passphrase = await this.#passphraseCache.get()
186
+
187
+ if (!walletLocked || !passphrase) return
188
+
189
+ try {
190
+ this._logger.log('unlocking with cache')
191
+
192
+ await this.#wallet.unlock({ passphrase })
193
+ await this.fire(HOOKS.unlock)
194
+
195
+ this.#restoreIfNeeded()
196
+
197
+ this._logger.log('unlocked with cache')
198
+ } catch (err) {
199
+ this._logger.error('failed to unlock, outdated cached passphrase?', err)
200
+ }
201
+ }
202
+
203
+ unlock = async ({ passphrase } = {}) => {
204
+ this._logger.log('unlocking')
205
+
206
+ await this.#applicationStarted
207
+ await this.#wallet.unlock({ passphrase })
208
+
209
+ await this.fire(HOOKS.migrate)
210
+ await this.fire(HOOKS.unlock)
211
+
212
+ this.#restoreIfNeeded()
213
+
214
+ if (passphrase) this.#passphraseCache.set(passphrase)
215
+
216
+ this._logger.log('unlocked')
217
+ }
218
+
219
+ changePassphrase = async ({ currentPassphrase, newPassphrase }) => {
220
+ this._logger.log('changing passphrase')
221
+
222
+ await this.#applicationStarted
223
+ await this.#wallet.changePassphrase({ currentPassphrase, newPassphrase })
224
+ await this.#passphraseCache.set(newPassphrase)
225
+ await this.fire(HOOKS['change-passphrase'])
226
+
227
+ this._logger.log('passphrase changed')
228
+ }
229
+
230
+ delete = async () => {
231
+ await this.#storage.set(DELETE_FLAG, true)
232
+ this.emit('restart')
233
+ }
234
+
235
+ changeLockTimer = async ({ ttl }) => {
236
+ await this.#passphraseCache.changeTtl(ttl)
237
+ }
238
+
239
+ isRestoring = async () => {
240
+ return this.#storage.get(RESTORE_FLAG)
241
+ }
242
+ }
243
+
244
+ const createApplication = (args = {}) => new Application({ ...args })
245
+
246
+ export default createApplication
@@ -0,0 +1,6 @@
1
+ import { wrapConstant } from './utils'
2
+
3
+ const createAdapterDependencies = ({ adapters }) =>
4
+ Object.entries(adapters).map(([id, value]) => wrapConstant({ id, type: 'adapter', value }))
5
+
6
+ export default createAdapterDependencies
@@ -0,0 +1,15 @@
1
+ import { createInMemoryAtom } from '@exodus/atoms'
2
+ import { withType } from './utils'
3
+
4
+ const createAtomDependencies = () =>
5
+ [
6
+ {
7
+ definition: {
8
+ id: 'lockedAtom',
9
+ factory: () => createInMemoryAtom({ defaultValue: true }),
10
+ dependencies: [],
11
+ },
12
+ },
13
+ ].map(withType('atom'))
14
+
15
+ export default createAtomDependencies
@@ -0,0 +1,14 @@
1
+ import { withType } from './utils'
2
+
3
+ const createConfigDependencies = ({ config }) => {
4
+ return [
5
+ {
6
+ definition: {
7
+ id: 'config',
8
+ factory: () => config,
9
+ },
10
+ },
11
+ ].map(withType('config'))
12
+ }
13
+
14
+ export default createConfigDependencies
@@ -0,0 +1,44 @@
1
+ /* eslint-disable unicorn/prefer-array-flat */
2
+ /* eslint-disable unicorn/prefer-spread */
3
+
4
+ import assert from 'minimalistic-assert'
5
+
6
+ import createConfigDependencies from './configs'
7
+ import createAdapterDependencies from './adapters'
8
+ import createAtomDependencies from './atoms'
9
+ import createModuleDependencies from './modules'
10
+ import { wrapConstant } from './utils'
11
+
12
+ const adapterKeys = [
13
+ // ...
14
+ 'assetsModule',
15
+ 'createLogger',
16
+ 'legacyPrivToPub',
17
+ 'seco',
18
+ 'unsafeStorage',
19
+ ]
20
+
21
+ const createDependencies = ({ adapters, config }) => {
22
+ assert(config, 'expected config object')
23
+
24
+ adapterKeys.forEach((key) => assert(adapters[key], `expected adapter "${key}"`))
25
+
26
+ const logger = adapters.createLogger('exodus')
27
+
28
+ const configs = createConfigDependencies({ adapters, config })
29
+
30
+ const modules = createModuleDependencies({ adapters, config })
31
+
32
+ const atoms = createAtomDependencies({ adapters, config })
33
+
34
+ const adaptersTree = createAdapterDependencies({ adapters, config })
35
+
36
+ return []
37
+ .concat(adaptersTree)
38
+ .concat(configs)
39
+ .concat(modules)
40
+ .concat(atoms)
41
+ .concat(wrapConstant({ id: 'logger', type: 'module', value: logger }))
42
+ }
43
+
44
+ export default createDependencies
@@ -0,0 +1,46 @@
1
+ import { createKeychain } from '@exodus/keychain'
2
+ import walletDefinition from '@exodus/wallet/module'
3
+ import createKeyIdentifierProvider from '@exodus/key-identifier-provider'
4
+ import walletCompatibilityModesDefinition from '@exodus/wallet-compatibility-modes/module'
5
+
6
+ import createApplication from '../application'
7
+ import { withType } from './utils'
8
+
9
+ const createModuleDependencies = () =>
10
+ [
11
+ {
12
+ definition: {
13
+ id: 'application',
14
+ factory: createApplication,
15
+ dependencies: ['unsafeStorage', 'passphraseCache', 'wallet', 'logger'],
16
+ },
17
+ },
18
+ {
19
+ definition: {
20
+ id: 'keyIdentifierProvider',
21
+ factory: createKeyIdentifierProvider,
22
+ dependencies: [],
23
+ },
24
+ },
25
+ {
26
+ definition: {
27
+ id: 'keychain',
28
+ factory: createKeychain,
29
+ dependencies: ['legacyPrivToPub'],
30
+ },
31
+ },
32
+ {
33
+ definition: walletDefinition,
34
+ aliases: [
35
+ {
36
+ implementationId: 'unsafeStorage',
37
+ interfaceId: 'storage',
38
+ },
39
+ ],
40
+ },
41
+ {
42
+ definition: walletCompatibilityModesDefinition,
43
+ },
44
+ ].map(withType('module'))
45
+
46
+ export default createModuleDependencies
@@ -0,0 +1,14 @@
1
+ export const wrapConstant = ({ id, type, value }) => ({
2
+ definition: { id, type, factory: () => value },
3
+ })
4
+
5
+ export const insertIf = (module, enabled) => {
6
+ return enabled ? [module] : []
7
+ }
8
+
9
+ export const withType =
10
+ (type) =>
11
+ ({ definition, ...rest }) => ({
12
+ ...rest,
13
+ definition: { type, ...definition },
14
+ })
package/src/index.js ADDED
@@ -0,0 +1,38 @@
1
+ import createIOC from './ioc'
2
+ import createApi from './api'
3
+
4
+ const createExodus = ({ adapters, config, port }) => {
5
+ const ioc = createIOC({ adapters, config })
6
+
7
+ const resolve = () => {
8
+ ioc.resolve()
9
+
10
+ const { assetsModule } = ioc.getByType('adapter')
11
+
12
+ const { application } = ioc.getByType('module')
13
+
14
+ application.hook('start', () => {
15
+ port.emit('start')
16
+ })
17
+
18
+ application.hook('unlock', async () => {
19
+ await assetsModule.load()
20
+ })
21
+
22
+ application.hook('clear', async () => {
23
+ await Promise.all([assetsModule.clear()])
24
+ })
25
+
26
+ application.on('restart', () => {
27
+ port.emit('restart')
28
+ })
29
+
30
+ application.start()
31
+
32
+ return createApi({ ioc })
33
+ }
34
+
35
+ return { ...ioc, resolve }
36
+ }
37
+
38
+ export default createExodus
package/src/ioc.js ADDED
@@ -0,0 +1,23 @@
1
+ import createIocContainer from '@exodus/dependency-injection'
2
+ import preprocess from '@exodus/dependency-preprocessors'
3
+ import alias from '@exodus/dependency-preprocessors/src/preprocessors/alias'
4
+ import logify from '@exodus/dependency-preprocessors/src/preprocessors/logify'
5
+ import namespaceConfig from '@exodus/dependency-preprocessors/src/preprocessors/namespace-config'
6
+
7
+ import createDependencies from './dependencies'
8
+
9
+ const createIOC = ({ adapters, config }) => {
10
+ const { createLogger } = adapters
11
+
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 })
17
+
18
+ ioc.registerMultiple(definitions)
19
+
20
+ return ioc
21
+ }
22
+
23
+ export default createIOC