usdc options public ws test
This commit is contained in:
@@ -158,7 +158,7 @@ The WebsocketClient can be configured to a specific API group using the market p
|
|||||||
| 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. |
|
| 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 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. |
|
| 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 private topics. |
|
| 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 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. |
|
| USDC Options | TBC | The [USDC options](https://bybit-exchange.github.io/docs/usdc/option/#t-websocket) category. |
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from '../src';
|
|||||||
{
|
{
|
||||||
// key: key,
|
// key: key,
|
||||||
// secret: secret,
|
// secret: secret,
|
||||||
market: 'linear',
|
// market: 'linear',
|
||||||
// market: 'inverse',
|
// market: 'inverse',
|
||||||
// market: 'spot',
|
// market: 'spot',
|
||||||
|
market: 'usdcOption',
|
||||||
},
|
},
|
||||||
logger
|
logger
|
||||||
);
|
);
|
||||||
@@ -51,10 +52,15 @@ import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from '../src';
|
|||||||
// Linear
|
// Linear
|
||||||
wsClient.subscribe('trade.BTCUSDT');
|
wsClient.subscribe('trade.BTCUSDT');
|
||||||
|
|
||||||
setTimeout(() => {
|
// usdc options
|
||||||
console.log('unsubscribing');
|
wsClient.subscribe(`recenttrades.BTC`);
|
||||||
wsClient.unsubscribe('trade.BTCUSDT');
|
wsClient.subscribe(`recenttrades.ETH`);
|
||||||
}, 5 * 1000);
|
wsClient.subscribe(`recenttrades.SOL`);
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// console.log('unsubscribing');
|
||||||
|
// wsClient.unsubscribe('trade.BTCUSDT');
|
||||||
|
// }, 5 * 1000);
|
||||||
|
|
||||||
// For spot, request public connection first then send required topics on 'open'
|
// For spot, request public connection first then send required topics on 'open'
|
||||||
// wsClient.connectPublic();
|
// wsClient.connectPublic();
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { InverseClient } from '../inverse-client';
|
|||||||
import { LinearClient } from '../linear-client';
|
import { LinearClient } from '../linear-client';
|
||||||
import { SpotClient } from '../spot-client';
|
import { SpotClient } from '../spot-client';
|
||||||
import { SpotClientV3 } from '../spot-client-v3';
|
import { SpotClientV3 } from '../spot-client-v3';
|
||||||
|
import { USDCOptionClient } from '../usdc-option-client';
|
||||||
|
|
||||||
export type RESTClient =
|
export type RESTClient =
|
||||||
| InverseClient
|
| InverseClient
|
||||||
| LinearClient
|
| LinearClient
|
||||||
| SpotClient
|
| SpotClient
|
||||||
| SpotClientV3;
|
| SpotClientV3
|
||||||
|
| USDCOptionClient;
|
||||||
|
|
||||||
export type numberInString = string;
|
export type numberInString = string;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { RestClientOptions, WS_KEY_MAP } from '../util';
|
import { RestClientOptions, WS_KEY_MAP } from '../util';
|
||||||
|
|
||||||
/** For spot markets, spotV3 is recommended */
|
/** For spot markets, spotV3 is recommended */
|
||||||
export type APIMarket = 'inverse' | 'linear' | 'spot' | 'spotv3'; //| 'v3';
|
export type APIMarket = 'inverse' | 'linear' | 'spot' | 'spotv3' | 'usdcOption';
|
||||||
|
|
||||||
// Same as inverse futures
|
// Same as inverse futures
|
||||||
export type WsPublicInverseTopic =
|
export type WsPublicInverseTopic =
|
||||||
|
|||||||
@@ -61,15 +61,23 @@ export function getRestBaseUrl(
|
|||||||
return exchangeBaseUrls.testnet;
|
return exchangeBaseUrls.testnet;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isWsPong(response: any) {
|
export function isWsPong(msg: any): boolean {
|
||||||
if (response.pong || response.ping) {
|
if (!msg) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (msg.pong || msg.ping) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (msg['op'] === 'pong') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
response.request &&
|
msg.request &&
|
||||||
response.request.op === 'ping' &&
|
msg.request.op === 'ping' &&
|
||||||
response.ret_msg === 'pong' &&
|
msg.ret_msg === 'pong' &&
|
||||||
response.success === true
|
msg.success === true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,18 @@ export const WS_BASE_URL_MAP: Record<
|
|||||||
testnet: 'wss://stream-testnet.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',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WS_KEY_MAP = {
|
export const WS_KEY_MAP = {
|
||||||
@@ -67,6 +79,10 @@ export const WS_KEY_MAP = {
|
|||||||
spotPublic: 'spotPublic',
|
spotPublic: 'spotPublic',
|
||||||
spotV3Private: 'spotV3Private',
|
spotV3Private: 'spotV3Private',
|
||||||
spotV3Public: 'spotV3Public',
|
spotV3Public: 'spotV3Public',
|
||||||
|
usdcOptionPrivate: 'usdcOptionPrivate',
|
||||||
|
usdcOptionPublic: 'usdcOptionPublic',
|
||||||
|
// usdcPerpPrivate: 'usdcPerpPrivate',
|
||||||
|
// usdcPerpPublic: 'usdcPerpPublic',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [WS_KEY_MAP.spotV3Private];
|
export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [WS_KEY_MAP.spotV3Private];
|
||||||
@@ -74,51 +90,103 @@ export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [WS_KEY_MAP.spotV3Private];
|
|||||||
export const PUBLIC_WS_KEYS = [
|
export const PUBLIC_WS_KEYS = [
|
||||||
WS_KEY_MAP.linearPublic,
|
WS_KEY_MAP.linearPublic,
|
||||||
WS_KEY_MAP.spotPublic,
|
WS_KEY_MAP.spotPublic,
|
||||||
|
WS_KEY_MAP.spotV3Public,
|
||||||
|
WS_KEY_MAP.usdcOptionPublic,
|
||||||
] as string[];
|
] as string[];
|
||||||
|
|
||||||
export function getLinearWsKeyForTopic(topic: string): WsKey {
|
/** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */
|
||||||
const privateTopics = [
|
const PRIVATE_TOPICS = [
|
||||||
'position',
|
'position',
|
||||||
'execution',
|
'execution',
|
||||||
'order',
|
'order',
|
||||||
'stop_order',
|
'stop_order',
|
||||||
'wallet',
|
'wallet',
|
||||||
];
|
|
||||||
if (privateTopics.includes(topic)) {
|
|
||||||
return WS_KEY_MAP.linearPrivate;
|
|
||||||
}
|
|
||||||
|
|
||||||
return WS_KEY_MAP.linearPublic;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSpotWsKeyForTopic(
|
|
||||||
topic: string,
|
|
||||||
apiVersion: 'v1' | 'v3'
|
|
||||||
): WsKey {
|
|
||||||
const privateTopics = [
|
|
||||||
'position',
|
|
||||||
'execution',
|
|
||||||
'order',
|
|
||||||
'stop_order',
|
|
||||||
'outboundAccountInfo',
|
'outboundAccountInfo',
|
||||||
'executionReport',
|
'executionReport',
|
||||||
'ticketInfo',
|
'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',
|
||||||
|
];
|
||||||
|
|
||||||
if (apiVersion === 'v3') {
|
export function getWsKeyForTopic(
|
||||||
if (privateTopics.includes(topic)) {
|
market: APIMarket,
|
||||||
return WS_KEY_MAP.spotV3Private;
|
topic: string,
|
||||||
|
isPrivate?: boolean
|
||||||
|
): WsKey {
|
||||||
|
const isPrivateTopic = isPrivate === true || PRIVATE_TOPICS.includes(topic);
|
||||||
|
switch (market) {
|
||||||
|
case 'inverse': {
|
||||||
|
return WS_KEY_MAP.inverse;
|
||||||
}
|
}
|
||||||
return WS_KEY_MAP.spotV3Public;
|
case 'linear': {
|
||||||
|
return isPrivateTopic
|
||||||
|
? WS_KEY_MAP.linearPrivate
|
||||||
|
: WS_KEY_MAP.linearPublic;
|
||||||
}
|
}
|
||||||
|
case 'spot': {
|
||||||
|
return isPrivateTopic ? WS_KEY_MAP.spotPrivate : WS_KEY_MAP.spotPublic;
|
||||||
|
}
|
||||||
|
case 'spotv3': {
|
||||||
|
return isPrivateTopic
|
||||||
|
? WS_KEY_MAP.spotV3Private
|
||||||
|
: WS_KEY_MAP.spotV3Public;
|
||||||
|
}
|
||||||
|
case 'usdcOption': {
|
||||||
|
return isPrivateTopic
|
||||||
|
? WS_KEY_MAP.usdcOptionPrivate
|
||||||
|
: WS_KEY_MAP.usdcOptionPublic;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw neverGuard(market, `getWsKeyForTopic(): Unhandled market`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (privateTopics.includes(topic)) {
|
export function getUsdcWsKeyForTopic(
|
||||||
return WS_KEY_MAP.spotPrivate;
|
topic: string,
|
||||||
|
subGroup: 'option' | 'perp'
|
||||||
|
): WsKey {
|
||||||
|
const isPrivateTopic = PRIVATE_TOPICS.includes(topic);
|
||||||
|
if (subGroup === 'option') {
|
||||||
|
return isPrivateTopic
|
||||||
|
? WS_KEY_MAP.usdcOptionPrivate
|
||||||
|
: WS_KEY_MAP.usdcOptionPublic;
|
||||||
}
|
}
|
||||||
return WS_KEY_MAP.spotPublic;
|
return isPrivateTopic
|
||||||
|
? WS_KEY_MAP.usdcOptionPrivate
|
||||||
|
: WS_KEY_MAP.usdcOptionPublic;
|
||||||
|
// return isPrivateTopic
|
||||||
|
// ? WS_KEY_MAP.usdcPerpPrivate
|
||||||
|
// : WS_KEY_MAP.usdcPerpPublic;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WS_ERROR_ENUM = {
|
export const WS_ERROR_ENUM = {
|
||||||
NOT_AUTHENTICATED_SPOT_V3: '-1004',
|
NOT_AUTHENTICATED_SPOT_V3: '-1004',
|
||||||
BAD_API_KEY_SPOT_V3: '10003',
|
BAD_API_KEY_SPOT_V3: '10003',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function neverGuard(x: never, msg: string): Error {
|
||||||
|
return new Error(`Unhandled value exception "x", ${msg}`);
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,19 +22,16 @@ import {
|
|||||||
import {
|
import {
|
||||||
serializeParams,
|
serializeParams,
|
||||||
isWsPong,
|
isWsPong,
|
||||||
getLinearWsKeyForTopic,
|
|
||||||
getSpotWsKeyForTopic,
|
|
||||||
WsConnectionStateEnum,
|
WsConnectionStateEnum,
|
||||||
PUBLIC_WS_KEYS,
|
PUBLIC_WS_KEYS,
|
||||||
WS_AUTH_ON_CONNECT_KEYS,
|
WS_AUTH_ON_CONNECT_KEYS,
|
||||||
WS_KEY_MAP,
|
WS_KEY_MAP,
|
||||||
DefaultLogger,
|
DefaultLogger,
|
||||||
WS_BASE_URL_MAP,
|
WS_BASE_URL_MAP,
|
||||||
|
getWsKeyForTopic,
|
||||||
|
neverGuard,
|
||||||
} from './util';
|
} from './util';
|
||||||
|
import { USDCOptionClient } from './usdc-option-client';
|
||||||
function neverGuard(x: never, msg: string): Error {
|
|
||||||
return new Error(`Unhandled value exception "x", ${msg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const loggerCategory = { category: 'bybit-ws' };
|
const loggerCategory = { category: 'bybit-ws' };
|
||||||
|
|
||||||
@@ -94,10 +91,8 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.options.fetchTimeOffsetBeforeAuth) {
|
|
||||||
this.prepareRESTClient();
|
this.prepareRESTClient();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only used if we fetch exchange time before attempting auth.
|
* Only used if we fetch exchange time before attempting auth.
|
||||||
@@ -148,15 +143,28 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
this.connectPublic();
|
this.connectPublic();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// if (this.isV3()) {
|
case 'spotv3': {
|
||||||
// this.restClient = new SpotClientV3(
|
this.restClient = new SpotClientV3(
|
||||||
// undefined,
|
undefined,
|
||||||
// undefined,
|
undefined,
|
||||||
// this.isLivenet(),
|
!this.isTestnet(),
|
||||||
// this.options.restOptions,
|
this.options.restOptions,
|
||||||
// this.options.requestOptions
|
this.options.requestOptions
|
||||||
// );
|
);
|
||||||
// }
|
this.connectPublic();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'usdcOption': {
|
||||||
|
this.restClient = new USDCOptionClient(
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
!this.isTestnet(),
|
||||||
|
this.options.restOptions,
|
||||||
|
this.options.requestOptions
|
||||||
|
);
|
||||||
|
this.connectPublic();
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw neverGuard(
|
throw neverGuard(
|
||||||
this.options.market,
|
this.options.market,
|
||||||
@@ -208,25 +216,15 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
public connectAll(): Promise<WebSocket | undefined>[] {
|
public connectAll(): Promise<WebSocket | undefined>[] {
|
||||||
switch (this.options.market) {
|
switch (this.options.market) {
|
||||||
case 'inverse': {
|
case 'inverse': {
|
||||||
return [this.connect(WS_KEY_MAP.inverse)];
|
// only one for inverse
|
||||||
|
return [this.connectPublic()];
|
||||||
}
|
}
|
||||||
case 'linear': {
|
// these all have separate public & private ws endpoints
|
||||||
return [
|
case 'linear':
|
||||||
this.connect(WS_KEY_MAP.linearPublic),
|
case 'spot':
|
||||||
this.connect(WS_KEY_MAP.linearPrivate),
|
case 'spotv3':
|
||||||
];
|
case 'usdcOption': {
|
||||||
}
|
return [this.connectPublic(), this.connectPrivate()];
|
||||||
case 'spot': {
|
|
||||||
return [
|
|
||||||
this.connect(WS_KEY_MAP.spotPublic),
|
|
||||||
this.connect(WS_KEY_MAP.spotPrivate),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
case 'spotv3': {
|
|
||||||
return [
|
|
||||||
this.connect(WS_KEY_MAP.spotV3Public),
|
|
||||||
this.connect(WS_KEY_MAP.spotV3Private),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
throw neverGuard(this.options.market, `connectAll(): Unhandled market`);
|
throw neverGuard(this.options.market, `connectAll(): Unhandled market`);
|
||||||
@@ -248,6 +246,9 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
case 'spotv3': {
|
case 'spotv3': {
|
||||||
return this.connect(WS_KEY_MAP.spotV3Public);
|
return this.connect(WS_KEY_MAP.spotV3Public);
|
||||||
}
|
}
|
||||||
|
case 'usdcOption': {
|
||||||
|
return this.connect(WS_KEY_MAP.usdcOptionPublic);
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw neverGuard(
|
throw neverGuard(
|
||||||
this.options.market,
|
this.options.market,
|
||||||
@@ -257,7 +258,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectPrivate(): Promise<WebSocket | undefined> | undefined {
|
public connectPrivate(): Promise<WebSocket | undefined> {
|
||||||
switch (this.options.market) {
|
switch (this.options.market) {
|
||||||
case 'inverse': {
|
case 'inverse': {
|
||||||
return this.connect(WS_KEY_MAP.inverse);
|
return this.connect(WS_KEY_MAP.inverse);
|
||||||
@@ -271,6 +272,9 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
case 'spotv3': {
|
case 'spotv3': {
|
||||||
return this.connect(WS_KEY_MAP.spotV3Private);
|
return this.connect(WS_KEY_MAP.spotV3Private);
|
||||||
}
|
}
|
||||||
|
case 'usdcOption': {
|
||||||
|
return this.connect(WS_KEY_MAP.usdcOptionPrivate);
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw neverGuard(
|
throw neverGuard(
|
||||||
this.options.market,
|
this.options.market,
|
||||||
@@ -596,10 +600,15 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
// any message can clear the pong timer - wouldn't get a message if the ws dropped
|
// any message can clear the pong timer - wouldn't get a message if the ws dropped
|
||||||
this.clearPongTimer(wsKey);
|
this.clearPongTimer(wsKey);
|
||||||
|
|
||||||
// this.logger.silly('Received event', { ...this.logger, wsKey, event });
|
|
||||||
|
|
||||||
const msg = JSON.parse((event && event.data) || event);
|
const msg = JSON.parse((event && event.data) || event);
|
||||||
if (msg['success'] || msg?.pong) {
|
this.logger.silly('Received event', {
|
||||||
|
...this.logger,
|
||||||
|
wsKey,
|
||||||
|
msg: JSON.stringify(msg, null, 2),
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: cleanme
|
||||||
|
if (msg['success'] || msg?.pong || isWsPong(msg)) {
|
||||||
if (isWsPong(msg)) {
|
if (isWsPong(msg)) {
|
||||||
this.logger.silly('Received pong', { ...loggerCategory, wsKey });
|
this.logger.silly('Received pong', { ...loggerCategory, wsKey });
|
||||||
} else {
|
} else {
|
||||||
@@ -608,6 +617,9 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (msg['finalFragment']) {
|
||||||
|
return this.emit('response', msg);
|
||||||
|
}
|
||||||
if (msg?.topic) {
|
if (msg?.topic) {
|
||||||
return this.emit('update', msg);
|
return this.emit('update', msg);
|
||||||
}
|
}
|
||||||
@@ -701,6 +713,18 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
// private and public are on the same WS connection
|
// private and public are on the same WS connection
|
||||||
return WS_BASE_URL_MAP.inverse.public[networkKey];
|
return WS_BASE_URL_MAP.inverse.public[networkKey];
|
||||||
}
|
}
|
||||||
|
case WS_KEY_MAP.usdcOptionPublic: {
|
||||||
|
return WS_BASE_URL_MAP.usdcOption.public[networkKey];
|
||||||
|
}
|
||||||
|
case WS_KEY_MAP.usdcOptionPrivate: {
|
||||||
|
return WS_BASE_URL_MAP.usdcOption.private[networkKey];
|
||||||
|
}
|
||||||
|
// case WS_KEY_MAP.usdcPerpPublic: {
|
||||||
|
// return WS_BASE_URL_MAP.usdcOption.public[networkKey];
|
||||||
|
// }
|
||||||
|
// case WS_KEY_MAP.usdcPerpPrivate: {
|
||||||
|
// return WS_BASE_URL_MAP.usdcOption.private[networkKey];
|
||||||
|
// }
|
||||||
default: {
|
default: {
|
||||||
this.logger.error('getWsUrl(): Unhandled wsKey: ', {
|
this.logger.error('getWsUrl(): Unhandled wsKey: ', {
|
||||||
...loggerCategory,
|
...loggerCategory,
|
||||||
@@ -711,29 +735,6 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getWsKeyForTopic(topic: string): WsKey {
|
|
||||||
switch (this.options.market) {
|
|
||||||
case 'inverse': {
|
|
||||||
return WS_KEY_MAP.inverse;
|
|
||||||
}
|
|
||||||
case 'linear': {
|
|
||||||
return getLinearWsKeyForTopic(topic);
|
|
||||||
}
|
|
||||||
case 'spot': {
|
|
||||||
return getSpotWsKeyForTopic(topic, 'v1');
|
|
||||||
}
|
|
||||||
case 'spotv3': {
|
|
||||||
return getSpotWsKeyForTopic(topic, 'v3');
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw neverGuard(
|
|
||||||
this.options.market,
|
|
||||||
`connectPublic(): Unhandled market`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private wrongMarketError(market: APIMarket) {
|
private wrongMarketError(market: APIMarket) {
|
||||||
return new Error(
|
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`
|
`This WS client was instanced for the ${this.options.market} market. Make another WebsocketClient instance with "market: '${market}' to listen to spot topics`
|
||||||
@@ -742,11 +743,16 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add topic/topics to WS subscription list
|
* 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)
|
||||||
*/
|
*/
|
||||||
public subscribe(wsTopics: WsTopic[] | WsTopic) {
|
public subscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) {
|
||||||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
||||||
topics.forEach((topic) =>
|
topics.forEach((topic) =>
|
||||||
this.wsStore.addTopic(this.getWsKeyForTopic(topic), topic)
|
this.wsStore.addTopic(
|
||||||
|
getWsKeyForTopic(this.options.market, topic, isPrivateTopic),
|
||||||
|
topic
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// attempt to send subscription topic per websocket
|
// attempt to send subscription topic per websocket
|
||||||
@@ -776,11 +782,16 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove topic/topics from WS subscription list
|
* 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)
|
||||||
*/
|
*/
|
||||||
public unsubscribe(wsTopics: WsTopic[] | WsTopic) {
|
public unsubscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) {
|
||||||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
||||||
topics.forEach((topic) =>
|
topics.forEach((topic) =>
|
||||||
this.wsStore.deleteTopic(this.getWsKeyForTopic(topic), topic)
|
this.wsStore.deleteTopic(
|
||||||
|
getWsKeyForTopic(this.options.market, topic, isPrivateTopic),
|
||||||
|
topic
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.wsStore.getKeys().forEach((wsKey: WsKey) => {
|
this.wsStore.getKeys().forEach((wsKey: WsKey) => {
|
||||||
|
|||||||
79
test/usdc/options/ws.public.test.ts
Normal file
79
test/usdc/options/ws.public.test.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
WebsocketClient,
|
||||||
|
WSClientConfigurableOptions,
|
||||||
|
WS_KEY_MAP,
|
||||||
|
} from '../../../src';
|
||||||
|
import {
|
||||||
|
logAllEvents,
|
||||||
|
getSilentLogger,
|
||||||
|
waitForSocketEvent,
|
||||||
|
WS_OPEN_EVENT_PARTIAL,
|
||||||
|
} from '../../ws.util';
|
||||||
|
|
||||||
|
describe('Public USDC Option Websocket Client', () => {
|
||||||
|
let wsClient: WebsocketClient;
|
||||||
|
|
||||||
|
const wsClientOptions: WSClientConfigurableOptions = {
|
||||||
|
market: 'usdcOption',
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
wsClient = new WebsocketClient(
|
||||||
|
wsClientOptions,
|
||||||
|
getSilentLogger('expectSuccessNoAuth')
|
||||||
|
);
|
||||||
|
// logAllEvents(wsClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wsClient.removeAllListeners();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
wsClient.closeAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open a public ws connection', async () => {
|
||||||
|
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
||||||
|
|
||||||
|
expect(wsOpenPromise).resolves.toMatchObject({
|
||||||
|
event: WS_OPEN_EVENT_PARTIAL,
|
||||||
|
wsKey: WS_KEY_MAP.usdcOptionPublic,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([wsOpenPromise]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should subscribe to public trade events', async () => {
|
||||||
|
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||||
|
// const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||||
|
|
||||||
|
wsClient.subscribe([
|
||||||
|
'recenttrades.BTC',
|
||||||
|
'recenttrades.ETH',
|
||||||
|
'recenttrades.SOL',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
expect(await wsResponsePromise).toMatchObject({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
failTopics: [],
|
||||||
|
successTopics: expect.any(Array),
|
||||||
|
},
|
||||||
|
type: 'COMMAND_RESP',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// sub failed
|
||||||
|
expect(e).toBeFalsy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a while to get an event from USDC options - testing this manually for now
|
||||||
|
// try {
|
||||||
|
// expect(await wsUpdatePromise).toStrictEqual('asdfasdf');
|
||||||
|
// } catch (e) {
|
||||||
|
// // no data
|
||||||
|
// expect(e).toBeFalsy();
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -73,6 +73,36 @@ export function waitForSocketEvent(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listenToSocketEvents(wsClient: WebsocketClient) {
|
||||||
|
const retVal: Record<
|
||||||
|
'update' | 'open' | 'response' | 'close' | 'error',
|
||||||
|
typeof jest.fn
|
||||||
|
> = {
|
||||||
|
open: jest.fn(),
|
||||||
|
response: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
close: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
wsClient.on('open', retVal.open);
|
||||||
|
wsClient.on('response', retVal.response);
|
||||||
|
wsClient.on('update', retVal.update);
|
||||||
|
wsClient.on('close', retVal.close);
|
||||||
|
wsClient.on('error', retVal.error);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...retVal,
|
||||||
|
cleanup: () => {
|
||||||
|
wsClient.removeListener('open', retVal.open);
|
||||||
|
wsClient.removeListener('response', retVal.response);
|
||||||
|
wsClient.removeListener('update', retVal.update);
|
||||||
|
wsClient.removeListener('close', retVal.close);
|
||||||
|
wsClient.removeListener('error', retVal.error);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function logAllEvents(wsClient: WebsocketClient) {
|
export function logAllEvents(wsClient: WebsocketClient) {
|
||||||
wsClient.on('update', (data) => {
|
wsClient.on('update', (data) => {
|
||||||
console.log('wsUpdate: ', JSON.stringify(data, null, 2));
|
console.log('wsUpdate: ', JSON.stringify(data, null, 2));
|
||||||
|
|||||||
Reference in New Issue
Block a user