From 20e28d6c70a7c4af9f3da2ebda1e81d9f8ba7414 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 4 Dec 2017 23:58:33 -0800 Subject: Add requestId to subscription messages and update json-schemas --- packages/connect/CHANGELOG.md | 4 ++ packages/connect/src/index.ts | 4 ++ packages/connect/src/types.ts | 12 ++++-- .../src/utils/orderbook_channel_message_parsers.ts | 12 ++---- packages/connect/src/ws_orderbook_channel.ts | 43 +++++++++++++--------- .../snapshot_orderbook_channel_message.ts | 4 +- .../unknown_orderbook_channel_message.ts | 2 +- .../update_orderbook_channel_message.ts | 4 +- .../test/orderbook_channel_message_parsers_test.ts | 12 +++++- ...layer_api_orberbook_channel_subscribe_schema.ts | 3 +- ...elayer_api_orderbook_channel_snapshot_schema.ts | 4 +- ...api_orderbook_channel_update_response_schema.ts | 4 +- packages/json-schemas/test/schema_test.ts | 43 +++++++++++++++++----- 13 files changed, 101 insertions(+), 50 deletions(-) diff --git a/packages/connect/CHANGELOG.md b/packages/connect/CHANGELOG.md index b6a9eb6ea..28f4149bf 100644 --- a/packages/connect/CHANGELOG.md +++ b/packages/connect/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +vx.x.x +------------------------ + * Expose WebSocketOrderbookChannel and associated types to public interface + v0.2.0 - _November 29, 2017_ ------------------------ * Add SignedOrder and TokenTradeInfo to the public interface diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index aabf233fe..ec369a606 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -1,10 +1,14 @@ export {HttpClient} from './http_client'; +export {WebSocketOrderbookChannel} from './ws_orderbook_channel'; export { Client, ECSignature, FeesRequest, FeesResponse, Order, + OrderbookChannel, + OrderbookChannelHandler, + OrderbookChannelSubscriptionOpts, OrderbookRequest, OrderbookResponse, OrdersRequest, diff --git a/packages/connect/src/types.ts b/packages/connect/src/types.ts index 71dd8dac2..0657a1d63 100644 --- a/packages/connect/src/types.ts +++ b/packages/connect/src/types.ts @@ -57,9 +57,12 @@ export interface OrderbookChannelSubscriptionOpts { } export interface OrderbookChannelHandler { - onSnapshot: (channel: OrderbookChannel, snapshot: OrderbookResponse) => void; - onUpdate: (channel: OrderbookChannel, order: SignedOrder) => void; - onError: (channel: OrderbookChannel, err: Error) => void; + onSnapshot: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts, + snapshot: OrderbookResponse) => void; + onUpdate: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts, + order: SignedOrder) => void; + onError: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts, + err: Error) => void; onClose: (channel: OrderbookChannel) => void; } @@ -76,16 +79,19 @@ export enum OrderbookChannelMessageTypes { export interface SnapshotOrderbookChannelMessage { type: OrderbookChannelMessageTypes.Snapshot; + requestId: number; payload: OrderbookResponse; } export interface UpdateOrderbookChannelMessage { type: OrderbookChannelMessageTypes.Update; + requestId: number; payload: SignedOrder; } export interface UnknownOrderbookChannelMessage { type: OrderbookChannelMessageTypes.Unknown; + requestId: number; payload: undefined; } diff --git a/packages/connect/src/utils/orderbook_channel_message_parsers.ts b/packages/connect/src/utils/orderbook_channel_message_parsers.ts index 9cd160428..486a416ef 100644 --- a/packages/connect/src/utils/orderbook_channel_message_parsers.ts +++ b/packages/connect/src/utils/orderbook_channel_message_parsers.ts @@ -15,28 +15,24 @@ export const orderbookChannelMessageParsers = { const messageObj = JSON.parse(utf8Data); const type: string = _.get(messageObj, 'type'); assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`); + assert.isString('type', type); switch (type) { case (OrderbookChannelMessageTypes.Snapshot): { assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema); const orderbook = messageObj.payload; typeConverters.convertOrderbookStringFieldsToBigNumber(orderbook); - return { - type, - payload: orderbook, - }; + return messageObj; } case (OrderbookChannelMessageTypes.Update): { assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema); const order = messageObj.payload; typeConverters.convertOrderStringFieldsToBigNumber(order); - return { - type, - payload: order, - }; + return messageObj; } default: { return { type: OrderbookChannelMessageTypes.Unknown, + requestId: 0, payload: undefined, }; } diff --git a/packages/connect/src/ws_orderbook_channel.ts b/packages/connect/src/ws_orderbook_channel.ts index d9cc1b4c5..346be20c0 100644 --- a/packages/connect/src/ws_orderbook_channel.ts +++ b/packages/connect/src/ws_orderbook_channel.ts @@ -22,9 +22,10 @@ export class WebSocketOrderbookChannel implements OrderbookChannel { private apiEndpointUrl: string; private client: WebSocket.client; private connectionIfExists?: WebSocket.connection; + private subscriptionCounter = 0; /** * 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 */ constructor(url: string) { @@ -46,23 +47,25 @@ export class WebSocketOrderbookChannel implements OrderbookChannel { assert.isFunction('handler.onUpdate', _.get(handler, 'onUpdate')); assert.isFunction('handler.onError', _.get(handler, 'onError')); assert.isFunction('handler.onClose', _.get(handler, 'onClose')); + this.subscriptionCounter += 1; const subscribeMessage = { type: 'subscribe', channel: 'orderbook', + requestId: this.subscriptionCounter, payload: subscriptionOpts, }; this._getConnection((error, connection) => { if (!_.isUndefined(error)) { - handler.onError(this, error); + handler.onError(this, subscriptionOpts, error); } else if (!_.isUndefined(connection) && connection.connected) { connection.on(WebsocketConnectionEventType.Error, wsError => { - handler.onError(this, wsError); + handler.onError(this, subscriptionOpts, wsError); }); connection.on(WebsocketConnectionEventType.Close, () => { handler.onClose(this); }); connection.on(WebsocketConnectionEventType.Message, message => { - this._handleWebSocketMessage(message, handler); + this._handleWebSocketMessage(subscribeMessage.requestId, subscriptionOpts, message, handler); }); connection.sendUTF(JSON.stringify(subscribeMessage)); } @@ -90,30 +93,34 @@ export class WebSocketOrderbookChannel implements OrderbookChannel { 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)) { try { const utf8Data = message.utf8Data; const parserResult = orderbookChannelMessageParsers.parser(utf8Data); const type = parserResult.type; - switch (parserResult.type) { - case (OrderbookChannelMessageTypes.Snapshot): { - handler.onSnapshot(this, parserResult.payload); - break; - } - case (OrderbookChannelMessageTypes.Update): { - handler.onUpdate(this, parserResult.payload); - break; - } - default: { - handler.onError(this, new Error(`Message has missing a type parameter: ${utf8Data}`)); + if (parserResult.requestId === requestId) { + switch (parserResult.type) { + case (OrderbookChannelMessageTypes.Snapshot): { + handler.onSnapshot(this, subscriptionOpts, parserResult.payload); + break; + } + case (OrderbookChannelMessageTypes.Update): { + handler.onUpdate(this, subscriptionOpts, parserResult.payload); + break; + } + default: { + handler.onError( + this, subscriptionOpts, new Error(`Message has missing a type parameter: ${utf8Data}`)); + } } } } catch (error) { - handler.onError(this, error); + handler.onError(this, subscriptionOpts, error); } } else { - handler.onError(this, new Error(`Message does not contain utf8Data`)); + handler.onError(this, subscriptionOpts, new Error(`Message does not contain utf8Data`)); } } } diff --git a/packages/connect/test/fixtures/standard_relayer_api/snapshot_orderbook_channel_message.ts b/packages/connect/test/fixtures/standard_relayer_api/snapshot_orderbook_channel_message.ts index 3cedafb20..1d7e67055 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/snapshot_orderbook_channel_message.ts +++ b/packages/connect/test/fixtures/standard_relayer_api/snapshot_orderbook_channel_message.ts @@ -5,13 +5,13 @@ const orderbookJsonString = JSON.stringify(orderbookJSON); export const snapshotOrderbookChannelMessage = `{ "type": "snapshot", "channel": "orderbook", - "channelId": 1, + "requestId": 1, "payload": ${orderbookJsonString} }`; export const malformedSnapshotOrderbookChannelMessage = `{ "type": "snapshot", "channel": "orderbook", - "channelId": 1, + "requestId": 1, "payload": {} }`; diff --git a/packages/connect/test/fixtures/standard_relayer_api/unknown_orderbook_channel_message.ts b/packages/connect/test/fixtures/standard_relayer_api/unknown_orderbook_channel_message.ts index 842738d99..cbedff60e 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/unknown_orderbook_channel_message.ts +++ b/packages/connect/test/fixtures/standard_relayer_api/unknown_orderbook_channel_message.ts @@ -5,6 +5,6 @@ const orderJSONString = JSON.stringify(orderResponseJSON); export const unknownOrderbookChannelMessage = `{ "type": "superGoodUpdate", "channel": "orderbook", - "channelId": 1, + "requestId": 1, "payload": ${orderJSONString} }`; diff --git a/packages/connect/test/fixtures/standard_relayer_api/update_orderbook_channel_message.ts b/packages/connect/test/fixtures/standard_relayer_api/update_orderbook_channel_message.ts index bc83854c6..0e2c7523b 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/update_orderbook_channel_message.ts +++ b/packages/connect/test/fixtures/standard_relayer_api/update_orderbook_channel_message.ts @@ -5,13 +5,13 @@ const orderJSONString = JSON.stringify(orderResponseJSON); export const updateOrderbookChannelMessage = `{ "type": "update", "channel": "orderbook", - "channelId": 1, + "requestId": 1, "payload": ${orderJSONString} }`; export const malformedUpdateOrderbookChannelMessage = `{ "type": "update", "channel": "orderbook", - "channelId": 1, + "requestId": 1, "payload": {} }`; diff --git a/packages/connect/test/orderbook_channel_message_parsers_test.ts b/packages/connect/test/orderbook_channel_message_parsers_test.ts index 0ad0b12b3..2c776b095 100644 --- a/packages/connect/test/orderbook_channel_message_parsers_test.ts +++ b/packages/connect/test/orderbook_channel_message_parsers_test.ts @@ -41,12 +41,22 @@ describe('orderbookChannelMessageParsers', () => { it('throws when message does not include a type', () => { const typelessMessage = `{ "channel": "orderbook", - "channelId": 1, + "requestId": 1, "payload": {} }`; const badCall = () => orderbookChannelMessageParsers.parser(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', () => { const badCall = () => orderbookChannelMessageParsers.parser(malformedSnapshotOrderbookChannelMessage); diff --git a/packages/json-schemas/schemas/relayer_api_orberbook_channel_subscribe_schema.ts b/packages/json-schemas/schemas/relayer_api_orberbook_channel_subscribe_schema.ts index 8ded9adb0..2f71531c6 100644 --- a/packages/json-schemas/schemas/relayer_api_orberbook_channel_subscribe_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_orberbook_channel_subscribe_schema.ts @@ -4,9 +4,10 @@ export const relayerApiOrderbookChannelSubscribeSchema = { properties: { type: {enum: ['subscribe']}, channel: {enum: ['orderbook']}, + requestId: {type: 'number'}, payload: {$ref: '/RelayerApiOrderbookChannelSubscribePayload'}, }, - required: ['type', 'channel', 'payload'], + required: ['type', 'channel', 'requestId', 'payload'], }; export const relayerApiOrderbookChannelSubscribePayload = { diff --git a/packages/json-schemas/schemas/relayer_api_orderbook_channel_snapshot_schema.ts b/packages/json-schemas/schemas/relayer_api_orderbook_channel_snapshot_schema.ts index cfc0ddc8f..99037865e 100644 --- a/packages/json-schemas/schemas/relayer_api_orderbook_channel_snapshot_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_orderbook_channel_snapshot_schema.ts @@ -4,10 +4,10 @@ export const relayerApiOrderbookChannelSnapshotSchema = { properties: { type: {enum: ['snapshot']}, channel: {enum: ['orderbook']}, - channelId: {type: 'number'}, + requestId: {type: 'number'}, payload: {$ref: '/RelayerApiOrderbookChannelSnapshotPayload'}, }, - required: ['type', 'channel', 'channelId', 'payload'], + required: ['type', 'channel', 'requestId', 'payload'], }; export const relayerApiOrderbookChannelSnapshotPayload = { diff --git a/packages/json-schemas/schemas/relayer_api_orderbook_channel_update_response_schema.ts b/packages/json-schemas/schemas/relayer_api_orderbook_channel_update_response_schema.ts index 51308ed49..90cfd8749 100644 --- a/packages/json-schemas/schemas/relayer_api_orderbook_channel_update_response_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_orderbook_channel_update_response_schema.ts @@ -4,8 +4,8 @@ export const relayerApiOrderbookChannelUpdateSchema = { properties: { type: {enum: ['update']}, channel: {enum: ['orderbook']}, - channelId: {type: 'number'}, + requestId: {type: 'number'}, payload: {$ref: '/SignedOrder'}, }, - required: ['type', 'channel', 'channelId', 'payload'], + required: ['type', 'channel', 'requestId', 'payload'], }; diff --git a/packages/json-schemas/test/schema_test.ts b/packages/json-schemas/test/schema_test.ts index 653f70852..758ccac61 100644 --- a/packages/json-schemas/test/schema_test.ts +++ b/packages/json-schemas/test/schema_test.ts @@ -432,6 +432,7 @@ describe('Schema', () => { { type: 'subscribe', channel: 'orderbook', + requestId: 1, payload: { baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', @@ -442,6 +443,7 @@ describe('Schema', () => { { type: 'subscribe', channel: 'orderbook', + requestId: 1, payload: { baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', @@ -453,9 +455,20 @@ describe('Schema', () => { it('should fail for invalid orderbook channel websocket subscribe message', () => { const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; const testCases = [ + { + type: 'subscribe', + channel: 'orderbook', + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + snapshot: true, + limit: 100, + }, + }, { type: 'foo', channel: 'orderbook', + requestId: 1, payload: { baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', @@ -464,6 +477,7 @@ describe('Schema', () => { { type: 'subscribe', channel: 'bar', + requestId: 1, payload: { baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', @@ -472,6 +486,7 @@ describe('Schema', () => { { type: 'subscribe', channel: 'orderbook', + requestId: 1, payload: { baseTokenAddress: checksummedAddress, quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', @@ -480,6 +495,7 @@ describe('Schema', () => { { type: 'subscribe', channel: 'orderbook', + requestId: 1, payload: { baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: checksummedAddress, @@ -488,6 +504,7 @@ describe('Schema', () => { { type: 'subscribe', channel: 'orderbook', + requestId: 1, payload: { quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', }, @@ -495,6 +512,7 @@ describe('Schema', () => { { type: 'subscribe', channel: 'orderbook', + requestId: 1, payload: { baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', }, @@ -502,6 +520,7 @@ describe('Schema', () => { { type: 'subscribe', channel: 'orderbook', + requestId: 1, payload: { baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', @@ -512,6 +531,7 @@ describe('Schema', () => { { type: 'subscribe', channel: 'orderbook', + requestId: 1, payload: { baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', @@ -530,7 +550,7 @@ describe('Schema', () => { { type: 'snapshot', channel: 'orderbook', - channelId: 2, + requestId: 2, payload: { bids: [], asks: [], @@ -539,7 +559,7 @@ describe('Schema', () => { { type: 'snapshot', channel: 'orderbook', - channelId: 2, + requestId: 2, payload: { bids: [ signedOrder, @@ -557,7 +577,7 @@ describe('Schema', () => { { type: 'foo', channel: 'orderbook', - channelId: 2, + requestId: 2, payload: { bids: [ signedOrder, @@ -570,7 +590,7 @@ describe('Schema', () => { { type: 'snapshot', channel: 'bar', - channelId: 2, + requestId: 2, payload: { bids: [ signedOrder, @@ -595,7 +615,7 @@ describe('Schema', () => { { type: 'snapshot', channel: 'orderbook', - channelId: '2', + requestId: '2', payload: { bids: [ signedOrder, @@ -608,7 +628,7 @@ describe('Schema', () => { { type: 'snapshot', channel: 'orderbook', - channelId: 2, + requestId: 2, payload: { bids: [ signedOrder, @@ -618,7 +638,7 @@ describe('Schema', () => { { type: 'snapshot', channel: 'orderbook', - channelId: 2, + requestId: 2, payload: { asks: [ signedOrder, @@ -628,7 +648,7 @@ describe('Schema', () => { { type: 'snapshot', channel: 'orderbook', - channelId: 2, + requestId: 2, payload: { bids: [ signedOrder, @@ -641,7 +661,7 @@ describe('Schema', () => { { type: 'snapshot', channel: 'orderbook', - channelId: 2, + requestId: 2, payload: { bids: [ {}, @@ -662,7 +682,7 @@ describe('Schema', () => { { type: 'update', channel: 'orderbook', - channelId: 2, + requestId: 2, payload: signedOrder, }, ]; @@ -673,16 +693,19 @@ describe('Schema', () => { { type: 'foo', channel: 'orderbook', + requestId: 2, payload: signedOrder, }, { type: 'update', channel: 'bar', + requestId: 2, payload: signedOrder, }, { type: 'update', channel: 'orderbook', + requestId: 2, payload: {}, }, ]; -- cgit v1.2.3