feat(v4.0.0-beta.5): BREAKING CHANGE: rename "error" event to "exception" to avoid unhandled exceptions

This commit is contained in:
tiagosiebler
2025-02-06 12:08:51 +00:00
parent a8f8d6bf15
commit 57b1a72b7f
10 changed files with 42 additions and 45 deletions

View File

@@ -91,7 +91,7 @@ function setWsClientEventListeners(
websocketClient.on('reconnected', (data) => { websocketClient.on('reconnected', (data) => {
console.log(new Date(), accountRef, 'ws has reconnected ', data?.wsKey); 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); console.error(new Date(), accountRef, 'ws exception: ', data);
}); });
}); });

View File

@@ -117,7 +117,7 @@ function setWsClientEventListeners(
websocketClient.on('reconnected', (data) => { websocketClient.on('reconnected', (data) => {
console.log(new Date(), accountRef, 'ws has reconnected ', data?.wsKey); 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); console.error(new Date(), accountRef, 'ws exception: ', data);
}); });
}); });

View File

@@ -40,8 +40,8 @@ wsClient.on('reconnected', (data) => {
wsClient.on('authenticated', (data) => { wsClient.on('authenticated', (data) => {
console.log('ws has authenticated ', data?.wsKey); console.log('ws has authenticated ', data?.wsKey);
}); });
wsClient.on('error', (data) => { wsClient.on('exception', (data) => {
console.error('ws error: ', data); console.error('ws exception: ', data);
}); });
async function main() { async function main() {

View File

@@ -17,12 +17,8 @@ const logger = {
* - Heartbeats/ping/pong/reconnects are all handled automatically. * - 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. * If a connection drops, the client will clean it up, respawn a fresh connection and resubscribe for you.
*/ */
const wsClient = new WebsocketClient(
{ const wsClient = new WebsocketClient();
// demoTrading: true,
},
logger,
);
wsClient.on('update', (data) => { wsClient.on('update', (data) => {
console.log('raw message received ', JSON.stringify(data)); console.log('raw message received ', JSON.stringify(data));
@@ -40,9 +36,10 @@ wsClient.on('reconnect', ({ wsKey }) => {
wsClient.on('reconnected', (data) => { wsClient.on('reconnected', (data) => {
console.log('ws has reconnected ', data?.wsKey); 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. * For public V5 topics, use the subscribeV5 method and include the API category this topic is for.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "bybit-api", "name": "bybit-api",
"version": "4.0.0-beta.4", "version": "4.0.0-beta.5",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bybit-api", "name": "bybit-api",
"version": "4.0.0-beta.4", "version": "4.0.0-beta.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",

View File

@@ -1,6 +1,6 @@
{ {
"name": "bybit-api", "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.", "description": "Complete & robust Node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & strong end to end tests.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@@ -23,6 +23,8 @@ import {
} from './websockets'; } from './websockets';
import { WsOperation } from '../types/websockets/ws-api'; import { WsOperation } from '../types/websockets/ws-api';
type UseTheExceptionEventInstead = never;
interface WSClientEventMap<WsKey extends string> { interface WSClientEventMap<WsKey extends string> {
/** Connection opened. If this connection was previously opened and reconnected, expect the reconnected event instead */ /** Connection opened. If this connection was previously opened and reconnected, expect the reconnected event instead */
open: (evt: { wsKey: WsKey; event: any }) => void; open: (evt: { wsKey: WsKey; event: any }) => void;
@@ -39,10 +41,10 @@ interface WSClientEventMap<WsKey extends string> {
/** Received data for topic */ /** Received data for topic */
update: (response: any & { wsKey: WsKey }) => void; update: (response: any & { wsKey: WsKey }) => void;
/** /**
* Exception from ws client OR custom listeners (e.g. if you throw inside your event handler) * See for more information: https://github.com/tiagosiebler/bybit-api/issues/413
* @deprecated Use 'exception' instead. The 'error' event had the unintended consequence of throwing an unhandled promise rejection. * @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) * Exception from ws client OR custom listeners (e.g. if you throw inside your event handler)
*/ */
@@ -57,12 +59,6 @@ interface WSClientEventMap<WsKey extends string> {
}) => void; }) => void;
} }
export interface EmittableEvent<TEvent = any> {
eventType: 'response' | 'update' | 'error' | 'authenticated';
event: TEvent;
isWSAPIResponse?: boolean;
}
// Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837 // Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837
export interface BaseWebsocketClient< export interface BaseWebsocketClient<
TWSKey extends string, TWSKey extends string,
@@ -80,6 +76,12 @@ export interface BaseWebsocketClient<
): boolean; ): boolean;
} }
export interface EmittableEvent<TEvent = any> {
eventType: 'response' | 'update' | 'exception' | 'authenticated';
event: TEvent;
isWSAPIResponse?: boolean;
}
/** /**
* A midflight WS request event (e.g. subscribe to these topics). * A midflight WS request event (e.g. subscribe to these topics).
* *
@@ -167,12 +169,6 @@ export abstract class BaseWebsocketClient<
authPrivateRequests: false, authPrivateRequests: false,
...options, ...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) { if (!error.message) {
this.logger.error(`${context} due to unexpected error: `, error); this.logger.error(`${context} due to unexpected error: `, error);
this.emit('response', { ...error, wsKey }); this.emit('response', { ...error, wsKey });
this.emit('error', { ...error, wsKey }); this.emit('exception', { ...error, wsKey });
return; return;
} }
@@ -628,7 +624,7 @@ export abstract class BaseWebsocketClient<
this.logger.error(`parseWsError(${context}, ${error}, ${wsKey}) `, error); this.logger.error(`parseWsError(${context}, ${error}, ${wsKey}) `, error);
this.emit('response', { ...error, wsKey }); 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 */ /** Get a signature, build the auth request and send it */

View File

@@ -136,7 +136,7 @@ export class WebsocketClient extends BaseWebsocketClient<
perWsKeyTopics[derivedWsKey] = []; perWsKeyTopics[derivedWsKey] = [];
} }
perWsKeyTopics[derivedWsKey].push(wsRequest); perWsKeyTopics[derivedWsKey]!.push(wsRequest);
} }
const promises: Promise<unknown>[] = []; const promises: Promise<unknown>[] = [];
@@ -755,7 +755,7 @@ export class WebsocketClient extends BaseWebsocketClient<
} }
results.push({ results.push({
eventType: 'error', eventType: 'exception',
event: parsed, event: parsed,
isWSAPIResponse: true, isWSAPIResponse: true,
}); });
@@ -804,7 +804,7 @@ export class WebsocketClient extends BaseWebsocketClient<
// Failed request // Failed request
if (parsed.success === false) { if (parsed.success === false) {
results.push({ results.push({
eventType: 'error', eventType: 'exception',
event: parsed, event: parsed,
}); });
return results; return results;
@@ -851,7 +851,7 @@ export class WebsocketClient extends BaseWebsocketClient<
exception: e, exception: e,
eventData: event.data, eventData: event.data,
}, },
eventType: 'error', eventType: 'exception',
}); });
this.logger.error('Failed to parse event data due to exception: ', { this.logger.error('Failed to parse event data due to exception: ', {

View File

@@ -9,13 +9,17 @@ describe.skip('Public V5 Websocket client', () => {
describe('Topics subscription confirmation', () => { describe('Topics subscription confirmation', () => {
it('can subscribeV5 to LINEAR with valid topic', async () => { it('can subscribeV5 to LINEAR with valid topic', async () => {
await expect( await expect(
api.subscribeV5(`publicTrade.${linearSymbol}`, linearCategory), Promise.allSettled(
).resolves.toBeUndefined(); api.subscribeV5(`publicTrade.${linearSymbol}`, linearCategory),
),
).resolves.toStrictEqual([]);
}); });
it('cannot subscribeV5 to LINEAR with valid topic', async () => { it('cannot subscribeV5 to LINEAR with valid topic', async () => {
try { try {
await api.subscribeV5(`publicTrade.${linearSymbol}X`, linearCategory); await Promise.allSettled(
api.subscribeV5(`publicTrade.${linearSymbol}X`, linearCategory),
);
} catch (e) { } catch (e) {
expect(e).toBeDefined(); expect(e).toBeDefined();
expect(e).toMatch(`(publicTrade.${linearSymbol}X) failed to subscribe`); expect(e).toMatch(`(publicTrade.${linearSymbol}X) failed to subscribe`);

View File

@@ -62,7 +62,7 @@ export function waitForSocketEvent(
} }
wsClient.on(event, (e) => resolver(e)); wsClient.on(event, (e) => resolver(e));
wsClient.on('error', (e) => rejector(e)); wsClient.on('exception', (e) => rejector(e));
// if (event !== 'close') { // if (event !== 'close') {
// wsClient.on('close', (event) => { // wsClient.on('close', (event) => {
@@ -78,21 +78,21 @@ export function waitForSocketEvent(
export function listenToSocketEvents(wsClient: WebsocketClient) { export function listenToSocketEvents(wsClient: WebsocketClient) {
const retVal: Record< const retVal: Record<
'update' | 'open' | 'response' | 'close' | 'error', 'update' | 'open' | 'response' | 'close' | 'exception',
typeof jest.fn typeof jest.fn
> = { > = {
open: jest.fn(), open: jest.fn(),
response: jest.fn(), response: jest.fn(),
update: jest.fn(), update: jest.fn(),
close: jest.fn(), close: jest.fn(),
error: jest.fn(), exception: jest.fn(),
}; };
wsClient.on('open', retVal.open); wsClient.on('open', retVal.open);
wsClient.on('response', retVal.response); wsClient.on('response', retVal.response);
wsClient.on('update', retVal.update); wsClient.on('update', retVal.update);
wsClient.on('close', retVal.close); wsClient.on('close', retVal.close);
wsClient.on('error', retVal.error); wsClient.on('exception', retVal.exception);
return { return {
...retVal, ...retVal,
@@ -101,7 +101,7 @@ export function listenToSocketEvents(wsClient: WebsocketClient) {
wsClient.removeListener('response', retVal.response); wsClient.removeListener('response', retVal.response);
wsClient.removeListener('update', retVal.update); wsClient.removeListener('update', retVal.update);
wsClient.removeListener('close', retVal.close); wsClient.removeListener('close', retVal.close);
wsClient.removeListener('error', retVal.error); wsClient.removeListener('exception', retVal.exception);
}, },
}; };
} }