Initial implementation of OrderbookChannelFactory

This commit is contained in:
Brandon Millman 2018-05-24 17:19:27 -07:00
parent 16ddd1edfc
commit 47debf0134
4 changed files with 113 additions and 88 deletions

View File

@ -1,5 +1,4 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as WebSocket from 'websocket';
import { import {
OrderbookChannel, OrderbookChannel,
@ -22,17 +21,16 @@ interface Subscription {
* that implements the standard relayer API v0 in a browser environment * that implements the standard relayer API v0 in a browser environment
*/ */
export class BrowserWebSocketOrderbookChannel implements OrderbookChannel { export class BrowserWebSocketOrderbookChannel implements OrderbookChannel {
private _apiEndpointUrl: string; private _client: WebSocket;
private _clientIfExists?: WebSocket.w3cwebsocket;
private _subscriptions: Subscription[] = []; private _subscriptions: Subscription[] = [];
/** /**
* Instantiates a new WebSocketOrderbookChannel instance * Instantiates a new WebSocketOrderbookChannel instance
* @param url The relayer API base WS url you would like to interact with * @param url The relayer API base WS url you would like to interact with
* @return An instance of WebSocketOrderbookChannel * @return An instance of WebSocketOrderbookChannel
*/ */
constructor(url: string) { constructor(client: WebSocket) {
assert.isUri('url', url); // assert.isUri('url', url);
this._apiEndpointUrl = url; this._client = client;
} }
/** /**
* Subscribe to orderbook snapshots and updates from the websocket * Subscribe to orderbook snapshots and updates from the websocket
@ -55,40 +53,31 @@ export class BrowserWebSocketOrderbookChannel implements OrderbookChannel {
requestId: this._subscriptions.length - 1, requestId: this._subscriptions.length - 1,
payload: subscriptionOpts, payload: subscriptionOpts,
}; };
if (_.isUndefined(this._clientIfExists)) { this._client.onerror = () => {
this._clientIfExists = new WebSocket.w3cwebsocket(this._apiEndpointUrl); this._alertAllHandlersToError(new Error('hello'));
this._clientIfExists.onopen = () => { };
this._sendMessage(subscribeMessage); this._client.onclose = () => {
}; _.forEach(this._subscriptions, subscription => {
this._clientIfExists.onerror = error => { subscription.handler.onClose(this, subscription.subscriptionOpts);
this._alertAllHandlersToError(error); });
}; };
this._clientIfExists.onclose = () => { this._client.onmessage = message => {
_.forEach(this._subscriptions, subscription => { this._handleWebSocketMessage(message);
subscription.handler.onClose(this, subscription.subscriptionOpts); };
}); this._sendMessage(subscribeMessage);
};
this._clientIfExists.onmessage = message => {
this._handleWebSocketMessage(message);
};
} else {
this._sendMessage(subscribeMessage);
}
} }
/** /**
* Close the websocket and stop receiving updates * Close the websocket and stop receiving updates
*/ */
public close(): void { public close(): void {
if (!_.isUndefined(this._clientIfExists)) { this._client.close();
this._clientIfExists.close();
}
} }
/** /**
* Send a message to the client if it has been instantiated and it is open * Send a message to the client if it has been instantiated and it is open
*/ */
private _sendMessage(message: any): void { private _sendMessage(message: any): void {
if (!_.isUndefined(this._clientIfExists) && this._clientIfExists.readyState === WebSocket.w3cwebsocket.OPEN) { if (this._client.readyState === WebSocket.OPEN) {
this._clientIfExists.send(JSON.stringify(message)); this._client.send(JSON.stringify(message));
} }
} }
/** /**

View File

@ -1,6 +1,7 @@
export { HttpClient } from './http_client'; export { HttpClient } from './http_client';
export { BrowserWebSocketOrderbookChannel } from './browser_ws_orderbook_channel'; export { BrowserWebSocketOrderbookChannel } from './browser_ws_orderbook_channel';
export { NodeWebSocketOrderbookChannel } from './node_ws_orderbook_channel'; export { NodeWebSocketOrderbookChannel } from './node_ws_orderbook_channel';
export { orderbookChannelFactory } from './orderbook_channel_factory';
export { export {
Client, Client,
FeesRequest, FeesRequest,

View File

@ -0,0 +1,33 @@
// import * as WebSocket from 'websocket';
import { BrowserWebSocketOrderbookChannel } from './browser_ws_orderbook_channel';
import { NodeWebSocketOrderbookChannel } from './node_ws_orderbook_channel';
export const orderbookChannelFactory = {
async createBrowserOrderbookChannelAsync(url: string): Promise<BrowserWebSocketOrderbookChannel> {
return new Promise<BrowserWebSocketOrderbookChannel>((resolve, reject) => {
const client = new WebSocket(url);
console.log(client);
client.onopen = () => {
const orderbookChannel = new BrowserWebSocketOrderbookChannel(client);
console.log(orderbookChannel);
resolve(orderbookChannel);
};
client.onerror = err => {
reject(err);
};
});
},
// async createNodeOrderbookChannelAsync(url: string): Promise<NodeWebSocketOrderbookChannel> {
// return new Promise<BrowserWebSocketOrderbookChannel>((resolve, reject) => {
// const client = new WebSocket.w3cwebsocket(url);
// client.onopen = () => {
// const orderbookChannel = new BrowserWebSocketOrderbookChannel(client);
// resolve(orderbookChannel);
// };
// client.onerror = err => {
// reject(err);
// };
// });
// },
};

View File

@ -1,61 +1,63 @@
import * as chai from 'chai'; // import * as chai from 'chai';
import * as dirtyChai from 'dirty-chai'; // import * as dirtyChai from 'dirty-chai';
import * as _ from 'lodash'; // import * as _ from 'lodash';
import 'mocha'; // import 'mocha';
// import * as WebSocket from 'websocket';
import { BrowserWebSocketOrderbookChannel } from '../src/browser_ws_orderbook_channel'; // import { BrowserWebSocketOrderbookChannel } from '../src/browser_ws_orderbook_channel';
chai.config.includeStack = true; // chai.config.includeStack = true;
chai.use(dirtyChai); // chai.use(dirtyChai);
const expect = chai.expect; // const expect = chai.expect;
describe('BrowserWebSocketOrderbookChannel', () => { // describe('BrowserWebSocketOrderbookChannel', () => {
const websocketUrl = 'ws://localhost:8080'; // const websocketUrl = 'ws://localhost:8080';
const orderbookChannel = new BrowserWebSocketOrderbookChannel(websocketUrl); // const client = new WebSocket.w3cwebsocket(websocketUrl);
const subscriptionOpts = { // const orderbookChannel = new BrowserWebSocketOrderbookChannel(client);
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', // const subscriptionOpts = {
quoteTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', // baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
snapshot: true, // quoteTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
limit: 100, // snapshot: true,
}; // limit: 100,
const emptyOrderbookChannelHandler = { // };
onSnapshot: () => { // const emptyOrderbookChannelHandler = {
_.noop(); // onSnapshot: () => {
}, // _.noop();
onUpdate: () => { // },
_.noop(); // onUpdate: () => {
}, // _.noop();
onError: () => { // },
_.noop(); // onError: () => {
}, // _.noop();
onClose: () => { // },
_.noop(); // onClose: () => {
}, // _.noop();
}; // },
describe('#subscribe', () => { // };
it('throws when subscriptionOpts does not conform to schema', () => { // describe('#subscribe', () => {
const badSubscribeCall = orderbookChannel.subscribe.bind( // it('throws when subscriptionOpts does not conform to schema', () => {
orderbookChannel, // const badSubscribeCall = orderbookChannel.subscribe.bind(
{}, // orderbookChannel,
emptyOrderbookChannelHandler, // {},
); // emptyOrderbookChannelHandler,
expect(badSubscribeCall).throws( // );
'Expected subscriptionOpts to conform to schema /RelayerApiOrderbookChannelSubscribePayload\nEncountered: {}\nValidation errors: instance requires property "baseTokenAddress", instance requires property "quoteTokenAddress"', // expect(badSubscribeCall).throws(
); // 'Expected subscriptionOpts to conform to schema /RelayerApiOrderbookChannelSubscribePayload\nEncountered: {}\nValidation errors: instance requires property "baseTokenAddress", instance requires property "quoteTokenAddress"',
}); // );
it('throws when handler has the incorrect members', () => { // });
const badSubscribeCall = orderbookChannel.subscribe.bind(orderbookChannel, subscriptionOpts, {}); // it('throws when handler has the incorrect members', () => {
expect(badSubscribeCall).throws( // const badSubscribeCall = orderbookChannel.subscribe.bind(orderbookChannel, subscriptionOpts, {});
'Expected handler.onSnapshot to be of type function, encountered: undefined', // expect(badSubscribeCall).throws(
); // 'Expected handler.onSnapshot to be of type function, encountered: undefined',
}); // );
it('does not throw when inputs are of correct types', () => { // });
const goodSubscribeCall = orderbookChannel.subscribe.bind( // it('does not throw when inputs are of correct types', () => {
orderbookChannel, // const goodSubscribeCall = orderbookChannel.subscribe.bind(
subscriptionOpts, // orderbookChannel,
emptyOrderbookChannelHandler, // subscriptionOpts,
); // emptyOrderbookChannelHandler,
expect(goodSubscribeCall).to.not.throw(); // );
}); // expect(goodSubscribeCall).to.not.throw();
}); // });
}); // });
// });