Update websocket for SRA v2
This commit is contained in:
parent
f2d1d95355
commit
075e3a41c8
@ -1,4 +1,12 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Updated for SRA v2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1534210131,
|
"timestamp": 1534210131,
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
|
@ -31,7 +31,7 @@ export interface OrdersChannelHandler {
|
|||||||
onUpdate: (
|
onUpdate: (
|
||||||
channel: OrdersChannel,
|
channel: OrdersChannel,
|
||||||
subscriptionOpts: OrdersChannelSubscriptionOpts,
|
subscriptionOpts: OrdersChannelSubscriptionOpts,
|
||||||
order: APIOrder,
|
orders: APIOrder[],
|
||||||
) => void;
|
) => void;
|
||||||
onError: (channel: OrdersChannel, err: Error, subscriptionOpts?: OrdersChannelSubscriptionOpts) => void;
|
onError: (channel: OrdersChannel, err: Error, subscriptionOpts?: OrdersChannelSubscriptionOpts) => void;
|
||||||
onClose: (channel: OrdersChannel) => void;
|
onClose: (channel: OrdersChannel) => void;
|
||||||
@ -48,13 +48,13 @@ export enum OrdersChannelMessageTypes {
|
|||||||
|
|
||||||
export interface UpdateOrdersChannelMessage {
|
export interface UpdateOrdersChannelMessage {
|
||||||
type: OrdersChannelMessageTypes.Update;
|
type: OrdersChannelMessageTypes.Update;
|
||||||
requestId: number;
|
requestId: string;
|
||||||
payload: APIOrder;
|
payload: APIOrder[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnknownOrdersChannelMessage {
|
export interface UnknownOrdersChannelMessage {
|
||||||
type: OrdersChannelMessageTypes.Unknown;
|
type: OrdersChannelMessageTypes.Unknown;
|
||||||
requestId: number;
|
requestId: string;
|
||||||
payload: undefined;
|
payload: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,15 +15,15 @@ export const ordersChannelMessageParser = {
|
|||||||
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);
|
assert.isString('type', type);
|
||||||
// ensure we have a request id for the resulting message
|
// 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.assert(!_.isUndefined(requestId), `Message is missing a requestId parameter: ${utf8Data}`);
|
||||||
assert.isNumber('requestId', requestId);
|
assert.isString('requestId', requestId);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case OrdersChannelMessageTypes.Update: {
|
case OrdersChannelMessageTypes.Update: {
|
||||||
assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrdersChannelUpdateSchema);
|
assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrdersChannelUpdateSchema);
|
||||||
const orderJson = messageObj.payload;
|
const ordersJson = messageObj.payload;
|
||||||
const order = relayerResponseJsonParsers.parseAPIOrderJson(orderJson);
|
const orders = relayerResponseJsonParsers.parseAPIOrdersJson(ordersJson);
|
||||||
return _.assign(messageObj, { payload: order });
|
return _.assign(messageObj, { payload: orders });
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return {
|
return {
|
@ -22,7 +22,10 @@ export const relayerResponseJsonParsers = {
|
|||||||
},
|
},
|
||||||
parseOrdersJson(json: any): OrdersResponse {
|
parseOrdersJson(json: any): OrdersResponse {
|
||||||
assert.doesConformToSchema('relayerApiOrdersResponse', json, schemas.relayerApiOrdersResponseSchema);
|
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 {
|
parseAPIOrderJson(json: any): APIOrder {
|
||||||
assert.doesConformToSchema('relayerApiOrder', json, schemas.relayerApiOrderSchema);
|
assert.doesConformToSchema('relayerApiOrder', json, schemas.relayerApiOrderSchema);
|
||||||
|
@ -9,7 +9,11 @@ import {
|
|||||||
OrdersChannelSubscriptionOpts,
|
OrdersChannelSubscriptionOpts,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { assert } from './utils/assert';
|
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
|
* 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 {
|
export class WebSocketOrdersChannel implements OrdersChannel {
|
||||||
private readonly _client: WebSocket.w3cwebsocket;
|
private readonly _client: WebSocket.w3cwebsocket;
|
||||||
private readonly _handler: OrdersChannelHandler;
|
private readonly _handler: OrdersChannelHandler;
|
||||||
private readonly _subscriptionOptsList: OrdersChannelSubscriptionOpts[] = [];
|
private readonly _subscriptionOptsMap: OrdersChannelSubscriptionOptsMap = {};
|
||||||
/**
|
/**
|
||||||
* Instantiates a new WebSocketOrdersChannel instance
|
* Instantiates a new WebSocketOrdersChannel instance
|
||||||
* @param client A WebSocket client
|
* @param client A WebSocket client
|
||||||
@ -50,11 +54,12 @@ export class WebSocketOrdersChannel implements OrdersChannel {
|
|||||||
public subscribe(subscriptionOpts: OrdersChannelSubscriptionOpts): void {
|
public subscribe(subscriptionOpts: OrdersChannelSubscriptionOpts): void {
|
||||||
assert.isOrdersChannelSubscriptionOpts('subscriptionOpts', subscriptionOpts);
|
assert.isOrdersChannelSubscriptionOpts('subscriptionOpts', subscriptionOpts);
|
||||||
assert.assert(this._client.readyState === WebSocket.w3cwebsocket.OPEN, 'WebSocket connection is closed');
|
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 = {
|
const subscribeMessage = {
|
||||||
type: 'subscribe',
|
type: 'subscribe',
|
||||||
channel: 'orders',
|
channel: 'orders',
|
||||||
requestId: uuid(),
|
requestId,
|
||||||
payload: subscriptionOpts,
|
payload: subscriptionOpts,
|
||||||
};
|
};
|
||||||
this._client.send(JSON.stringify(subscribeMessage));
|
this._client.send(JSON.stringify(subscribeMessage));
|
||||||
@ -73,7 +78,7 @@ export class WebSocketOrdersChannel implements OrdersChannel {
|
|||||||
try {
|
try {
|
||||||
const data = message.data;
|
const data = message.data;
|
||||||
const parserResult = ordersChannelMessageParser.parse(data);
|
const parserResult = ordersChannelMessageParser.parse(data);
|
||||||
const subscriptionOpts = this._subscriptionOptsList[parserResult.requestId];
|
const subscriptionOpts = this._subscriptionOptsMap[parserResult.requestId];
|
||||||
if (_.isUndefined(subscriptionOpts)) {
|
if (_.isUndefined(subscriptionOpts)) {
|
||||||
this._handler.onError(
|
this._handler.onError(
|
||||||
this,
|
this,
|
||||||
|
@ -5,6 +5,6 @@ const orderJSONString = JSON.stringify(orderResponseJSON);
|
|||||||
export const unknownOrdersChannelMessage = `{
|
export const unknownOrdersChannelMessage = `{
|
||||||
"type": "superGoodUpdate",
|
"type": "superGoodUpdate",
|
||||||
"channel": "orderbook",
|
"channel": "orderbook",
|
||||||
"requestId": 1,
|
"requestId": "6ce8c5a6-5c46-4027-a44a-51831c77b8a1",
|
||||||
"payload": ${orderJSONString}
|
"payload": [${orderJSONString}]
|
||||||
}`;
|
}`;
|
@ -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": {}
|
|
||||||
}`;
|
|
17
packages/connect/test/fixtures/standard_relayer_api/update_orders_channel_message.ts
vendored
Normal file
17
packages/connect/test/fixtures/standard_relayer_api/update_orders_channel_message.ts
vendored
Normal 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": {}
|
||||||
|
}`;
|
@ -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
|
|
@ -9,15 +9,9 @@ chai.config.includeStack = true;
|
|||||||
chai.use(dirtyChai);
|
chai.use(dirtyChai);
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
const emptyOrdersChannelHandler = {
|
const emptyOrdersChannelHandler = {
|
||||||
onUpdate: () => {
|
onUpdate: _.noop,
|
||||||
_.noop();
|
onError: _.noop,
|
||||||
},
|
onClose: _.noop,
|
||||||
onError: () => {
|
|
||||||
_.noop();
|
|
||||||
},
|
|
||||||
onClose: () => {
|
|
||||||
_.noop();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('ordersChannelFactory', () => {
|
describe('ordersChannelFactory', () => {
|
@ -2,14 +2,14 @@ import * as chai from 'chai';
|
|||||||
import * as dirtyChai from 'dirty-chai';
|
import * as dirtyChai from 'dirty-chai';
|
||||||
import 'mocha';
|
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 { 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 {
|
import {
|
||||||
malformedUpdateOrdersChannelMessage,
|
malformedUpdateOrdersChannelMessage,
|
||||||
updateOrdersChannelMessage,
|
updateOrdersChannelMessage,
|
||||||
} from './fixtures/standard_relayer_api/update_orderbook_channel_message';
|
} from './fixtures/standard_relayer_api/update_orders_channel_message';
|
||||||
|
|
||||||
chai.config.includeStack = true;
|
chai.config.includeStack = true;
|
||||||
chai.use(dirtyChai);
|
chai.use(dirtyChai);
|
||||||
@ -20,7 +20,7 @@ describe('ordersChannelMessageParser', () => {
|
|||||||
it('parses update messages', () => {
|
it('parses update messages', () => {
|
||||||
const updateMessage = ordersChannelMessageParser.parse(updateOrdersChannelMessage);
|
const updateMessage = ordersChannelMessageParser.parse(updateOrdersChannelMessage);
|
||||||
expect(updateMessage.type).to.be.equal('update');
|
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', () => {
|
it('returns unknown message for messages with unsupported types', () => {
|
||||||
const unknownMessage = ordersChannelMessageParser.parse(unknownOrdersChannelMessage);
|
const unknownMessage = ordersChannelMessageParser.parse(unknownOrdersChannelMessage);
|
||||||
@ -29,9 +29,9 @@ describe('ordersChannelMessageParser', () => {
|
|||||||
});
|
});
|
||||||
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": "orders",
|
||||||
"requestId": 1,
|
"requestId": "4d8efcee-adde-4475-9601-f0b30962ca2b",
|
||||||
"payload": {}
|
"payload": []
|
||||||
}`;
|
}`;
|
||||||
const badCall = () => ordersChannelMessageParser.parse(typelessMessage);
|
const badCall = () => ordersChannelMessageParser.parse(typelessMessage);
|
||||||
expect(badCall).throws(`Message is missing a type parameter: ${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', () => {
|
it('throws when type is not a string', () => {
|
||||||
const messageWithBadType = `{
|
const messageWithBadType = `{
|
||||||
"type": 1,
|
"type": 1,
|
||||||
"channel": "orderbook",
|
"channel": "orders",
|
||||||
"requestId": 1,
|
"requestId": "4d8efcee-adde-4475-9601-f0b30962ca2b",
|
||||||
"payload": {}
|
"payload": []
|
||||||
}`;
|
}`;
|
||||||
const badCall = () => ordersChannelMessageParser.parse(messageWithBadType);
|
const badCall = () => ordersChannelMessageParser.parse(messageWithBadType);
|
||||||
expect(badCall).throws('Expected type to be of type string, encountered: 1');
|
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', () => {
|
it('throws when input message is not valid JSON', () => {
|
||||||
const nonJsonString = 'h93b{sdfs9fsd f';
|
const nonJsonString = 'h93b{sdfs9fsd f';
|
||||||
const badCall = () => ordersChannelMessageParser.parse(nonJsonString);
|
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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -11,18 +11,9 @@ chai.config.includeStack = true;
|
|||||||
chai.use(dirtyChai);
|
chai.use(dirtyChai);
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
const emptyOrdersChannelHandler = {
|
const emptyOrdersChannelHandler = {
|
||||||
onSnapshot: () => {
|
onUpdate: _.noop,
|
||||||
_.noop();
|
onError: _.noop,
|
||||||
},
|
onClose: _.noop,
|
||||||
onUpdate: () => {
|
|
||||||
_.noop();
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
_.noop();
|
|
||||||
},
|
|
||||||
onClose: () => {
|
|
||||||
_.noop();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('WebSocketOrdersChannel', () => {
|
describe('WebSocketOrdersChannel', () => {
|
||||||
@ -34,15 +25,14 @@ describe('WebSocketOrdersChannel', () => {
|
|||||||
const subscriptionOpts = {
|
const subscriptionOpts = {
|
||||||
baseAssetData: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
|
baseAssetData: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
|
||||||
quoteAssetData: '0xef7fff64389b814a946f3e92105513705ca6b990',
|
quoteAssetData: '0xef7fff64389b814a946f3e92105513705ca6b990',
|
||||||
snapshot: true,
|
|
||||||
limit: 100,
|
limit: 100,
|
||||||
};
|
};
|
||||||
describe('#subscribe', () => {
|
describe('#subscribe', () => {
|
||||||
it('throws when subscriptionOpts does not conform to schema', () => {
|
it('throws when subscriptionOpts does not conform to schema', () => {
|
||||||
const badSubscribeCall = openOrdersChannel.subscribe.bind(openOrdersChannel, {});
|
const badSubscribeCall = openOrdersChannel.subscribe.bind(openOrdersChannel, {
|
||||||
expect(badSubscribeCall).throws(
|
makerAssetData: 5,
|
||||||
'Expected subscriptionOpts to conform to schema /RelayerApiOrdersChannelSubscribePayload\nEncountered: {}\nValidation errors: instance requires property "baseAssetData", instance requires property "quoteAssetData"',
|
});
|
||||||
);
|
expect(badSubscribeCall).throws();
|
||||||
});
|
});
|
||||||
it('does not throw when inputs are of correct types', () => {
|
it('does not throw when inputs are of correct types', () => {
|
||||||
const goodSubscribeCall = openOrdersChannel.subscribe.bind(openOrdersChannel, subscriptionOpts);
|
const goodSubscribeCall = openOrdersChannel.subscribe.bind(openOrdersChannel, subscriptionOpts);
|
Loading…
x
Reference in New Issue
Block a user