Created
July 10, 2018 17:38
-
-
Save sentientwaffle/c69a71c62afb16c58ff27d607f561a92 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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