1 | import reduct = require('reduct')
|
2 | import compat from 'ilp-compat-plugin'
|
3 | import Store from '../services/store'
|
4 | import Config from './config'
|
5 | import { EventEmitter } from 'events'
|
6 | import { AccountInfo } from '../types/accounts'
|
7 | import {
|
8 | ConnectOptions,
|
9 | PluginInstance
|
10 | } from '../types/plugin'
|
11 | import ILDCP = require('ilp-protocol-ildcp')
|
12 |
|
13 | import { create as createLogger } from '../common/log'
|
14 | const log = createLogger('accounts')
|
15 |
|
16 | export interface AccountEntry {
|
17 | plugin: PluginInstance,
|
18 | info: AccountInfo
|
19 | }
|
20 |
|
21 | export default class Accounts extends EventEmitter {
|
22 | protected config: Config
|
23 | protected store: Store
|
24 |
|
25 | protected address: string
|
26 | protected accounts: Map<string, AccountEntry>
|
27 |
|
28 | constructor (deps: reduct.Injector) {
|
29 | super()
|
30 |
|
31 | this.config = deps(Config)
|
32 | this.store = deps(Store)
|
33 |
|
34 | this.address = this.config.ilpAddress || 'unknown'
|
35 | this.accounts = new Map()
|
36 | }
|
37 |
|
38 | async loadIlpAddress () {
|
39 | const inheritFrom = this.config.ilpAddressInheritFrom ||
|
40 |
|
41 | [...this.accounts]
|
42 | .filter(([key, value]) => value.info.relation === 'parent')
|
43 | .map(([key]) => key)[0]
|
44 |
|
45 | if (this.config.ilpAddress === 'unknown' && !inheritFrom) {
|
46 | throw new Error('When there is no parent, ILP address must be specified in configuration.')
|
47 | } else if (this.config.ilpAddress === 'unknown' && inheritFrom) {
|
48 | const parent = this.getPlugin(inheritFrom)
|
49 |
|
50 | log.trace('connecting to parent. accountId=%s', inheritFrom)
|
51 | await parent.connect({})
|
52 |
|
53 | const ildcpInfo = await ILDCP.fetch(parent.sendData.bind(parent))
|
54 |
|
55 | this.setOwnAddress(ildcpInfo.clientAddress)
|
56 |
|
57 | if (this.address === 'unknown') {
|
58 | log.error('could not get ilp address from parent.')
|
59 | throw new Error('no ilp address configured.')
|
60 | }
|
61 | }
|
62 | }
|
63 |
|
64 | async connect (options: ConnectOptions) {
|
65 | const unconnectedAccounts = Array.from(this.accounts.values())
|
66 | .filter(account => !account.plugin.isConnected())
|
67 | return Promise.all(unconnectedAccounts.map(account => account.plugin.connect(options)))
|
68 | }
|
69 |
|
70 | async disconnect () {
|
71 | const connectedAccounts = Array.from(this.accounts.values())
|
72 | .filter(account => account.plugin.isConnected())
|
73 | return Promise.all(connectedAccounts.map(account => account.plugin.disconnect()))
|
74 | }
|
75 |
|
76 | getOwnAddress () {
|
77 | return this.address
|
78 | }
|
79 |
|
80 | setOwnAddress (newAddress: string) {
|
81 | log.trace('setting ilp address. oldAddress=%s newAddress=%s', this.address, newAddress)
|
82 | this.address = newAddress
|
83 | }
|
84 |
|
85 | getPlugin (accountId: string) {
|
86 | const account = this.accounts.get(accountId)
|
87 |
|
88 | if (!account) {
|
89 | log.error('could not find plugin for account id. accountId=%s', accountId)
|
90 | throw new Error('unknown account id. accountId=' + accountId)
|
91 | }
|
92 |
|
93 | return account.plugin
|
94 | }
|
95 |
|
96 | exists (accountId: string) {
|
97 | return this.accounts.has(accountId)
|
98 | }
|
99 |
|
100 | getAccountIds () {
|
101 | return Array.from(this.accounts.keys())
|
102 | }
|
103 |
|
104 | getAssetCode (accountId: string) {
|
105 | const account = this.accounts.get(accountId)
|
106 |
|
107 | if (!account) {
|
108 | log.error('no currency found. account=%s', accountId)
|
109 | return undefined
|
110 | }
|
111 |
|
112 | return account.info.assetCode
|
113 | }
|
114 |
|
115 | add (accountId: string, creds: any) {
|
116 | log.info('add account. accountId=%s', accountId)
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | try {
|
127 | this.config.validateAccount(accountId, creds)
|
128 | } catch (err) {
|
129 | if (err.name === 'InvalidJsonBodyError') {
|
130 | log.error('validation error in account config. id=%s', accountId)
|
131 | err.debugPrint(log.warn.bind(log))
|
132 | throw new Error('error while adding account, see error log for details.')
|
133 | }
|
134 |
|
135 | throw err
|
136 | }
|
137 |
|
138 | const plugin = this.getPluginFromCreds(accountId, creds)
|
139 | this.accounts.set(accountId, {
|
140 | info: creds,
|
141 | plugin
|
142 | })
|
143 |
|
144 | this.emit('add', accountId, plugin)
|
145 | }
|
146 |
|
147 | remove (accountId: string) {
|
148 | const plugin = this.getPlugin(accountId)
|
149 | if (!plugin) {
|
150 | return undefined
|
151 | }
|
152 | log.info('remove account. accountId=' + accountId)
|
153 |
|
154 | this.emit('remove', accountId, plugin)
|
155 |
|
156 | this.accounts.delete(accountId)
|
157 | return plugin
|
158 | }
|
159 |
|
160 | getInfo (accountId: string) {
|
161 | const account = this.accounts.get(accountId)
|
162 |
|
163 | if (!account) {
|
164 | throw new Error('unknown account id. accountId=' + accountId)
|
165 | }
|
166 |
|
167 | return account.info
|
168 | }
|
169 |
|
170 | getChildAddress (accountId: string) {
|
171 | const info = this.getInfo(accountId)
|
172 |
|
173 | if (info.relation !== 'child') {
|
174 | throw new Error('Can\'t generate child address for account that is isn\'t a child')
|
175 | }
|
176 |
|
177 | const ilpAddressSegment = info.ilpAddressSegment || accountId
|
178 |
|
179 | return this.address + '.' + ilpAddressSegment
|
180 | }
|
181 |
|
182 | getStatus () {
|
183 | const accounts = {}
|
184 | this.accounts.forEach((account, accountId) => {
|
185 | accounts[accountId] = {
|
186 |
|
187 | info: Object.assign({}, account.info, { options: undefined }),
|
188 | connected: account.plugin.isConnected(),
|
189 | adminInfo: !!account.plugin.getAdminInfo
|
190 | }
|
191 | })
|
192 | return {
|
193 | address: this.address,
|
194 | accounts
|
195 | }
|
196 | }
|
197 |
|
198 | private getPluginFromCreds (accountId: string, creds: any): PluginInstance {
|
199 |
|
200 | if (typeof creds.plugin === 'object') return creds.plugin
|
201 | const Plugin = require(creds.plugin)
|
202 |
|
203 | const api: any = {
|
204 | store: this.store.getPluginStore(accountId),
|
205 | log: createLogger(`${creds.plugin}[${accountId}]`)
|
206 | }
|
207 |
|
208 | const opts = Object.assign({
|
209 |
|
210 | _store: api.store,
|
211 | _log: api.log
|
212 | }, creds.options)
|
213 | return compat(new Plugin(opts, api))
|
214 | }
|
215 | }
|