bybit-api
Advanced tools
Comparing version 2.4.0-beta.1 to 2.4.0-beta.2
@@ -13,4 +13,4 @@ export * from './account-asset-client'; | ||
export * from './util/logger'; | ||
export * from './util'; | ||
export * from './types'; | ||
export * from './util/WsStore'; | ||
export * from './constants/enum'; |
@@ -25,5 +25,5 @@ "use strict"; | ||
__exportStar(require("./util/logger"), exports); | ||
__exportStar(require("./util"), exports); | ||
__exportStar(require("./types"), exports); | ||
__exportStar(require("./util/WsStore"), exports); | ||
__exportStar(require("./constants/enum"), exports); | ||
//# sourceMappingURL=index.js.map |
@@ -5,3 +5,5 @@ import { InverseClient } from '../inverse-client'; | ||
import { SpotClientV3 } from '../spot-client-v3'; | ||
export declare type RESTClient = InverseClient | LinearClient | SpotClient | SpotClientV3; | ||
import { USDCOptionClient } from '../usdc-option-client'; | ||
import { USDCPerpetualClient } from '../usdc-perpetual-client'; | ||
export declare type RESTClient = InverseClient | LinearClient | SpotClient | SpotClientV3 | USDCOptionClient | USDCPerpetualClient; | ||
export declare type numberInString = string; | ||
@@ -8,0 +10,0 @@ export declare type OrderSide = 'Buy' | 'Sell'; |
@@ -1,3 +0,4 @@ | ||
import { RestClientOptions } from '../util'; | ||
export declare type APIMarket = 'inverse' | 'linear' | 'spot' | 'v3'; | ||
import { RestClientOptions, WS_KEY_MAP } from '../util'; | ||
/** For spot markets, spotV3 is recommended */ | ||
export declare type APIMarket = 'inverse' | 'linear' | 'spot' | 'spotv3' | 'usdcOption' | 'usdcPerp'; | ||
export declare type WsPublicInverseTopic = 'orderBookL2_25' | 'orderBookL2_200' | 'trade' | 'insurance' | 'instrument_info' | 'klineV2'; | ||
@@ -14,7 +15,7 @@ export declare type WsPublicUSDTPerpTopic = 'orderBookL2_25' | 'orderBookL2_200' | 'trade' | 'insurance' | 'instrument_info' | 'kline'; | ||
/** This is used to differentiate between each of the available websocket streams (as bybit has multiple websockets) */ | ||
export declare type WsKey = 'inverse' | 'linearPrivate' | 'linearPublic' | 'spotPrivate' | 'spotPublic'; | ||
export declare type WsKey = typeof WS_KEY_MAP[keyof typeof WS_KEY_MAP]; | ||
export interface WSClientConfigurableOptions { | ||
key?: string; | ||
secret?: string; | ||
livenet?: boolean; | ||
testnet?: boolean; | ||
/** | ||
@@ -36,3 +37,3 @@ * The API group this client should connect to. | ||
export interface WebsocketClientOptions extends WSClientConfigurableOptions { | ||
livenet: boolean; | ||
testnet?: boolean; | ||
market: APIMarket; | ||
@@ -39,0 +40,0 @@ pongTimeout: number; |
@@ -0,1 +1,2 @@ | ||
/** This is async because the browser version uses a promise (browser-support) */ | ||
export declare function signMessage(message: string, secret: string): Promise<string>; |
@@ -14,2 +14,3 @@ "use strict"; | ||
const crypto_1 = require("crypto"); | ||
/** This is async because the browser version uses a promise (browser-support) */ | ||
function signMessage(message, secret) { | ||
@@ -16,0 +17,0 @@ return __awaiter(this, void 0, void 0, function* () { |
@@ -22,3 +22,3 @@ export interface RestClientOptions { | ||
export declare function getRestBaseUrl(useLivenet: boolean, restInverseOptions: RestClientOptions): string; | ||
export declare function isWsPong(response: any): any; | ||
export declare function isWsPong(msg: any): boolean; | ||
export declare const agentSource = "bybitapinode"; | ||
@@ -25,0 +25,0 @@ /** |
@@ -31,10 +31,16 @@ "use strict"; | ||
exports.getRestBaseUrl = getRestBaseUrl; | ||
function isWsPong(response) { | ||
if (response.pong || response.ping) { | ||
function isWsPong(msg) { | ||
if (!msg) { | ||
return false; | ||
} | ||
if (msg.pong || msg.ping) { | ||
return true; | ||
} | ||
return (response.request && | ||
response.request.op === 'ping' && | ||
response.ret_msg === 'pong' && | ||
response.success === true); | ||
if (msg['op'] === 'pong') { | ||
return true; | ||
} | ||
return (msg.request && | ||
msg.request.op === 'ping' && | ||
msg.ret_msg === 'pong' && | ||
msg.success === true); | ||
} | ||
@@ -41,0 +47,0 @@ exports.isWsPong = isWsPong; |
@@ -1,8 +0,34 @@ | ||
import { WsKey } from '../types'; | ||
export declare const wsKeyInverse = "inverse"; | ||
export declare const wsKeyLinearPrivate = "linearPrivate"; | ||
export declare const wsKeyLinearPublic = "linearPublic"; | ||
export declare const wsKeySpotPrivate = "spotPrivate"; | ||
export declare const wsKeySpotPublic = "spotPublic"; | ||
export declare function getLinearWsKeyForTopic(topic: string): WsKey; | ||
export declare function getSpotWsKeyForTopic(topic: string): WsKey; | ||
import { APIMarket, WsKey } from '../types'; | ||
interface NetworkMapV3 { | ||
livenet: string; | ||
livenet2?: string; | ||
testnet: string; | ||
testnet2?: string; | ||
} | ||
declare type PublicPrivateNetwork = 'public' | 'private'; | ||
export declare const WS_BASE_URL_MAP: Record<APIMarket, Record<PublicPrivateNetwork, NetworkMapV3>>; | ||
export declare const WS_KEY_MAP: { | ||
readonly inverse: "inverse"; | ||
readonly linearPrivate: "linearPrivate"; | ||
readonly linearPublic: "linearPublic"; | ||
readonly spotPrivate: "spotPrivate"; | ||
readonly spotPublic: "spotPublic"; | ||
readonly spotV3Private: "spotV3Private"; | ||
readonly spotV3Public: "spotV3Public"; | ||
readonly usdcOptionPrivate: "usdcOptionPrivate"; | ||
readonly usdcOptionPublic: "usdcOptionPublic"; | ||
readonly usdcPerpPrivate: "usdcPerpPrivate"; | ||
readonly usdcPerpPublic: "usdcPerpPublic"; | ||
}; | ||
export declare const WS_AUTH_ON_CONNECT_KEYS: WsKey[]; | ||
export declare const PUBLIC_WS_KEYS: string[]; | ||
export declare function getWsKeyForTopic(market: APIMarket, topic: string, isPrivate?: boolean): WsKey; | ||
export declare function getUsdcWsKeyForTopic(topic: string, subGroup: 'option' | 'perp'): WsKey; | ||
export declare const WS_ERROR_ENUM: { | ||
NOT_AUTHENTICATED_SPOT_V3: string; | ||
API_ERROR_GENERIC: string; | ||
API_SIGN_AUTH_FAILED: string; | ||
USDC_OPTION_AUTH_FAILED: string; | ||
}; | ||
export declare function neverGuard(x: never, msg: string): Error; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getSpotWsKeyForTopic = exports.getLinearWsKeyForTopic = exports.wsKeySpotPublic = exports.wsKeySpotPrivate = exports.wsKeyLinearPublic = exports.wsKeyLinearPrivate = exports.wsKeyInverse = void 0; | ||
exports.wsKeyInverse = 'inverse'; | ||
exports.wsKeyLinearPrivate = 'linearPrivate'; | ||
exports.wsKeyLinearPublic = 'linearPublic'; | ||
exports.wsKeySpotPrivate = 'spotPrivate'; | ||
exports.wsKeySpotPublic = 'spotPublic'; | ||
function getLinearWsKeyForTopic(topic) { | ||
const privateLinearTopics = [ | ||
'position', | ||
'execution', | ||
'order', | ||
'stop_order', | ||
'wallet', | ||
]; | ||
if (privateLinearTopics.includes(topic)) { | ||
return exports.wsKeyLinearPrivate; | ||
exports.neverGuard = exports.WS_ERROR_ENUM = exports.getUsdcWsKeyForTopic = exports.getWsKeyForTopic = exports.PUBLIC_WS_KEYS = exports.WS_AUTH_ON_CONNECT_KEYS = exports.WS_KEY_MAP = exports.WS_BASE_URL_MAP = void 0; | ||
exports.WS_BASE_URL_MAP = { | ||
inverse: { | ||
public: { | ||
livenet: 'wss://stream.bybit.com/realtime', | ||
testnet: 'wss://stream-testnet.bybit.com/realtime', | ||
}, | ||
private: { | ||
livenet: 'wss://stream.bybit.com/realtime', | ||
testnet: 'wss://stream-testnet.bybit.com/realtime', | ||
}, | ||
}, | ||
linear: { | ||
public: { | ||
livenet: 'wss://stream.bybit.com/realtime_public', | ||
livenet2: 'wss://stream.bytick.com/realtime_public', | ||
testnet: 'wss://stream-testnet.bybit.com/realtime_public', | ||
}, | ||
private: { | ||
livenet: 'wss://stream.bybit.com/realtime_private', | ||
livenet2: 'wss://stream.bytick.com/realtime_private', | ||
testnet: 'wss://stream-testnet.bybit.com/realtime_private', | ||
}, | ||
}, | ||
spot: { | ||
public: { | ||
livenet: 'wss://stream.bybit.com/spot/quote/ws/v1', | ||
livenet2: 'wss://stream.bybit.com/spot/quote/ws/v2', | ||
testnet: 'wss://stream-testnet.bybit.com/spot/quote/ws/v1', | ||
testnet2: 'wss://stream-testnet.bybit.com/spot/quote/ws/v2', | ||
}, | ||
private: { | ||
livenet: 'wss://stream.bybit.com/spot/ws', | ||
testnet: 'wss://stream-testnet.bybit.com/spot/ws', | ||
}, | ||
}, | ||
spotv3: { | ||
public: { | ||
livenet: 'wss://stream.bybit.com/spot/public/v3', | ||
testnet: 'wss://stream-testnet.bybit.com/spot/public/v3', | ||
}, | ||
private: { | ||
livenet: 'wss://stream.bybit.com/spot/private/v3', | ||
testnet: 'wss://stream-testnet.bybit.com/spot/private/v3', | ||
}, | ||
}, | ||
usdcOption: { | ||
public: { | ||
livenet: 'wss://stream.bybit.com/trade/option/usdc/public/v1', | ||
livenet2: 'wss://stream.bytick.com/trade/option/usdc/public/v1', | ||
testnet: 'wss://stream-testnet.bybit.com/trade/option/usdc/public/v1', | ||
}, | ||
private: { | ||
livenet: 'wss://stream.bybit.com/trade/option/usdc/private/v1', | ||
livenet2: 'wss://stream.bytick.com/trade/option/usdc/private/v1', | ||
testnet: 'wss://stream-testnet.bybit.com/trade/option/usdc/private/v1', | ||
}, | ||
}, | ||
usdcPerp: { | ||
public: { | ||
livenet: 'wss://stream.bybit.com/perpetual/ws/v1/realtime_public', | ||
livenet2: 'wss://stream.bytick.com/perpetual/ws/v1/realtime_public', | ||
testnet: 'wss://stream-testnet.bybit.com/perpetual/ws/v1/realtime_public', | ||
}, | ||
private: { | ||
livenet: 'wss://stream.bybit.com/trade/option/usdc/private/v1', | ||
livenet2: 'wss://stream.bytick.com/trade/option/usdc/private/v1', | ||
testnet: 'wss://stream-testnet.bybit.com/trade/option/usdc/private/v1', | ||
}, | ||
}, | ||
}; | ||
exports.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', | ||
}; | ||
exports.WS_AUTH_ON_CONNECT_KEYS = [ | ||
exports.WS_KEY_MAP.spotV3Private, | ||
exports.WS_KEY_MAP.usdcOptionPrivate, | ||
exports.WS_KEY_MAP.usdcPerpPrivate, | ||
]; | ||
exports.PUBLIC_WS_KEYS = [ | ||
exports.WS_KEY_MAP.linearPublic, | ||
exports.WS_KEY_MAP.spotPublic, | ||
exports.WS_KEY_MAP.spotV3Public, | ||
exports.WS_KEY_MAP.usdcOptionPublic, | ||
exports.WS_KEY_MAP.usdcPerpPublic, | ||
]; | ||
/** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */ | ||
const PRIVATE_TOPICS = [ | ||
'position', | ||
'execution', | ||
'order', | ||
'stop_order', | ||
'wallet', | ||
'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', | ||
]; | ||
function getWsKeyForTopic(market, topic, isPrivate) { | ||
const isPrivateTopic = isPrivate === true || PRIVATE_TOPICS.includes(topic); | ||
switch (market) { | ||
case 'inverse': { | ||
return exports.WS_KEY_MAP.inverse; | ||
} | ||
case 'linear': { | ||
return isPrivateTopic | ||
? exports.WS_KEY_MAP.linearPrivate | ||
: exports.WS_KEY_MAP.linearPublic; | ||
} | ||
case 'spot': { | ||
return isPrivateTopic ? exports.WS_KEY_MAP.spotPrivate : exports.WS_KEY_MAP.spotPublic; | ||
} | ||
case 'spotv3': { | ||
return isPrivateTopic | ||
? exports.WS_KEY_MAP.spotV3Private | ||
: exports.WS_KEY_MAP.spotV3Public; | ||
} | ||
case 'usdcOption': { | ||
return isPrivateTopic | ||
? exports.WS_KEY_MAP.usdcOptionPrivate | ||
: exports.WS_KEY_MAP.usdcOptionPublic; | ||
} | ||
case 'usdcPerp': { | ||
return isPrivateTopic | ||
? exports.WS_KEY_MAP.usdcPerpPrivate | ||
: exports.WS_KEY_MAP.usdcPerpPublic; | ||
} | ||
default: { | ||
throw neverGuard(market, `getWsKeyForTopic(): Unhandled market`); | ||
} | ||
} | ||
return exports.wsKeyLinearPublic; | ||
} | ||
exports.getLinearWsKeyForTopic = getLinearWsKeyForTopic; | ||
function getSpotWsKeyForTopic(topic) { | ||
const privateLinearTopics = [ | ||
'position', | ||
'execution', | ||
'order', | ||
'stop_order', | ||
'outboundAccountInfo', | ||
'executionReport', | ||
'ticketInfo', | ||
]; | ||
if (privateLinearTopics.includes(topic)) { | ||
return exports.wsKeySpotPrivate; | ||
exports.getWsKeyForTopic = getWsKeyForTopic; | ||
function getUsdcWsKeyForTopic(topic, subGroup) { | ||
const isPrivateTopic = PRIVATE_TOPICS.includes(topic); | ||
if (subGroup === 'option') { | ||
return isPrivateTopic | ||
? exports.WS_KEY_MAP.usdcOptionPrivate | ||
: exports.WS_KEY_MAP.usdcOptionPublic; | ||
} | ||
return exports.wsKeySpotPublic; | ||
return isPrivateTopic | ||
? exports.WS_KEY_MAP.usdcOptionPrivate | ||
: exports.WS_KEY_MAP.usdcOptionPublic; | ||
// return isPrivateTopic | ||
// ? WS_KEY_MAP.usdcPerpPrivate | ||
// : WS_KEY_MAP.usdcPerpPublic; | ||
} | ||
exports.getSpotWsKeyForTopic = getSpotWsKeyForTopic; | ||
exports.getUsdcWsKeyForTopic = getUsdcWsKeyForTopic; | ||
exports.WS_ERROR_ENUM = { | ||
NOT_AUTHENTICATED_SPOT_V3: '-1004', | ||
API_ERROR_GENERIC: '10001', | ||
API_SIGN_AUTH_FAILED: '10003', | ||
USDC_OPTION_AUTH_FAILED: '3303006', | ||
}; | ||
function neverGuard(x, msg) { | ||
return new Error(`Unhandled value exception "x", ${msg}`); | ||
} | ||
exports.neverGuard = neverGuard; | ||
//# sourceMappingURL=websocket-util.js.map |
import WebSocket from 'isomorphic-ws'; | ||
import { WsKey } from '../types'; | ||
import { DefaultLogger } from './logger'; | ||
@@ -26,2 +27,4 @@ export declare enum WsConnectionStateEnum { | ||
activePongTimer?: ReturnType<typeof setTimeout> | undefined; | ||
/** If a reconnection is in progress, this will have the timer for the delayed reconnect */ | ||
activeReconnectTimer?: ReturnType<typeof setTimeout> | undefined; | ||
/** | ||
@@ -37,19 +40,19 @@ * All the topics we are expected to be subscribed to (and we automatically resubscribe to if the connection drops) | ||
/** Get WS stored state for key, optionally create if missing */ | ||
get(key: string, createIfMissing?: true): WsStoredState; | ||
get(key: string, createIfMissing?: false): WsStoredState | undefined; | ||
getKeys(): string[]; | ||
create(key: string): WsStoredState | undefined; | ||
delete(key: string): void; | ||
hasExistingActiveConnection(key: string): boolean; | ||
getWs(key: string): WebSocket | undefined; | ||
setWs(key: string, wsConnection: WebSocket): WebSocket; | ||
isWsOpen(key: string): boolean; | ||
getConnectionState(key: string): WsConnectionStateEnum; | ||
setConnectionState(key: string, state: WsConnectionStateEnum): void; | ||
isConnectionState(key: string, state: WsConnectionStateEnum): boolean; | ||
getTopics(key: string): WsTopicList; | ||
get(key: WsKey, createIfMissing?: true): WsStoredState; | ||
get(key: WsKey, createIfMissing?: false): WsStoredState | undefined; | ||
getKeys(): WsKey[]; | ||
create(key: WsKey): WsStoredState | undefined; | ||
delete(key: WsKey): void; | ||
hasExistingActiveConnection(key: WsKey): boolean; | ||
getWs(key: WsKey): WebSocket | undefined; | ||
setWs(key: WsKey, wsConnection: WebSocket): WebSocket; | ||
isWsOpen(key: WsKey): boolean; | ||
getConnectionState(key: WsKey): WsConnectionStateEnum; | ||
setConnectionState(key: WsKey, state: WsConnectionStateEnum): void; | ||
isConnectionState(key: WsKey, state: WsConnectionStateEnum): boolean; | ||
getTopics(key: WsKey): WsTopicList; | ||
getTopicsByKey(): Record<string, WsTopicList>; | ||
addTopic(key: string, topic: WsTopic): WsTopicList; | ||
deleteTopic(key: string, topic: WsTopic): boolean; | ||
addTopic(key: WsKey, topic: WsTopic): WsTopicList; | ||
deleteTopic(key: WsKey, topic: WsTopic): boolean; | ||
} | ||
export {}; |
@@ -12,2 +12,3 @@ "use strict"; | ||
WsConnectionStateEnum[WsConnectionStateEnum["RECONNECTING"] = 4] = "RECONNECTING"; | ||
// ERROR = 5, | ||
})(WsConnectionStateEnum = exports.WsConnectionStateEnum || (exports.WsConnectionStateEnum = {})); | ||
@@ -14,0 +15,0 @@ class WsStore { |
/// <reference types="node" /> | ||
import { EventEmitter } from 'events'; | ||
import WebSocket from 'isomorphic-ws'; | ||
import { DefaultLogger } from './util/logger'; | ||
import { KlineInterval, WSClientConfigurableOptions, WsKey, WsTopic } from './types'; | ||
export declare interface WebsocketClient { | ||
on(event: 'open' | 'reconnected', listener: ({ wsKey: WsKey, event: any }: { | ||
wsKey: any; | ||
import { DefaultLogger } from './util'; | ||
export declare type WsClientEvent = 'open' | 'update' | 'close' | 'errorEvent' | 'reconnect' | 'reconnected' | 'response'; | ||
interface WebsocketClientEvents { | ||
open: (evt: { | ||
wsKey: WsKey; | ||
event: any; | ||
}) => void): this; | ||
on(event: 'response' | 'update' | 'error', listener: (response: any) => void): this; | ||
on(event: 'reconnect' | 'close', listener: ({ wsKey: WsKey }: { | ||
wsKey: any; | ||
}) => void): this; | ||
}) => void; | ||
reconnect: (evt: { | ||
wsKey: WsKey; | ||
event: any; | ||
}) => void; | ||
reconnected: (evt: { | ||
wsKey: WsKey; | ||
event: any; | ||
}) => void; | ||
close: (evt: { | ||
wsKey: WsKey; | ||
event: any; | ||
}) => void; | ||
response: (response: any) => void; | ||
update: (response: any) => void; | ||
errorEvent: (response: any) => void; | ||
} | ||
export declare interface WebsocketClient { | ||
on<U extends keyof WebsocketClientEvents>(event: U, listener: WebsocketClientEvents[U]): this; | ||
emit<U extends keyof WebsocketClientEvents>(event: U, ...args: Parameters<WebsocketClientEvents[U]>): boolean; | ||
} | ||
export declare class WebsocketClient extends EventEmitter { | ||
private logger; | ||
/** Purely used */ | ||
private restClient; | ||
private restClient?; | ||
private options; | ||
private wsStore; | ||
constructor(options: WSClientConfigurableOptions, logger?: typeof DefaultLogger); | ||
isLivenet(): boolean; | ||
isLinear(): boolean; | ||
isSpot(): boolean; | ||
isInverse(): boolean; | ||
/** USDC, spot v3, unified margin, account asset */ | ||
isV3(): boolean; | ||
/** | ||
* Add topic/topics to WS subscription list | ||
* Only used if we fetch exchange time before attempting auth. | ||
* Disabled by default. | ||
* I've removed this for ftx and it's working great, tempted to remove this here | ||
*/ | ||
subscribe(wsTopics: WsTopic[] | WsTopic): void; | ||
/** | ||
* Remove topic/topics from WS subscription list | ||
*/ | ||
unsubscribe(wsTopics: WsTopic[] | WsTopic): void; | ||
prepareRESTClient(): void; | ||
isTestnet(): boolean; | ||
close(wsKey: WsKey): void; | ||
closeAll(): void; | ||
/** | ||
* Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library | ||
*/ | ||
connectAll(): Promise<WebSocket | undefined>[] | undefined; | ||
connectPublic(): Promise<WebSocket | undefined> | undefined; | ||
connectPrivate(): Promise<WebSocket | undefined> | undefined; | ||
connectAll(): Promise<WebSocket | undefined>[]; | ||
connectPublic(): Promise<WebSocket | undefined>; | ||
connectPrivate(): Promise<WebSocket | undefined>; | ||
private connect; | ||
@@ -50,2 +59,4 @@ private parseWsError; | ||
private getAuthParams; | ||
private sendAuthRequest; | ||
private getWsAuthSignature; | ||
private reconnectWithDelay; | ||
@@ -64,3 +75,3 @@ private ping; | ||
private requestUnsubscribeTopics; | ||
private tryWsSend; | ||
tryWsSend(wsKey: WsKey, wsMessage: string): void; | ||
private connectToWsUrl; | ||
@@ -71,12 +82,27 @@ private onWsOpen; | ||
private onWsClose; | ||
private onWsMessageResponse; | ||
private getWs; | ||
private setWsState; | ||
private getWsUrl; | ||
private getWsKeyForTopic; | ||
private wrongMarketError; | ||
/** | ||
* Add topic/topics to WS subscription list | ||
* @param wsTopics topic or list of topics | ||
* @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet) | ||
*/ | ||
subscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean): void; | ||
/** | ||
* Remove topic/topics from WS subscription list | ||
* @param wsTopics topic or list of topics | ||
* @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet) | ||
*/ | ||
unsubscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean): void; | ||
/** @deprecated use "market: 'spotv3" client */ | ||
subscribePublicSpotTrades(symbol: string, binary?: boolean): void; | ||
/** @deprecated use "market: 'spotv3" client */ | ||
subscribePublicSpotTradingPair(symbol: string, binary?: boolean): void; | ||
/** @deprecated use "market: 'spotv3" client */ | ||
subscribePublicSpotV1Kline(symbol: string, candleSize: KlineInterval, binary?: boolean): void; | ||
/** @deprecated use "market: 'spotv3" client */ | ||
subscribePublicSpotOrderbook(symbol: string, depth: 'full' | 'merge' | 'delta', dumpScale?: number, binary?: boolean): void; | ||
} | ||
export {}; |
@@ -22,34 +22,7 @@ "use strict"; | ||
const spot_client_1 = require("./spot-client"); | ||
const logger_1 = require("./util/logger"); | ||
const node_support_1 = require("./util/node-support"); | ||
const WsStore_1 = __importDefault(require("./util/WsStore")); | ||
const util_1 = require("./util"); | ||
const inverseEndpoints = { | ||
livenet: 'wss://stream.bybit.com/realtime', | ||
testnet: 'wss://stream-testnet.bybit.com/realtime', | ||
}; | ||
const linearEndpoints = { | ||
private: { | ||
livenet: 'wss://stream.bybit.com/realtime_private', | ||
livenet2: 'wss://stream.bytick.com/realtime_private', | ||
testnet: 'wss://stream-testnet.bybit.com/realtime_private', | ||
}, | ||
public: { | ||
livenet: 'wss://stream.bybit.com/realtime_public', | ||
livenet2: 'wss://stream.bytick.com/realtime_public', | ||
testnet: 'wss://stream-testnet.bybit.com/realtime_public', | ||
}, | ||
}; | ||
const spotEndpoints = { | ||
private: { | ||
livenet: 'wss://stream.bybit.com/spot/ws', | ||
testnet: 'wss://stream-testnet.bybit.com/spot/ws', | ||
}, | ||
public: { | ||
livenet: 'wss://stream.bybit.com/spot/quote/ws/v1', | ||
livenet2: 'wss://stream.bybit.com/spot/quote/ws/v2', | ||
testnet: 'wss://stream-testnet.bybit.com/spot/quote/ws/v1', | ||
testnet2: 'wss://stream-testnet.bybit.com/spot/quote/ws/v2', | ||
}, | ||
}; | ||
const usdc_option_client_1 = require("./usdc-option-client"); | ||
const usdc_perpetual_client_1 = require("./usdc-perpetual-client"); | ||
const loggerCategory = { category: 'bybit-ws' }; | ||
@@ -59,67 +32,47 @@ class WebsocketClient extends events_1.EventEmitter { | ||
super(); | ||
this.logger = logger || logger_1.DefaultLogger; | ||
this.logger = logger || util_1.DefaultLogger; | ||
this.wsStore = new WsStore_1.default(this.logger); | ||
this.options = Object.assign({ livenet: false, pongTimeout: 1000, pingInterval: 10000, reconnectTimeout: 500, fetchTimeOffsetBeforeAuth: false }, options); | ||
if (this.isV3()) { | ||
this.restClient = new spot_client_v3_1.SpotClientV3(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions); | ||
} | ||
else if (this.isLinear()) { | ||
this.restClient = new linear_client_1.LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions); | ||
} | ||
else if (this.isSpot()) { | ||
this.restClient = new spot_client_1.SpotClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions); | ||
this.connectPublic(); | ||
} | ||
else { | ||
this.restClient = new inverse_client_1.InverseClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions); | ||
} | ||
this.options = Object.assign({ testnet: false, pongTimeout: 1000, pingInterval: 10000, reconnectTimeout: 500, fetchTimeOffsetBeforeAuth: false }, options); | ||
this.prepareRESTClient(); | ||
} | ||
isLivenet() { | ||
return this.options.livenet === true; | ||
} | ||
isLinear() { | ||
return this.options.market === 'linear'; | ||
} | ||
isSpot() { | ||
return this.options.market === 'spot'; | ||
} | ||
isInverse() { | ||
return this.options.market === 'inverse'; | ||
} | ||
/** USDC, spot v3, unified margin, account asset */ | ||
isV3() { | ||
return this.options.market === 'v3'; | ||
} | ||
/** | ||
* Add topic/topics to WS subscription list | ||
* Only used if we fetch exchange time before attempting auth. | ||
* Disabled by default. | ||
* I've removed this for ftx and it's working great, tempted to remove this here | ||
*/ | ||
subscribe(wsTopics) { | ||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; | ||
topics.forEach((topic) => this.wsStore.addTopic(this.getWsKeyForTopic(topic), topic)); | ||
// attempt to send subscription topic per websocket | ||
this.wsStore.getKeys().forEach((wsKey) => { | ||
// if connected, send subscription request | ||
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) { | ||
return this.requestSubscribeTopics(wsKey, topics); | ||
prepareRESTClient() { | ||
switch (this.options.market) { | ||
case 'inverse': { | ||
this.restClient = new inverse_client_1.InverseClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions); | ||
break; | ||
} | ||
// start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect | ||
if (!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING) && | ||
!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) { | ||
return this.connect(wsKey); | ||
case 'linear': { | ||
this.restClient = new linear_client_1.LinearClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions); | ||
break; | ||
} | ||
}); | ||
} | ||
/** | ||
* Remove topic/topics from WS subscription list | ||
*/ | ||
unsubscribe(wsTopics) { | ||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; | ||
topics.forEach((topic) => this.wsStore.deleteTopic(this.getWsKeyForTopic(topic), topic)); | ||
this.wsStore.getKeys().forEach((wsKey) => { | ||
// unsubscribe request only necessary if active connection exists | ||
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) { | ||
this.requestUnsubscribeTopics(wsKey, topics); | ||
case 'spot': { | ||
this.restClient = new spot_client_1.SpotClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions); | ||
this.connectPublic(); | ||
break; | ||
} | ||
}); | ||
case 'spotv3': { | ||
this.restClient = new spot_client_v3_1.SpotClientV3(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions); | ||
break; | ||
} | ||
case 'usdcOption': { | ||
this.restClient = new usdc_option_client_1.USDCOptionClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions); | ||
break; | ||
} | ||
case 'usdcPerp': { | ||
this.restClient = new usdc_perpetual_client_1.USDCPerpetualClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions); | ||
break; | ||
} | ||
default: { | ||
throw util_1.neverGuard(this.options.market, `prepareRESTClient(): Unhandled market`); | ||
} | ||
} | ||
} | ||
isTestnet() { | ||
return this.options.testnet === true; | ||
} | ||
close(wsKey) { | ||
@@ -132,2 +85,8 @@ var _a; | ||
} | ||
closeAll() { | ||
const keys = this.wsStore.getKeys(); | ||
keys.forEach((key) => { | ||
this.close(key); | ||
}); | ||
} | ||
/** | ||
@@ -137,36 +96,69 @@ * Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library | ||
connectAll() { | ||
if (this.isInverse()) { | ||
return [this.connect(util_1.wsKeyInverse)]; | ||
switch (this.options.market) { | ||
case 'inverse': { | ||
// only one for inverse | ||
return [this.connectPublic()]; | ||
} | ||
// these all have separate public & private ws endpoints | ||
case 'linear': | ||
case 'spot': | ||
case 'spotv3': | ||
case 'usdcOption': | ||
case 'usdcPerp': { | ||
return [this.connectPublic(), this.connectPrivate()]; | ||
} | ||
default: { | ||
throw util_1.neverGuard(this.options.market, `connectAll(): Unhandled market`); | ||
} | ||
} | ||
if (this.isLinear()) { | ||
return [ | ||
this.connect(util_1.wsKeyLinearPublic), | ||
this.connect(util_1.wsKeyLinearPrivate), | ||
]; | ||
} | ||
if (this.isSpot()) { | ||
return [this.connect(util_1.wsKeySpotPublic), this.connect(util_1.wsKeySpotPrivate)]; | ||
} | ||
} | ||
connectPublic() { | ||
if (this.isInverse()) { | ||
return this.connect(util_1.wsKeyInverse); | ||
switch (this.options.market) { | ||
case 'inverse': { | ||
return this.connect(util_1.WS_KEY_MAP.inverse); | ||
} | ||
case 'linear': { | ||
return this.connect(util_1.WS_KEY_MAP.linearPublic); | ||
} | ||
case 'spot': { | ||
return this.connect(util_1.WS_KEY_MAP.spotPublic); | ||
} | ||
case 'spotv3': { | ||
return this.connect(util_1.WS_KEY_MAP.spotV3Public); | ||
} | ||
case 'usdcOption': { | ||
return this.connect(util_1.WS_KEY_MAP.usdcOptionPublic); | ||
} | ||
case 'usdcPerp': { | ||
return this.connect(util_1.WS_KEY_MAP.usdcPerpPublic); | ||
} | ||
default: { | ||
throw util_1.neverGuard(this.options.market, `connectPublic(): Unhandled market`); | ||
} | ||
} | ||
if (this.isLinear()) { | ||
return this.connect(util_1.wsKeyLinearPublic); | ||
} | ||
if (this.isSpot()) { | ||
return this.connect(util_1.wsKeySpotPublic); | ||
} | ||
} | ||
connectPrivate() { | ||
if (this.isInverse()) { | ||
return this.connect(util_1.wsKeyInverse); | ||
switch (this.options.market) { | ||
case 'inverse': { | ||
return this.connect(util_1.WS_KEY_MAP.inverse); | ||
} | ||
case 'linear': { | ||
return this.connect(util_1.WS_KEY_MAP.linearPrivate); | ||
} | ||
case 'spot': { | ||
return this.connect(util_1.WS_KEY_MAP.spotPrivate); | ||
} | ||
case 'spotv3': { | ||
return this.connect(util_1.WS_KEY_MAP.spotV3Private); | ||
} | ||
case 'usdcOption': { | ||
return this.connect(util_1.WS_KEY_MAP.usdcOptionPrivate); | ||
} | ||
case 'usdcPerp': { | ||
return this.connect(util_1.WS_KEY_MAP.usdcPerpPrivate); | ||
} | ||
default: { | ||
throw util_1.neverGuard(this.options.market, `connectPrivate(): Unhandled market`); | ||
} | ||
} | ||
if (this.isLinear()) { | ||
return this.connect(util_1.wsKeyLinearPrivate); | ||
} | ||
if (this.isSpot()) { | ||
return this.connect(util_1.wsKeySpotPrivate); | ||
} | ||
} | ||
@@ -202,2 +194,3 @@ connect(wsKey) { | ||
this.logger.error(`${context} due to unexpected error: `, error); | ||
this.emit('errorEvent', error); | ||
return; | ||
@@ -213,2 +206,3 @@ } | ||
} | ||
this.emit('errorEvent', error); | ||
} | ||
@@ -220,27 +214,57 @@ /** | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { key, secret } = this.options; | ||
if (key && | ||
secret && | ||
wsKey !== util_1.wsKeyLinearPublic && | ||
wsKey !== util_1.wsKeySpotPublic) { | ||
this.logger.debug("Getting auth'd request params", Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
const timeOffset = this.options.fetchTimeOffsetBeforeAuth | ||
? yield this.restClient.fetchTimeOffset() | ||
: 0; | ||
const params = { | ||
if (util_1.PUBLIC_WS_KEYS.includes(wsKey)) { | ||
this.logger.debug('Starting public only websocket client.', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
return ''; | ||
} | ||
try { | ||
const { signature, expiresAt } = yield this.getWsAuthSignature(wsKey); | ||
const authParams = { | ||
api_key: this.options.key, | ||
expires: Date.now() + timeOffset + 5000, | ||
expires: expiresAt, | ||
signature, | ||
}; | ||
params.signature = yield node_support_1.signMessage('GET/realtime' + params.expires, secret); | ||
return '?' + util_1.serializeParams(params); | ||
return '?' + util_1.serializeParams(authParams); | ||
} | ||
else if (!key || !secret) { | ||
this.logger.warning('Cannot authenticate websocket, either api or private keys missing.', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
catch (e) { | ||
this.logger.error(e, Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
return ''; | ||
} | ||
else { | ||
this.logger.debug('Starting public only websocket client.', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
}); | ||
} | ||
sendAuthRequest(wsKey) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
const { signature, expiresAt } = yield this.getWsAuthSignature(wsKey); | ||
const request = { | ||
op: 'auth', | ||
args: [this.options.key, expiresAt, signature], | ||
req_id: `${wsKey}-auth`, | ||
}; | ||
return this.tryWsSend(wsKey, JSON.stringify(request)); | ||
} | ||
return ''; | ||
catch (e) { | ||
this.logger.error(e, Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
} | ||
}); | ||
} | ||
getWsAuthSignature(wsKey) { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { key, secret } = this.options; | ||
if (!key || !secret) { | ||
this.logger.warning('Cannot authenticate websocket, either api or private keys missing.', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
throw new Error(`Cannot auth - missing api or secret in config`); | ||
} | ||
this.logger.debug("Getting auth'd request params", Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
const timeOffset = this.options.fetchTimeOffsetBeforeAuth | ||
? (yield ((_a = this.restClient) === null || _a === void 0 ? void 0 : _a.fetchTimeOffset())) || 0 | ||
: 0; | ||
const signatureExpiresAt = Date.now() + timeOffset + 5000; | ||
const signature = yield node_support_1.signMessage('GET/realtime' + signatureExpiresAt, secret); | ||
return { | ||
expiresAt: signatureExpiresAt, | ||
signature, | ||
}; | ||
}); | ||
} | ||
reconnectWithDelay(wsKey, connectionDelayMs) { | ||
@@ -252,3 +276,3 @@ this.clearTimers(wsKey); | ||
} | ||
setTimeout(() => { | ||
this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => { | ||
this.logger.info('Reconnecting to websocket', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
@@ -271,2 +295,6 @@ this.connect(wsKey); | ||
this.clearPongTimer(wsKey); | ||
const wsState = this.wsStore.get(wsKey); | ||
if (wsState === null || wsState === void 0 ? void 0 : wsState.activeReconnectTimer) { | ||
clearTimeout(wsState.activeReconnectTimer); | ||
} | ||
} | ||
@@ -297,2 +325,3 @@ // Send a ping at intervals | ||
const wsMessage = JSON.stringify({ | ||
req_id: topics.join(','), | ||
op: 'subscribe', | ||
@@ -346,16 +375,23 @@ args: topics, | ||
onWsOpen(event, wsKey) { | ||
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING)) { | ||
this.logger.info('Websocket connected', Object.assign(Object.assign({}, loggerCategory), { wsKey, livenet: this.isLivenet(), linear: this.isLinear(), spot: this.isSpot() })); | ||
this.emit('open', { wsKey, event }); | ||
} | ||
else if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) { | ||
this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
this.emit('reconnected', { wsKey, event }); | ||
} | ||
this.setWsState(wsKey, util_1.WsConnectionStateEnum.CONNECTED); | ||
// TODO: persistence not working yet for spot topics | ||
if (wsKey !== 'spotPublic' && wsKey !== 'spotPrivate') { | ||
this.requestSubscribeTopics(wsKey, [...this.wsStore.getTopics(wsKey)]); | ||
} | ||
this.wsStore.get(wsKey, true).activePingTimer = setInterval(() => this.ping(wsKey), this.options.pingInterval); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING)) { | ||
this.logger.info('Websocket connected', Object.assign(Object.assign({}, loggerCategory), { wsKey, testnet: this.isTestnet(), market: this.options.market })); | ||
this.emit('open', { wsKey, event }); | ||
} | ||
else if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) { | ||
this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
this.emit('reconnected', { wsKey, event }); | ||
} | ||
this.setWsState(wsKey, util_1.WsConnectionStateEnum.CONNECTED); | ||
// Some websockets require an auth packet to be sent after opening the connection | ||
if (util_1.WS_AUTH_ON_CONNECT_KEYS.includes(wsKey)) { | ||
this.logger.info(`Sending auth request...`); | ||
yield this.sendAuthRequest(wsKey); | ||
} | ||
// TODO: persistence not working yet for spot v1 topics | ||
if (wsKey !== util_1.WS_KEY_MAP.spotPublic && wsKey !== util_1.WS_KEY_MAP.spotPrivate) { | ||
this.requestSubscribeTopics(wsKey, [...this.wsStore.getTopics(wsKey)]); | ||
} | ||
this.wsStore.get(wsKey, true).activePingTimer = setInterval(() => this.ping(wsKey), this.options.pingInterval); | ||
}); | ||
} | ||
@@ -367,9 +403,29 @@ onWsMessage(event, wsKey) { | ||
const msg = JSON.parse((event && event.data) || event); | ||
if (msg['success'] || (msg === null || msg === void 0 ? void 0 : msg.pong)) { | ||
return this.onWsMessageResponse(msg, wsKey); | ||
this.logger.silly('Received event', Object.assign(Object.assign({}, this.logger), { wsKey, msg: JSON.stringify(msg, null, 2) })); | ||
// TODO: cleanme | ||
if (msg['success'] || (msg === null || msg === void 0 ? void 0 : msg.pong) || util_1.isWsPong(msg)) { | ||
if (util_1.isWsPong(msg)) { | ||
this.logger.silly('Received pong', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
} | ||
else { | ||
this.emit('response', msg); | ||
} | ||
return; | ||
} | ||
if (msg.topic) { | ||
if (msg['finalFragment']) { | ||
return this.emit('response', msg); | ||
} | ||
if (msg === null || msg === void 0 ? void 0 : msg.topic) { | ||
return this.emit('update', msg); | ||
} | ||
this.logger.warning('Got unhandled ws message', Object.assign(Object.assign({}, loggerCategory), { message: msg, event, | ||
if ( | ||
// spot v1 | ||
(msg === null || msg === void 0 ? void 0 : msg.code) || | ||
// spot v3 | ||
(msg === null || msg === void 0 ? void 0 : msg.type) === 'error' || | ||
// usdc options | ||
(msg === null || msg === void 0 ? void 0 : msg.success) === false) { | ||
return this.emit('errorEvent', msg); | ||
} | ||
this.logger.warning('Unhandled/unrecognised ws event message', Object.assign(Object.assign({}, loggerCategory), { message: msg, event, | ||
wsKey })); | ||
@@ -385,3 +441,3 @@ } | ||
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) { | ||
this.emit('error', error); | ||
this.emit('errorEvent', error); | ||
} | ||
@@ -393,17 +449,9 @@ } | ||
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout); | ||
this.emit('reconnect', { wsKey }); | ||
this.emit('reconnect', { wsKey, event }); | ||
} | ||
else { | ||
this.setWsState(wsKey, util_1.WsConnectionStateEnum.INITIAL); | ||
this.emit('close', { wsKey }); | ||
this.emit('close', { wsKey, event }); | ||
} | ||
} | ||
onWsMessageResponse(response, wsKey) { | ||
if (util_1.isWsPong(response)) { | ||
this.logger.silly('Received pong', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
} | ||
else { | ||
this.emit('response', response); | ||
} | ||
} | ||
getWs(wsKey) { | ||
@@ -419,45 +467,89 @@ return this.wsStore.getWs(wsKey); | ||
} | ||
const networkKey = this.isLivenet() ? 'livenet' : 'testnet'; | ||
// TODO: repetitive | ||
if (this.isLinear() || wsKey.startsWith('linear')) { | ||
if (wsKey === util_1.wsKeyLinearPublic) { | ||
return linearEndpoints.public[networkKey]; | ||
const networkKey = this.isTestnet() ? 'testnet' : 'livenet'; | ||
switch (wsKey) { | ||
case util_1.WS_KEY_MAP.linearPublic: { | ||
return util_1.WS_BASE_URL_MAP.linear.public[networkKey]; | ||
} | ||
if (wsKey === util_1.wsKeyLinearPrivate) { | ||
return linearEndpoints.private[networkKey]; | ||
case util_1.WS_KEY_MAP.linearPrivate: { | ||
return util_1.WS_BASE_URL_MAP.linear.private[networkKey]; | ||
} | ||
this.logger.error('Unhandled linear wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
return linearEndpoints[networkKey]; | ||
} | ||
if (this.isSpot() || wsKey.startsWith('spot')) { | ||
if (wsKey === util_1.wsKeySpotPublic) { | ||
return spotEndpoints.public[networkKey]; | ||
case util_1.WS_KEY_MAP.spotPublic: { | ||
return util_1.WS_BASE_URL_MAP.spot.public[networkKey]; | ||
} | ||
if (wsKey === util_1.wsKeySpotPrivate) { | ||
return spotEndpoints.private[networkKey]; | ||
case util_1.WS_KEY_MAP.spotPrivate: { | ||
return util_1.WS_BASE_URL_MAP.spot.private[networkKey]; | ||
} | ||
this.logger.error('Unhandled spot wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
return spotEndpoints[networkKey]; | ||
case util_1.WS_KEY_MAP.spotV3Public: { | ||
return util_1.WS_BASE_URL_MAP.spotv3.public[networkKey]; | ||
} | ||
case util_1.WS_KEY_MAP.spotV3Private: { | ||
return util_1.WS_BASE_URL_MAP.spotv3.private[networkKey]; | ||
} | ||
case util_1.WS_KEY_MAP.inverse: { | ||
// private and public are on the same WS connection | ||
return util_1.WS_BASE_URL_MAP.inverse.public[networkKey]; | ||
} | ||
case util_1.WS_KEY_MAP.usdcOptionPublic: { | ||
return util_1.WS_BASE_URL_MAP.usdcOption.public[networkKey]; | ||
} | ||
case util_1.WS_KEY_MAP.usdcOptionPrivate: { | ||
return util_1.WS_BASE_URL_MAP.usdcOption.private[networkKey]; | ||
} | ||
case util_1.WS_KEY_MAP.usdcPerpPublic: { | ||
return util_1.WS_BASE_URL_MAP.usdcPerp.public[networkKey]; | ||
} | ||
case util_1.WS_KEY_MAP.usdcPerpPrivate: { | ||
return util_1.WS_BASE_URL_MAP.usdcPerp.private[networkKey]; | ||
} | ||
default: { | ||
this.logger.error('getWsUrl(): Unhandled wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey })); | ||
throw util_1.neverGuard(wsKey, `getWsUrl(): Unhandled wsKey`); | ||
} | ||
} | ||
// fallback to inverse | ||
return inverseEndpoints[networkKey]; | ||
} | ||
getWsKeyForTopic(topic) { | ||
if (this.isInverse()) { | ||
return util_1.wsKeyInverse; | ||
} | ||
if (this.isLinear()) { | ||
return util_1.getLinearWsKeyForTopic(topic); | ||
} | ||
return util_1.getSpotWsKeyForTopic(topic); | ||
} | ||
wrongMarketError(market) { | ||
return new Error(`This WS client was instanced for the ${this.options.market} market. Make another WebsocketClient instance with "market: '${market}' to listen to spot topics`); | ||
} | ||
// TODO: persistance for subbed topics. Look at ftx-api implementation. | ||
/** | ||
* Add topic/topics to WS subscription list | ||
* @param wsTopics topic or list of topics | ||
* @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet) | ||
*/ | ||
subscribe(wsTopics, isPrivateTopic) { | ||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; | ||
topics.forEach((topic) => this.wsStore.addTopic(util_1.getWsKeyForTopic(this.options.market, topic, isPrivateTopic), topic)); | ||
// attempt to send subscription topic per websocket | ||
this.wsStore.getKeys().forEach((wsKey) => { | ||
// if connected, send subscription request | ||
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) { | ||
return this.requestSubscribeTopics(wsKey, topics); | ||
} | ||
// start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect | ||
if (!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING) && | ||
!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) { | ||
return this.connect(wsKey); | ||
} | ||
}); | ||
} | ||
/** | ||
* Remove topic/topics from WS subscription list | ||
* @param wsTopics topic or list of topics | ||
* @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet) | ||
*/ | ||
unsubscribe(wsTopics, isPrivateTopic) { | ||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; | ||
topics.forEach((topic) => this.wsStore.deleteTopic(util_1.getWsKeyForTopic(this.options.market, topic, isPrivateTopic), topic)); | ||
this.wsStore.getKeys().forEach((wsKey) => { | ||
// unsubscribe request only necessary if active connection exists | ||
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) { | ||
this.requestUnsubscribeTopics(wsKey, topics); | ||
} | ||
}); | ||
} | ||
/** @deprecated use "market: 'spotv3" client */ | ||
subscribePublicSpotTrades(symbol, binary) { | ||
if (!this.isSpot()) { | ||
if (this.options.market !== 'spot') { | ||
throw this.wrongMarketError('spot'); | ||
} | ||
return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify({ | ||
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({ | ||
topic: 'trade', | ||
@@ -471,7 +563,8 @@ event: 'sub', | ||
} | ||
/** @deprecated use "market: 'spotv3" client */ | ||
subscribePublicSpotTradingPair(symbol, binary) { | ||
if (!this.isSpot()) { | ||
if (this.options.market !== 'spot') { | ||
throw this.wrongMarketError('spot'); | ||
} | ||
return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify({ | ||
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({ | ||
symbol, | ||
@@ -485,7 +578,8 @@ topic: 'realtimes', | ||
} | ||
/** @deprecated use "market: 'spotv3" client */ | ||
subscribePublicSpotV1Kline(symbol, candleSize, binary) { | ||
if (!this.isSpot()) { | ||
if (this.options.market !== 'spot') { | ||
throw this.wrongMarketError('spot'); | ||
} | ||
return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify({ | ||
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({ | ||
symbol, | ||
@@ -502,4 +596,5 @@ topic: 'kline_' + candleSize, | ||
//ws.send('{"symbol":"BTCUSDT","topic":"diffDepth","event":"sub","params":{"binary":false}}'); | ||
/** @deprecated use "market: 'spotv3" client */ | ||
subscribePublicSpotOrderbook(symbol, depth, dumpScale, binary) { | ||
if (!this.isSpot()) { | ||
if (this.options.market !== 'spot') { | ||
throw this.wrongMarketError('spot'); | ||
@@ -536,3 +631,3 @@ } | ||
} | ||
return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify(msg)); | ||
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify(msg)); | ||
} | ||
@@ -539,0 +634,0 @@ } |
{ | ||
"name": "bybit-api", | ||
"version": "2.4.0-beta.1", | ||
"version": "2.4.0-beta.2", | ||
"description": "Node.js connector for Bybit's REST APIs and WebSockets, with TypeScript & integration tests.", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -14,4 +14,4 @@ # bybit-api | ||
- TypeScript support (with type declarations for most API requests & responses). | ||
- Over 200 integration tests making real API calls, validating any changes before they reach npm. | ||
- Robust WebSocket integration with connection heartbeats & automatic reconnection. | ||
- Over 300 integration tests making real API calls & WebSocket connections, validating any changes before they reach npm. | ||
- Robust WebSocket integration with configurable connection heartbeats & automatic reconnect then resubscribe workflows. | ||
- Browser support (via webpack bundle - see "Browser Usage" below). | ||
@@ -49,3 +49,3 @@ | ||
- [lib](./lib) - the JavaScript version of the project (built from TypeScript). This should not be edited directly, as it will be overwritten with each release. | ||
- [dist](./dist) - the packed bundle of the project for use in browser environments. | ||
- [dist](./dist) - the web-packed bundle of the project for use in browser environments. | ||
- [examples](./examples) - some implementation examples & demonstrations. Contributions are welcome! | ||
@@ -154,2 +154,15 @@ | ||
The WebsocketClient can be configured to a specific API group using the market parameter. These are the currently available API groups: | ||
| API Category | Market | Description | | ||
|:----------------------------: |:-------------------: |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| Unified Margin | TBC | The [derivatives v3](https://bybit-exchange.github.io/docs/derivativesV3/unified_margin/#t-websocket) category for unified margin. | | ||
| Futures v2 - Inverse Perps | `market: 'inverse'` | The [inverse v2 perps](https://bybit-exchange.github.io/docs/futuresV2/inverse/#t-websocket) category. | | ||
| Futures v2 - USDT Perps | `market: 'linear'` | The [USDT/linear v2 perps](https://bybit-exchange.github.io/docs/futuresV2/linear/#t-websocket) category. | | ||
| Futures v2 - Inverse Futures | `market: 'inverse'` | The [inverse futures v2](https://bybit-exchange.github.io/docs/futuresV2/inverse_futures/#t-websocket) category uses the same market as inverse perps. | | ||
| Spot v3 | `market: 'spotv3'` | The [spot v3](https://bybit-exchange.github.io/docs/spot/v3/#t-websocket) category. | | ||
| Spot v1 | `market: 'spot'` | The older [spot v1](https://bybit-exchange.github.io/docs/spot/v1/#t-websocket) category. Use the `spotv3` market if possible, as the v1 category does not have automatic re-subscribe if reconnected. | | ||
| Copy Trading | `market: 'linear'` | The [copy trading](https://bybit-exchange.github.io/docs/copy_trading/#t-websocket) category. Use the linear market to listen to all copy trading topics. | | ||
| USDC Perps | TBC | The [USDC perps](https://bybit-exchange.github.io/docs/usdc/perpetual/#t-websocket) category. | | ||
| USDC Options | TBC | The [USDC options](https://bybit-exchange.github.io/docs/usdc/option/#t-websocket) category. | | ||
```javascript | ||
@@ -156,0 +169,0 @@ const { WebsocketClient } = require('bybit-api'); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
321864
5861
293