Update websocket for SRA v2

This commit is contained in:
fragosti 2018-08-20 11:42:29 -07:00
parent f2d1d95355
commit 075e3a41c8
12 changed files with 71 additions and 74 deletions

View File

@ -1,4 +1,12 @@
[
{
"version": "2.0.0",
"changes": [
{
"note": "Updated for SRA v2"
}
]
},
{
"timestamp": 1534210131,
"version": "1.0.5",

View File

@ -31,7 +31,7 @@ export interface OrdersChannelHandler {
onUpdate: (
channel: OrdersChannel,
subscriptionOpts: OrdersChannelSubscriptionOpts,
order: APIOrder,
orders: APIOrder[],
) => void;
onError: (channel: OrdersChannel, err: Error, subscriptionOpts?: OrdersChannelSubscriptionOpts) => void;
onClose: (channel: OrdersChannel) => void;
@ -48,13 +48,13 @@ export enum OrdersChannelMessageTypes {
export interface UpdateOrdersChannelMessage {
type: OrdersChannelMessageTypes.Update;
requestId: number;
payload: APIOrder;
requestId: string;
payload: APIOrder[];
}
export interface UnknownOrdersChannelMessage {
type: OrdersChannelMessageTypes.Unknown;
requestId: number;
requestId: string;
payload: undefined;
}

View File

@ -15,15 +15,15 @@ export const ordersChannelMessageParser = {
assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`);
assert.isString('type', type);
// ensure we have a request id for the resulting message
const requestId: number = _.get(messageObj, 'requestId');
const requestId: string = _.get(messageObj, 'requestId');
assert.assert(!_.isUndefined(requestId), `Message is missing a requestId parameter: ${utf8Data}`);
assert.isNumber('requestId', requestId);
assert.isString('requestId', requestId);
switch (type) {
case OrdersChannelMessageTypes.Update: {
assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrdersChannelUpdateSchema);
const orderJson = messageObj.payload;
const order = relayerResponseJsonParsers.parseAPIOrderJson(orderJson);
return _.assign(messageObj, { payload: order });
const ordersJson = messageObj.payload;
const orders = relayerResponseJsonParsers.parseAPIOrdersJson(ordersJson);
return _.assign(messageObj, { payload: orders });
}
default: {
return {

View File

@ -22,7 +22,10 @@ export const relayerResponseJsonParsers = {
},
parseOrdersJson(json: any): OrdersResponse {
assert.doesConformToSchema('relayerApiOrdersResponse', json, schemas.relayerApiOrdersResponseSchema);
return { ...json, records: json.records.map(relayerResponseJsonParsers.parseAPIOrderJson.bind(relayerResponseJsonParsers)) };
return { ...json, records: relayerResponseJsonParsers.parseAPIOrdersJson(json.records) };
},
parseAPIOrdersJson(json: any): APIOrder[] {
return json.map(relayerResponseJsonParsers.parseAPIOrderJson.bind(relayerResponseJsonParsers));
},
parseAPIOrderJson(json: any): APIOrder {
assert.doesConformToSchema('relayerApiOrder', json, schemas.relayerApiOrderSchema);

View File

@ -9,7 +9,11 @@ import {
OrdersChannelSubscriptionOpts,
} from './types';
import { assert } from './utils/assert';
import { ordersChannelMessageParser } from './utils/orderbook_channel_message_parser';
import { ordersChannelMessageParser } from './utils/orders_channel_message_parser';
export interface OrdersChannelSubscriptionOptsMap {
[key: string]: OrdersChannelSubscriptionOpts;
}
/**
* This class includes all the functionality related to interacting with a websocket endpoint
@ -18,7 +22,7 @@ import { ordersChannelMessageParser } from './utils/orderbook_channel_message_pa
export class WebSocketOrdersChannel implements OrdersChannel {
private readonly _client: WebSocket.w3cwebsocket;
private readonly _handler: OrdersChannelHandler;
private readonly _subscriptionOptsList: OrdersChannelSubscriptionOpts[] = [];
private readonly _subscriptionOptsMap: OrdersChannelSubscriptionOptsMap = {};
/**
* Instantiates a new WebSocketOrdersChannel instance
* @param client A WebSocket client
@ -50,11 +54,12 @@ export class WebSocketOrdersChannel implements OrdersChannel {
public subscribe(subscriptionOpts: OrdersChannelSubscriptionOpts): void {
assert.isOrdersChannelSubscriptionOpts('subscriptionOpts', subscriptionOpts);
assert.assert(this._client.readyState === WebSocket.w3cwebsocket.OPEN, 'WebSocket connection is closed');
this._subscriptionOptsList.push(subscriptionOpts);
const requestId = uuid();
this._subscriptionOptsMap[requestId] = subscriptionOpts;
const subscribeMessage = {
type: 'subscribe',
channel: 'orders',
requestId: uuid(),
requestId,
payload: subscriptionOpts,
};
this._client.send(JSON.stringify(subscribeMessage));
@ -73,7 +78,7 @@ export class WebSocketOrdersChannel implements OrdersChannel {
try {
const data = message.data;
const parserResult = ordersChannelMessageParser.parse(data);
const subscriptionOpts = this._subscriptionOptsList[parserResult.requestId];
const subscriptionOpts = this._subscriptionOptsMap[parserResult.requestId];
if (_.isUndefined(subscriptionOpts)) {
this._handler.onError(
this,

View File

@ -5,6 +5,6 @@ const orderJSONString = JSON.stringify(orderResponseJSON);
export const unknownOrdersChannelMessage = `{
"type": "superGoodUpdate",
"channel": "orderbook",
"requestId": 1,
"payload": ${orderJSONString}
"requestId": "6ce8c5a6-5c46-4027-a44a-51831c77b8a1",
"payload": [${orderJSONString}]
}`;

View File

@ -1,17 +0,0 @@
import * as orderResponseJSON from './order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.json';
const orderJSONString = JSON.stringify(orderResponseJSON);
export const updateOrdersChannelMessage = `{
"type": "update",
"channel": "orderbook",
"requestId": 1,
"payload": ${orderJSONString}
}`;
export const malformedUpdateOrdersChannelMessage = `{
"type": "update",
"channel": "orderbook",
"requestId": 1,
"payload": {}
}`;

View File

@ -0,0 +1,17 @@
import * as apiOrderJSON from './order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.json';
const apiOrderJSONString = JSON.stringify(apiOrderJSON);
export const updateOrdersChannelMessage = `{
"type": "update",
"channel": "orders",
"requestId": "5a1ce3a2-22b9-41e6-a615-68077512e9e2",
"payload": [${apiOrderJSONString}]
}`;
export const malformedUpdateOrdersChannelMessage = `{
"type": "update",
"channel": "orders",
"requestId": "4d8efcee-adde-4475-9601-f0b30962ca2b",
"payload": {}
}`;

View File

@ -191,6 +191,3 @@ describe('HttpClient', () => {
});
});
});
// https://example.com/fee_recipients?networkId=42&page=3&perPage=50
// https://example.com/fee_recipients?networkId=42&page=3&perPage=50

View File

@ -9,15 +9,9 @@ chai.config.includeStack = true;
chai.use(dirtyChai);
const expect = chai.expect;
const emptyOrdersChannelHandler = {
onUpdate: () => {
_.noop();
},
onError: () => {
_.noop();
},
onClose: () => {
_.noop();
},
onUpdate: _.noop,
onError: _.noop,
onClose: _.noop,
};
describe('ordersChannelFactory', () => {

View File

@ -2,14 +2,14 @@ import * as chai from 'chai';
import * as dirtyChai from 'dirty-chai';
import 'mocha';
import { ordersChannelMessageParser } from '../src/utils/orderbook_channel_message_parser';
import { ordersChannelMessageParser } from '../src/utils/orders_channel_message_parser';
import { orderResponse } from './fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f';
import { unknownOrdersChannelMessage } from './fixtures/standard_relayer_api/unknown_orderbook_channel_message';
import { unknownOrdersChannelMessage } from './fixtures/standard_relayer_api/unknown_orders_channel_message';
import {
malformedUpdateOrdersChannelMessage,
updateOrdersChannelMessage,
} from './fixtures/standard_relayer_api/update_orderbook_channel_message';
} from './fixtures/standard_relayer_api/update_orders_channel_message';
chai.config.includeStack = true;
chai.use(dirtyChai);
@ -20,7 +20,7 @@ describe('ordersChannelMessageParser', () => {
it('parses update messages', () => {
const updateMessage = ordersChannelMessageParser.parse(updateOrdersChannelMessage);
expect(updateMessage.type).to.be.equal('update');
expect(updateMessage.payload).to.be.deep.equal(orderResponse);
expect(updateMessage.payload).to.be.deep.equal([orderResponse]);
});
it('returns unknown message for messages with unsupported types', () => {
const unknownMessage = ordersChannelMessageParser.parse(unknownOrdersChannelMessage);
@ -29,9 +29,9 @@ describe('ordersChannelMessageParser', () => {
});
it('throws when message does not include a type', () => {
const typelessMessage = `{
"channel": "orderbook",
"requestId": 1,
"payload": {}
"channel": "orders",
"requestId": "4d8efcee-adde-4475-9601-f0b30962ca2b",
"payload": []
}`;
const badCall = () => ordersChannelMessageParser.parse(typelessMessage);
expect(badCall).throws(`Message is missing a type parameter: ${typelessMessage}`);
@ -39,9 +39,9 @@ describe('ordersChannelMessageParser', () => {
it('throws when type is not a string', () => {
const messageWithBadType = `{
"type": 1,
"channel": "orderbook",
"requestId": 1,
"payload": {}
"channel": "orders",
"requestId": "4d8efcee-adde-4475-9601-f0b30962ca2b",
"payload": []
}`;
const badCall = () => ordersChannelMessageParser.parse(messageWithBadType);
expect(badCall).throws('Expected type to be of type string, encountered: 1');
@ -53,7 +53,7 @@ describe('ordersChannelMessageParser', () => {
it('throws when input message is not valid JSON', () => {
const nonJsonString = 'h93b{sdfs9fsd f';
const badCall = () => ordersChannelMessageParser.parse(nonJsonString);
expect(badCall).throws('Unexpected assetData h in JSON at position 0');
expect(badCall).throws('Unexpected token h in JSON at position 0');
});
});
});

View File

@ -11,18 +11,9 @@ chai.config.includeStack = true;
chai.use(dirtyChai);
const expect = chai.expect;
const emptyOrdersChannelHandler = {
onSnapshot: () => {
_.noop();
},
onUpdate: () => {
_.noop();
},
onError: () => {
_.noop();
},
onClose: () => {
_.noop();
},
onUpdate: _.noop,
onError: _.noop,
onClose: _.noop,
};
describe('WebSocketOrdersChannel', () => {
@ -34,15 +25,14 @@ describe('WebSocketOrdersChannel', () => {
const subscriptionOpts = {
baseAssetData: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteAssetData: '0xef7fff64389b814a946f3e92105513705ca6b990',
snapshot: true,
limit: 100,
};
describe('#subscribe', () => {
it('throws when subscriptionOpts does not conform to schema', () => {
const badSubscribeCall = openOrdersChannel.subscribe.bind(openOrdersChannel, {});
expect(badSubscribeCall).throws(
'Expected subscriptionOpts to conform to schema /RelayerApiOrdersChannelSubscribePayload\nEncountered: {}\nValidation errors: instance requires property "baseAssetData", instance requires property "quoteAssetData"',
);
const badSubscribeCall = openOrdersChannel.subscribe.bind(openOrdersChannel, {
makerAssetData: 5,
});
expect(badSubscribeCall).throws();
});
it('does not throw when inputs are of correct types', () => {
const goodSubscribeCall = openOrdersChannel.subscribe.bind(openOrdersChannel, subscriptionOpts);