feat(): upgrade WebSocket layer to extend BaseWS abstraction. feat(): add promisified WS workflows, feat(): add WS API integration
This commit is contained in:
136
src/types/websockets/ws-api.ts
Normal file
136
src/types/websockets/ws-api.ts
Normal 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'
|
||||
// >;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
1305
src/util/BaseWSClient.ts
Normal file
1305
src/util/BaseWSClient.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,22 +4,13 @@
|
||||
export type LogParams = null | any;
|
||||
|
||||
export const DefaultLogger = {
|
||||
/** Ping/pong events and other raw messages that might be noisy */
|
||||
silly: (...params: LogParams): void => {
|
||||
// console.log(params);
|
||||
},
|
||||
debug: (...params: LogParams): void => {
|
||||
console.log(params);
|
||||
},
|
||||
notice: (...params: LogParams): void => {
|
||||
console.log(params);
|
||||
/** Ping/pong events and other raw messages that might be noisy. Enable this while troubleshooting. */
|
||||
trace: (..._params: LogParams): void => {
|
||||
// console.log(_params);
|
||||
},
|
||||
info: (...params: LogParams): void => {
|
||||
console.info(params);
|
||||
},
|
||||
warning: (...params: LogParams): void => {
|
||||
console.error(params);
|
||||
},
|
||||
error: (...params: LogParams): void => {
|
||||
console.error(params);
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
WebsocketSucceededTopicSubscriptionConfirmationEvent,
|
||||
WebsocketTopicSubscriptionConfirmationEvent,
|
||||
} from '../types/websockets/ws-confirmations';
|
||||
import { WSAPIResponse, WS_API_Operations } from '../types/websockets/ws-api';
|
||||
|
||||
export interface RestClientOptions {
|
||||
/** Your API key */
|
||||
@@ -199,6 +200,20 @@ export function isTopicSubscriptionConfirmation(
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isWSAPIResponse(
|
||||
msg: unknown,
|
||||
): msg is Omit<WSAPIResponse, 'wsKey'> {
|
||||
if (typeof msg !== 'object' || !msg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof msg['op'] !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (WS_API_Operations as string[]).includes(msg['op']);
|
||||
}
|
||||
|
||||
export function isTopicSubscriptionSuccess(
|
||||
msg: unknown,
|
||||
): msg is WebsocketSucceededTopicSubscriptionConfirmationEvent {
|
||||
|
||||
@@ -32,8 +32,9 @@ export function isDeepObjectMatch(object1: unknown, object2: unknown): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
const DEFERRED_PROMISE_REF = {
|
||||
export const DEFERRED_PROMISE_REF = {
|
||||
CONNECTION_IN_PROGRESS: 'CONNECTION_IN_PROGRESS',
|
||||
AUTHENTICATION_IN_PROGRESS: 'AUTHENTICATION_IN_PROGRESS',
|
||||
} as const;
|
||||
|
||||
type DeferredPromiseRef =
|
||||
@@ -266,6 +267,15 @@ export class WsStore<
|
||||
);
|
||||
}
|
||||
|
||||
getAuthenticationInProgressPromise(
|
||||
wsKey: WsKey,
|
||||
): DeferredPromise<WSConnectedResult & { event: any }> | undefined {
|
||||
return this.getDeferredPromise(
|
||||
wsKey,
|
||||
DEFERRED_PROMISE_REF.AUTHENTICATION_IN_PROGRESS,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deferred promise designed to track a connection attempt in progress.
|
||||
*
|
||||
@@ -282,6 +292,17 @@ export class WsStore<
|
||||
);
|
||||
}
|
||||
|
||||
createAuthenticationInProgressPromise(
|
||||
wsKey: WsKey,
|
||||
throwIfExists: boolean,
|
||||
): DeferredPromise<WSConnectedResult & { event: any }> {
|
||||
return this.createDeferredPromise(
|
||||
wsKey,
|
||||
DEFERRED_PROMISE_REF.AUTHENTICATION_IN_PROGRESS,
|
||||
throwIfExists,
|
||||
);
|
||||
}
|
||||
|
||||
/** Remove promise designed to track a connection attempt in progress */
|
||||
removeConnectingInProgressPromise(wsKey: WsKey): void {
|
||||
return this.removeDeferredPromise(
|
||||
@@ -290,6 +311,13 @@ export class WsStore<
|
||||
);
|
||||
}
|
||||
|
||||
removeAuthenticationInProgressPromise(wsKey: WsKey): void {
|
||||
return this.removeDeferredPromise(
|
||||
wsKey,
|
||||
DEFERRED_PROMISE_REF.AUTHENTICATION_IN_PROGRESS,
|
||||
);
|
||||
}
|
||||
|
||||
/* connection state */
|
||||
|
||||
isWsOpen(key: WsKey): boolean {
|
||||
|
||||
@@ -1,7 +1,141 @@
|
||||
import WebSocket from 'isomorphic-ws';
|
||||
import {
|
||||
APIMarket,
|
||||
CategoryV5,
|
||||
WebsocketClientOptions,
|
||||
WsKey,
|
||||
} from '../../types';
|
||||
|
||||
import { APIMarket, CategoryV5, WebsocketClientOptions, WsKey } from '../types';
|
||||
import { DefaultLogger } from './logger';
|
||||
import { DefaultLogger } from '../logger';
|
||||
import { WSAPIRequest } from '../../types/websockets/ws-api';
|
||||
|
||||
export const WS_LOGGER_CATEGORY = { category: 'bybit-ws' };
|
||||
|
||||
export const WS_KEY_MAP = {
|
||||
inverse: 'inverse',
|
||||
linearPrivate: 'linearPrivate',
|
||||
linearPublic: 'linearPublic',
|
||||
spotPrivate: 'spotPrivate',
|
||||
spotPublic: 'spotPublic',
|
||||
spotV3Private: 'spotV3Private',
|
||||
spotV3Public: 'spotV3Public',
|
||||
usdcOptionPrivate: 'usdcOptionPrivate',
|
||||
usdcOptionPublic: 'usdcOptionPublic',
|
||||
usdcPerpPrivate: 'usdcPerpPrivate',
|
||||
usdcPerpPublic: 'usdcPerpPublic',
|
||||
unifiedPrivate: 'unifiedPrivate',
|
||||
unifiedOptionPublic: 'unifiedOptionPublic',
|
||||
unifiedPerpUSDTPublic: 'unifiedPerpUSDTPublic',
|
||||
unifiedPerpUSDCPublic: 'unifiedPerpUSDCPublic',
|
||||
contractUSDTPublic: 'contractUSDTPublic',
|
||||
contractUSDTPrivate: 'contractUSDTPrivate',
|
||||
contractInversePublic: 'contractInversePublic',
|
||||
contractInversePrivate: 'contractInversePrivate',
|
||||
v5SpotPublic: 'v5SpotPublic',
|
||||
v5LinearPublic: 'v5LinearPublic',
|
||||
v5InversePublic: 'v5InversePublic',
|
||||
v5OptionPublic: 'v5OptionPublic',
|
||||
v5Private: 'v5Private',
|
||||
/**
|
||||
* The V5 Websocket API (for sending orders over WS)
|
||||
*/
|
||||
v5PrivateTrade: 'v5PrivateTrade',
|
||||
} as const;
|
||||
|
||||
export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [
|
||||
WS_KEY_MAP.spotV3Private,
|
||||
WS_KEY_MAP.usdcOptionPrivate,
|
||||
WS_KEY_MAP.usdcPerpPrivate,
|
||||
WS_KEY_MAP.unifiedPrivate,
|
||||
WS_KEY_MAP.contractUSDTPrivate,
|
||||
WS_KEY_MAP.contractInversePrivate,
|
||||
WS_KEY_MAP.v5Private,
|
||||
WS_KEY_MAP.v5PrivateTrade,
|
||||
];
|
||||
|
||||
export const PUBLIC_WS_KEYS = [
|
||||
WS_KEY_MAP.linearPublic,
|
||||
WS_KEY_MAP.spotPublic,
|
||||
WS_KEY_MAP.spotV3Public,
|
||||
WS_KEY_MAP.usdcOptionPublic,
|
||||
WS_KEY_MAP.usdcPerpPublic,
|
||||
WS_KEY_MAP.unifiedOptionPublic,
|
||||
WS_KEY_MAP.unifiedPerpUSDTPublic,
|
||||
WS_KEY_MAP.unifiedPerpUSDCPublic,
|
||||
WS_KEY_MAP.contractUSDTPublic,
|
||||
WS_KEY_MAP.contractInversePublic,
|
||||
WS_KEY_MAP.v5SpotPublic,
|
||||
WS_KEY_MAP.v5LinearPublic,
|
||||
WS_KEY_MAP.v5InversePublic,
|
||||
WS_KEY_MAP.v5OptionPublic,
|
||||
] as string[];
|
||||
|
||||
/** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */
|
||||
const PRIVATE_TOPICS = [
|
||||
'stop_order',
|
||||
'outboundAccountInfo',
|
||||
'executionReport',
|
||||
'ticketInfo',
|
||||
// copy trading apis
|
||||
'copyTradePosition',
|
||||
'copyTradeOrder',
|
||||
'copyTradeExecution',
|
||||
'copyTradeWallet',
|
||||
// usdc options
|
||||
'user.openapi.option.position',
|
||||
'user.openapi.option.trade',
|
||||
'user.order',
|
||||
'user.openapi.option.order',
|
||||
'user.service',
|
||||
'user.openapi.greeks',
|
||||
'user.mmp.event',
|
||||
// usdc perps
|
||||
'user.openapi.perp.position',
|
||||
'user.openapi.perp.trade',
|
||||
'user.openapi.perp.order',
|
||||
'user.service',
|
||||
// unified margin
|
||||
'user.position.unifiedAccount',
|
||||
'user.execution.unifiedAccount',
|
||||
'user.order.unifiedAccount',
|
||||
'user.wallet.unifiedAccount',
|
||||
'user.greeks.unifiedAccount',
|
||||
// contract v3
|
||||
'user.position.contractAccount',
|
||||
'user.execution.contractAccount',
|
||||
'user.order.contractAccount',
|
||||
'user.wallet.contractAccount',
|
||||
// v5
|
||||
'position',
|
||||
'execution',
|
||||
'order',
|
||||
'wallet',
|
||||
'greeks',
|
||||
];
|
||||
|
||||
/**
|
||||
* Normalised internal format for a request (subscribe/unsubscribe/etc) on a topic, with optional parameters.
|
||||
*
|
||||
* - Topic: the topic this event is for
|
||||
* - Payload: the parameters to include, optional. E.g. auth requires key + sign. Some topics allow configurable parameters.
|
||||
* - Category: required for bybit, since different categories have different public endpoints
|
||||
*/
|
||||
export interface WsTopicRequest<
|
||||
TWSTopic extends string = string,
|
||||
TWSPayload = unknown,
|
||||
> {
|
||||
topic: TWSTopic;
|
||||
payload?: TWSPayload;
|
||||
category?: CategoryV5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conveniently allow users to request a topic either as string topics or objects (containing string topic + params)
|
||||
*/
|
||||
export type WsTopicRequestOrStringTopic<
|
||||
TWSTopic extends string,
|
||||
TWSPayload = unknown,
|
||||
> = WsTopicRequest<TWSTopic, TWSPayload> | string;
|
||||
|
||||
interface NetworkMapV3 {
|
||||
livenet: string;
|
||||
@@ -33,7 +167,55 @@ export const WS_BASE_URL_MAP: Record<
|
||||
APIMarket,
|
||||
Record<PublicPrivateNetwork, NetworkMapV3>
|
||||
> &
|
||||
Record<PublicOnlyWsKeys, Record<'public', NetworkMapV3>> = {
|
||||
Record<PublicOnlyWsKeys, Record<'public', NetworkMapV3>> &
|
||||
Record<
|
||||
typeof WS_KEY_MAP.v5PrivateTrade,
|
||||
Record<PublicPrivateNetwork, NetworkMapV3>
|
||||
> = {
|
||||
v5: {
|
||||
public: {
|
||||
livenet: 'public topics are routed internally via the public wskeys',
|
||||
testnet: 'public topics are routed internally via the public wskeys',
|
||||
},
|
||||
private: {
|
||||
livenet: 'wss://stream.bybit.com/v5/private',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/private',
|
||||
},
|
||||
},
|
||||
v5PrivateTrade: {
|
||||
public: {
|
||||
livenet: 'public topics are routed internally via the public wskeys',
|
||||
testnet: 'public topics are routed internally via the public wskeys',
|
||||
},
|
||||
private: {
|
||||
livenet: 'wss://stream.bybit.com/v5/trade',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/trade',
|
||||
},
|
||||
},
|
||||
v5SpotPublic: {
|
||||
public: {
|
||||
livenet: 'wss://stream.bybit.com/v5/public/spot',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/public/spot',
|
||||
},
|
||||
},
|
||||
v5LinearPublic: {
|
||||
public: {
|
||||
livenet: 'wss://stream.bybit.com/v5/public/linear',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/public/linear',
|
||||
},
|
||||
},
|
||||
v5InversePublic: {
|
||||
public: {
|
||||
livenet: 'wss://stream.bybit.com/v5/public/inverse',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/public/inverse',
|
||||
},
|
||||
},
|
||||
v5OptionPublic: {
|
||||
public: {
|
||||
livenet: 'wss://stream.bybit.com/v5/public/option',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/public/option',
|
||||
},
|
||||
},
|
||||
inverse: {
|
||||
public: {
|
||||
livenet: 'wss://stream.bybit.com/realtime',
|
||||
@@ -154,139 +336,8 @@ export const WS_BASE_URL_MAP: Record<
|
||||
testnet: 'wss://stream-testnet.bybit.com/contract/private/v3',
|
||||
},
|
||||
},
|
||||
v5: {
|
||||
public: {
|
||||
livenet: 'public topics are routed internally via the public wskeys',
|
||||
testnet: 'public topics are routed internally via the public wskeys',
|
||||
},
|
||||
private: {
|
||||
livenet: 'wss://stream.bybit.com/v5/private',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/private',
|
||||
},
|
||||
},
|
||||
v5SpotPublic: {
|
||||
public: {
|
||||
livenet: 'wss://stream.bybit.com/v5/public/spot',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/public/spot',
|
||||
},
|
||||
},
|
||||
v5LinearPublic: {
|
||||
public: {
|
||||
livenet: 'wss://stream.bybit.com/v5/public/linear',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/public/linear',
|
||||
},
|
||||
},
|
||||
v5InversePublic: {
|
||||
public: {
|
||||
livenet: 'wss://stream.bybit.com/v5/public/inverse',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/public/inverse',
|
||||
},
|
||||
},
|
||||
v5OptionPublic: {
|
||||
public: {
|
||||
livenet: 'wss://stream.bybit.com/v5/public/option',
|
||||
testnet: 'wss://stream-testnet.bybit.com/v5/public/option',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const WS_KEY_MAP = {
|
||||
inverse: 'inverse',
|
||||
linearPrivate: 'linearPrivate',
|
||||
linearPublic: 'linearPublic',
|
||||
spotPrivate: 'spotPrivate',
|
||||
spotPublic: 'spotPublic',
|
||||
spotV3Private: 'spotV3Private',
|
||||
spotV3Public: 'spotV3Public',
|
||||
usdcOptionPrivate: 'usdcOptionPrivate',
|
||||
usdcOptionPublic: 'usdcOptionPublic',
|
||||
usdcPerpPrivate: 'usdcPerpPrivate',
|
||||
usdcPerpPublic: 'usdcPerpPublic',
|
||||
unifiedPrivate: 'unifiedPrivate',
|
||||
unifiedOptionPublic: 'unifiedOptionPublic',
|
||||
unifiedPerpUSDTPublic: 'unifiedPerpUSDTPublic',
|
||||
unifiedPerpUSDCPublic: 'unifiedPerpUSDCPublic',
|
||||
contractUSDTPublic: 'contractUSDTPublic',
|
||||
contractUSDTPrivate: 'contractUSDTPrivate',
|
||||
contractInversePublic: 'contractInversePublic',
|
||||
contractInversePrivate: 'contractInversePrivate',
|
||||
v5SpotPublic: 'v5SpotPublic',
|
||||
v5LinearPublic: 'v5LinearPublic',
|
||||
v5InversePublic: 'v5InversePublic',
|
||||
v5OptionPublic: 'v5OptionPublic',
|
||||
v5Private: 'v5Private',
|
||||
} as const;
|
||||
|
||||
export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [
|
||||
WS_KEY_MAP.spotV3Private,
|
||||
WS_KEY_MAP.usdcOptionPrivate,
|
||||
WS_KEY_MAP.usdcPerpPrivate,
|
||||
WS_KEY_MAP.unifiedPrivate,
|
||||
WS_KEY_MAP.contractUSDTPrivate,
|
||||
WS_KEY_MAP.contractInversePrivate,
|
||||
WS_KEY_MAP.v5Private,
|
||||
];
|
||||
|
||||
export const PUBLIC_WS_KEYS = [
|
||||
WS_KEY_MAP.linearPublic,
|
||||
WS_KEY_MAP.spotPublic,
|
||||
WS_KEY_MAP.spotV3Public,
|
||||
WS_KEY_MAP.usdcOptionPublic,
|
||||
WS_KEY_MAP.usdcPerpPublic,
|
||||
WS_KEY_MAP.unifiedOptionPublic,
|
||||
WS_KEY_MAP.unifiedPerpUSDTPublic,
|
||||
WS_KEY_MAP.unifiedPerpUSDCPublic,
|
||||
WS_KEY_MAP.contractUSDTPublic,
|
||||
WS_KEY_MAP.contractInversePublic,
|
||||
WS_KEY_MAP.v5SpotPublic,
|
||||
WS_KEY_MAP.v5LinearPublic,
|
||||
WS_KEY_MAP.v5InversePublic,
|
||||
WS_KEY_MAP.v5OptionPublic,
|
||||
] as string[];
|
||||
|
||||
/** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */
|
||||
const PRIVATE_TOPICS = [
|
||||
'stop_order',
|
||||
'outboundAccountInfo',
|
||||
'executionReport',
|
||||
'ticketInfo',
|
||||
// copy trading apis
|
||||
'copyTradePosition',
|
||||
'copyTradeOrder',
|
||||
'copyTradeExecution',
|
||||
'copyTradeWallet',
|
||||
// usdc options
|
||||
'user.openapi.option.position',
|
||||
'user.openapi.option.trade',
|
||||
'user.order',
|
||||
'user.openapi.option.order',
|
||||
'user.service',
|
||||
'user.openapi.greeks',
|
||||
'user.mmp.event',
|
||||
// usdc perps
|
||||
'user.openapi.perp.position',
|
||||
'user.openapi.perp.trade',
|
||||
'user.openapi.perp.order',
|
||||
'user.service',
|
||||
// unified margin
|
||||
'user.position.unifiedAccount',
|
||||
'user.execution.unifiedAccount',
|
||||
'user.order.unifiedAccount',
|
||||
'user.wallet.unifiedAccount',
|
||||
'user.greeks.unifiedAccount',
|
||||
// contract v3
|
||||
'user.position.contractAccount',
|
||||
'user.execution.contractAccount',
|
||||
'user.order.contractAccount',
|
||||
'user.wallet.contractAccount',
|
||||
// v5
|
||||
'position',
|
||||
'execution',
|
||||
'order',
|
||||
'wallet',
|
||||
'greeks',
|
||||
];
|
||||
|
||||
export function isPrivateWsTopic(topic: string): boolean {
|
||||
return PRIVATE_TOPICS.includes(topic);
|
||||
}
|
||||
@@ -416,6 +467,24 @@ export function getWsUrl(
|
||||
const networkKey = isTestnet ? 'testnet' : 'livenet';
|
||||
|
||||
switch (wsKey) {
|
||||
case WS_KEY_MAP.v5Private: {
|
||||
return WS_BASE_URL_MAP.v5.private[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5PrivateTrade: {
|
||||
return WS_BASE_URL_MAP[wsKey].private[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5SpotPublic: {
|
||||
return WS_BASE_URL_MAP.v5SpotPublic.public[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5LinearPublic: {
|
||||
return WS_BASE_URL_MAP.v5LinearPublic.public[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5InversePublic: {
|
||||
return WS_BASE_URL_MAP.v5InversePublic.public[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5OptionPublic: {
|
||||
return WS_BASE_URL_MAP.v5OptionPublic.public[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.linearPublic: {
|
||||
return WS_BASE_URL_MAP.linear.public[networkKey];
|
||||
}
|
||||
@@ -474,21 +543,6 @@ export function getWsUrl(
|
||||
case WS_KEY_MAP.contractUSDTPublic: {
|
||||
return WS_BASE_URL_MAP.contractUSDT.public[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5Private: {
|
||||
return WS_BASE_URL_MAP.v5.private[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5SpotPublic: {
|
||||
return WS_BASE_URL_MAP.v5SpotPublic.public[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5LinearPublic: {
|
||||
return WS_BASE_URL_MAP.v5LinearPublic.public[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5InversePublic: {
|
||||
return WS_BASE_URL_MAP.v5InversePublic.public[networkKey];
|
||||
}
|
||||
case WS_KEY_MAP.v5OptionPublic: {
|
||||
return WS_BASE_URL_MAP.v5OptionPublic.public[networkKey];
|
||||
}
|
||||
default: {
|
||||
logger.error('getWsUrl(): Unhandled wsKey: ', {
|
||||
category: 'bybit-ws',
|
||||
@@ -569,3 +623,13 @@ export function safeTerminateWs(ws?: WebSocket | unknown) {
|
||||
ws.terminate();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* WS API promises are stored using a primary key. This key is constructed using
|
||||
* properties found in every request & reply.
|
||||
*/
|
||||
export function getPromiseRefForWSAPIRequest(
|
||||
requestEvent: WSAPIRequest<unknown>,
|
||||
): string {
|
||||
const promiseRef = [requestEvent.op, requestEvent.reqId].join('_');
|
||||
return promiseRef;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user