Skip to content

Commit

Permalink
feat(): working spot & futures heartbeats
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagosiebler committed Mar 11, 2024
1 parent 9d399a3 commit b2e6998
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 21 deletions.
16 changes: 9 additions & 7 deletions examples/ws-spot-public.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import {
DefaultLogger,
LogParams,
WebsocketClient,
// WsSpotOperation,
} from '../src';

DefaultLogger.silly = (...params: LogParams): void => {
console.log('silly', ...params);
};

async function start() {
const client = new WebsocketClient();

Expand Down Expand Up @@ -60,6 +54,12 @@ async function start() {
});

try {
/**
* Use the client subscribe(topic, market) pattern to subscribe to any websocket topic.
*
* You can subscribe to topics one at a time:
*/

// Ticker Channel
// client.subscribe('spot/ticker:BTC_USDT', 'spot');

Expand All @@ -75,7 +75,9 @@ async function start() {
// Trade Channel
// client.subscribe('spot/trade:BTC_USDT', 'spot');

// Or have multiple topics in one array:
/**
* Or have multiple topics in one array, in a single request:
*/
client.subscribe(
[
'spot/ticker:BTC_USDT',
Expand Down
55 changes: 52 additions & 3 deletions src/WebsocketClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,65 @@ export class WebsocketClient extends BaseWebsocketClient<
*
*/

protected sendPingEvent(wsKey: WsKey) {
switch (wsKey) {
case WS_KEY_MAP.spotPublicV1:
case WS_KEY_MAP.spotPrivateV1: {
this.tryWsSend(wsKey, 'ping');
break;
}
case WS_KEY_MAP.futuresPublicV1:
case WS_KEY_MAP.futuresPrivateV1: {
this.tryWsSend(wsKey, '{"action":"ping"}');
break;
}
default: {
throw neverGuard(wsKey, `Unhandled ping format: "${wsKey}"`);
}
}
if (
wsKey === WS_KEY_MAP.spotPrivateV1 ||
wsKey === WS_KEY_MAP.spotPublicV1
) {
this.tryWsSend(wsKey, 'ping');
return;
}
}

protected isWsPong(msg: any): boolean {
// bitmart spot
if (msg?.data === 'pong') {
return true;
}

// bitmart futures
// if (typeof event?.data === 'string') {
// return true;
// }
if (
typeof msg?.event?.data === 'string' &&
msg.event.data.startsWith('pong')
) {
return true;
}

// this.logger.info(`Not a pong: `, msg);

return false;
}

protected resolveEmittableEvents(event: MessageEventLike): EmittableEvent[] {
const results: EmittableEvent[] = [];

try {
const parsed = JSON.parse(event.data);

const responseEvents = ['subscribe', 'unsubscribe'];
if (typeof parsed.event === 'string') {

const eventAction = parsed.event || parsed.action;
if (typeof eventAction === 'string') {
// These are request/reply pattern events (e.g. after subscribing to topics or authenticating)
if (responseEvents.includes(parsed.event)) {
if (responseEvents.includes(eventAction)) {
results.push({
eventType: 'response',
event: parsed,
Expand All @@ -114,7 +163,7 @@ export class WebsocketClient extends BaseWebsocketClient<
}

this.logger.error(
`!! Unhandled string event type "${parsed.event}. Defaulting to "update" channel...`,
`!! Unhandled string event type "${eventAction}. Defaulting to "update" channel...`,
parsed,
);
}
Expand Down
18 changes: 15 additions & 3 deletions src/lib/BaseWSClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '../types/websockets/client.js';
import { WS_LOGGER_CATEGORY } from '../WebsocketClient.js';
import { DefaultLogger } from './logger.js';
import { isMessageEvent, isWsPong, MessageEventLike } from './requestUtils.js';
import { isMessageEvent, MessageEventLike } from './requestUtils.js';
import { WsStore } from './websocket/WsStore.js';
import { WsConnectionStateEnum } from './websocket/WsStore.types.js';

Expand Down Expand Up @@ -91,6 +91,9 @@ export abstract class BaseWebsocketClient<
isPrivate?: boolean,
): TWSKey;

protected abstract sendPingEvent(wsKey: TWSKey, ws: WebSocket): void;
protected abstract isWsPong(data: any): boolean;

protected abstract getWsAuthSignature(): Promise<{
expiresAt: number;
signature: string;
Expand Down Expand Up @@ -448,7 +451,7 @@ export abstract class BaseWebsocketClient<
this.clearPongTimer(wsKey);

this.logger.silly('Sending ping', { ...WS_LOGGER_CATEGORY, wsKey });
this.tryWsSend(wsKey, 'ping');
this.sendPingEvent(wsKey, this.wsStore.get(wsKey, true).ws);

this.wsStore.get(wsKey, true).activePongTimer = setTimeout(() => {
this.logger.info('Pong timeout - closing socket to reconnect', {
Expand Down Expand Up @@ -651,7 +654,7 @@ export abstract class BaseWebsocketClient<
// any message can clear the pong timer - wouldn't get a message if the ws wasn't working
this.clearPongTimer(wsKey);

if (isWsPong(event)) {
if (this.isWsPong(event)) {
this.logger.silly('Received pong', { ...WS_LOGGER_CATEGORY, wsKey });
return;
}
Expand Down Expand Up @@ -679,6 +682,15 @@ export abstract class BaseWebsocketClient<
}

for (const emittable of emittableEvents) {
if (this.isWsPong(emittable)) {
this.logger.silly('Received pong', {
...WS_LOGGER_CATEGORY,
wsKey,
data,
});
continue;
}

this.emit(emittable.eventType, { ...emittable.event, wsKey });
}

Expand Down
8 changes: 0 additions & 8 deletions src/lib/requestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,6 @@ export function getRestBaseUrl(

export const APIID = 'bitmartapinode1';

export function isWsPong(msg: any): boolean {
// bitmart
if (msg?.data === 'pong') {
return true;
}
return false;
}

export interface MessageEventLike {
target: WebSocket;
type: 'message';
Expand Down

0 comments on commit b2e6998

Please sign in to comment.