1 | import reduct, { Injector } from 'reduct'
|
2 | import { partial } from 'lodash'
|
3 | import { create as createLogger } from './common/log'
|
4 | const log = createLogger('app')
|
5 |
|
6 | import Config from './services/config'
|
7 | import RouteBuilder from './services/route-builder'
|
8 | import RouteBroadcaster from './services/route-broadcaster'
|
9 | import Accounts from './services/accounts'
|
10 | import RateBackend from './services/rate-backend'
|
11 | import Store from './services/store'
|
12 | import MiddlewareManager from './services/middleware-manager'
|
13 | import AdminApi from './services/admin-api'
|
14 | import * as Prometheus from 'prom-client'
|
15 |
|
16 | const version = require('../package.json').version
|
17 |
|
18 | function listen (
|
19 | config: Config,
|
20 | accounts: Accounts,
|
21 | backend: RateBackend,
|
22 | store: Store,
|
23 | routeBuilder: RouteBuilder,
|
24 | routeBroadcaster: RouteBroadcaster,
|
25 | middlewareManager: MiddlewareManager,
|
26 | adminApi: AdminApi
|
27 | ) {
|
28 |
|
29 |
|
30 | return (async function () {
|
31 | adminApi.listen()
|
32 |
|
33 | try {
|
34 | await backend.connect()
|
35 | } catch (error) {
|
36 | log.error(error)
|
37 | process.exit(1)
|
38 | }
|
39 |
|
40 | await middlewareManager.setup()
|
41 |
|
42 |
|
43 | await accounts.loadIlpAddress()
|
44 |
|
45 | if (config.routeBroadcastEnabled) {
|
46 | routeBroadcaster.start()
|
47 | }
|
48 |
|
49 |
|
50 | await new Promise((resolve, reject) => {
|
51 | const connectTimeout = setTimeout(() => {
|
52 | log.warn('one or more accounts failed to connect within the time limit, continuing anyway.')
|
53 | resolve()
|
54 | }, config.initialConnectTimeout)
|
55 | accounts.connect({ timeout: config.initialConnectTimeout })
|
56 | .then(() => {
|
57 | clearTimeout(connectTimeout)
|
58 | resolve()
|
59 | }, reject)
|
60 | })
|
61 |
|
62 | await middlewareManager.startup()
|
63 |
|
64 | if (config.collectDefaultMetrics) {
|
65 | Prometheus.collectDefaultMetrics()
|
66 | }
|
67 |
|
68 | log.info('connector ready (republic attitude). address=%s version=%s', accounts.getOwnAddress(), version)
|
69 | })().catch((err) => log.error(err))
|
70 | }
|
71 |
|
72 | async function addPlugin (
|
73 | config: Config,
|
74 | accounts: Accounts,
|
75 | backend: RateBackend,
|
76 | routeBroadcaster: RouteBroadcaster,
|
77 | middlewareManager: MiddlewareManager,
|
78 |
|
79 | id: string,
|
80 | options: any
|
81 | ) {
|
82 | accounts.add(id, options)
|
83 | const plugin = accounts.getPlugin(id)
|
84 | await middlewareManager.addPlugin(id, plugin)
|
85 |
|
86 | await plugin.connect({ timeout: Infinity })
|
87 | routeBroadcaster.track(id)
|
88 | routeBroadcaster.reloadLocalRoutes()
|
89 | }
|
90 |
|
91 | async function removePlugin (
|
92 | config: Config,
|
93 | accounts: Accounts,
|
94 | backend: RateBackend,
|
95 | routeBroadcaster: RouteBroadcaster,
|
96 | middlewareManager: MiddlewareManager,
|
97 |
|
98 | id: string
|
99 | ) {
|
100 | const plugin = accounts.getPlugin(id)
|
101 | await middlewareManager.removePlugin(id, plugin)
|
102 | await plugin.disconnect()
|
103 | routeBroadcaster.untrack(id)
|
104 | accounts.remove(id)
|
105 | routeBroadcaster.reloadLocalRoutes()
|
106 | }
|
107 |
|
108 | function getPlugin (
|
109 | accounts: Accounts,
|
110 |
|
111 | id: string
|
112 | ) {
|
113 | return accounts.getPlugin(id)
|
114 | }
|
115 |
|
116 | function shutdown (
|
117 | accounts: Accounts,
|
118 | routeBroadcaster: RouteBroadcaster
|
119 | ) {
|
120 | routeBroadcaster.stop()
|
121 | return accounts.disconnect()
|
122 | }
|
123 |
|
124 | export default function createApp (opts?: object, container?: Injector) {
|
125 | const deps = container || reduct()
|
126 |
|
127 | const config = deps(Config)
|
128 |
|
129 | try {
|
130 | if (opts) {
|
131 | config.loadFromOpts(opts)
|
132 | } else {
|
133 | config.loadFromEnv()
|
134 | }
|
135 | } catch (err) {
|
136 | if (err.name === 'InvalidJsonBodyError') {
|
137 | log.warn('config validation error.')
|
138 | err.debugPrint(log.warn.bind(log))
|
139 | log.error('invalid configuration, shutting down.')
|
140 | throw new Error('failed to initialize due to invalid configuration.')
|
141 | }
|
142 |
|
143 | throw err
|
144 | }
|
145 |
|
146 | const accounts = deps(Accounts)
|
147 | const routeBuilder = deps(RouteBuilder)
|
148 | const routeBroadcaster = deps(RouteBroadcaster)
|
149 | const backend = deps(RateBackend)
|
150 | const store = deps(Store)
|
151 | const middlewareManager = deps(MiddlewareManager)
|
152 | const adminApi = deps(AdminApi)
|
153 |
|
154 | const credentials = config.accounts
|
155 | for (let id of Object.keys(credentials)) {
|
156 | accounts.add(id, credentials[id])
|
157 | }
|
158 |
|
159 | return {
|
160 | config,
|
161 | listen: partial(listen, config, accounts, backend, store, routeBuilder, routeBroadcaster, middlewareManager, adminApi),
|
162 | addPlugin: partial(addPlugin, config, accounts, backend, routeBroadcaster, middlewareManager),
|
163 | removePlugin: partial(removePlugin, config, accounts, backend, routeBroadcaster, middlewareManager),
|
164 | getPlugin: partial(getPlugin, accounts),
|
165 | shutdown: partial(shutdown, accounts, routeBroadcaster)
|
166 | }
|
167 | }
|