feat(): add example for REST-like WS API usage for Bybit in Node.js/JavaScript/TypeScript. Update type flowing and docs for stricter types.
This commit is contained in:
69
README.md
69
README.md
@@ -19,19 +19,31 @@
|
|||||||
|
|
||||||
[1]: https://www.npmjs.com/package/bybit-api
|
[1]: https://www.npmjs.com/package/bybit-api
|
||||||
|
|
||||||
Node.js & JavaScript SDK for the Bybit REST APIs and WebSockets:
|
Node.js, JavaScript & TypeScript SDK for the Bybit REST APIs and WebSockets:
|
||||||
|
|
||||||
- Complete integration with all Bybit REST APIs & WebSockets.
|
- Complete integration with all Bybit REST APIs & WebSockets, including the WebSocket API.
|
||||||
- Actively maintained with a modern, promise-driven interface.
|
- Actively maintained with a modern, promise-driven interface.
|
||||||
- TypeScript support (with type declarations for most API requests & responses).
|
- TypeScript support (with type declarations for most API requests & responses).
|
||||||
- Over 450 end-to-end tests making real API calls & WebSocket connections, validating any changes before they reach npm.
|
- Thorough end-to-end tests making real API calls & WebSocket connections, validating any changes before they reach npm.
|
||||||
|
- Proxy support via axios integration.
|
||||||
- Robust WebSocket integration with configurable connection heartbeats & automatic reconnect then resubscribe workflows.
|
- Robust WebSocket integration with configurable connection heartbeats & automatic reconnect then resubscribe workflows.
|
||||||
- Event driven messaging.
|
- Event driven messaging.
|
||||||
- Smart websocket persistence
|
- Smart websocket persistence
|
||||||
- Automatically handle silent websocket disconnections through timed heartbeats, including the scheduled 24hr disconnect.
|
- Automatically handle silent websocket disconnections through timed heartbeats, including the scheduled 24hr disconnect.
|
||||||
- Automatically handle listenKey persistence and expiration/refresh.
|
- Automatically handle listenKey persistence and expiration/refresh.
|
||||||
- Emit `reconnected` event when dropped connection is restored.
|
- Emit `reconnected` event when dropped connection is restored.
|
||||||
- Proxy support via axios integration.
|
- WebSocket API integration, with two design patterns to choose from:
|
||||||
|
- Asynchronous event-driven responses:
|
||||||
|
- Subscribe to `response` and `error` events from WebsocketClient's event emitter.
|
||||||
|
- Send commands with the sendWSAPIRequest(...) method.
|
||||||
|
- Responses to commands will arrive via the `response` and `error` events.
|
||||||
|
- See example for more details: [examples/ws-api-events.ts](./examples/ws-api-events.ts)
|
||||||
|
- Asynchronous promise-driven responses:
|
||||||
|
- This behaves very much like a REST API. No need to subscribe to asynchronous events.
|
||||||
|
- Send commands with the await sendWSAPIRequest(...) method.
|
||||||
|
- Await responses to commands directly in the fully typed sendWSAPIRequest() call.
|
||||||
|
- The method directly returns a promise. Use a try/catch block for convenient error handling without the complexity of asynchronous WebSockets.
|
||||||
|
- See example for more details: [examples/ws-api-promises.ts](./examples/ws-api-promises.ts)
|
||||||
- Active community support & collaboration in telegram: [Node.js Algo Traders](https://t.me/nodetraders).
|
- Active community support & collaboration in telegram: [Node.js Algo Traders](https://t.me/nodetraders).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -88,23 +100,17 @@ The version on npm is the output from the `build` command and can be used in pro
|
|||||||
|
|
||||||
## REST API Clients
|
## REST API Clients
|
||||||
|
|
||||||
Bybit has several API groups (originally one per product). Each generation is labelled with the version number (e.g. v1/v2/v3/v5). New projects & developments should use the newest available API generation (e.g. use the V5 APIs instead of V3).
|
Bybit used to have several API groups (originally one per product). You should be using the V5 APIs. If you aren't, you should upgrade your project to use the V5 APIs as soon as possible.
|
||||||
|
|
||||||
Refer to the [V5 interface mapping page](https://bybit-exchange.github.io/docs/v5/intro#v5-and-v3-interface-mapping-list) for more information on which V5 endpoints can be used instead of previous V3 endpoints.
|
Refer to the [V5 interface mapping page](https://bybit-exchange.github.io/docs/v5/intro#v5-and-v3-interface-mapping-list) for more information on which V5 endpoints can be used instead of previous V3 endpoints. To learn more about the V5 API, please read the [V5 upgrade guideline](https://bybit-exchange.github.io/docs/v5/upgrade-guide).
|
||||||
|
|
||||||
Here are the available REST clients and the corresponding API groups described in the documentation:
|
Here are the available REST clients and the corresponding API groups described in the documentation:
|
||||||
|
|
||||||
| Class | Description |
|
| Class | Description |
|
||||||
| :----------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
| :----------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------: |
|
||||||
| [ **V5 API** ] | The new unified V5 APIs (successor to previously fragmented APIs for all API groups). To learn more about the V5 API, please read the [V5 upgrade guideline](https://bybit-exchange.github.io/docs/v5/upgrade-guide). |
|
| [ **V5 API** ] | The new unified V5 APIs (successor to previously fragmented APIs for all API groups). |
|
||||||
| [RestClientV5](src/rest-client-v5.ts) | Unified V5 all-in-one REST client for all [V5 REST APIs](https://bybit-exchange.github.io/docs/v5/intro) |
|
| [RestClientV5](src/rest-client-v5.ts) | Unified V5 all-in-one REST client for all [V5 REST APIs](https://bybit-exchange.github.io/docs/v5/intro) |
|
||||||
| [WebsocketClient](src/websocket-client.ts) | All WebSocket Events (Public & Private for all API categories) |
|
| [WebsocketClient](src/websocket-client.ts) | All WebSocket Events (Public & Private for all API categories) |
|
||||||
| [ **Derivatives v3** ] | The Derivatives v3 APIs (successor to the Futures V2 APIs) |
|
|
||||||
| [UnifiedMarginClient](src/unified-margin-client.ts) | [Derivatives (v3) Unified Margin APIs](https://bybit-exchange.github.io/docs/derivatives/unified/place-order) |
|
|
||||||
| [ContractClient](src/contract-client.ts) | [Derivatives (v3) Contract APIs](https://bybit-exchange.github.io/docs/derivatives/contract/place-order). |
|
|
||||||
| [ **Other** ] | Other standalone API groups |
|
|
||||||
| [CopyTradingClient](src/copy-trading-client.ts) | [Copy Trading APIs](https://bybit-exchange.github.io/docs/category/copy-trade) |
|
|
||||||
| [AccountAssetClientV3](src/account-asset-client-v3.ts) | [Account Asset V3 APIs](https://bybit-exchange.github.io/docs/account-asset/internal-transfer) |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -112,11 +118,16 @@ Here are the available REST clients and the corresponding API groups described i
|
|||||||
|
|
||||||
The following API clients are for previous generation REST APIs and will be removed in the next major release. Some have already stopped working (because bybit stopped supporting them). You should use the V5 APIs for all new development.
|
The following API clients are for previous generation REST APIs and will be removed in the next major release. Some have already stopped working (because bybit stopped supporting them). You should use the V5 APIs for all new development.
|
||||||
|
|
||||||
|
Each generation is labelled with the version number (e.g. v1/v2/v3/v5). New projects & developments should use the newest available API generation (e.g. use the V5 APIs instead of V3).
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Click me to see the list of APIs</summary>
|
<summary>Click me to see the list of APIs</summary>
|
||||||
|
|
||||||
| Class | Description |
|
| Class | Description |
|
||||||
| :--------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------: |
|
| :--------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------: |
|
||||||
|
| [ **Derivatives v3** ] | The Derivatives v3 APIs (successor to the Futures V2 APIs) |
|
||||||
|
| [UnifiedMarginClient](src/unified-margin-client.ts) |[Derivatives (v3) Unified Margin APIs](https://bybit-exchange.github.io/docs/derivatives/unified/place-order) |
|
||||||
|
| [ContractClient](src/contract-client.ts) | [Derivatives (v3) Contract APIs](https://bybit-exchange.github.io/docs/derivatives/contract/place-order). |
|
||||||
| [ **Futures v2** ] | The Futures v2 APIs |
|
| [ **Futures v2** ] | The Futures v2 APIs |
|
||||||
| [~~InverseClient~~](src/inverse-client.ts) | [Inverse Perpetual Futures (v2) APIs](https://bybit-exchange.github.io/docs/futuresV2/inverse/) |
|
| [~~InverseClient~~](src/inverse-client.ts) | [Inverse Perpetual Futures (v2) APIs](https://bybit-exchange.github.io/docs/futuresV2/inverse/) |
|
||||||
| [~~LinearClient~~](src/linear-client.ts) | [USDT Perpetual Futures (v2) APIs](https://bybit-exchange.github.io/docs/futuresV2/linear/#t-introduction) |
|
| [~~LinearClient~~](src/linear-client.ts) | [USDT Perpetual Futures (v2) APIs](https://bybit-exchange.github.io/docs/futuresV2/linear/#t-introduction) |
|
||||||
@@ -127,7 +138,10 @@ The following API clients are for previous generation REST APIs and will be remo
|
|||||||
| [ **USDC Contract** ] | The USDC Contract APIs |
|
| [ **USDC Contract** ] | The USDC Contract APIs |
|
||||||
| [USDCPerpetualClient](src/usdc-perpetual-client.ts) | [USDC Perpetual APIs](https://bybit-exchange.github.io/docs/usdc/option/?console#t-querydeliverylog) |
|
| [USDCPerpetualClient](src/usdc-perpetual-client.ts) | [USDC Perpetual APIs](https://bybit-exchange.github.io/docs/usdc/option/?console#t-querydeliverylog) |
|
||||||
| [USDCOptionClient](src/usdc-option-client.ts) | [USDC Option APIs](https://bybit-exchange.github.io/docs/usdc/option/#t-introduction) |
|
| [USDCOptionClient](src/usdc-option-client.ts) | [USDC Option APIs](https://bybit-exchange.github.io/docs/usdc/option/#t-introduction) |
|
||||||
| [~~AccountAssetClient~~](src/account-asset-client.ts) (deprecated, AccountAssetClientV3 recommended) | [Account Asset V1 APIs](https://bybit-exchange.github.io/docs/account_asset/v1/#t-introduction) |
|
| [~~AccountAssetClient~~](src/account-asset-client.ts) | [Account Asset V1 APIs](https://bybit-exchange.github.io/docs/account_asset/v1/#t-introduction) |
|
||||||
|
| [ **Other** ] | Other standalone API groups |
|
||||||
|
| [CopyTradingClient](src/copy-trading-client.ts) | [Copy Trading APIs](https://bybit-exchange.github.io/docs/category/copy-trade) |
|
||||||
|
| [AccountAssetClientV3](src/account-asset-client-v3.ts) | [Account Asset V3 APIs](https://bybit-exchange.github.io/docs/account-asset/internal-transfer) |
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -318,12 +332,6 @@ const wsConfig = {
|
|||||||
|
|
||||||
const ws = new WebsocketClient(wsConfig);
|
const ws = new WebsocketClient(wsConfig);
|
||||||
|
|
||||||
// (before v5) subscribe to multiple topics at once
|
|
||||||
ws.subscribe(['position', 'execution', 'trade']);
|
|
||||||
|
|
||||||
// (before v5) and/or subscribe to individual topics on demand
|
|
||||||
ws.subscribe('kline.BTCUSD.1m');
|
|
||||||
|
|
||||||
// (v5) subscribe to multiple topics at once
|
// (v5) subscribe to multiple topics at once
|
||||||
ws.subscribeV5(['orderbook.50.BTCUSDT', 'orderbook.50.ETHUSDT'], 'linear');
|
ws.subscribeV5(['orderbook.50.BTCUSDT', 'orderbook.50.ETHUSDT'], 'linear');
|
||||||
|
|
||||||
@@ -351,7 +359,7 @@ ws.on('close', () => {
|
|||||||
console.log('connection closed');
|
console.log('connection closed');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Optional: Listen to raw error events. Recommended.
|
// Listen to raw error events. Recommended.
|
||||||
ws.on('error', (err) => {
|
ws.on('error', (err) => {
|
||||||
console.error('error', err);
|
console.error('error', err);
|
||||||
});
|
});
|
||||||
@@ -370,13 +378,13 @@ Pass a custom logger (or mutate the imported DefaultLogger class) which supports
|
|||||||
```javascript
|
```javascript
|
||||||
const { WebsocketClient, DefaultLogger } = require('bybit-api');
|
const { WebsocketClient, DefaultLogger } = require('bybit-api');
|
||||||
|
|
||||||
// Disable all logging on the silly level
|
// Enable all logging on the trace level (disabled by default)
|
||||||
const customLogger = {
|
const customLogger = {
|
||||||
...DefaultLogger,
|
...DefaultLogger,
|
||||||
silly: () => {},
|
trace: (...params) => console.log('silly', ...params),
|
||||||
};
|
};
|
||||||
|
|
||||||
const ws = new WebsocketClient({ key: 'xxx', secret: 'yyy' }, customLogger);
|
const wsClient = new WebsocketClient({ key: 'xxx', secret: 'yyy' }, customLogger);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debug HTTP requests
|
### Debug HTTP requests
|
||||||
@@ -391,16 +399,13 @@ This is the "modern" way, allowing the package to be directly imported into fron
|
|||||||
|
|
||||||
1. Install these dependencies
|
1. Install these dependencies
|
||||||
```sh
|
```sh
|
||||||
npm install crypto-browserify stream-browserify
|
npm install stream-browserify
|
||||||
```
|
```
|
||||||
2. Add this to your `tsconfig.json`
|
2. Add this to your `tsconfig.json`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"paths": {
|
"paths": {
|
||||||
"crypto": [
|
|
||||||
"./node_modules/crypto-browserify"
|
|
||||||
],
|
|
||||||
"stream": [
|
"stream": [
|
||||||
"./node_modules/stream-browserify"
|
"./node_modules/stream-browserify"
|
||||||
]
|
]
|
||||||
|
|||||||
176
examples/ws-api-promises.ts
Normal file
176
examples/ws-api-promises.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from '../src';
|
||||||
|
|
||||||
|
// or
|
||||||
|
// import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from 'bybit-api';
|
||||||
|
|
||||||
|
const logger = {
|
||||||
|
...DefaultLogger,
|
||||||
|
// For a more detailed view of the WebsocketClient, enable the `trace` level by uncommenting the below line:
|
||||||
|
// trace: (...params) => console.log('trace', ...params),
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = process.env.API_KEY;
|
||||||
|
const secret = process.env.API_SECRET;
|
||||||
|
|
||||||
|
const wsClient = new WebsocketClient(
|
||||||
|
{
|
||||||
|
key: key,
|
||||||
|
secret: secret,
|
||||||
|
// testnet: true, // Whether to use the testnet environment: https://testnet.bybit.com/app/user/api-management
|
||||||
|
// demoTrading: false, // note: As of Jan 2025, demo trading does NOT support the WS API
|
||||||
|
},
|
||||||
|
logger, // Optional: inject a custom logger
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General event handlers for monitoring the WebsocketClient
|
||||||
|
*/
|
||||||
|
wsClient.on('update', (data) => {
|
||||||
|
console.log('raw message received ', JSON.stringify(data));
|
||||||
|
});
|
||||||
|
wsClient.on('open', (data) => {
|
||||||
|
console.log('ws connected', data.wsKey);
|
||||||
|
});
|
||||||
|
wsClient.on('reconnect', ({ wsKey }) => {
|
||||||
|
console.log('ws automatically reconnecting.... ', wsKey);
|
||||||
|
});
|
||||||
|
wsClient.on('reconnected', (data) => {
|
||||||
|
console.log('ws has reconnected ', data?.wsKey);
|
||||||
|
});
|
||||||
|
wsClient.on('authenticated', (data) => {
|
||||||
|
console.log('ws has authenticated ', data?.wsKey);
|
||||||
|
});
|
||||||
|
wsClient.on('error', (data) => {
|
||||||
|
console.error('ws error: ', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* This SDK's WebSocket API integration can connect WS API responses to the request that caused them. Each call
|
||||||
|
* to the `sendWSAPIRequest(...)` method returns a promise.
|
||||||
|
*
|
||||||
|
* This promise will resolve when the matching response is detected, and reject if an exception for that request
|
||||||
|
* is detected. This allows using Bybit's Websocket API in the same way that a REST API normally works.
|
||||||
|
*
|
||||||
|
* Send a command and immediately await the result. Handle any exceptions in a catch block.
|
||||||
|
*
|
||||||
|
* TypeScript users can benefit from smart type flowing for increased type safety & convenience:
|
||||||
|
* - Request parameters are fully typed, depending on the operation in the second parameter to the call. E.g.
|
||||||
|
* the `order.create` operation will automatically require the params to match the `OrderParamsV5` interface.
|
||||||
|
*
|
||||||
|
* - Response parameters are fully typed, depending on the operation in the second parameter. E.g the `order.create`
|
||||||
|
* operation will automatically map the returned value to `WSAPIResponse<OrderResultV5, "order.create">`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// To make it easier to watch, wait a few seconds before sending the amend order
|
||||||
|
const AMEND_AFTER_SECONDS = 5;
|
||||||
|
|
||||||
|
// Then wait a few more before sending the cancel order
|
||||||
|
const CANCEL_AFTER_SECONDS = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* If you haven't connected yet, the WebsocketClient will automatically connect and authenticate you as soon as you send
|
||||||
|
* your first command. That connection will then be reused for every command you send, unless the connection drops - then
|
||||||
|
* it will automatically be replaced with a healthy connection.
|
||||||
|
*
|
||||||
|
* This "not connected yet" scenario can add an initial delay to your first command. If you want to prepare a connection
|
||||||
|
* in advance, you can ask the WebsocketClient to prepare it before you start submitting commands. This is optional.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Optional, see above. Can be used to prepare a connection before sending commands
|
||||||
|
await wsClient.connectWSAPI();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new order
|
||||||
|
*/
|
||||||
|
|
||||||
|
let orderId: string | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Step 1: Create an order');
|
||||||
|
|
||||||
|
// The type for `wsAPISubmitOrderResult` is automatically resolved to `WSAPIResponse<OrderResultV5, "order.create">`
|
||||||
|
const wsAPISubmitOrderResult = await wsClient.sendWSAPIRequest(
|
||||||
|
WS_KEY_MAP.v5PrivateTrade,
|
||||||
|
'order.create',
|
||||||
|
{
|
||||||
|
symbol: 'BTCUSDT',
|
||||||
|
side: 'Buy',
|
||||||
|
orderType: 'Limit',
|
||||||
|
price: '50000',
|
||||||
|
qty: '1',
|
||||||
|
category: 'linear',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save the orderId for the next call
|
||||||
|
orderId = wsAPISubmitOrderResult.data.orderId;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Step 1: Order result (order ID: "${orderId}"): `,
|
||||||
|
wsAPISubmitOrderResult,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Step 1: Order submit exception: ', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
console.log('Step 2: Amend an order');
|
||||||
|
|
||||||
|
// The type for `wsAPIAmendOrderResult` is automatically resolved to `WSAPIResponse<OrderResultV5, "order.amend">`
|
||||||
|
const wsAPIAmendOrderResult = await wsClient.sendWSAPIRequest(
|
||||||
|
WS_KEY_MAP.v5PrivateTrade,
|
||||||
|
'order.amend',
|
||||||
|
{
|
||||||
|
symbol: 'BTCUSDT',
|
||||||
|
category: 'linear',
|
||||||
|
orderId,
|
||||||
|
price: '55000',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save the orderId for the next call
|
||||||
|
orderId = wsAPIAmendOrderResult.data.orderId;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Step 2: Amend result (order ID: "${orderId}"): `,
|
||||||
|
wsAPIAmendOrderResult,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Step 2: Amend order exception: ', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, AMEND_AFTER_SECONDS * 1000);
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
console.log('Step 3: Cancel an order');
|
||||||
|
|
||||||
|
// The type for `wsAPICancelOrderResult` is automatically resolved to `WSAPIResponse<OrderResultV5, "order.cancel">`
|
||||||
|
const wsAPICancelOrderResult = await wsClient.sendWSAPIRequest(
|
||||||
|
WS_KEY_MAP.v5PrivateTrade,
|
||||||
|
'order.cancel',
|
||||||
|
{
|
||||||
|
category: 'linear',
|
||||||
|
symbol: 'BTCUSDT',
|
||||||
|
orderId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Step 3: Cancel result:', wsAPICancelOrderResult);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Step 3: Cancel order exception: ', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(-1);
|
||||||
|
}, CANCEL_AFTER_SECONDS * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start executing the example workflow
|
||||||
|
main();
|
||||||
@@ -26,10 +26,9 @@ const wsClient = new WebsocketClient(
|
|||||||
{
|
{
|
||||||
key: key,
|
key: key,
|
||||||
secret: secret,
|
secret: secret,
|
||||||
market: 'v5',
|
|
||||||
testnet: true,
|
testnet: true,
|
||||||
},
|
},
|
||||||
logger
|
logger,
|
||||||
);
|
);
|
||||||
|
|
||||||
wsClient.on('update', (data) => {
|
wsClient.on('update', (data) => {
|
||||||
|
|||||||
@@ -22,15 +22,10 @@ export const WS_API_Operations: WSAPIOperation[] = [
|
|||||||
'order.cancel',
|
'order.cancel',
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface WsRequestOperationBybit<
|
export interface WsRequestOperationBybit<TWSTopic extends string> {
|
||||||
TWSTopic extends string,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
|
|
||||||
// TWSPayload = any,
|
|
||||||
> {
|
|
||||||
req_id: string;
|
req_id: string;
|
||||||
op: WsOperation;
|
op: WsOperation;
|
||||||
args?: (TWSTopic | string | number)[];
|
args?: (TWSTopic | string | number)[];
|
||||||
// payload?: TWSPayload;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WSAPIRequest<
|
export interface WSAPIRequest<
|
||||||
@@ -48,17 +43,6 @@ export interface WSAPIRequest<
|
|||||||
args: [TRequestParams];
|
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 interface WSAPIResponse<
|
export interface WSAPIResponse<
|
||||||
TResponseData extends object = object,
|
TResponseData extends object = object,
|
||||||
TOperation extends WSAPIOperation = WSAPIOperation,
|
TOperation extends WSAPIOperation = WSAPIOperation,
|
||||||
@@ -80,12 +64,32 @@ export interface WSAPIResponse<
|
|||||||
connId: string;
|
connId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// export interface WsAPIResponseMap<TChannel extends WSAPITopic = WSAPITopic> {
|
export type Exact<T> = {
|
||||||
// 'spot.login': WSAPIResponse<WSAPILoginResponse, TChannel>;
|
// This part says: if there's any key that's not in T, it's an error
|
||||||
// 'futures.login': WSAPIResponse<WSAPILoginResponse, TChannel>;
|
[K: string]: never;
|
||||||
// string: object;
|
} & {
|
||||||
// }
|
[K in keyof T]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of operations supported for this WsKey (connection)
|
||||||
|
*/
|
||||||
|
export interface WsAPIWsKeyTopicMap {
|
||||||
|
[WS_KEY_MAP.v5PrivateTrade]: WSAPIOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request parameters expected per operation
|
||||||
|
*/
|
||||||
|
export interface WsAPITopicRequestParamMap {
|
||||||
|
'order.create': OrderParamsV5;
|
||||||
|
'order.amend': AmendOrderParamsV5;
|
||||||
|
'order.cancel': CancelOrderParamsV5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response structure expected for each operation
|
||||||
|
*/
|
||||||
export interface WsAPIOperationResponseMap {
|
export interface WsAPIOperationResponseMap {
|
||||||
'order.create': WSAPIResponse<OrderResultV5, 'order.create'>;
|
'order.create': WSAPIResponse<OrderResultV5, 'order.create'>;
|
||||||
'order.amend': WSAPIResponse<OrderResultV5, 'order.amend'>;
|
'order.amend': WSAPIResponse<OrderResultV5, 'order.amend'>;
|
||||||
@@ -97,36 +101,4 @@ export interface WsAPIOperationResponseMap {
|
|||||||
data: [string];
|
data: [string];
|
||||||
connId: 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'
|
|
||||||
// >;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,10 +39,7 @@ interface WSClientEventMap<WsKey extends string> {
|
|||||||
/** Received data for topic */
|
/** Received data for topic */
|
||||||
update: (response: any & { wsKey: WsKey }) => void;
|
update: (response: any & { wsKey: WsKey }) => void;
|
||||||
/** Exception from ws client OR custom listeners (e.g. if you throw inside your event handler) */
|
/** Exception from ws client OR custom listeners (e.g. if you throw inside your event handler) */
|
||||||
exception: (
|
error: (response: any & { wsKey: WsKey; isWSAPIResponse?: boolean }) => void;
|
||||||
response: any & { wsKey: WsKey; isWSAPIResponse?: boolean },
|
|
||||||
) => void;
|
|
||||||
error: (response: any & { wsKey: WsKey }) => void;
|
|
||||||
/** Confirmation that a connection successfully authenticated */
|
/** Confirmation that a connection successfully authenticated */
|
||||||
authenticated: (event: {
|
authenticated: (event: {
|
||||||
wsKey: WsKey;
|
wsKey: WsKey;
|
||||||
@@ -52,7 +49,7 @@ interface WSClientEventMap<WsKey extends string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EmittableEvent<TEvent = any> {
|
export interface EmittableEvent<TEvent = any> {
|
||||||
eventType: 'response' | 'update' | 'exception' | 'authenticated';
|
eventType: 'response' | 'update' | 'error' | 'authenticated';
|
||||||
event: TEvent;
|
event: TEvent;
|
||||||
isWSAPIResponse?: boolean;
|
isWSAPIResponse?: boolean;
|
||||||
}
|
}
|
||||||
@@ -594,7 +591,7 @@ export abstract class BaseWebsocketClient<
|
|||||||
if (!error.message) {
|
if (!error.message) {
|
||||||
this.logger.error(`${context} due to unexpected error: `, error);
|
this.logger.error(`${context} due to unexpected error: `, error);
|
||||||
this.emit('response', { ...error, wsKey });
|
this.emit('response', { ...error, wsKey });
|
||||||
this.emit('exception', { ...error, wsKey });
|
this.emit('error', { ...error, wsKey });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,7 +624,7 @@ export abstract class BaseWebsocketClient<
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.emit('response', { ...error, wsKey });
|
this.emit('response', { ...error, wsKey });
|
||||||
this.emit('exception', { ...error, wsKey });
|
this.emit('error', { ...error, wsKey });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a signature, build the auth request and send it */
|
/** Get a signature, build the auth request and send it */
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
MidflightWsRequestEvent,
|
MidflightWsRequestEvent,
|
||||||
} from './util/BaseWSClient';
|
} from './util/BaseWSClient';
|
||||||
import {
|
import {
|
||||||
|
Exact,
|
||||||
WSAPIRequest,
|
WSAPIRequest,
|
||||||
WsAPIOperationResponseMap,
|
WsAPIOperationResponseMap,
|
||||||
WsAPITopicRequestParamMap,
|
WsAPITopicRequestParamMap,
|
||||||
@@ -838,7 +839,7 @@ export class WebsocketClient extends BaseWebsocketClient<
|
|||||||
}
|
}
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
eventType: 'exception',
|
eventType: 'error',
|
||||||
event: parsed,
|
event: parsed,
|
||||||
isWSAPIResponse: true,
|
isWSAPIResponse: true,
|
||||||
});
|
});
|
||||||
@@ -888,7 +889,7 @@ export class WebsocketClient extends BaseWebsocketClient<
|
|||||||
// Failed request
|
// Failed request
|
||||||
if (parsed.success === false) {
|
if (parsed.success === false) {
|
||||||
results.push({
|
results.push({
|
||||||
eventType: 'exception',
|
eventType: 'error',
|
||||||
event: parsed,
|
event: parsed,
|
||||||
// isWSAPIResponse: isWSAPIResponseEvent,
|
// isWSAPIResponse: isWSAPIResponseEvent,
|
||||||
});
|
});
|
||||||
@@ -938,7 +939,7 @@ export class WebsocketClient extends BaseWebsocketClient<
|
|||||||
exception: e,
|
exception: e,
|
||||||
eventData: event.data,
|
eventData: event.data,
|
||||||
},
|
},
|
||||||
eventType: 'exception',
|
eventType: 'error',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.error('Failed to parse event data due to exception: ', {
|
this.logger.error('Failed to parse event data due to exception: ', {
|
||||||
@@ -963,43 +964,59 @@ export class WebsocketClient extends BaseWebsocketClient<
|
|||||||
/**
|
/**
|
||||||
* Send a Websocket API event on a connection. Returns a promise that resolves on reply.
|
* Send a Websocket API event on a connection. Returns a promise that resolves on reply.
|
||||||
*
|
*
|
||||||
* Returned promise is rejected if an exception is detected in the reply OR the connection disconnects for any reason (even if automatic reconnect will happen).
|
|
||||||
*
|
|
||||||
* Authentication is automatic. If you didn't request authentication yourself, there might be a small delay after your first request, while the SDK automatically authenticates.
|
* Authentication is automatic. If you didn't request authentication yourself, there might be a small delay after your first request, while the SDK automatically authenticates.
|
||||||
*
|
*
|
||||||
|
* Returned promise is rejected if:
|
||||||
|
* - an exception is detected in the reply, OR
|
||||||
|
* - the connection disconnects for any reason (even if automatic reconnect will happen).
|
||||||
|
*
|
||||||
* If you authenticated once and you're reconnected later (e.g. connection temporarily lost), the SDK will by default automatically:
|
* If you authenticated once and you're reconnected later (e.g. connection temporarily lost), the SDK will by default automatically:
|
||||||
* - Detect you were authenticated to the WS API before
|
* - Detect you were authenticated to the WS API before
|
||||||
* - Try to re-authenticate (up to 5 times, in case something (bad timestamp) goes wrong)
|
* - Try to re-authenticate (up to 5 times, in case something (bad timestamp) goes wrong)
|
||||||
* - If it succeeds, it will emit the 'authenticated' event.
|
* - If it succeeds, it will emit the 'authenticated' event.
|
||||||
* - If it fails and gives up, it will emit an 'exception' event (type: 'wsapi.auth', reason: detailed text).
|
* - If it fails and gives up, it will emit an 'exception' event.
|
||||||
*
|
*
|
||||||
* You can turn off the automatic re-auth WS API logic using `reauthWSAPIOnReconnect: false` in the WSClient config.
|
* @param wsKey - The connection this event is for. Currently only "v5PrivateTrade" is supported, since that is the dedicated WS API connection.
|
||||||
*
|
* @param operation - The command being sent, e.g. "order.create" to submit a new order.
|
||||||
* @param wsKey - The connection this event is for (e.g. "spotV4" | "perpFuturesUSDTV4" | "perpFuturesBTCV4" | "deliveryFuturesUSDTV4" | "deliveryFuturesBTCV4" | "optionsV4")
|
* @param params - Any request parameters for the command. E.g. `OrderParamsV5` to submit a new order. Only send parameters for the request body. Everything else is automatically handled.
|
||||||
* @param channel - The channel this event is for (e.g. "spot.login" to authenticate)
|
|
||||||
* @param params - Any request parameters for the payload (contents of req_param in the docs). Signature generation is automatic, only send parameters such as order ID as per the docs.
|
|
||||||
* @returns Promise - tries to resolve with async WS API response. Rejects if disconnected or exception is seen in async WS API response
|
* @returns Promise - tries to resolve with async WS API response. Rejects if disconnected or exception is seen in async WS API response
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// This overload allows the caller to omit the 3rd param, if it isn't required (e.g. for the login call)
|
// This overload allows the caller to omit the 3rd param, if it isn't required
|
||||||
async sendWSAPIRequest<
|
sendWSAPIRequest<
|
||||||
TWSKey extends keyof WsAPIWsKeyTopicMap = keyof WsAPIWsKeyTopicMap,
|
TWSKey extends keyof WsAPIWsKeyTopicMap,
|
||||||
TWSOperation extends
|
TWSOperation extends WsAPIWsKeyTopicMap[TWSKey],
|
||||||
WsAPIWsKeyTopicMap[TWSKey] = WsAPIWsKeyTopicMap[TWSKey],
|
TWSParams extends Exact<WsAPITopicRequestParamMap[TWSOperation]>,
|
||||||
TWSParams extends
|
|
||||||
WsAPITopicRequestParamMap[TWSOperation] = WsAPITopicRequestParamMap[TWSOperation],
|
|
||||||
>(
|
>(
|
||||||
wsKey: TWSKey,
|
wsKey: TWSKey,
|
||||||
operation: TWSOperation,
|
operation: TWSOperation,
|
||||||
...params: TWSParams extends undefined ? [] : [TWSParams]
|
...params: TWSParams extends undefined ? [] : [TWSParams]
|
||||||
): Promise<WsAPIOperationResponseMap[TWSOperation]>;
|
): Promise<WsAPIOperationResponseMap[TWSOperation]>;
|
||||||
|
|
||||||
|
// These overloads give stricter types than mapped generics, since generic constraints do not trigger excess property checks
|
||||||
|
// Without these overloads, TypeScript won't complain if you include an unexpected property with your request (if it doesn't clash with an existing property)
|
||||||
|
sendWSAPIRequest(
|
||||||
|
wsKey: typeof WS_KEY_MAP.v5PrivateTrade,
|
||||||
|
operation: 'order.create',
|
||||||
|
params: WsAPITopicRequestParamMap['order.create'],
|
||||||
|
): Promise<WsAPIOperationResponseMap['order.create']>;
|
||||||
|
|
||||||
|
sendWSAPIRequest(
|
||||||
|
wsKey: typeof WS_KEY_MAP.v5PrivateTrade,
|
||||||
|
operation: 'order.amend',
|
||||||
|
params: WsAPITopicRequestParamMap['order.amend'],
|
||||||
|
): Promise<WsAPIOperationResponseMap['order.amend']>;
|
||||||
|
|
||||||
|
sendWSAPIRequest(
|
||||||
|
wsKey: typeof WS_KEY_MAP.v5PrivateTrade,
|
||||||
|
operation: 'order.cancel',
|
||||||
|
params: WsAPITopicRequestParamMap['order.cancel'],
|
||||||
|
): Promise<WsAPIOperationResponseMap['order.cancel']>;
|
||||||
|
|
||||||
async sendWSAPIRequest<
|
async sendWSAPIRequest<
|
||||||
TWSKey extends keyof WsAPIWsKeyTopicMap = keyof WsAPIWsKeyTopicMap,
|
TWSKey extends keyof WsAPIWsKeyTopicMap,
|
||||||
TWSOperation extends
|
TWSOperation extends WsAPIWsKeyTopicMap[TWSKey],
|
||||||
WsAPIWsKeyTopicMap[TWSKey] = WsAPIWsKeyTopicMap[TWSKey],
|
TWSParams extends Exact<WsAPITopicRequestParamMap[TWSOperation]>,
|
||||||
TWSParams extends
|
|
||||||
WsAPITopicRequestParamMap[TWSOperation] = WsAPITopicRequestParamMap[TWSOperation],
|
|
||||||
TWSAPIResponse extends
|
TWSAPIResponse extends
|
||||||
WsAPIOperationResponseMap[TWSOperation] = WsAPIOperationResponseMap[TWSOperation],
|
WsAPIOperationResponseMap[TWSOperation] = WsAPIOperationResponseMap[TWSOperation],
|
||||||
>(
|
>(
|
||||||
|
|||||||
Reference in New Issue
Block a user