Skip to content

Commit

Permalink
feat(): upgrade WebSocket layer to extend BaseWS abstraction. feat():…
Browse files Browse the repository at this point in the history
… add promisified WS workflows, feat(): add WS API integration
  • Loading branch information
tiagosiebler committed Jan 16, 2025
1 parent b613fd9 commit 8a7c8ea
Show file tree
Hide file tree
Showing 9 changed files with 2,492 additions and 1,180 deletions.
136 changes: 136 additions & 0 deletions src/types/websockets/ws-api.ts
Original file line number Diff line number Diff line change
@@ -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'
// >;
}
20 changes: 19 additions & 1 deletion src/types/websockets/ws-events.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import WebSocket from 'isomorphic-ws';

import {
CategoryV5,
ExecTypeV5,
Expand All @@ -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;
Expand Down
53 changes: 46 additions & 7 deletions src/types/websockets/ws-general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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;
/** 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;
/** Override the recv window for authenticating over websockets (default: 5000 ms) */
recvWindow?: 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;
}
Loading

0 comments on commit 8a7c8ea

Please sign in to comment.