1
import reduct = require('reduct')
2 2
import compat from 'ilp-compat-plugin'
3 2
import Store from '../services/store'
4 2
import Config from './config'
5 2
import { EventEmitter } from 'events'
6
import { AccountInfo } from '../types/accounts'
7
import {
8
  ConnectOptions,
9
  PluginInstance
10
} from '../types/plugin'
11 2
import ILDCP = require('ilp-protocol-ildcp')
12

13 2
import { create as createLogger } from '../common/log'
14 2
const log = createLogger('accounts')
15

16
export interface AccountEntry {
17
  plugin: PluginInstance,
18
  info: AccountInfo
19
}
20

21 2
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 2
    super()
30

31 2
    this.config = deps(Config)
32 2
    this.store = deps(Store)
33

34 2
    this.address = this.config.ilpAddress || 'unknown'
35 2
    this.accounts = new Map()
36
  }
37

38
  async loadIlpAddress () {
39 2
    const inheritFrom = this.config.ilpAddressInheritFrom ||
40
      // Get account id of first parent
41
      [...this.accounts]
42 0
        .filter(([key, value]) => value.info.relation === 'parent')
43 0
        .map(([key]) => key)[0]
44

45 2
    if (this.config.ilpAddress === 'unknown' && !inheritFrom) {
46 0
      throw new Error('When there is no parent, ILP address must be specified in configuration.')
47 2
    } else if (this.config.ilpAddress === 'unknown' && inheritFrom) {
48 0
      const parent = this.getPlugin(inheritFrom)
49

50 0
      log.trace('connecting to parent. accountId=%s', inheritFrom)
51 0
      await parent.connect({})
52

53 0
      const ildcpInfo = await ILDCP.fetch(parent.sendData.bind(parent))
54

55 0
      this.setOwnAddress(ildcpInfo.clientAddress)
56

57 2
      if (this.address === 'unknown') {
58 0
        log.error('could not get ilp address from parent.')
59 0
        throw new Error('no ilp address configured.')
60
      }
61
    }
62
  }
63

64
  async connect (options: ConnectOptions) {
65 2
    const unconnectedAccounts = Array.from(this.accounts.values())
66 2
      .filter(account => !account.plugin.isConnected())
67 2
    return Promise.all(unconnectedAccounts.map(account => account.plugin.connect(options)))
68
  }
69

70
  async disconnect () {
71 0
    const connectedAccounts = Array.from(this.accounts.values())
72 0
      .filter(account => account.plugin.isConnected())
73 0
    return Promise.all(connectedAccounts.map(account => account.plugin.disconnect()))
74
  }
75

76
  getOwnAddress () {
77 2
    return this.address
78
  }
79

80
  setOwnAddress (newAddress: string) {
81 0
    log.trace('setting ilp address. oldAddress=%s newAddress=%s', this.address, newAddress)
82 0
    this.address = newAddress
83
  }
84

85
  getPlugin (accountId: string) {
86 2
    const account = this.accounts.get(accountId)
87

88 2
    if (!account) {
89 2
      log.error('could not find plugin for account id. accountId=%s', accountId)
90 2
      throw new Error('unknown account id. accountId=' + accountId)
91
    }
92

93 2
    return account.plugin
94
  }
95

96
  exists (accountId: string) {
97 2
    return this.accounts.has(accountId)
98
  }
99

100
  getAccountIds () {
101 2
    return Array.from(this.accounts.keys())
102
  }
103

104
  getAssetCode (accountId: string) {
105 0
    const account = this.accounts.get(accountId)
106

107 2
    if (!account) {
108 0
      log.error('no currency found. account=%s', accountId)
109 0
      return undefined
110
    }
111

112 0
    return account.info.assetCode
113
  }
114

115
  add (accountId: string, creds: any) {
116 2
    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 2
    try {
127 2
      this.config.validateAccount(accountId, creds)
128
    } catch (err) {
129 2
      if (err.name === 'InvalidJsonBodyError') {
130 0
        log.error('validation error in account config. id=%s', accountId)
131 0
        err.debugPrint(log.warn.bind(log))
132 0
        throw new Error('error while adding account, see error log for details.')
133
      }
134

135 0
      throw err
136
    }
137

138 2
    const plugin = this.getPluginFromCreds(accountId, creds)
139 2
    this.accounts.set(accountId, {
140
      info: creds,
141
      plugin
142
    })
143

144 2
    this.emit('add', accountId, plugin)
145
  }
146

147
  remove (accountId: string) {
148 2
    const plugin = this.getPlugin(accountId)
149 2
    if (!plugin) {
150 0
      return undefined
151
    }
152 2
    log.info('remove account. accountId=' + accountId)
153

154 2
    this.emit('remove', accountId, plugin)
155

156 2
    this.accounts.delete(accountId)
157 2
    return plugin
158
  }
159

160
  getInfo (accountId: string) {
161 2
    const account = this.accounts.get(accountId)
162

163 2
    if (!account) {
164 2
      throw new Error('unknown account id. accountId=' + accountId)
165
    }
166

167 2
    return account.info
168
  }
169

170
  getChildAddress (accountId: string) {
171 2
    const info = this.getInfo(accountId)
172

173 2
    if (info.relation !== 'child') {
174 0
      throw new Error('Can\'t generate child address for account that is isn\'t a child')
175
    }
176

177 2
    const ilpAddressSegment = info.ilpAddressSegment || accountId
178

179 2
    return this.address + '.' + ilpAddressSegment
180
  }
181

182
  getStatus () {
183 2
    const accounts = {}
184 2
    this.accounts.forEach((account, accountId) => {
185 2
      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 2
    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 2
    if (typeof creds.plugin === 'object') return creds.plugin
201 2
    const Plugin = require(creds.plugin)
202

203 2
    const api: any = {
204
      store: this.store.getPluginStore(accountId),
205
      log: createLogger(`${creds.plugin}[${accountId}]`)
206
    }
207

208 2
    const opts = Object.assign({
209
      // Provide old deprecated _store and _log properties
210
      _store: api.store,
211
      _log: api.log
212
    }, creds.options)
213 2
    return compat(new Plugin(opts, api))
214
  }
215
}

Read our documentation on viewing source code .

Loading