Add requestId to subscription messages and update json-schemas

This commit is contained in:
Brandon Millman
2017-12-04 23:58:33 -08:00
parent c0015c2c11
commit 20e28d6c70
13 changed files with 101 additions and 50 deletions

View File

@@ -1,5 +1,9 @@
# CHANGELOG # CHANGELOG
vx.x.x
------------------------
* Expose WebSocketOrderbookChannel and associated types to public interface
v0.2.0 - _November 29, 2017_ v0.2.0 - _November 29, 2017_
------------------------ ------------------------
* Add SignedOrder and TokenTradeInfo to the public interface * Add SignedOrder and TokenTradeInfo to the public interface

View File

@@ -1,10 +1,14 @@
export {HttpClient} from './http_client'; export {HttpClient} from './http_client';
export {WebSocketOrderbookChannel} from './ws_orderbook_channel';
export { export {
Client, Client,
ECSignature, ECSignature,
FeesRequest, FeesRequest,
FeesResponse, FeesResponse,
Order, Order,
OrderbookChannel,
OrderbookChannelHandler,
OrderbookChannelSubscriptionOpts,
OrderbookRequest, OrderbookRequest,
OrderbookResponse, OrderbookResponse,
OrdersRequest, OrdersRequest,

View File

@@ -57,9 +57,12 @@ export interface OrderbookChannelSubscriptionOpts {
} }
export interface OrderbookChannelHandler { export interface OrderbookChannelHandler {
onSnapshot: (channel: OrderbookChannel, snapshot: OrderbookResponse) => void; onSnapshot: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts,
onUpdate: (channel: OrderbookChannel, order: SignedOrder) => void; snapshot: OrderbookResponse) => void;
onError: (channel: OrderbookChannel, err: Error) => void; onUpdate: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts,
order: SignedOrder) => void;
onError: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts,
err: Error) => void;
onClose: (channel: OrderbookChannel) => void; onClose: (channel: OrderbookChannel) => void;
} }
@@ -76,16 +79,19 @@ export enum OrderbookChannelMessageTypes {
export interface SnapshotOrderbookChannelMessage { export interface SnapshotOrderbookChannelMessage {
type: OrderbookChannelMessageTypes.Snapshot; type: OrderbookChannelMessageTypes.Snapshot;
requestId: number;
payload: OrderbookResponse; payload: OrderbookResponse;
} }
export interface UpdateOrderbookChannelMessage { export interface UpdateOrderbookChannelMessage {
type: OrderbookChannelMessageTypes.Update; type: OrderbookChannelMessageTypes.Update;
requestId: number;
payload: SignedOrder; payload: SignedOrder;
} }
export interface UnknownOrderbookChannelMessage { export interface UnknownOrderbookChannelMessage {
type: OrderbookChannelMessageTypes.Unknown; type: OrderbookChannelMessageTypes.Unknown;
requestId: number;
payload: undefined; payload: undefined;
} }

View File

@@ -15,28 +15,24 @@ export const orderbookChannelMessageParsers = {
const messageObj = JSON.parse(utf8Data); const messageObj = JSON.parse(utf8Data);
const type: string = _.get(messageObj, 'type'); const type: string = _.get(messageObj, 'type');
assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`); assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`);
assert.isString('type', type);
switch (type) { switch (type) {
case (OrderbookChannelMessageTypes.Snapshot): { case (OrderbookChannelMessageTypes.Snapshot): {
assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema); assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema);
const orderbook = messageObj.payload; const orderbook = messageObj.payload;
typeConverters.convertOrderbookStringFieldsToBigNumber(orderbook); typeConverters.convertOrderbookStringFieldsToBigNumber(orderbook);
return { return messageObj;
type,
payload: orderbook,
};
} }
case (OrderbookChannelMessageTypes.Update): { case (OrderbookChannelMessageTypes.Update): {
assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema); assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema);
const order = messageObj.payload; const order = messageObj.payload;
typeConverters.convertOrderStringFieldsToBigNumber(order); typeConverters.convertOrderStringFieldsToBigNumber(order);
return { return messageObj;
type,
payload: order,
};
} }
default: { default: {
return { return {
type: OrderbookChannelMessageTypes.Unknown, type: OrderbookChannelMessageTypes.Unknown,
requestId: 0,
payload: undefined, payload: undefined,
}; };
} }

