Skip to content

Instantly share code, notes, and snippets.

@sentientwaffle
Created July 10, 2018 17:38
Show Gist options
  • Save sentientwaffle/c69a71c62afb16c58ff27d607f561a92 to your computer and use it in GitHub Desktop.
Save sentientwaffle/c69a71c62afb16c58ff27d607f561a92 to your computer and use it in GitHub Desktop.
diff --git a/index.js b/src/index.ts
index 2df5b31..d7c016a 100644
--- a/index.js
+++ b/src/index.ts
@@ -1,28 +1,80 @@
-'use strict'
-
-const crypto = require('crypto')
+import * as crypto from 'crypto'
const BtpPacket = require('btp-packet')
-const WebSocket = require('ws')
-const assert = require('assert')
-const debug = require('debug')
-const AbstractBtpPlugin = require('ilp-plugin-btp')
-const base64url = require('base64url')
-const ILDCP = require('ilp-protocol-ildcp')
-const IlpPacket = require('ilp-packet')
+import * as WebSocket from 'ws'
+import * as assert from 'assert'
+import AbstractBtpPlugin, * as BtpPlugin from 'ilp-plugin-btp'
+import * as ILDCP from 'ilp-protocol-ildcp'
+import * as IlpPacket from 'ilp-packet'
const { Errors } = IlpPacket
const StoreWrapper = require('ilp-store-wrapper')
-const OriginWhitelist = require('./src/lib/origin-whitelist')
-const Token = require('./src/token')
+import OriginWhitelist from './lib/origin-whitelist'
+import Token from './token'
+import { Store, StoreWrapper } from './types'
const createLogger = require('ilp-logger')
+import { IncomingMessage } from 'http'
+
+export { BtpSubProtocol } from 'ilp-plugin-btp'
const DEBUG_NAMESPACE = 'ilp-plugin-mini-accounts'
-function tokenToAccount (token) {
- return base64url(crypto.createHash('sha256').update(token).digest('sha256'))
+function tokenToAccount (token: string): string {
+ return BtpPacket.base64url(crypto.createHash('sha256').update(token).digest())
+}
+
+interface Logger {
+ info (...msg: any[]): void
+ warn (...msg: any[]): void
+ error (...msg: any[]): void
+ debug (...msg: any[]): void
+ trace (...msg: any[]): void
+}
+
+export type Protocol = {
+ protocolName: string
+ contentType: number
+ data: Buffer
+}
+
+export interface BtpData {
+ data: {
+ protocolData: Protocol[]
+ }
+ requestId: number
}
-class Plugin extends AbstractBtpPlugin {
- constructor (opts, { log, store } = {}) {
+/* tslint:disable-next-line:no-empty */
+function noopTrace (...msg: any[]): void { }
+
+export default class Plugin extends AbstractBtpPlugin {
+ static version = 2
+
+ private _wsOpts: WebSocket.ServerOptions
+ protected _currencyScale: number
+ private _debugHostIldcpInfo?: ILDCP.IldcpResponse
+ protected _log: Logger
+ private _trace: (...msg: any[]) => void
+ private _connections: Map<string, Set<WebSocket>> = new Map()
+ private _allowedOrigins: OriginWhitelist
+ protected _store?: StoreWrapper
+
+ private _hostIldcpInfo: ILDCP.IldcpResponse
+ protected _prefix: string
+ // These can be overridden.
+ // TODO can this be overridden via `extends`??
+ protected _handleCustomData: (from: string, btpPacket: BtpPlugin.BtpPacket) => Promise<BtpPlugin.BtpSubProtocol[]>
+ protected _handlePrepareResponse: (destination: string, parsedIlpResponse: IlpPacket.IlpPacket, preparePacket: IlpPacket.IlpPacket) => void
+
+ constructor (opts: {
+ port?: number,
+ wsOpts?: WebSocket.ServerOptions,
+ currencyScale?: number,
+ debugHostIldcpInfo?: ILDCP.IldcpResponse,
+ allowedOrigins?: string[],
+ _store?: Store
+ }, { log, store }: {
+ log?: Logger,
+ store?: Store
+ } = {}) {
super({})
const defaultPort = opts.port || 3000
this._wsOpts = opts.wsOpts || { port: defaultPort }
@@ -30,18 +82,31 @@ class Plugin extends AbstractBtpPlugin {
this._debugHostIldcpInfo = opts.debugHostIldcpInfo
this._log = log || createLogger(DEBUG_NAMESPACE)
- this._log.trace = this._log.trace || debug(this._log.debug.namespace + ':trace')
- this._wss = null
- this._connections = new Map()
+ this._log.trace = this._log.trace || noopTrace
- this._allowedOrigins = new OriginWhitelist(opts.allowedOrigins)
+ this._allowedOrigins = new OriginWhitelist(opts.allowedOrigins || [])
if (store || opts._store) {
this._store = new StoreWrapper(store || opts._store)
}
}
- ilpAddressToAccount (ilpAddress) {
+ /* tslint:disable:no-empty */
+ // These can be overridden.
+ protected async _preConnect (): Promise<void> {}
+ // FIXME: right now plugin-btp and plugin-mini-accounts use different signatures
+ // for _connect -- ideally mini-accounts would use a different function name, but
+ // this is as close as it can get without being a breaking change.
+ // @ts-ignore
+ protected async _connect (address: string, authPacket: BtpData, opts: {
+ ws: WebSocket,
+ req: IncomingMessage
+ }): Promise<void> {}
+ protected async _close (account: string, err?: Error): Promise<void> {}
+ protected _sendPrepare (destination: string, parsedPacket: IlpPacket.IlpPacket): void {}
+ /* tslint:enable:no-empty */
+
+ ilpAddressToAccount (ilpAddress: string): string {
if (ilpAddress.substr(0, this._prefix.length) !== this._prefix) {
throw new Error('ILP address (' + ilpAddress + ') must start with prefix (' + this._prefix + ')')
}
@@ -49,10 +114,17 @@ class Plugin extends AbstractBtpPlugin {
return ilpAddress.substr(this._prefix.length).split('.')[0]
}
- async connect () {
+ async connect (): Promise<void> {
if (this._wss) return
- this._hostIldcpInfo = this._debugHostIldcpInfo || await ILDCP.fetch(this._dataHandler.bind(this))
+ if (this._debugHostIldcpInfo) {
+ this._hostIldcpInfo = this._debugHostIldcpInfo
+ } else if (this._dataHandler) {
+ this._hostIldcpInfo = await ILDCP.fetch(this._dataHandler.bind(this))
+ } else {
+ throw new Error('no request handler registered')
+ }
+
this._prefix = this._hostIldcpInfo.clientAddress + '.'
if (this._preConnect) {
@@ -68,7 +140,7 @@ class Plugin extends AbstractBtpPlugin {
const wss = this._wss = new WebSocket.Server(this._wsOpts)
wss.on('connection', (wsIncoming, req) => {
this._log.trace('got connection')
- if (req.headers && req.headers.origin && !this._allowedOrigins.isOk(req.headers.origin)) {
+ if (typeof req.headers.origin === 'string' && !this._allowedOrigins.isOk(req.headers.origin)) {
this._log.debug(`Closing a websocket connection received from a browser. Origin is ${req.headers.origin}`)
this._log.debug('If you are running moneyd, you may allow this origin with the flag --allow-origin.' +
' Run moneyd --help for details.')
@@ -76,10 +148,10 @@ class Plugin extends AbstractBtpPlugin {
return
}
- let token
- let account
+ let token: string
+ let account: string
- const closeHandler = error => {
+ const closeHandler = (error?: Error) => {
this._log.debug('incoming ws closed. error=', error)
if (account) this._removeConnection(account, wsIncoming)
if (this._close) {
@@ -95,13 +167,13 @@ class Plugin extends AbstractBtpPlugin {
// The first message must be an auth packet
// with the macaroon as the auth_token
- let authPacket
+ let authPacket: BtpPlugin.BtpPacket
wsIncoming.once('message', async (binaryAuthMessage) => {
try {
authPacket = BtpPacket.deserialize(binaryAuthMessage)
- assert.equal(authPacket.type, BtpPacket.TYPE_MESSAGE, 'First message sent over BTP connection must be auth packet')
+ assert.strictEqual(authPacket.type, BtpPacket.TYPE_MESSAGE, 'First message sent over BTP connection must be auth packet')
assert(authPacket.data.protocolData.length >= 2, 'Auth packet must have auth and auth_token subprotocols')
- assert.equal(authPacket.data.protocolData[0].protocolName, 'auth', 'First subprotocol must be auth')
+ assert.strictEqual(authPacket.data.protocolData[0].protocolName, 'auth', 'First subprotocol must be auth')
for (let subProtocol of authPacket.data.protocolData) {
if (subProtocol.protocolName === 'auth_token') {
// TODO: Do some validation on the token
@@ -118,9 +190,9 @@ class Plugin extends AbstractBtpPlugin {
this._log.trace('got auth info. token=' + token, 'account=' + account)
if (this._store) {
- const storedToken = await Token.load({account, store: this._store})
- const receivedToken = new Token({account, token, store: this._store})
- if (storedToken.exists()) {
+ const storedToken = await Token.load({ account, store: this._store })
+ const receivedToken = new Token({ account, token, store: this._store })
+ if (storedToken) {
if (!storedToken.equal(receivedToken)) {
throw new Error('incorrect token for account.' +
' account=' + account +
@@ -148,7 +220,7 @@ class Plugin extends AbstractBtpPlugin {
data: err.message || err.name,
triggeredAt: new Date().toISOString()
}, authPacket.requestId, [])
- wsIncoming.send(errorResponse)
+ wsIncoming.send(errorResponse) // TODO throws error "not opened"
}
wsIncoming.close()
return
@@ -180,8 +252,6 @@ class Plugin extends AbstractBtpPlugin {
})
})
})
-
- return null
}
async disconnect () {
@@ -190,10 +260,9 @@ class Plugin extends AbstractBtpPlugin {
}
if (this._wss) {
- return new Promise(resolve => {
- this._wss.close(resolve)
- this._wss = null
- })
+ const wss = this._wss
+ await new Promise((resolve) => wss.close(resolve))
+ this._wss = null
}
}
@@ -201,7 +270,7 @@ class Plugin extends AbstractBtpPlugin {
return !!this._wss
}
- async sendData (buffer) {
+ async sendData (buffer: Buffer): Promise<Buffer> {
const parsedPacket = IlpPacket.deserializeIlpPacket(buffer)
let destination
@@ -209,11 +278,11 @@ class Plugin extends AbstractBtpPlugin {
switch (parsedPacket.type) {
case IlpPacket.Type.TYPE_ILP_PAYMENT:
case IlpPacket.Type.TYPE_ILP_FORWARDED_PAYMENT:
- destination = parsedPacket.data.account
+ destination = parsedPacket.data['account']
break
case IlpPacket.Type.TYPE_ILP_PREPARE:
isPrepare = true
- destination = parsedPacket.data.destination
+ destination = parsedPacket.data['destination']
if (this._sendPrepare) {
this._sendPrepare(destination, parsedPacket)
}
@@ -221,7 +290,7 @@ class Plugin extends AbstractBtpPlugin {
case IlpPacket.Type.TYPE_ILQP_LIQUIDITY_REQUEST:
case IlpPacket.Type.TYPE_ILQP_BY_SOURCE_REQUEST:
case IlpPacket.Type.TYPE_ILQP_BY_DESTINATION_REQUEST:
- destination = parsedPacket.data.destinationAccount
+ destination = parsedPacket.data['destinationAccount']
break
default:
throw new Error('can\'t route packet with no destination. type=' + parsedPacket.type)
@@ -237,7 +306,7 @@ class Plugin extends AbstractBtpPlugin {
const response = await this._call(destination, {
type: BtpPacket.TYPE_MESSAGE,
- requestId: crypto.randomBytes(4).readUInt32BE(),
+ requestId: crypto.randomBytes(4).readUInt32BE(0),
data: { protocolData: [{
protocolName: 'ilp',
contentType: BtpPacket.MIME_APPLICATION_OCTET_STREAM,
@@ -249,15 +318,18 @@ class Plugin extends AbstractBtpPlugin {
const parsedIlpResponse = IlpPacket.deserializeIlpPacket(ilpResponse.data)
if (parsedIlpResponse.type === IlpPacket.Type.TYPE_ILP_FULFILL) {
+ const executionCondition = parsedPacket.data['executionCondition'] || Buffer.alloc(0)
+ /* tslint:disable-next-line:no-unnecessary-type-assertion */
+ const fulfillResponse = parsedIlpResponse.data as IlpPacket.IlpFulfill
if (!crypto.createHash('sha256')
- .update(parsedIlpResponse.data.fulfillment)
+ .update(fulfillResponse.fulfillment)
.digest()
- .equals(parsedPacket.data.executionCondition)) {
+ .equals(executionCondition)) {
return IlpPacket.errorToReject(this._hostIldcpInfo.clientAddress,
new Errors.WrongConditionError(
'condition and fulfillment don\'t match. ' +
- `condition=${parsedPacket.data.executionCondition.toString('hex')} ` +
- `fulfillment=${parsedIlpResponse.data.fulfillment.toString('hex')}`))
+ `condition=${executionCondition.toString('hex')} ` +
+ `fulfillment=${fulfillResponse.fulfillment.toString('hex')}`))
}
}
@@ -274,13 +346,13 @@ class Plugin extends AbstractBtpPlugin {
: Buffer.alloc(0)
}
- async _handleData (from, btpPacket) {
+ protected async _handleData (from: string, btpPacket: BtpPlugin.BtpPacket): Promise<BtpPlugin.BtpSubProtocol[]> {
const { ilp } = this.protocolDataToIlpAndCustom(btpPacket.data)
if (ilp) {
const parsedPacket = IlpPacket.deserializeIlpPacket(ilp)
- if (parsedPacket.data.destination === 'peer.config') {
+ if (parsedPacket.data['destination'] === 'peer.config') {
this._log.trace('responding to ILDCP request. clientAddress=%s', from)
return [{
protocolName: 'ilp',
@@ -298,7 +370,7 @@ class Plugin extends AbstractBtpPlugin {
}
if (this._handleCustomData) {
- this._trace('passing non-ILDCP data to custom handler')
+ this._log.trace('passing non-ILDCP data to custom handler')
return this._handleCustomData(from, btpPacket)
}
@@ -315,7 +387,7 @@ class Plugin extends AbstractBtpPlugin {
return this.ilpAndCustomToProtocolData({ ilp: response })
}
- async _handleOutgoingBtpPacket (to, btpPacket) {
+ protected async _handleOutgoingBtpPacket (to: string, btpPacket: BtpPlugin.BtpPacket) {
if (!to.startsWith(this._prefix)) {
throw new Error(`invalid destination, must start with prefix. destination=${to} prefix=${this._prefix}`)
}
@@ -335,11 +407,9 @@ class Plugin extends AbstractBtpPlugin {
this._log.debug('unable to send btp message to client: ' + errorInfo, 'btp packet:', JSON.stringify(btpPacket))
})
})
-
- return null
}
- _addConnection (account, wsIncoming) {
+ private _addConnection (account: string, wsIncoming: WebSocket) {
let connections = this._connections.get(account)
if (!connections) {
this._connections.set(account, connections = new Set())
@@ -347,7 +417,7 @@ class Plugin extends AbstractBtpPlugin {
connections.add(wsIncoming)
}
- _removeConnection (account, wsIncoming) {
+ private _removeConnection (account: string, wsIncoming: WebSocket) {
const connections = this._connections.get(account)
if (!connections) return
connections.delete(wsIncoming)
@@ -356,7 +426,3 @@ class Plugin extends AbstractBtpPlugin {
}
}
}
-
-Plugin.version = 2
-
-module.exports = Plugin
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment