From 57b1a72b7f7777c660f211bd30f290a0180022d5 Mon Sep 17 00:00:00 2001 From: tiagosiebler Date: Thu, 6 Feb 2025 12:08:51 +0000 Subject: [PATCH] feat(v4.0.0-beta.5): BREAKING CHANGE: rename "error" event to "exception" to avoid unhandled exceptions --- examples/demo-trading.ts | 2 +- examples/fasterHmacSign.ts | 2 +- examples/ws-api-promises.ts | 4 ++-- examples/ws-public-v5.ts | 15 ++++++--------- package-lock.json | 4 ++-- package.json | 2 +- src/util/BaseWSClient.ts | 30 +++++++++++++----------------- src/websocket-client.ts | 8 ++++---- test/v5/public.ws.test.ts | 10 +++++++--- test/ws.util.ts | 10 +++++----- 10 files changed, 42 insertions(+), 45 deletions(-) diff --git a/examples/demo-trading.ts b/examples/demo-trading.ts index 190e41c0..b4002a07 100644 --- a/examples/demo-trading.ts +++ b/examples/demo-trading.ts @@ -91,7 +91,7 @@ function setWsClientEventListeners( websocketClient.on('reconnected', (data) => { console.log(new Date(), accountRef, 'ws has reconnected ', data?.wsKey); }); - websocketClient.on('error', (data) => { + websocketClient.on('exception', (data) => { console.error(new Date(), accountRef, 'ws exception: ', data); }); }); diff --git a/examples/fasterHmacSign.ts b/examples/fasterHmacSign.ts index d9dcca2b..99b3f4f4 100644 --- a/examples/fasterHmacSign.ts +++ b/examples/fasterHmacSign.ts @@ -117,7 +117,7 @@ function setWsClientEventListeners( websocketClient.on('reconnected', (data) => { console.log(new Date(), accountRef, 'ws has reconnected ', data?.wsKey); }); - websocketClient.on('error', (data) => { + websocketClient.on('exception', (data) => { console.error(new Date(), accountRef, 'ws exception: ', data); }); }); diff --git a/examples/ws-api-promises.ts b/examples/ws-api-promises.ts index 5be77d9e..4ccf41e5 100644 --- a/examples/ws-api-promises.ts +++ b/examples/ws-api-promises.ts @@ -40,8 +40,8 @@ wsClient.on('reconnected', (data) => { wsClient.on('authenticated', (data) => { console.log('ws has authenticated ', data?.wsKey); }); -wsClient.on('error', (data) => { - console.error('ws error: ', data); +wsClient.on('exception', (data) => { + console.error('ws exception: ', data); }); async function main() { diff --git a/examples/ws-public-v5.ts b/examples/ws-public-v5.ts index 3fe50a4e..1feea009 100644 --- a/examples/ws-public-v5.ts +++ b/examples/ws-public-v5.ts @@ -17,12 +17,8 @@ const logger = { * - Heartbeats/ping/pong/reconnects are all handled automatically. * If a connection drops, the client will clean it up, respawn a fresh connection and resubscribe for you. */ -const wsClient = new WebsocketClient( - { - // demoTrading: true, - }, - logger, -); + +const wsClient = new WebsocketClient(); wsClient.on('update', (data) => { console.log('raw message received ', JSON.stringify(data)); @@ -40,9 +36,10 @@ wsClient.on('reconnect', ({ wsKey }) => { wsClient.on('reconnected', (data) => { console.log('ws has reconnected ', data?.wsKey); }); -// wsClient.on('error', (data) => { -// console.error('ws exception: ', data); -// }); + +wsClient.on('exception', (data) => { + console.error('ws exception: ', data); +}); /** * For public V5 topics, use the subscribeV5 method and include the API category this topic is for. diff --git a/package-lock.json b/package-lock.json index c3edbf45..35b92ac3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bybit-api", - "version": "4.0.0-beta.4", + "version": "4.0.0-beta.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bybit-api", - "version": "4.0.0-beta.4", + "version": "4.0.0-beta.5", "license": "MIT", "dependencies": { "axios": "^1.7.9", diff --git a/package.json b/package.json index 8018b866..ae5ab4ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bybit-api", - "version": "4.0.0-beta.4", + "version": "4.0.0-beta.5", "description": "Complete & robust Node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & strong end to end tests.", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/util/BaseWSClient.ts b/src/util/BaseWSClient.ts index 59657dae..f344907f 100644 --- a/src/util/BaseWSClient.ts +++ b/src/util/BaseWSClient.ts @@ -23,6 +23,8 @@ import { } from './websockets'; import { WsOperation } from '../types/websockets/ws-api'; +type UseTheExceptionEventInstead = never; + interface WSClientEventMap { /** Connection opened. If this connection was previously opened and reconnected, expect the reconnected event instead */ open: (evt: { wsKey: WsKey; event: any }) => void; @@ -39,10 +41,10 @@ interface WSClientEventMap { /** Received data for topic */ update: (response: any & { wsKey: WsKey }) => void; /** - * Exception from ws client OR custom listeners (e.g. if you throw inside your event handler) - * @deprecated Use 'exception' instead. The 'error' event had the unintended consequence of throwing an unhandled promise rejection. + * See for more information: https://github.com/tiagosiebler/bybit-api/issues/413 + * @deprecated Use the 'exception' event instead. The 'error' event had the unintended consequence of throwing an unhandled promise rejection. */ - error: (response: any & { wsKey: WsKey; isWSAPIResponse?: boolean }) => void; + error: UseTheExceptionEventInstead; /** * Exception from ws client OR custom listeners (e.g. if you throw inside your event handler) */ @@ -57,12 +59,6 @@ interface WSClientEventMap { }) => void; } -export interface EmittableEvent { - eventType: 'response' | 'update' | 'error' | 'authenticated'; - event: TEvent; - isWSAPIResponse?: boolean; -} - // Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837 export interface BaseWebsocketClient< TWSKey extends string, @@ -80,6 +76,12 @@ export interface BaseWebsocketClient< ): boolean; } +export interface EmittableEvent { + eventType: 'response' | 'update' | 'exception' | 'authenticated'; + event: TEvent; + isWSAPIResponse?: boolean; +} + /** * A midflight WS request event (e.g. subscribe to these topics). * @@ -167,12 +169,6 @@ export abstract class BaseWebsocketClient< authPrivateRequests: false, ...options, }; - - // add default error handling so this doesn't crash node (if the user didn't set a handler) - // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars, no-unused-vars - this.on('error', (e) => { - // console.log('basewserr: ', e); - }); } /** @@ -593,7 +589,7 @@ export abstract class BaseWebsocketClient< if (!error.message) { this.logger.error(`${context} due to unexpected error: `, error); this.emit('response', { ...error, wsKey }); - this.emit('error', { ...error, wsKey }); + this.emit('exception', { ...error, wsKey }); return; } @@ -628,7 +624,7 @@ export abstract class BaseWebsocketClient< this.logger.error(`parseWsError(${context}, ${error}, ${wsKey}) `, error); this.emit('response', { ...error, wsKey }); - this.emit('error', { ...error, wsKey }); + this.emit('exception', { ...error, wsKey }); } /** Get a signature, build the auth request and send it */ diff --git a/src/websocket-client.ts b/src/websocket-client.ts index c19de741..d87bbd0d 100644 --- a/src/websocket-client.ts +++ b/src/websocket-client.ts @@ -136,7 +136,7 @@ export class WebsocketClient extends BaseWebsocketClient< perWsKeyTopics[derivedWsKey] = []; } - perWsKeyTopics[derivedWsKey].push(wsRequest); + perWsKeyTopics[derivedWsKey]!.push(wsRequest); } const promises: Promise[] = []; @@ -755,7 +755,7 @@ export class WebsocketClient extends BaseWebsocketClient< } results.push({ - eventType: 'error', + eventType: 'exception', event: parsed, isWSAPIResponse: true, }); @@ -804,7 +804,7 @@ export class WebsocketClient extends BaseWebsocketClient< // Failed request if (parsed.success === false) { results.push({ - eventType: 'error', + eventType: 'exception', event: parsed, }); return results; @@ -851,7 +851,7 @@ export class WebsocketClient extends BaseWebsocketClient< exception: e, eventData: event.data, }, - eventType: 'error', + eventType: 'exception', }); this.logger.error('Failed to parse event data due to exception: ', { diff --git a/test/v5/public.ws.test.ts b/test/v5/public.ws.test.ts index f389411c..3d35898e 100644 --- a/test/v5/public.ws.test.ts +++ b/test/v5/public.ws.test.ts @@ -9,13 +9,17 @@ describe.skip('Public V5 Websocket client', () => { describe('Topics subscription confirmation', () => { it('can subscribeV5 to LINEAR with valid topic', async () => { await expect( - api.subscribeV5(`publicTrade.${linearSymbol}`, linearCategory), - ).resolves.toBeUndefined(); + Promise.allSettled( + api.subscribeV5(`publicTrade.${linearSymbol}`, linearCategory), + ), + ).resolves.toStrictEqual([]); }); it('cannot subscribeV5 to LINEAR with valid topic', async () => { try { - await api.subscribeV5(`publicTrade.${linearSymbol}X`, linearCategory); + await Promise.allSettled( + api.subscribeV5(`publicTrade.${linearSymbol}X`, linearCategory), + ); } catch (e) { expect(e).toBeDefined(); expect(e).toMatch(`(publicTrade.${linearSymbol}X) failed to subscribe`); diff --git a/test/ws.util.ts b/test/ws.util.ts index a8c15349..db82eee9 100644 --- a/test/ws.util.ts +++ b/test/ws.util.ts @@ -62,7 +62,7 @@ export function waitForSocketEvent( } wsClient.on(event, (e) => resolver(e)); - wsClient.on('error', (e) => rejector(e)); + wsClient.on('exception', (e) => rejector(e)); // if (event !== 'close') { // wsClient.on('close', (event) => { @@ -78,21 +78,21 @@ export function waitForSocketEvent( export function listenToSocketEvents(wsClient: WebsocketClient) { const retVal: Record< - 'update' | 'open' | 'response' | 'close' | 'error', + 'update' | 'open' | 'response' | 'close' | 'exception', typeof jest.fn > = { open: jest.fn(), response: jest.fn(), update: jest.fn(), close: jest.fn(), - error: jest.fn(), + exception: jest.fn(), }; wsClient.on('open', retVal.open); wsClient.on('response', retVal.response); wsClient.on('update', retVal.update); wsClient.on('close', retVal.close); - wsClient.on('error', retVal.error); + wsClient.on('exception', retVal.exception); return { ...retVal, @@ -101,7 +101,7 @@ export function listenToSocketEvents(wsClient: WebsocketClient) { wsClient.removeListener('response', retVal.response); wsClient.removeListener('update', retVal.update); wsClient.removeListener('close', retVal.close); - wsClient.removeListener('error', retVal.error); + wsClient.removeListener('exception', retVal.exception); }, }; }