Add configurable heartbeat to WebSocketOrderbookChannel

This commit is contained in:
Brandon Millman 2018-02-15 14:22:00 -08:00
parent e2b51c5dc4
commit c4bcf24640
7 changed files with 43 additions and 2 deletions

View File

@ -3,6 +3,7 @@
## v0.6.0 - _TBD, 2018_ ## v0.6.0 - _TBD, 2018_
* Add pagination options to HttpClient methods (#393) * Add pagination options to HttpClient methods (#393)
* Add heartbeat configuration to WebSocketOrderbookChannel constructor (#393)
## v0.5.7 - _February 9, 2018_ ## v0.5.7 - _February 9, 2018_

View File

@ -17,4 +17,5 @@ export {
TokenPairsItem, TokenPairsItem,
TokenPairsRequestOpts, TokenPairsRequestOpts,
TokenTradeInfo, TokenTradeInfo,
WebSocketOrderbookChannelConfig,
} from './types'; } from './types';

View File

@ -3,6 +3,7 @@ import { orderBookRequestSchema } from './orderbook_request_schema';
import { ordersRequestOptsSchema } from './orders_request_opts_schema'; import { ordersRequestOptsSchema } from './orders_request_opts_schema';
import { pagedRequestOptsSchema } from './paged_request_opts_schema'; import { pagedRequestOptsSchema } from './paged_request_opts_schema';
import { tokenPairsRequestOptsSchema } from './token_pairs_request_opts_schema'; import { tokenPairsRequestOptsSchema } from './token_pairs_request_opts_schema';
import { webSocketOrderbookChannelConfigSchema } from './websocket_orderbook_channel_config_schema';
export const schemas = { export const schemas = {
feesRequestSchema, feesRequestSchema,
@ -10,4 +11,5 @@ export const schemas = {
ordersRequestOptsSchema, ordersRequestOptsSchema,
pagedRequestOptsSchema, pagedRequestOptsSchema,
tokenPairsRequestOptsSchema, tokenPairsRequestOptsSchema,
webSocketOrderbookChannelConfigSchema,
}; };

View File

@ -0,0 +1,7 @@
export const webSocketOrderbookChannelConfigSchema = {
id: '/WebSocketOrderbookChannelConfig',
type: 'object',
properties: {
heartbeatIntervalMs: { type: 'number' },
},
};

View File

@ -43,6 +43,13 @@ export interface OrderbookChannel {
close: () => void; close: () => void;
} }
/*
* heartbeatInterval: Interval in milliseconds that the orderbook channel should ping the underlying websocket. Default: 15000
*/
export interface WebSocketOrderbookChannelConfig {
heartbeatIntervalMs?: number;
}
/* /*
* baseTokenAddress: The address of token designated as the baseToken in the currency pair calculation of price * baseTokenAddress: The address of token designated as the baseToken in the currency pair calculation of price
* quoteTokenAddress: The address of token designated as the quoteToken in the currency pair calculation of price * quoteTokenAddress: The address of token designated as the quoteToken in the currency pair calculation of price

View File

@ -3,6 +3,7 @@ import { schemas } from '@0xproject/json-schemas';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as WebSocket from 'websocket'; import * as WebSocket from 'websocket';
import { schemas as clientSchemas } from './schemas/schemas';
import { import {
OrderbookChannel, OrderbookChannel,
OrderbookChannelHandler, OrderbookChannelHandler,
@ -10,9 +11,12 @@ import {
OrderbookChannelSubscriptionOpts, OrderbookChannelSubscriptionOpts,
WebsocketClientEventType, WebsocketClientEventType,
WebsocketConnectionEventType, WebsocketConnectionEventType,
WebSocketOrderbookChannelConfig,
} from './types'; } from './types';
import { orderbookChannelMessageParser } from './utils/orderbook_channel_message_parser'; import { orderbookChannelMessageParser } from './utils/orderbook_channel_message_parser';
const DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
/** /**
* 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
* that implements the standard relayer API v0 * that implements the standard relayer API v0
@ -21,15 +25,25 @@ 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 _heartbeatTimerIfExists?: NodeJS.Timer;
private _subscriptionCounter = 0; private _subscriptionCounter = 0;
private _heartbeatIntervalMs: number;
/** /**
* 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
* @param url The configuration object. Look up the type for the description.
* @return An instance of WebSocketOrderbookChannel * @return An instance of WebSocketOrderbookChannel
*/ */
constructor(url: string) { constructor(url: string, config?: WebSocketOrderbookChannelConfig) {
assert.isUri('url', url); assert.isUri('url', url);
if (!_.isUndefined(config)) {
assert.doesConformToSchema('config', config, clientSchemas.webSocketOrderbookChannelConfigSchema);
}
this._apiEndpointUrl = url; this._apiEndpointUrl = url;
this._heartbeatIntervalMs =
_.isUndefined(config) || _.isUndefined(config.heartbeatIntervalMs)
? DEFAULT_HEARTBEAT_INTERVAL_MS
: config.heartbeatIntervalMs;
this._client = new WebSocket.client(); this._client = new WebSocket.client();
} }
/** /**
@ -63,7 +77,7 @@ export class WebSocketOrderbookChannel implements OrderbookChannel {
connection.on(WebsocketConnectionEventType.Error, wsError => { connection.on(WebsocketConnectionEventType.Error, wsError => {
handler.onError(this, subscriptionOpts, wsError); handler.onError(this, subscriptionOpts, wsError);
}); });
connection.on(WebsocketConnectionEventType.Close, () => { connection.on(WebsocketConnectionEventType.Close, (code: number, desc: string) => {
handler.onClose(this, subscriptionOpts); handler.onClose(this, subscriptionOpts);
}); });
connection.on(WebsocketConnectionEventType.Message, message => { connection.on(WebsocketConnectionEventType.Message, message => {
@ -80,6 +94,9 @@ export class WebSocketOrderbookChannel implements OrderbookChannel {
if (!_.isUndefined(this._connectionIfExists)) { if (!_.isUndefined(this._connectionIfExists)) {
this._connectionIfExists.close(); this._connectionIfExists.close();
} }
if (!_.isUndefined(this._heartbeatTimerIfExists)) {
clearInterval(this._heartbeatTimerIfExists);
}
} }
private _getConnection(callback: (error?: Error, connection?: WebSocket.connection) => void) { private _getConnection(callback: (error?: Error, connection?: WebSocket.connection) => void) {
if (!_.isUndefined(this._connectionIfExists) && this._connectionIfExists.connected) { if (!_.isUndefined(this._connectionIfExists) && this._connectionIfExists.connected) {
@ -87,6 +104,11 @@ export class WebSocketOrderbookChannel implements OrderbookChannel {
} else { } else {
this._client.on(WebsocketClientEventType.Connect, connection => { this._client.on(WebsocketClientEventType.Connect, connection => {
this._connectionIfExists = connection; this._connectionIfExists = connection;
if (this._heartbeatIntervalMs !== 0) {
this._heartbeatTimerIfExists = setInterval(() => {
connection.ping('');
}, this._heartbeatIntervalMs);
}
callback(undefined, this._connectionIfExists); callback(undefined, this._connectionIfExists);
}); });
this._client.on(WebsocketClientEventType.ConnectFailed, error => { this._client.on(WebsocketClientEventType.ConnectFailed, error => {

View File

@ -63,6 +63,7 @@ const docsInfoConfig: DocsInfoConfig = {
'TokenPairsRequest', 'TokenPairsRequest',
'TokenPairsRequestOpts', 'TokenPairsRequestOpts',
'TokenTradeInfo', 'TokenTradeInfo',
'WebSocketOrderbookChannelConfig',
'Order', 'Order',
'SignedOrder', 'SignedOrder',
'ECSignature', 'ECSignature',