UNPKG

6.11 kBPlain TextView Raw
1import reduct = require('reduct')
2import compat from 'ilp-compat-plugin'
3import Store from '../services/store'
4import Config from './config'
5import { EventEmitter } from 'events'
6import { AccountInfo } from '../types/accounts'
7import {
8 ConnectOptions,
9 PluginInstance
10} from '../types/plugin'
11import ILDCP = require('ilp-protocol-ildcp')
12
13import { create as createLogger } from '../common/log'
14const log = createLogger('accounts')
15
16export interface AccountEntry {
17 plugin: PluginInstance,
18 info: AccountInfo
19}
20
21export 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 // Get account id of first parent
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 // Although cloning the options object that comes in from
119 // code that includes ilp-connector is good practice,
120 // this breaks for instance when the plugin options
121 // contain for instance a https server like in `wsOpts` in
122 // https://github.com/interledgerjs/ilp-plugin-mini-accounts/blob/a77f1a6b984b6816856a0948dfa57fe95e7ddd8b/README.md#example
123 //
124 // creds = cloneDeep(creds)
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 // Set info.options to undefined so that credentials aren't exposed.
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 // Use a plugin instance directly.
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 // Provide old deprecated _store and _log properties
210 _store: api.store,
211 _log: api.log
212 }, creds.options)
213 return compat(new Plugin(opts, api))
214 }
215}