Skip to content
This repository has been archived by the owner on Apr 2, 2024. It is now read-only.

Commit

Permalink
feat: Add additional WebSocket message types (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
hjoelr committed Jan 3, 2022
1 parent 6c79874 commit 6bf86e4
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 2 deletions.
130 changes: 130 additions & 0 deletions src/client/WebSocketClient.test.ts
Expand Up @@ -2,6 +2,13 @@ import WebSocket = require('ws');
import tickerBTCUSD from '../test/fixtures/ws/ticker/BTC-USD.json';
import statusPayload from '../test/fixtures/ws/status/status.json';
import matchesBTCUSD from '../test/fixtures/ws/matches/BTC-USD.json';
import l2snapshotBTCUSD from '../test/fixtures/ws/level2/snapshot.json';
import l2updateBTCUSD from '../test/fixtures/ws/level2/l2update.json';
import fullReceivedLimitBTCUSD from '../test/fixtures/ws/full/received-limit.json';
import fullActivateBTCUSD from '../test/fixtures/ws/full/activate.json';
import fullOpenBTCUSD from '../test/fixtures/ws/full/open.json';
import fullDoneBTCUSD from '../test/fixtures/ws/full/done.json';
import fullChangeBTCUSD from '../test/fixtures/ws/full/change.json';
import emptySubscriptions from '../test/fixtures/ws/empty-subscriptions.json';
import {
WebSocketChannelName,
Expand Down Expand Up @@ -205,6 +212,129 @@ describe('WebSocketClient', () => {
ws.connect();
});

it('receives typed "snapshot" messages from "level2" channel', done => {
const channel = {
name: WebSocketChannelName.LEVEL2,
product_ids: ['BTC-USD'],
};

const ws = mockWebSocketResponse(done, channel, l2snapshotBTCUSD);

ws.on(WebSocketEvent.ON_MESSAGE_L2SNAPSHOT, snapshotMessage => {
expect<number>(snapshotMessage.asks.length).toBe(10);
expect(snapshotMessage.asks[0]).toEqual(['47009.28', '0.00100000']);
expect<number>(snapshotMessage.bids.length).toBe(10);
ws.unsubscribe(channel);
});

ws.connect();
});

it('receives typed "l2update" messages from "level2" channel', done => {
const channel = {
name: WebSocketChannelName.LEVEL2,
product_ids: ['BTC-USD'],
};

const ws = mockWebSocketResponse(done, channel, l2updateBTCUSD);

ws.on(WebSocketEvent.ON_MESSAGE_L2UPDATE, updateMessage => {
expect<number>(updateMessage.changes.length).toBe(5);
expect(updateMessage.changes[0]).toEqual(['buy', '46961.95', '0.00000000']);
expect(updateMessage.changes[1]).toEqual(['sell', '47027.24', '0.04443115']);
ws.unsubscribe(channel);
});

ws.connect();
});

it('receives typed "activate" messages from "full" channel', done => {
const channel = {
name: WebSocketChannelName.FULL,
product_ids: ['BTC-USD'],
};

const ws = mockWebSocketResponse(done, channel, fullActivateBTCUSD);

ws.on(WebSocketEvent.ON_MESSAGE_FULL_ACTIVATE, message => {
expect(message.profile_id).toBe('30000727-d308-cf50-7b1c-c06deb1934fc');
expect(message.private).toBe(true);
expect(message.stop_type).toBe('entry');
ws.unsubscribe(channel);
});

ws.connect();
});

it('receives typed "received" messages from "full" channel', done => {
const channel = {
name: WebSocketChannelName.FULL,
product_ids: ['BTC-USD'],
};

const ws = mockWebSocketResponse(done, channel, fullReceivedLimitBTCUSD);

ws.on(WebSocketEvent.ON_MESSAGE_FULL_RECEIVED, message => {
expect(message.order_type).toBe('limit');
expect(message.order_id).toBe('d50ec984-77a8-460a-b958-66f114b0de9b');
ws.unsubscribe(channel);
});

ws.connect();
});

it('receives typed "open" messages from "full" channel', done => {
const channel = {
name: WebSocketChannelName.FULL,
product_ids: ['BTC-USD'],
};

const ws = mockWebSocketResponse(done, channel, fullOpenBTCUSD);

ws.on(WebSocketEvent.ON_MESSAGE_FULL_OPEN, message => {
expect(message.profile_id).toBe(undefined);
expect(message.remaining_size).toBe('1.00');
ws.unsubscribe(channel);
});

ws.connect();
});

it('receives typed "done" messages from "full" channel', done => {
const channel = {
name: WebSocketChannelName.FULL,
product_ids: ['BTC-USD'],
};

const ws = mockWebSocketResponse(done, channel, fullDoneBTCUSD);

ws.on(WebSocketEvent.ON_MESSAGE_FULL_DONE, message => {
expect(message.profile_id).toBe(undefined);
expect(message.remaining_size).toBe('0');
expect(message.reason).toBe('filled');
ws.unsubscribe(channel);
});

ws.connect();
});

it('receives typed "change" messages from "full" channel', done => {
const channel = {
name: WebSocketChannelName.FULL,
product_ids: ['BTC-USD'],
};

const ws = mockWebSocketResponse(done, channel, fullChangeBTCUSD);

ws.on(WebSocketEvent.ON_MESSAGE_FULL_CHANGE, message => {
expect(message.new_size).toBe('5.23512');
expect(message.old_size).toBe('12.234412');
ws.unsubscribe(channel);
});

ws.connect();
});

it('receives typed "ticker" messages from the special "ticker_1000" channel', done => {
const channel = {
name: WebSocketChannelName.TICKER_1000,
Expand Down
144 changes: 142 additions & 2 deletions src/client/WebSocketClient.ts
Expand Up @@ -130,21 +130,31 @@ type WebSocketMessage =
| WebSocketStatusMessage
| WebSocketTickerMessage
| WebSocketMatchMessage
| WebSocketErrorMessage;
| WebSocketErrorMessage
| WebSocketLastMatchMessage
| WebSocketL2SnapshotMessage
| WebSocketL2UpdateMessage
| WebSocketFullReceivedMessage
| WebSocketFullOpenMessage
| WebSocketFullDoneMessage
| WebSocketFullChangeMessage
| WebSocketFullActivateMessage;

export interface WebSocketErrorMessage {
message: string;
reason: string;
type: WebSocketResponseType.ERROR;
}

export interface WebSocketMatchMessage {
export interface WebSocketMatchMessage extends WebSocketUserMessage {
maker_fee_rate?: string;
maker_order_id: UUID_V4;
price: string;
product_id: string;
sequence: number;
side: OrderSide;
size: string;
taker_fee_rate?: string;
taker_order_id: UUID_V4;
time: ISO_8601_MS_UTC;
trade_id: number;
Expand Down Expand Up @@ -185,6 +195,88 @@ export interface WebSocketTickerMessage {
volume_30d: string;
}

export interface WebSocketL2SnapshotMessage {
asks: [string, string][];
bids: [string, string][];
product_id: string;
type: WebSocketResponseType.LEVEL2_SNAPSHOT;
}

export interface WebSocketL2UpdateMessage {
changes: [string, string, string][];
product_id: string;
type: WebSocketResponseType.LEVEL2_UPDATE;
}

export interface WebSocketFullReceivedMessage extends WebSocketUserMessage {
client_oid: string;
funds?: string;
order_id: string;
order_type: 'limit' | 'market';
price?: string;
product_id: string;
sequence: number;
side: OrderSide;
size?: string;
time: ISO_8601_MS_UTC;
type: WebSocketResponseType.FULL_RECEIVED;
}

export interface WebSocketFullOpenMessage extends WebSocketUserMessage {
order_id: string;
price: string;
product_id: string;
remaining_size: string;
sequence: number;
side: OrderSide;
time: ISO_8601_MS_UTC;
type: WebSocketResponseType.FULL_OPEN;
}

export interface WebSocketFullDoneMessage extends WebSocketUserMessage {
order_id: string;
price: string;
product_id: string;
reason: 'filled' | 'canceled';
remaining_size: string;
sequence: number;
side: OrderSide;
time: ISO_8601_MS_UTC;
type: WebSocketResponseType.FULL_DONE;
}

export interface WebSocketFullChangeMessage extends WebSocketUserMessage {
new_funds?: string;
new_size?: string;
old_funds?: string;
old_size?: string;
order_id: string;
price: string;
product_id: string;
sequence: number;
side: OrderSide;
time: ISO_8601_MS_UTC;
type: WebSocketResponseType.FULL_CHANGE;
}

export interface WebSocketFullActivateMessage extends WebSocketUserMessage {
funds: string;
order_id: string;
private: boolean;
product_id: string;
side: OrderSide;
size: string;
stop_price: string;
stop_type: string;
timestamp: string;
type: WebSocketResponseType.FULL_ACTIVATE;
}

interface WebSocketUserMessage {
profile_id?: string;
user_id?: string;
}

export type WebSocketLastMatchMessage = Omit<WebSocketMatchMessage, 'type'> & {type: WebSocketResponseType.LAST_MATCH};

export interface WebSocketSubscription {
Expand All @@ -197,6 +289,13 @@ export enum WebSocketEvent {
ON_ERROR = 'WebSocketEvent.ON_ERROR',
ON_MESSAGE = 'WebSocketEvent.ON_MESSAGE',
ON_MESSAGE_ERROR = 'WebSocketEvent.ON_MESSAGE_ERROR',
ON_MESSAGE_FULL_ACTIVATE = 'WebSocketEvent.ON_MESSAGE_FULL_ACTIVATE',
ON_MESSAGE_FULL_CHANGE = 'WebSocketEvent.ON_MESSAGE_FULL_CHANGE',
ON_MESSAGE_FULL_DONE = 'WebSocketEvent.ON_MESSAGE_FULL_DONE',
ON_MESSAGE_FULL_OPEN = 'WebSocketEvent.ON_MESSAGE_FULL_OPEN',
ON_MESSAGE_FULL_RECEIVED = 'WebSocketEvent.ON_MESSAGE_FULL_RECEIVED',
ON_MESSAGE_L2SNAPSHOT = 'WebSocketEvent.ON_MESSAGE_L2SNAPSHOT',
ON_MESSAGE_L2UPDATE = 'WebSocketEvent.ON_MESSAGE_L2UPDATE',
ON_MESSAGE_MATCHES = 'WebSocketEvent.ON_MESSAGE_MATCHES',
ON_MESSAGE_STATUS = 'WebSocketEvent.ON_MESSAGE_STATUS',
ON_MESSAGE_TICKER = 'WebSocketEvent.ON_MESSAGE_TICKER',
Expand Down Expand Up @@ -224,6 +323,26 @@ export interface WebSocketClient {

on(event: WebSocketEvent.ON_SUBSCRIPTION_UPDATE, listener: (subscriptions: WebSocketSubscription) => void): this;

on(event: WebSocketEvent.ON_MESSAGE_L2SNAPSHOT, listener: (subscriptions: WebSocketL2SnapshotMessage) => void): this;

on(event: WebSocketEvent.ON_MESSAGE_L2UPDATE, listener: (subscriptions: WebSocketL2UpdateMessage) => void): this;

on(
event: WebSocketEvent.ON_MESSAGE_FULL_RECEIVED,
listener: (subscriptions: WebSocketFullReceivedMessage) => void
): this;

on(event: WebSocketEvent.ON_MESSAGE_FULL_OPEN, listener: (subscriptions: WebSocketFullOpenMessage) => void): this;

on(event: WebSocketEvent.ON_MESSAGE_FULL_DONE, listener: (subscriptions: WebSocketFullDoneMessage) => void): this;

on(event: WebSocketEvent.ON_MESSAGE_FULL_CHANGE, listener: (subscriptions: WebSocketFullChangeMessage) => void): this;

on(
event: WebSocketEvent.ON_MESSAGE_FULL_ACTIVATE,
listener: (subscriptions: WebSocketFullActivateMessage) => void
): this;

on(event: WebSocketEvent.ON_OPEN, listener: (event: Event) => void): this;
}

Expand Down Expand Up @@ -303,6 +422,27 @@ export class WebSocketClient extends EventEmitter {
case WebSocketResponseType.LAST_MATCH:
this.emit(WebSocketEvent.ON_MESSAGE_MATCHES, response);
break;
case WebSocketResponseType.LEVEL2_SNAPSHOT:
this.emit(WebSocketEvent.ON_MESSAGE_L2SNAPSHOT, response);
break;
case WebSocketResponseType.LEVEL2_UPDATE:
this.emit(WebSocketEvent.ON_MESSAGE_L2UPDATE, response);
break;
case WebSocketResponseType.FULL_RECEIVED:
this.emit(WebSocketEvent.ON_MESSAGE_FULL_RECEIVED, response);
break;
case WebSocketResponseType.FULL_OPEN:
this.emit(WebSocketEvent.ON_MESSAGE_FULL_OPEN, response);
break;
case WebSocketResponseType.FULL_DONE:
this.emit(WebSocketEvent.ON_MESSAGE_FULL_DONE, response);
break;
case WebSocketResponseType.FULL_CHANGE:
this.emit(WebSocketEvent.ON_MESSAGE_FULL_CHANGE, response);
break;
case WebSocketResponseType.FULL_ACTIVATE:
this.emit(WebSocketEvent.ON_MESSAGE_FULL_ACTIVATE, response);
break;
}
};

Expand Down
14 changes: 14 additions & 0 deletions src/test/fixtures/ws/full/activate.json
@@ -0,0 +1,14 @@
{
"type": "activate",
"product_id": "BTC-USD",
"timestamp": "1483736448.299000",
"user_id": "12",
"profile_id": "30000727-d308-cf50-7b1c-c06deb1934fc",
"order_id": "7b52009b-64fd-0a2a-49e6-d8a939753077",
"stop_type": "entry",
"side": "buy",
"stop_price": "80",
"size": "2",
"funds": "50",
"private": true
}
11 changes: 11 additions & 0 deletions src/test/fixtures/ws/full/change.json
@@ -0,0 +1,11 @@
{
"type": "change",
"time": "2014-11-07T08:19:27.028459Z",
"sequence": 80,
"order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
"product_id": "BTC-USD",
"new_size": "5.23512",
"old_size": "12.234412",
"price": "400.23",
"side": "sell"
}
11 changes: 11 additions & 0 deletions src/test/fixtures/ws/full/done.json
@@ -0,0 +1,11 @@
{
"type": "done",
"time": "2014-11-07T08:19:27.028459Z",
"product_id": "BTC-USD",
"sequence": 10,
"price": "200.2",
"order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
"reason": "filled",
"side": "sell",
"remaining_size": "0"
}
10 changes: 10 additions & 0 deletions src/test/fixtures/ws/full/open.json
@@ -0,0 +1,10 @@
{
"type": "open",
"time": "2014-11-07T08:19:27.028459Z",
"product_id": "BTC-USD",
"sequence": 10,
"order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
"price": "200.2",
"remaining_size": "1.00",
"side": "sell"
}
11 changes: 11 additions & 0 deletions src/test/fixtures/ws/full/received-limit.json
@@ -0,0 +1,11 @@
{
"type": "received",
"time": "2014-11-07T08:19:27.028459Z",
"product_id": "BTC-USD",
"sequence": 10,
"order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
"size": "1.34",
"price": "502.1",
"side": "buy",
"order_type": "limit"
}

0 comments on commit 6bf86e4

Please sign in to comment.