feat(): upgrade WebSocket layer to extend BaseWS abstraction. feat(): add promisified WS workflows, feat(): add WS API integration

This commit is contained in:
tiagosiebler
2025-01-16 16:47:09 +00:00
parent b613fd956d
commit 8a7c8ea274
9 changed files with 2512 additions and 1200 deletions

View File

@@ -0,0 +1,136 @@
import { APIID, WS_KEY_MAP } from '../../util';
import {
AmendOrderParamsV5,
CancelOrderParamsV5,
OrderParamsV5,
} from '../request';
import { WsKey } from './ws-general';
export type WSAPIOperation = 'order.create' | 'order.amend' | 'order.cancel';
export type WsOperation =
| 'subscribe'
| 'unsubscribe'
| 'auth'
| 'ping'
| 'pong';
export const WS_API_Operations: WSAPIOperation[] = [
'order.create',
'order.amend',
'order.cancel',
];
export interface WsRequestOperationBybit<
TWSTopic extends string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
// TWSPayload = any,
> {
req_id: string;
op: WsOperation;
args?: (TWSTopic | string | number)[];
// payload?: TWSPayload;
}
export interface WSAPIRequest<
TRequestParams = undefined,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TWSOperation extends WSAPIOperation = any,
> {
reqId: string;
op: TWSOperation;
header: {
'X-BAPI-TIMESTAMP': string;
'X-BAPI-RECV-WINDOW': string;
Referer: typeof APIID;
};
args: [TRequestParams];
}
export interface WsAPIWsKeyTopicMap {
[WS_KEY_MAP.v5PrivateTrade]: WSAPIOperation;
}
export interface WsAPITopicRequestParamMap {
'order.create': OrderParamsV5;
'order.amend': AmendOrderParamsV5;
'order.cancel': CancelOrderParamsV5;
// ping: undefined;
}
export type WsAPITopicRequestParams =
WsAPITopicRequestParamMap[keyof WsAPITopicRequestParamMap];
export interface WSAPIResponse<
TResponseData extends object = object,
TOperation extends WSAPIOperation = WSAPIOperation,
> {
wsKey: WsKey;
/** Auto-generated */
reqId: string;
retCode: 0 | number;
retMsg: 'OK' | string;
op: TOperation;
data: [TResponseData];
header?: {
'X-Bapi-Limit': string;
'X-Bapi-Limit-Status': string;
'X-Bapi-Limit-Reset-Timestamp': string;
Traceid: string;
Timenow: string;
};
connId: string;
}
// export interface WsAPIResponseMap<TChannel extends WSAPITopic = WSAPITopic> {
// 'spot.login': WSAPIResponse<WSAPILoginResponse, TChannel>;
// 'futures.login': WSAPIResponse<WSAPILoginResponse, TChannel>;
// string: object;
// }
export interface WsAPIOperationResponseMap<
TResponseType extends object = object,
> {
'order.create': WSAPIResponse<TResponseType, 'order.cancel'>;
'order.amend': WSAPIResponse<TResponseType, 'order.amend'>;
'order.cancel': WSAPIResponse<TResponseType, 'order.cancel'>;
ping: {
retCode: 0 | number;
retMsg: 'OK' | string;
op: 'pong';
data: [string];
connId: string;
};
// 'spot.login': WSAPIResponse<WSAPILoginResponse, 'spot.login'>;
// 'futures.login': WSAPIResponse<WSAPILoginResponse, 'futures.login'>;
// 'spot.order_place': WSAPIResponse<TResponseType, 'spot.order_place'>;
// 'spot.order_cancel': WSAPIResponse<TResponseType, 'spot.order_cancel'>;
// 'spot.order_cancel_ids': WSAPIResponse<
// TResponseType,
// 'spot.order_cancel_ids'
// >;
// 'spot.order_cancel_cp': WSAPIResponse<TResponseType, 'spot.order_cancel_cp'>;
// 'spot.order_amend': WSAPIResponse<TResponseType, 'spot.order_amend'>;
// 'spot.order_status': WSAPIResponse<
// WSAPIOrderStatusResponse,
// 'spot.order_status'
// >;
// 'futures.order_place': WSAPIResponse<TResponseType[], 'futures.order_place'>;
// 'futures.order_batch_place': WSAPIResponse<
// TResponseType[],
// 'futures.order_batch_place'
// >;
// 'futures.order_cancel': WSAPIResponse<TResponseType, 'futures.order_cancel'>;
// 'futures.order_cancel_cp': WSAPIResponse<
// TResponseType,
// 'futures.order_cancel_cp'
// >;
// 'futures.order_amend': WSAPIResponse<TResponseType, 'futures.order_amend'>;
// 'futures.order_list': WSAPIResponse<TResponseType[], 'futures.order_list'>;
// 'futures.order_status': WSAPIResponse<
// WSAPIOrderStatusResponse,
// 'futures.order_status'
// >;
}