View File

@@ -22,9 +22,10 @@ export class WebSocketOrderbookChannel implements OrderbookChannel {
private apiEndpointUrl: string; private apiEndpointUrl: string;
private client: WebSocket.client; private client: WebSocket.client;
private connectionIfExists?: WebSocket.connection; private connectionIfExists?: WebSocket.connection;
private subscriptionCounter = 0;
/** /**
* Instantiates a new WebSocketOrderbookChannel instance * Instantiates a new WebSocketOrderbookChannel instance
* @param url The base url for making API calls * @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(url: string) {
@@ -46,23 +47,25 @@ export class WebSocketOrderbookChannel implements OrderbookChannel {
assert.isFunction('handler.onUpdate', _.get(handler, 'onUpdate')); assert.isFunction('handler.onUpdate', _.get(handler, 'onUpdate'));
assert.isFunction('handler.onError', _.get(handler, 'onError')); assert.isFunction('handler.onError', _.get(handler, 'onError'));
assert.isFunction('handler.onClose', _.get(handler, 'onClose')); assert.isFunction('handler.onClose', _.get(handler, 'onClose'));
this.subscriptionCounter += 1;
const subscribeMessage = { const subscribeMessage = {
type: 'subscribe', type: 'subscribe',
channel: 'orderbook', channel: 'orderbook',
requestId: this.subscriptionCounter,
payload: subscriptionOpts, payload: subscriptionOpts,
}; };
this._getConnection((error, connection) => { this._getConnection((error, connection) => {
if (!_.isUndefined(error)) { if (!_.isUndefined(error)) {
handler.onError(this, error); handler.onError(this, subscriptionOpts, error);
} else if (!_.isUndefined(connection) && connection.connected) { } else if (!_.isUndefined(connection) && connection.connected) {
connection.on(WebsocketConnectionEventType.Error, wsError => { connection.on(WebsocketConnectionEventType.Error, wsError => {
handler.onError(this, wsError); handler.onError(this, subscriptionOpts, wsError);
}); });
connection.on(WebsocketConnectionEventType.Close, () => { connection.on(WebsocketConnectionEventType.Close, () => {
handler.onClose(this); handler.onClose(this);
}); });
connection.on(WebsocketConnectionEventType.Message, message => { connection.on(WebsocketConnectionEventType.Message, message => {
this._handleWebSocketMessage(message, handler); this._handleWebSocketMessage(subscribeMessage.requestId, subscriptionOpts, message, handler);
}); });
connection.sendUTF(JSON.stringify(subscribeMessage)); connection.sendUTF(JSON.stringify(subscribeMessage));
} }
@@ -90,30 +93,34 @@ export class WebSocketOrderbookChannel implements OrderbookChannel {
this.client.connect(this.apiEndpointUrl); this.client.connect(this.apiEndpointUrl);
} }
} }
private _handleWebSocketMessage(message: WebSocket.IMessage, handler: OrderbookChannelHandler): void { private _handleWebSocketMessage(requestId: number, subscriptionOpts: OrderbookChannelSubscriptionOpts,
message: WebSocket.IMessage, handler: OrderbookChannelHandler): void {
if (!_.isUndefined(message.utf8Data)) { if (!_.isUndefined(message.utf8Data)) {
try { try {
const utf8Data = message.utf8Data; const utf8Data = message.utf8Data;
const parserResult = orderbookChannelMessageParsers.parser(utf8Data); const parserResult = orderbookChannelMessageParsers.parser(utf8Data);
const type = parserResult.type; const type = parserResult.type;
switch (parserResult.type) { if (parserResult.requestId === requestId) {
case (OrderbookChannelMessageTypes.Snapshot): { switch (parserResult.type) {
handler.onSnapshot(this, parserResult.payload); case (OrderbookChannelMessageTypes.Snapshot): {
break; handler.onSnapshot(this, subscriptionOpts, parserResult.payload);
} break;
case (OrderbookChannelMessageTypes.Update): { }
handler.onUpdate(this, parserResult.payload); case (OrderbookChannelMessageTypes.Update): {
break; handler.onUpdate(this, subscriptionOpts, parserResult.payload);
} break;
default: { }
handler.onError(this, new Error(`Message has missing a type parameter: ${utf8Data}`)); default: {
handler.onError(
this, subscriptionOpts, new Error(`Message has missing a type parameter: ${utf8Data}`));
}
} }
} }
} catch (error) { } catch (error) {
handler.onError(this, error); handler.onError(this, subscriptionOpts, error);
} }
} else { } else {
handler.onError(this, new Error(`Message does not contain utf8Data`)); handler.onError(this, subscriptionOpts, new Error(`Message does not contain utf8Data`));
} }
} }
} }

View File

@@ -5,13 +5,13 @@ const orderbookJsonString = JSON.stringify(orderbookJSON);
export const snapshotOrderbookChannelMessage = `{ export const snapshotOrderbookChannelMessage = `{
"type": "snapshot", "type": "snapshot",
"channel": "orderbook", "channel": "orderbook",
"channelId": 1, "requestId": 1,
"payload": ${orderbookJsonString} "payload": ${orderbookJsonString}
}`; }`;
export const malformedSnapshotOrderbookChannelMessage = `{ export const malformedSnapshotOrderbookChannelMessage = `{
"type": "snapshot", "type": "snapshot",
"channel": "orderbook", "channel": "orderbook",
"channelId": 1, "requestId": 1,
"payload": {} "payload": {}
}`; }`;

View File

@@ -5,6 +5,6 @@ const orderJSONString = JSON.stringify(orderResponseJSON);
export const unknownOrderbookChannelMessage = `{ export const unknownOrderbookChannelMessage = `{
"type": "superGoodUpdate", "type": "superGoodUpdate",
"channel": "orderbook", "channel": "orderbook",
"channelId": 1, "requestId": 1,
"payload": ${orderJSONString} "payload": ${orderJSONString}
}`; }`;

View File

@@ -5,13 +5,13 @@ const orderJSONString = JSON.stringify(orderResponseJSON);
export const updateOrderbookChannelMessage = `{ export const updateOrderbookChannelMessage = `{
"type": "update", "type": "update",
"channel": "orderbook", "channel": "orderbook",
"channelId": 1, "requestId": 1,
"payload": ${orderJSONString} "payload": ${orderJSONString}
}`; }`;
export const malformedUpdateOrderbookChannelMessage = `{ export const malformedUpdateOrderbookChannelMessage = `{
"type": "update", "type": "update",
"channel": "orderbook", "channel": "orderbook",
"channelId": 1, "requestId": 1,
"payload": {} "payload": {}
}`; }`;

View File

@@ -41,12 +41,22 @@ describe('orderbookChannelMessageParsers', () => {
it('throws when message does not include a type', () => { it('throws when message does not include a type', () => {
const typelessMessage = `{ const typelessMessage = `{
"channel": "orderbook", "channel": "orderbook",
"channelId": 1, "requestId": 1,
"payload": {} "payload": {}
}`; }`;
const badCall = () => orderbookChannelMessageParsers.parser(typelessMessage); const badCall = () => orderbookChannelMessageParsers.parser(typelessMessage);
expect(badCall).throws(`Message is missing a type parameter: ${typelessMessage}`); expect(badCall).throws(`Message is missing a type parameter: ${typelessMessage}`);
}); });
it('throws when type is not a string', () => {
const messageWithBadType = `{
"type": 1,
"channel": "orderbook",
"requestId": 1,
"payload": {}
}`;
const badCall = () => orderbookChannelMessageParsers.parser(messageWithBadType);
expect(badCall).throws('Expected type to be of type string, encountered: 1');
});
it('throws when snapshot message has malformed payload', () => { it('throws when snapshot message has malformed payload', () => {
const badCall = () => const badCall = () =>
orderbookChannelMessageParsers.parser(malformedSnapshotOrderbookChannelMessage); orderbookChannelMessageParsers.parser(malformedSnapshotOrderbookChannelMessage);

View File

@@ -4,9 +4,10 @@ export const relayerApiOrderbookChannelSubscribeSchema = {
properties: { properties: {
type: {enum: ['subscribe']}, type: {enum: ['subscribe']},
channel: {enum: ['orderbook']}, channel: {enum: ['orderbook']},
requestId: {type: 'number'},
payload: {$ref: '/RelayerApiOrderbookChannelSubscribePayload'}, payload: {$ref: '/RelayerApiOrderbookChannelSubscribePayload'},
}, },
required: ['type', 'channel', 'payload'], required: ['type', 'channel', 'requestId', 'payload'],
}; };
export const relayerApiOrderbookChannelSubscribePayload = { export const relayerApiOrderbookChannelSubscribePayload = {

View File

@@ -4,10 +4,10 @@ export const relayerApiOrderbookChannelSnapshotSchema = {
properties: { properties: {
type: {enum: ['snapshot']}, type: {enum: ['snapshot']},
channel: {enum: ['orderbook']}, channel: {enum: ['orderbook']},
channelId: {type: 'number'}, requestId: {type: 'number'},
payload: {$ref: '/RelayerApiOrderbookChannelSnapshotPayload'}, payload: {$ref: '/RelayerApiOrderbookChannelSnapshotPayload'},
}, },
required: ['type', 'channel', 'channelId', 'payload'], required: ['type', 'channel', 'requestId', 'payload'],
}; };
export const relayerApiOrderbookChannelSnapshotPayload = { export const relayerApiOrderbookChannelSnapshotPayload = {

View File

@@ -4,8 +4,8 @@ export const relayerApiOrderbookChannelUpdateSchema = {
properties: { properties: {
type: {enum: ['update']}, type: {enum: ['update']},
channel: {enum: ['orderbook']}, channel: {enum: ['orderbook']},
channelId: {type: 'number'}, requestId: {type: 'number'},
payload: {$ref: '/SignedOrder'}, payload: {$ref: '/SignedOrder'},
}, },
required: ['type', 'channel', 'channelId', 'payload'], required: ['type', 'channel', 'requestId', 'payload'],
}; };

View File

@@ -432,6 +432,7 @@ describe('Schema', () => {
{ {
type: 'subscribe', type: 'subscribe',
channel: 'orderbook', channel: 'orderbook',
requestId: 1,
payload: { payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
@@ -442,6 +443,7 @@ describe('Schema', () => {
{ {
type: 'subscribe', type: 'subscribe',
channel: 'orderbook', channel: 'orderbook',
requestId: 1,
payload: { payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
@@ -453,9 +455,20 @@ describe('Schema', () => {
it('should fail for invalid orderbook channel websocket subscribe message', () => { it('should fail for invalid orderbook channel websocket subscribe message', () => {
const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32';
const testCases = [ const testCases = [
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
snapshot: true,
limit: 100,
},
},
{ {
type: 'foo', type: 'foo',
channel: 'orderbook', channel: 'orderbook',
requestId: 1,
payload: { payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
@@ -464,6 +477,7 @@ describe('Schema', () => {
{ {
type: 'subscribe', type: 'subscribe',
channel: 'bar', channel: 'bar',
requestId: 1,
payload: { payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
@@ -472,6 +486,7 @@ describe('Schema', () => {
{ {
type: 'subscribe', type: 'subscribe',
channel: 'orderbook', channel: 'orderbook',
requestId: 1,
payload: { payload: {
baseTokenAddress: checksummedAddress, baseTokenAddress: checksummedAddress,
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
@@ -480,6 +495,7 @@ describe('Schema', () => {
{ {
type: 'subscribe', type: 'subscribe',
channel: 'orderbook', channel: 'orderbook',
requestId: 1,
payload: { payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: checksummedAddress, quoteTokenAddress: checksummedAddress,
@@ -488,6 +504,7 @@ describe('Schema', () => {
{ {
type: 'subscribe', type: 'subscribe',
channel: 'orderbook', channel: 'orderbook',
requestId: 1,
payload: { payload: {
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
}, },
@@ -495,6 +512,7 @@ describe('Schema', () => {
{ {
type: 'subscribe', type: 'subscribe',
channel: 'orderbook', channel: 'orderbook',
requestId: 1,
payload: { payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
}, },
@@ -502,6 +520,7 @@ describe('Schema', () => {
{ {
type: 'subscribe', type: 'subscribe',
channel: 'orderbook', channel: 'orderbook',
requestId: 1,
payload: { payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
@@ -512,6 +531,7 @@ describe('Schema', () => {
{ {
type: 'subscribe', type: 'subscribe',
channel: 'orderbook', channel: 'orderbook',
requestId: 1,
payload: { payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
@@ -530,7 +550,7 @@ describe('Schema', () => {
{ {
type: 'snapshot', type: 'snapshot',
channel: 'orderbook', channel: 'orderbook',
channelId: 2, requestId: 2,
payload: { payload: {
bids: [], bids: [],
asks: [], asks: [],
@@ -539,7 +559,7 @@ describe('Schema', () => {
{ {
type: 'snapshot', type: 'snapshot',
channel: 'orderbook', channel: 'orderbook',
channelId: 2, requestId: 2,
payload: { payload: {
bids: [ bids: [
signedOrder, signedOrder,
@@ -557,7 +577,7 @@ describe('Schema', () => {
{ {
type: 'foo', type: 'foo',
channel: 'orderbook', channel: 'orderbook',
channelId: 2, requestId: 2,
payload: { payload: {
bids: [ bids: [
signedOrder, signedOrder,
@@ -570,7 +590,7 @@ describe('Schema', () => {
{ {
type: 'snapshot', type: 'snapshot',
channel: 'bar', channel: 'bar',
channelId: 2, requestId: 2,
payload: { payload: {
bids: [ bids: [
signedOrder, signedOrder,
@@ -595,7 +615,7 @@ describe('Schema', () => {
{ {
type: 'snapshot', type: 'snapshot',
channel: 'orderbook', channel: 'orderbook',
channelId: '2', requestId: '2',
payload: { payload: {
bids: [ bids: [
signedOrder, signedOrder,
@@ -608,7 +628,7 @@ describe('Schema', () => {
{ {
type: 'snapshot', type: 'snapshot',
channel: 'orderbook', channel: 'orderbook',
channelId: 2, requestId: 2,
payload: { payload: {
bids: [ bids: [
signedOrder, signedOrder,
@@ -618,7 +638,7 @@ describe('Schema', () => {
{ {
type: 'snapshot', type: 'snapshot',
channel: 'orderbook', channel: 'orderbook',
channelId: 2, requestId: 2,
payload: { payload: {
asks: [ asks: [
signedOrder, signedOrder,
@@ -628,7 +648,7 @@ describe('Schema', () => {
{ {
type: 'snapshot', type: 'snapshot',
channel: 'orderbook', channel: 'orderbook',
channelId: 2, requestId: 2,
payload: { payload: {
bids: [ bids: [
signedOrder, signedOrder,
@@ -641,7 +661,7 @@ describe('Schema', () => {
{ {
type: 'snapshot', type: 'snapshot',
channel: 'orderbook', channel: 'orderbook',
channelId: 2, requestId: 2,
payload: { payload: {
bids: [ bids: [
{}, {},
@@ -662,7 +682,7 @@ describe('Schema', () => {
{ {
type: 'update', type: 'update',
channel: 'orderbook', channel: 'orderbook',
channelId: 2, requestId: 2,
payload: signedOrder, payload: signedOrder,
}, },
]; ];
@@ -673,16 +693,19 @@ describe('Schema', () => {
{ {
type: 'foo', type: 'foo',
channel: 'orderbook', channel: 'orderbook',
requestId: 2,
payload: signedOrder, payload: signedOrder,
}, },
{ {
type: 'update', type: 'update',
channel: 'bar', channel: 'bar',
requestId: 2,
payload: signedOrder, payload: signedOrder,
}, },
{ {
type: 'update', type: 'update',
channel: 'orderbook', channel: 'orderbook',
requestId: 2,
payload: {}, payload: {},
}, },
]; ];