View File

@@ -1,3 +1,5 @@
import WebSocket from 'isomorphic-ws';
import {
CategoryV5,
ExecTypeV5,
@@ -18,7 +20,23 @@ import {
TPSLModeV5,
TradeModeV5,
} from '../shared-v5';
import { WsKey } from '.';
import { WsKey } from './ws-general';
export interface MessageEventLike {
target: WebSocket;
type: 'message';
data: string;
}
export function isMessageEvent(msg: unknown): msg is MessageEventLike {
if (typeof msg !== 'object' || !msg) {
return false;
}
const message = msg as MessageEventLike;
return message['type'] === 'message' && typeof message['data'] === 'string';
}
export interface WSPublicTopicEventV5<TTopic extends string, TType, TData> {
id?: string;

View File

@@ -82,10 +82,23 @@ export type WsTopic = WsPublicTopics | WsPrivateTopic;
/** This is used to differentiate between each of the available websocket streams (as bybit has multiple websockets) */
export type WsKey = (typeof WS_KEY_MAP)[keyof typeof WS_KEY_MAP];
export type WsMarket = 'all';
export interface WSClientConfigurableOptions {
/** Your API key */
key?: string;
/** Your API secret */
secret?: string;
/**
* Set to `true` to connect to Bybit's testnet environment.
*
* Notes:
*
* - If demo trading, `testnet` should be set to false!
* - If testing a strategy, use demo trading instead. Testnet market data is very different from real market conditions.
*/
testnet?: boolean;
/**
@@ -96,28 +109,54 @@ export interface WSClientConfigurableOptions {
demoTrading?: boolean;
/**
* The API group this client should connect to.
* The API group this client should connect to. The V5 market is currently used by default.
*
* For the V3 APIs use `v3` as the market (spot/unified margin/usdc/account asset/copy trading)
*/
market: APIMarket;
market?: APIMarket;
pongTimeout?: number;
pingInterval?: number;
reconnectTimeout?: number;
/** Override the recv window for authenticating over websockets (default: 5000 ms) */
/** Define a recv window when preparing a private websocket signature. This is in milliseconds, so 5000 == 5 seconds */
recvWindow?: number;
/** How often to check if the connection is alive */
pingInterval?: number;
/** How long to wait for a pong (heartbeat reply) before assuming the connection is dead */
pongTimeout?: number;
/** Delay in milliseconds before respawning the connection */
reconnectTimeout?: number;
restOptions?: RestClientOptions;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
requestOptions?: any;
wsUrl?: string;
/** If true, fetch server time before trying to authenticate (disabled by default) */
fetchTimeOffsetBeforeAuth?: boolean;
/**
* Allows you to provide a custom "signMessage" function, e.g. to use node's much faster createHmac method
*
* Look in the examples folder for a demonstration on using node's createHmac instead.
*/
customSignMessageFn?: (message: string, secret: string) => Promise<string>;
/**
* If you authenticated the WS API before, automatically try to
* re-authenticate the WS API if you're disconnected/reconnected for any reason.
*/
reauthWSAPIOnReconnect?: boolean;
}
/**
* WS configuration that's always defined, regardless of user configuration
* (usually comes from defaults if there's no user-provided values)
*/
export interface WebsocketClientOptions extends WSClientConfigurableOptions {
market: APIMarket;
pongTimeout: number;
pingInterval: number;
reconnectTimeout: number;
recvWindow: number;
authPrivateConnectionsOnConnect: boolean;
authPrivateRequests: boolean;
reauthWSAPIOnReconnect: boolean;
}