Merge pull request #45 from tiagosiebler/feat/ts
TypeScript Introduction
This commit is contained in:
1
.github/workflows/npmpublish.yml
vendored
1
.github/workflows/npmpublish.yml
vendored
@@ -41,6 +41,7 @@ jobs:
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
#- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm publish
|
||||
if: steps.version-updated.outputs.has-updated
|
||||
env:
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,3 +19,5 @@ node_modules/
|
||||
.env
|
||||
.env.test
|
||||
.cache
|
||||
lib
|
||||
bundleReport.html
|
||||
|
||||
63
README.md
63
README.md
@@ -5,16 +5,11 @@
|
||||
|
||||
[1]: https://www.npmjs.com/package/bybit-api
|
||||
|
||||
A production-ready Node.js connector for the Bybit APIs and WebSockets.
|
||||
A production-ready Node.js connector for the Bybit APIs and WebSockets, with TypeScript & browser support.
|
||||
|
||||
## Installation
|
||||
`npm install --save bybit-api`
|
||||
|
||||
## Usage
|
||||
Create API credentials at Bybit
|
||||
- [Livenet](https://bybit.com/app/user/api-management?affiliate_id=9410&language=en-US&group_id=0&group_type=1)
|
||||
- [Testnet](https://testnet.bybit.com/app/user/api-management)
|
||||
|
||||
## Issues & Discussion
|
||||
- Issues? Check the [issues tab](https://github.com/tiagosiebler/bybit-api/issues).
|
||||
- Discuss & collaborate with other node devs? Join our [Node.js Algo Traders](https://t.me/nodetraders) engineering community on telegram.
|
||||
@@ -24,6 +19,25 @@ Most methods accept JS objects. These can be populated using parameters specifie
|
||||
- [Bybit API Inverse Documentation](https://bybit-exchange.github.io/docs/inverse/#t-introduction).
|
||||
- [Bybit API Linear Documentation (not supported yet)](https://bybit-exchange.github.io/docs/linear/#t-introduction)
|
||||
|
||||
## Structure
|
||||
This project uses typescript. Resources are stored in 3 key structures:
|
||||
- [src](./src) - the whole connector written in typescript
|
||||
- [lib](./lib) - the javascript version of the project (compiled 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.
|
||||
|
||||
## Usage
|
||||
Create API credentials at Bybit
|
||||
- [Livenet](https://bybit.com/app/user/api-management?affiliate_id=9410&language=en-US&group_id=0&group_type=1)
|
||||
- [Testnet](https://testnet.bybit.com/app/user/api-management)
|
||||
|
||||
### Browser Usage
|
||||
Build a bundle using webpack:
|
||||
- `npm install`
|
||||
- `npm build`
|
||||
- `npm pack`
|
||||
|
||||
The bundle can be found in `dist/`. Altough usage should be largely consistent, smaller differences will exist. Documentation is still TODO.
|
||||
|
||||
### Inverse Contracts
|
||||
#### Rest client
|
||||
```javascript
|
||||
@@ -31,8 +45,39 @@ const {RestClient} = require('bybit-api');
|
||||
|
||||
const API_KEY = 'xxx';
|
||||
const PRIVATE_KEY = 'yyy';
|
||||
const useLivenet = false;
|
||||
|
||||
const client = new RestClient(API_KEY, PRIVATE_KEY);
|
||||
const restInverseOptions = {
|
||||
// override the max size of the request window (in ms)
|
||||
recv_window?: number;
|
||||
|
||||
// how often to sync time drift with bybit servers
|
||||
sync_interval_ms?: number | string;
|
||||
|
||||
// Default: false. Disable above sync mechanism if true.
|
||||
disable_time_sync?: boolean;
|
||||
|
||||
// Default: false. If true, we'll throw errors if any params are undefined
|
||||
strict_param_validation?: boolean;
|
||||
|
||||
// Optionally override API protocol + domain
|
||||
// e.g 'https://api.bytick.com'
|
||||
baseUrl?: string;
|
||||
|
||||
// Default: true. whether to try and post-process request exceptions.
|
||||
parse_exceptions?: boolean;
|
||||
};
|
||||
|
||||
const client = new RestClient(
|
||||
API_KEY,
|
||||
PRIVATE_KEY,
|
||||
|
||||
// optional, uses testnet by default. Set to 'true' to use livenet.
|
||||
useLivenet,
|
||||
|
||||
// restInverseOptions,
|
||||
// requestLibraryOptions
|
||||
);
|
||||
|
||||
client.changeUserLeverage({leverage: 4, symbol: 'ETHUSD'})
|
||||
.then(result => {
|
||||
@@ -43,7 +88,7 @@ client.changeUserLeverage({leverage: 4, symbol: 'ETHUSD'})
|
||||
});
|
||||
```
|
||||
|
||||
See inverse [rest-client.js](./lib/rest-client.js) for further information.
|
||||
See inverse [rest-client.ts](./src/rest-client.ts) for further information.
|
||||
|
||||
#### Websocket client
|
||||
```javascript
|
||||
@@ -105,7 +150,7 @@ ws.on('error', err => {
|
||||
console.error('ERR', err);
|
||||
});
|
||||
```
|
||||
See inverse [websocket-client.js](./lib/websocket-client.js) & [ws api docs](./doc/websocket-client.md) for further information.
|
||||
See inverse [websocket-client.ts](./src/websocket-client.ts) for further information.
|
||||
|
||||
### Customise Logging
|
||||
Pass a custom logger which supports the log methods `silly`, `debug`, `notice`, `info`, `warning` and `error`, or override methods from the default logger as desired:
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
# Rest API
|
||||
|
||||
|
||||
## Class: RestClient
|
||||
### new RestClient([key][, secret][, livenet][, options])
|
||||
- `key` {String} Bybit API Key
|
||||
- `secret` {String} Bybit private key
|
||||
- `livenet` {Boolean} If false (default), use testnet.
|
||||
- `options` {Object} Optional settings for custom behaviour.
|
||||
- `recv_window` {Number} Optional, default 5000. Increase if recv errors are seen.
|
||||
- `sync_interval_ms` {Number} Optional, default 3600000. Interval at which syncTime is performed.
|
||||
|
||||
If you only use the [public endpoints](#public-endpoints) you can omit key and secret.
|
||||
|
||||
### Private enpoints
|
||||
#### async placeActiveOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-placev2active)
|
||||
|
||||
#### async getActiveOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-getactive)
|
||||
|
||||
#### async cancelActiveOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-cancelv2active)
|
||||
|
||||
#### async cancelAllActiveOrders(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-cancelallactive)
|
||||
|
||||
#### async replaceActiveOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-replaceactive)
|
||||
|
||||
#### async queryActiveOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-queryactive)
|
||||
|
||||
#### async placeConditionalOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-placecond)
|
||||
|
||||
#### async getConditionalOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-getcond)
|
||||
|
||||
#### async cancelConditionalOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-cancelcond)
|
||||
|
||||
#### async cancelAllConditionalOrders(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-cancelallcond)
|
||||
|
||||
#### async replaceConditionalOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-replacecond)
|
||||
|
||||
#### async queryConditionalOrder(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-querycond)
|
||||
|
||||
#### async getUserLeverage()
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-getleverage)
|
||||
|
||||
#### async changeUserLeverage(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-changeleverage)
|
||||
|
||||
#### async getPosition(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-mypositionv2)
|
||||
|
||||
#### async getPositions()
|
||||
*Deprecated v1 method*
|
||||
[See bybit documentation](https://github.com/bybit-exchange/bybit-official-api-docs/blob/master/en/rest_api.md#positionlistget)
|
||||
|
||||
#### async changePositionMargin(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-changemargin)
|
||||
|
||||
#### async setTradingStop(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-tradingstop)
|
||||
|
||||
#### async getWalletFundRecords(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-walletrecords)
|
||||
|
||||
#### async getWithdrawRecords(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-withdrawrecords)
|
||||
|
||||
#### async getAssetExchangeRecords(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-assetexchangerecords)
|
||||
|
||||
#### async getWalletBalance(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-balance)
|
||||
|
||||
#### async setRiskLimit(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-setrisklimit)
|
||||
|
||||
#### async getRiskLimitList()
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-getrisklimit)
|
||||
|
||||
#### async getLastFundingRate(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-fundingrate)
|
||||
|
||||
#### async getMyLastFundingFee(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-mylastfundingfee)
|
||||
|
||||
#### async getPredictedFunding(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-predictedfunding)
|
||||
|
||||
#### async getTradeRecords(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-usertraderecords)
|
||||
|
||||
### Public enpoints
|
||||
|
||||
#### async getOrderBook(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-orderbook)
|
||||
|
||||
#### async getKline(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-querykline)
|
||||
|
||||
#### async getOpenInterest(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-marketopeninterest)
|
||||
|
||||
#### async getLatestBigDeal(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-marketbigdeal)
|
||||
|
||||
#### async getLongShortRatio(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-marketaccountratio)
|
||||
|
||||
#### async getLatestInformation()
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-latestsymbolinfo)
|
||||
|
||||
#### async getPublicTradingRecords(params)
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-publictradingrecords)
|
||||
|
||||
#### async getServerTime()
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-servertime)
|
||||
|
||||
#### async getApiAnnouncements()
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-announcement)
|
||||
|
||||
#### async getSymbols()
|
||||
Returns symbol information (such as tick size & min notional):
|
||||
[Meeting price restrictions](https://bybit-exchange.github.io/docs/inverse/#price-price)
|
||||
|
||||
[See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-querysymbol)
|
||||
|
||||
#### async getTimeOffset()
|
||||
|
||||
Returns the time offset in ms to the server time retrieved by [`async getServerTime`](#async-getservertime).
|
||||
If positive the time on the server is ahead of the clients time, if negative the time on the server is behind the clients time.
|
||||
## Example
|
||||
|
||||
```javascript
|
||||
const {RestClient} = require('bybit-api');
|
||||
|
||||
const API_KEY = 'xxx';
|
||||
const PRIVATE_KEY = 'yyy';
|
||||
|
||||
const client = new RestClient(API_KEY, PRIVATE_KEY);
|
||||
|
||||
client.changeUserLeverage({leverage: 4, symbol: 'ETHUSD'})
|
||||
.then(result => {
|
||||
console.log(result);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
10
index.js
10
index.js
@@ -1,9 +1 @@
|
||||
const RestClient = require('./lib/rest-client');
|
||||
const WebsocketClient = require('./lib/websocket-client');
|
||||
const DefaultLogger = require('./lib/logger');
|
||||
|
||||
module.exports = {
|
||||
RestClient,
|
||||
WebsocketClient,
|
||||
DefaultLogger
|
||||
};
|
||||
module.exports = require('lib/index');
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
silly: function() {console.log(arguments);},
|
||||
debug: function() {console.log(arguments);},
|
||||
notice: function() {console.log(arguments);},
|
||||
info: function() {console.info(arguments);},
|
||||
warning: function() {console.warn(arguments);},
|
||||
error: function() {console.error(arguments);},
|
||||
};
|
||||
@@ -1,346 +0,0 @@
|
||||
const assert = require('assert');
|
||||
const RequestWrapper = require('./util/requestWrapper');
|
||||
|
||||
module.exports = class RestClient {
|
||||
/**
|
||||
* @public Creates an instance of the inverse REST API client.
|
||||
*
|
||||
* @param {string} key - your API key
|
||||
* @param {string} secret - your API secret
|
||||
* @param {boolean} [livenet=false]
|
||||
* @param {*} [options={}] options to configure REST API connectivity
|
||||
* @param {*} [requestOptions={}] HTTP networking options for axios
|
||||
*/
|
||||
constructor(key, secret, livenet=false, options={}, requestOptions={}) {
|
||||
this.request = new RequestWrapper(...arguments);
|
||||
}
|
||||
|
||||
async placeActiveOrder(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.side, 'Parameter side is required');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.order_type, 'Parameter order_type is required');
|
||||
assert(params.qty, 'Parameter qty is required');
|
||||
assert(params.time_in_force, 'Parameter time_in_force is required');
|
||||
|
||||
if (params.order_type === 'Limit') assert(params.price, 'Parameter price is required for limit orders');
|
||||
|
||||
return await this.request.post('v2/private/order/create', params);
|
||||
}
|
||||
|
||||
async getActiveOrder(params) {
|
||||
return await this.request.get('open-api/order/list', params);
|
||||
}
|
||||
|
||||
async cancelActiveOrder(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.order_id || params.order_link_id, 'Parameter order_id OR order_link_id is required');
|
||||
|
||||
return await this.request.post('v2/private/order/cancel', params);
|
||||
}
|
||||
|
||||
async cancelAllActiveOrders(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.post('v2/private/order/cancelAll', params);
|
||||
}
|
||||
|
||||
async replaceActiveOrder(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.order_id || params.order_link_id, 'Parameter order_id OR order_link_id is required');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.post('v2/private/order/replace', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use replaceActiveOrder()
|
||||
*/
|
||||
async replaceActiveOrderOld(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.order_id || params.order_link_id, 'Parameter order_id OR order_link_id is required');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.post('open-api/order/replace', params);
|
||||
}
|
||||
|
||||
async queryActiveOrder(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.order_id || params.order_link_id, 'Parameter order_id OR order_link_id is required');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.get('v2/private/order', params);
|
||||
}
|
||||
|
||||
async placeConditionalOrder(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.side, 'Parameter side is required');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.order_type, 'Parameter order_type is required');
|
||||
assert(params.qty, 'Parameter qty is required');
|
||||
assert(params.base_price, 'Parameter base_price is required');
|
||||
assert(params.stop_px, 'Parameter stop_px is required');
|
||||
assert(params.time_in_force, 'Parameter time_in_force is required');
|
||||
|
||||
if (params.order_type === 'Limit') assert(params.price, 'Parameter price is required for limit orders');
|
||||
|
||||
return await this.request.post('v2/private/stop-order/create', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use placeConditionalOrder
|
||||
*/
|
||||
async placeConditionalOrderOld(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.side, 'Parameter side is required');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.order_type, 'Parameter order_type is required');
|
||||
assert(params.qty, 'Parameter qty is required');
|
||||
assert(params.time_in_force, 'Parameter time_in_force is required');
|
||||
assert(params.base_price, 'Parameter base_price is required');
|
||||
assert(params.stop_px, 'Parameter stop_px is required');
|
||||
|
||||
if (params.order_type === 'Limit') assert(params.price, 'Parameter price is required for limit orders');
|
||||
|
||||
return await this.request.post('open-api/stop-order/create', params);
|
||||
}
|
||||
|
||||
async getConditionalOrder(params) {
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
return await this.request.get('v2/private/stop-order/list', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use placeConditionalOrder
|
||||
*/
|
||||
async getConditionalOrderOld(params) {
|
||||
return await this.request.get('open-api/stop-order/list', params);
|
||||
}
|
||||
|
||||
async cancelConditionalOrder(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.stop_order_id || params.order_link_id, 'Parameter stop_order_id OR order_link_id is required');
|
||||
|
||||
return await this.request.post('v2/private/stop-order/cancel', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use cancelConditionalOrder
|
||||
*/
|
||||
async cancelConditionalOrderOld(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.stop_order_id, 'Parameter stop_order_id is required');
|
||||
|
||||
return await this.request.post('open-api/stop-order/cancel', params);
|
||||
}
|
||||
|
||||
async cancelAllConditionalOrders(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.post('v2/private/stop-order/cancelAll', params);
|
||||
}
|
||||
|
||||
async replaceConditionalOrder(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.stop_order_id || params.order_link_id, 'Parameter stop_order_id OR order_link_id is required');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.post('v2/private/stop-order/replace', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use replaceConditionalOrder
|
||||
*/
|
||||
async replaceConditionalOrderOld(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.stop_order_id, 'Parameter stop_order_id is required');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.post('open-api/stop-order/replace', params);
|
||||
}
|
||||
|
||||
async queryConditionalOrder(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.stop_order_id || params.order_link_id, 'Parameter order_id OR order_link_id is required');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.get('v2/private/stop-order', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getPosition() instead
|
||||
*/
|
||||
async getUserLeverage() {
|
||||
return await this.request.get('user/leverage');
|
||||
}
|
||||
|
||||
async getPosition(params) {
|
||||
return await this.request.get('v2/private/position/list', params);
|
||||
}
|
||||
|
||||
async changeUserLeverage(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
if (typeof params.leverage === 'undefined') {
|
||||
throw new Error('Parameter leverage is required');
|
||||
}
|
||||
|
||||
return await this.request.post('user/leverage/save', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getPosition() instead
|
||||
*/
|
||||
async getPositions() {
|
||||
return await this.request.get('position/list');
|
||||
}
|
||||
|
||||
async changePositionMargin(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.margin, 'Parameter margin is required');
|
||||
|
||||
return await this.request.post('position/change-position-margin', params);
|
||||
}
|
||||
|
||||
async setTradingStop(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.post('open-api/position/trading-stop', params);
|
||||
}
|
||||
|
||||
async getWalletFundRecords(params) {
|
||||
return await this.request.get('open-api/wallet/fund/records', params);
|
||||
}
|
||||
|
||||
async getWithdrawRecords(params) {
|
||||
return await this.request.get('open-api/wallet/withdraw/list', params);
|
||||
}
|
||||
|
||||
async getAssetExchangeRecords(params) {
|
||||
return await this.request.get('v2/private/exchange-order/list', params);
|
||||
}
|
||||
|
||||
async getWalletBalance(params) {
|
||||
return await this.request.get('v2/private/wallet/balance', params);
|
||||
}
|
||||
|
||||
async setRiskLimit(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.risk_id, 'Parameter risk_id is required');
|
||||
|
||||
return await this.request.post('open-api/wallet/risk-limit', params);
|
||||
}
|
||||
|
||||
async getRiskLimitList() {
|
||||
return await this.request.get('open-api/wallet/risk-limit/list');
|
||||
}
|
||||
|
||||
async getLastFundingRate(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.get('open-api/funding/prev-funding-rate', params);
|
||||
}
|
||||
|
||||
async getMyLastFundingFee(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.get('open-api/funding/prev-funding', params);
|
||||
}
|
||||
|
||||
async getPredictedFunding(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.get('open-api/funding/predicted-funding', params);
|
||||
}
|
||||
|
||||
async getTradeRecords(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.order_id || params.symbol, 'Parameter order_id OR symbol is required');
|
||||
|
||||
return await this.request.get('v2/private/execution/list', params);
|
||||
}
|
||||
|
||||
async getOrderBook(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.get('v2/public/orderBook/L2', params);
|
||||
}
|
||||
|
||||
async getKline(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.interval, 'Parameter interval is required');
|
||||
assert(params.from, 'Parameter from is required');
|
||||
|
||||
return await this.request.get('v2/public/kline/list', params);
|
||||
}
|
||||
|
||||
async getOpenInterest(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.period, 'Parameter period is required');
|
||||
|
||||
return await this.request.get('v2/public/open-interest', params);
|
||||
}
|
||||
|
||||
async getLatestBigDeal(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.get('v2/public/big-deal', params);
|
||||
}
|
||||
|
||||
async getLongShortRatio(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
assert(params.period, 'Parameter period is required');
|
||||
|
||||
return await this.request.get('v2/public/account-ratio', params);
|
||||
}
|
||||
|
||||
async getLatestInformation() {
|
||||
return await this.request.get('v2/public/tickers');
|
||||
}
|
||||
|
||||
async getPublicTradingRecords(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.get('v2/public/trading-records', params);
|
||||
}
|
||||
|
||||
async getPublicLiquidations(params) {
|
||||
assert(params, 'No params passed');
|
||||
assert(params.symbol, 'Parameter symbol is required');
|
||||
|
||||
return await this.request.get('v2/public/liq-records', params);
|
||||
}
|
||||
|
||||
async getServerTime() {
|
||||
return await this.request.get('v2/public/time');
|
||||
}
|
||||
|
||||
async getApiAnnouncements() {
|
||||
return await this.request.get('v2/public/announcement');
|
||||
}
|
||||
|
||||
async getSymbols() {
|
||||
return await this.request.get('v2/public/symbols');
|
||||
}
|
||||
|
||||
async getTimeOffset() {
|
||||
return await this.request.getTimeOffset();
|
||||
}
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
const { createHmac } = require('crypto');
|
||||
|
||||
module.exports = {
|
||||
signMessage(message, secret) {
|
||||
return createHmac('sha256', secret)
|
||||
.update(message)
|
||||
.digest('hex');
|
||||
},
|
||||
serializeParams(params = {}, strict_validation = false) {
|
||||
return Object.keys(params)
|
||||
.sort()
|
||||
.map(key => {
|
||||
const value = params[key];
|
||||
if (strict_validation === true && typeof value === 'undefined') {
|
||||
throw new Error('Failed to sign API request due to undefined parameter');
|
||||
}
|
||||
return `${key}=${value}`;
|
||||
})
|
||||
.join('&');
|
||||
}
|
||||
};
|
||||
@@ -1,167 +0,0 @@
|
||||
const assert = require('assert');
|
||||
const axios = require('axios');
|
||||
|
||||
const { signMessage, serializeParams } = require('./requestUtils');
|
||||
|
||||
const baseUrls = {
|
||||
livenet: 'https://api.bybit.com',
|
||||
testnet: 'https://api-testnet.bybit.com'
|
||||
};
|
||||
|
||||
module.exports = class RequestUtil {
|
||||
constructor(key, secret, livenet=false, options={}, requestOptions={}) {
|
||||
this._timeOffset = null;
|
||||
this._syncTimePromise = null;
|
||||
|
||||
this.options = {
|
||||
recv_window: 5000,
|
||||
// how often to sync time drift with bybit servers
|
||||
sync_interval_ms: 3600000,
|
||||
// if true, we'll throw errors if any params are undefined
|
||||
strict_param_validation: false,
|
||||
...options
|
||||
};
|
||||
|
||||
this.baseUrl = baseUrls[livenet === true ? 'livenet' : 'testnet'];
|
||||
if (options.baseUrl) {
|
||||
this.baseUrl = options.baseUrl;
|
||||
}
|
||||
|
||||
this.globalRequestOptions = {
|
||||
// in ms == 5 minutes by default
|
||||
timeout: 1000 * 60 * 5,
|
||||
// custom request options based on axios specs - see: https://github.com/axios/axios#request-config
|
||||
...requestOptions,
|
||||
headers: {
|
||||
'referer': 'bybitapinode'
|
||||
},
|
||||
};
|
||||
|
||||
if (key) {
|
||||
assert(secret, 'Secret is required for private enpoints');
|
||||
}
|
||||
|
||||
this._syncTime();
|
||||
setInterval(this._syncTime.bind(this), parseInt(this.options.sync_interval_ms));
|
||||
|
||||
this.key = key;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
async get(endpoint, params) {
|
||||
const result = await this._call('GET', endpoint, params);
|
||||
return result;
|
||||
}
|
||||
|
||||
async post(endpoint, params) {
|
||||
const result = await this._call('POST', endpoint, params);
|
||||
return result;
|
||||
}
|
||||
|
||||
async getTimeOffset() {
|
||||
const start = Date.now();
|
||||
const result = await this.get('v2/public/time');
|
||||
const end = Date.now();
|
||||
|
||||
return Math.ceil((result.time_now * 1000) - end + ((end - start) / 2));
|
||||
}
|
||||
|
||||
async _call(method, endpoint, params) {
|
||||
const publicEndpoint = endpoint.startsWith('v2/public');
|
||||
|
||||
if (!publicEndpoint) {
|
||||
if (!this.key || !this.secret) {
|
||||
throw new Error('Private endpoints require api and private keys set');
|
||||
}
|
||||
|
||||
if (this._timeOffset === null) {
|
||||
await this._syncTime();
|
||||
}
|
||||
|
||||
params = this._signRequest(params);
|
||||
}
|
||||
|
||||
const options = {
|
||||
...this.globalRequestOptions,
|
||||
url: [this.baseUrl, endpoint].join('/'),
|
||||
method: method,
|
||||
json: true
|
||||
};
|
||||
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
options.params = params;
|
||||
break;
|
||||
default:
|
||||
options.data = params;
|
||||
break;
|
||||
}
|
||||
|
||||
return axios(options).then(response => {
|
||||
if (response.status == 200) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
throw {
|
||||
code: response.status,
|
||||
message: response.statusText,
|
||||
body: response.data,
|
||||
requestOptions: options
|
||||
};
|
||||
})
|
||||
.catch(e => {
|
||||
if (!e.response) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
if (!e.request) {
|
||||
throw e.message;
|
||||
}
|
||||
|
||||
// request made but no response received
|
||||
throw e;
|
||||
}
|
||||
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
throw {
|
||||
code: e.response.statusCode,
|
||||
message: e.response.message,
|
||||
body: e.response.body,
|
||||
requestOptions: options,
|
||||
headers: e.response.headers
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
_signRequest(data) {
|
||||
const params = {
|
||||
...data,
|
||||
api_key: this.key,
|
||||
timestamp: Date.now() + this._timeOffset
|
||||
};
|
||||
|
||||
// Optional, set to 5000 by default. Increase if timestamp/recv_window errors are seen.
|
||||
if (this.options.recv_window && !params.recv_window) {
|
||||
params.recv_window = this.options.recv_window;
|
||||
}
|
||||
|
||||
if (this.key && this.secret) {
|
||||
const serializedParams = serializeParams(params, this.options.strict_param_validation);
|
||||
params.sign = signMessage(serializedParams, this.secret);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
_syncTime() {
|
||||
if (this._syncTimePromise !== null) {
|
||||
return this._syncTimePromise;
|
||||
}
|
||||
|
||||
this._syncTimePromise = this.getTimeOffset().then(offset => {
|
||||
this._timeOffset = offset;
|
||||
this._syncTimePromise = null;
|
||||
});
|
||||
|
||||
return this._syncTimePromise;
|
||||
}
|
||||
};
|
||||
2354
package-lock.json
generated
2354
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@@ -1,14 +1,43 @@
|
||||
{
|
||||
"name": "bybit-api",
|
||||
"version": "1.2.5",
|
||||
"version": "1.3.0",
|
||||
"description": "A production-ready Node.js connector for the Bybit APIs and WebSockets",
|
||||
"main": "index.js",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib/*",
|
||||
"index.js"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"clean": "rm -rf lib dist",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "tsc",
|
||||
"pack": "webpack --config webpack/webpack.config.js",
|
||||
"prepublish": "npm run build",
|
||||
"betapublish": "npm publish --tag beta"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tiagosiebler/bybit-api"
|
||||
"author": "Tiago Siebler (https://github.com/tiagosiebler)",
|
||||
"contributors": [
|
||||
"Stefan Aebischer <os@pixtron.ch> (https://pixtron.ch)"
|
||||
],
|
||||
"dependencies": {
|
||||
"axios": "^0.21.0",
|
||||
"isomorphic-ws": "^4.0.1",
|
||||
"ws": "^7.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.14.7",
|
||||
"buffer": "^6.0.2",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"eslint": "^7.10.0",
|
||||
"source-map-loader": "^1.1.2",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"ts-loader": "^8.0.11",
|
||||
"typescript": "^4.0.5",
|
||||
"webpack": "^5.4.0",
|
||||
"webpack-bundle-analyzer": "^4.1.0",
|
||||
"webpack-cli": "^4.2.0"
|
||||
},
|
||||
"keywords": [
|
||||
"bybit",
|
||||
@@ -24,20 +53,17 @@
|
||||
"bitcoin",
|
||||
"best"
|
||||
],
|
||||
"author": "Tiago Siebler (https://github.com/tiagosiebler)",
|
||||
"contributors": [
|
||||
"Stefan Aebischer <os@pixtron.ch> (https://pixtron.ch)"
|
||||
],
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/tiagosiebler"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tiagosiebler/bybit-api"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/tiagosiebler/bybit-api/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tiagosiebler/bybit-api#readme",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.0",
|
||||
"ws": "^7.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.10.0"
|
||||
}
|
||||
"homepage": "https://github.com/tiagosiebler/bybit-api#readme"
|
||||
}
|
||||
|
||||
3
src/index.ts
Normal file
3
src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './rest-client';
|
||||
export * from './websocket-client';
|
||||
export * from './logger';
|
||||
22
src/logger.ts
Normal file
22
src/logger.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export type LogParams = null | any;
|
||||
|
||||
export const DefaultLogger = {
|
||||
silly: (...params: LogParams): void => {
|
||||
console.log(params);
|
||||
},
|
||||
debug: (...params: LogParams): void => {
|
||||
console.log(params);
|
||||
},
|
||||
notice: (...params: LogParams): void => {
|
||||
console.log(params);
|
||||
},
|
||||
info: (...params: LogParams): void => {
|
||||
console.info(params);
|
||||
},
|
||||
warning: (...params: LogParams): void => {
|
||||
console.error(params);
|
||||
},
|
||||
error: (...params: LogParams): void => {
|
||||
console.error(params);
|
||||
}
|
||||
};
|
||||
531
src/rest-client.ts
Normal file
531
src/rest-client.ts
Normal file
@@ -0,0 +1,531 @@
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { GenericAPIResponse, getBaseRESTInverseUrl, RestClientInverseOptions } from './util/requestUtils';
|
||||
import RequestWrapper from './util/requestWrapper';
|
||||
|
||||
export class RestClient {
|
||||
private requestWrapper: RequestWrapper;
|
||||
|
||||
/**
|
||||
* @public Creates an instance of the inverse REST API client.
|
||||
*
|
||||
* @param {string} key - your API key
|
||||
* @param {string} secret - your API secret
|
||||
* @param {boolean} [useLivenet=false]
|
||||
* @param {RestClientInverseOptions} [restInverseOptions={}] options to configure REST API connectivity
|
||||
* @param {AxiosRequestConfig} [requestOptions={}] HTTP networking options for axios
|
||||
*/
|
||||
constructor(
|
||||
key?: string | undefined,
|
||||
secret?: string | undefined,
|
||||
useLivenet?: boolean,
|
||||
restInverseOptions: RestClientInverseOptions = {},
|
||||
httpOptions: AxiosRequestConfig = {}
|
||||
) {
|
||||
this.requestWrapper = new RequestWrapper(
|
||||
key,
|
||||
secret,
|
||||
getBaseRESTInverseUrl(useLivenet),
|
||||
restInverseOptions,
|
||||
httpOptions
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Market Data Endpoints
|
||||
*
|
||||
*/
|
||||
|
||||
getOrderBook(params: {
|
||||
symbol: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/orderBook/L2', params);
|
||||
}
|
||||
|
||||
getKline(params: {
|
||||
symbol: string;
|
||||
interval: string;
|
||||
from: number;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/kline/list', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getTickers() instead
|
||||
*/
|
||||
getLatestInformation(params?: {
|
||||
symbol?: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.getTickers(params);
|
||||
}
|
||||
|
||||
getTickers(params?: {
|
||||
symbol?: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/tickers', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getTrades() instead
|
||||
*/
|
||||
getPublicTradingRecords(params: {
|
||||
symbol: string;
|
||||
from?: number;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.getTrades(params);
|
||||
}
|
||||
|
||||
getTrades(params: {
|
||||
symbol: string;
|
||||
from?: number;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/trading-records', params);
|
||||
}
|
||||
|
||||
getSymbols(): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/symbols');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getLiquidations() instead
|
||||
*/
|
||||
getPublicLiquidations(params: {
|
||||
symbol: string;
|
||||
from?: number;
|
||||
limit?: number;
|
||||
start_time?: number;
|
||||
end_time?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.getLiquidations(params);
|
||||
}
|
||||
|
||||
getLiquidations(params: {
|
||||
symbol: string;
|
||||
from?: number;
|
||||
limit?: number;
|
||||
start_time?: number;
|
||||
end_time?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/liq-records', params);
|
||||
}
|
||||
|
||||
getMarkPriceKline(params: {
|
||||
symbol: string;
|
||||
interval: string;
|
||||
from: number;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/mark-price-kline', params);
|
||||
}
|
||||
|
||||
getOpenInterest(params: {
|
||||
symbol: string;
|
||||
period: string;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/open-interest', params);
|
||||
}
|
||||
|
||||
getLatestBigDeal(params: {
|
||||
symbol: string;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/big-deal', params);
|
||||
}
|
||||
|
||||
getLongShortRatio(params: {
|
||||
symbol: string;
|
||||
period: string;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/account-ratio', params);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Account Data Endpoints
|
||||
*
|
||||
*/
|
||||
|
||||
placeActiveOrder(orderRequest: {
|
||||
side: string;
|
||||
symbol: string;
|
||||
order_type: string;
|
||||
qty: number;
|
||||
price?: number;
|
||||
time_in_force: string;
|
||||
take_profit?: number;
|
||||
stop_loss?: number;
|
||||
reduce_only?: boolean;
|
||||
close_on_trigger?: boolean;
|
||||
order_link_id?: string;
|
||||
}): GenericAPIResponse {
|
||||
// if (orderRequest.order_type === 'Limit' && !orderRequest.price) {
|
||||
// throw new Error('Price required for limit orders');
|
||||
// }
|
||||
return this.requestWrapper.post('v2/private/order/create', orderRequest);
|
||||
}
|
||||
|
||||
getActiveOrderList(params: {
|
||||
symbol: string;
|
||||
order_status?: string;
|
||||
direction?: string;
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/private/order/list', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getActiveOrderList() instead
|
||||
*/
|
||||
getActiveOrder(params: {
|
||||
order_id?: string;
|
||||
order_link_id?: string;
|
||||
symbol?: string;
|
||||
order?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
order_status?: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('open-api/order/list', params);
|
||||
}
|
||||
|
||||
cancelActiveOrder(params: {
|
||||
symbol: string;
|
||||
order_id?: string;
|
||||
order_link_id?: string;
|
||||
}): GenericAPIResponse {
|
||||
// if (!params.order_id && !params.order_link_id) {
|
||||
// throw new Error('Parameter order_id OR order_link_id is required');
|
||||
// }
|
||||
return this.requestWrapper.post('v2/private/order/cancel', params);
|
||||
}
|
||||
|
||||
cancelAllActiveOrders(params: {
|
||||
symbol: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.post('v2/private/order/cancelAll', params);
|
||||
}
|
||||
|
||||
replaceActiveOrder(params: {
|
||||
order_id?: string;
|
||||
order_link_id?: string;
|
||||
symbol: string;
|
||||
p_r_qty?: string;
|
||||
p_r_price?: string;
|
||||
}): GenericAPIResponse {
|
||||
// if (!params.order_id && !params.order_link_id) {
|
||||
// throw new Error('Parameter order_id OR order_link_id is required');
|
||||
// }
|
||||
return this.requestWrapper.post('v2/private/order/replace', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use replaceActiveOrder()
|
||||
*/
|
||||
replaceActiveOrderOld(params: any): GenericAPIResponse {
|
||||
// if (!params.order_id && !params.order_link_id) {
|
||||
// throw new Error('Parameter order_id OR order_link_id is required');
|
||||
// }
|
||||
return this.requestWrapper.post('open-api/order/replace', params);
|
||||
}
|
||||
|
||||
queryActiveOrder(params: {
|
||||
order_id?: string;
|
||||
order_link_id?: string;
|
||||
symbol: string;
|
||||
}): GenericAPIResponse {
|
||||
// if (!params.order_id && !params.order_link_id) {
|
||||
// throw new Error('Parameter order_id OR order_link_id is required');
|
||||
// }
|
||||
return this.requestWrapper.get('v2/private/order', params);
|
||||
}
|
||||
|
||||
placeConditionalOrder(params: {
|
||||
side: string;
|
||||
symbol: string;
|
||||
order_type: string;
|
||||
qty: string;
|
||||
price?: string;
|
||||
base_price: string;
|
||||
stop_px: string;
|
||||
time_in_force: string;
|
||||
trigger_by?: string;
|
||||
close_on_trigger?: boolean;
|
||||
order_link_id?: string;
|
||||
}): GenericAPIResponse {
|
||||
// if (params.order_type === 'Limit' && !params.price) {
|
||||
// throw new Error('Parameter price is required for limit orders');
|
||||
// }
|
||||
return this.requestWrapper.post('v2/private/stop-order/create', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use placeConditionalOrder
|
||||
*/
|
||||
placeConditionalOrderOld(params: any): GenericAPIResponse {
|
||||
// if (params.order_type === 'Limit' && !params.price) {
|
||||
// throw new Error('Parameter price is required for limit orders');
|
||||
// }
|
||||
return this.requestWrapper.post('open-api/stop-order/create', params);
|
||||
}
|
||||
|
||||
getConditionalOrder(params: {
|
||||
symbol: string;
|
||||
stop_order_status?: string;
|
||||
direction?: string;
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/private/stop-order/list', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use placeConditionalOrder
|
||||
*/
|
||||
getConditionalOrderOld(params: any): GenericAPIResponse {
|
||||
return this.requestWrapper.get('open-api/stop-order/list', params);
|
||||
}
|
||||
|
||||
cancelConditionalOrder(params: {
|
||||
symbol: string;
|
||||
stop_order_id?: string;
|
||||
order_link_id?: string;
|
||||
}): GenericAPIResponse {
|
||||
// if (!params.stop_order_id && !params.order_link_id) {
|
||||
// throw new Error('Parameter stop_order_id OR order_link_id is required');
|
||||
// }
|
||||
return this.requestWrapper.post('v2/private/stop-order/cancel', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use cancelConditionalOrder
|
||||
*/
|
||||
cancelConditionalOrderOld(params: any): GenericAPIResponse {
|
||||
// if (!params.stop_order_id && !params.order_link_id) {
|
||||
// throw new Error('Parameter stop_order_id OR order_link_id is required');
|
||||
// }
|
||||
return this.requestWrapper.post('open-api/stop-order/cancel', params);
|
||||
}
|
||||
|
||||
cancelAllConditionalOrders(params: {
|
||||
symbol: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.post('v2/private/stop-order/cancelAll', params);
|
||||
}
|
||||
|
||||
replaceConditionalOrder(params: {
|
||||
stop_order_id?: string;
|
||||
order_link_id?: string;
|
||||
symbol: string;
|
||||
p_r_qty?: number;
|
||||
p_r_price?: string;
|
||||
p_r_trigger_price?: string;
|
||||
}): GenericAPIResponse {
|
||||
// if (!params.stop_order_id && !params.order_link_id) {
|
||||
// throw new Error('Parameter stop_order_id OR order_link_id is required');
|
||||
// }
|
||||
return this.requestWrapper.post('v2/private/stop-order/replace', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use replaceConditionalOrder
|
||||
*/
|
||||
replaceConditionalOrderOld(params: any): GenericAPIResponse {
|
||||
return this.requestWrapper.post('open-api/stop-order/replace', params);
|
||||
}
|
||||
|
||||
queryConditionalOrder(params: {
|
||||
symbol: string;
|
||||
stop_order_id?: string;
|
||||
order_link_id?: string;
|
||||
}): GenericAPIResponse {
|
||||
// if (!params.stop_order_id && !params.order_link_id) {
|
||||
// throw new Error('Parameter stop_order_id OR order_link_id is required');
|
||||
// }
|
||||
return this.requestWrapper.get('v2/private/stop-order', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getPosition() instead
|
||||
*/
|
||||
getUserLeverage(): GenericAPIResponse {
|
||||
return this.requestWrapper.get('user/leverage');
|
||||
}
|
||||
|
||||
getPosition(params?: {
|
||||
symbol?: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/private/position/list', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getPosition() instead
|
||||
*/
|
||||
getPositions(): GenericAPIResponse {
|
||||
return this.requestWrapper.get('position/list');
|
||||
}
|
||||
|
||||
changePositionMargin(params: {
|
||||
symbol: string;
|
||||
margin: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.post('position/change-position-margin', params);
|
||||
}
|
||||
|
||||
setTradingStop(params: {
|
||||
symbol: string;
|
||||
take_profit?: number;
|
||||
stop_loss?: number;
|
||||
tp_trigger_by?: string;
|
||||
sl_trigger_by?: string;
|
||||
new_trailing_active?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.post('open-api/position/trading-stop', params);
|
||||
}
|
||||
|
||||
setUserLeverage(params: {
|
||||
symbol: string;
|
||||
leverage: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.post('user/leverage/save', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use setUserLeverage() instead
|
||||
*/
|
||||
changeUserLeverage(params: any): GenericAPIResponse {
|
||||
return this.setUserLeverage(params);
|
||||
}
|
||||
|
||||
getTradeRecords(params: {
|
||||
order_id?: string;
|
||||
symbol: string;
|
||||
start_time?: number;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
order?: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/private/execution/list', params);
|
||||
}
|
||||
|
||||
getClosedPnl(params: {
|
||||
symbol: string;
|
||||
start_time?: number;
|
||||
end_time?: number;
|
||||
exec_type?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/private/trade/closed-pnl/list', params);
|
||||
}
|
||||
|
||||
getRiskLimitList(): GenericAPIResponse {
|
||||
return this.requestWrapper.get('open-api/wallet/risk-limit/list');
|
||||
}
|
||||
|
||||
setRiskLimit(params: {
|
||||
symbol: string;
|
||||
risk_id: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.post('open-api/wallet/risk-limit', params);
|
||||
}
|
||||
|
||||
getLastFundingRate(params: {
|
||||
symbol: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('open-api/funding/prev-funding-rate', params);
|
||||
}
|
||||
|
||||
getMyLastFundingFee(params: {
|
||||
symbol: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('open-api/funding/prev-funding', params);
|
||||
}
|
||||
|
||||
getPredictedFunding(params: {
|
||||
symbol: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('open-api/funding/predicted-funding', params);
|
||||
}
|
||||
|
||||
getApiKeyInfo(): GenericAPIResponse {
|
||||
return this.requestWrapper.get('open-api/api-key');
|
||||
}
|
||||
|
||||
getLcpInfo(params: {
|
||||
symbol: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/private/account/lcp', params);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Wallet Data Endpoints
|
||||
*
|
||||
*/
|
||||
|
||||
getWalletBalance(params?: {
|
||||
coin?: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/private/wallet/balance', params);
|
||||
}
|
||||
|
||||
getWalletFundRecords(params?: {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
currency?: string;
|
||||
coin?: string;
|
||||
wallet_fund_type?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('open-api/wallet/fund/records', params);
|
||||
}
|
||||
|
||||
getWithdrawRecords(params: {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
coin?: string;
|
||||
status?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('open-api/wallet/withdraw/list', params);
|
||||
}
|
||||
|
||||
getAssetExchangeRecords(params?: {
|
||||
limit?: number;
|
||||
from?: number;
|
||||
direction?: string;
|
||||
}): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/private/exchange-order/list', params);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* API Data Endpoints
|
||||
*
|
||||
*/
|
||||
|
||||
getServerTime(): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/time');
|
||||
}
|
||||
|
||||
getApiAnnouncements(): GenericAPIResponse {
|
||||
return this.requestWrapper.get('v2/public/announcement');
|
||||
}
|
||||
|
||||
async getTimeOffset(): Promise<number> {
|
||||
const start = Date.now();
|
||||
return this.getServerTime().then(result => {
|
||||
const end = Date.now();
|
||||
return Math.ceil((result.time_now * 1000) - end + ((end - start) / 2));
|
||||
});
|
||||
}
|
||||
};
|
||||
59
src/util/requestUtils.ts
Normal file
59
src/util/requestUtils.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { createHmac } from 'crypto';
|
||||
|
||||
export interface RestClientInverseOptions {
|
||||
// override the max size of the request window (in ms)
|
||||
recv_window?: number;
|
||||
|
||||
// how often to sync time drift with bybit servers
|
||||
sync_interval_ms?: number | string;
|
||||
|
||||
// Default: false. Disable above sync mechanism if true.
|
||||
disable_time_sync?: boolean;
|
||||
|
||||
// Default: false. If true, we'll throw errors if any params are undefined
|
||||
strict_param_validation?: boolean;
|
||||
|
||||
// Optionally override API protocol + domain
|
||||
// e.g 'https://api.bytick.com'
|
||||
baseUrl?: string;
|
||||
|
||||
// Default: true. whether to try and post-process request exceptions.
|
||||
parse_exceptions?: boolean;
|
||||
}
|
||||
|
||||
export type GenericAPIResponse = Promise<any>;
|
||||
|
||||
export function signMessage(message: string, secret: string): string {
|
||||
return createHmac('sha256', secret)
|
||||
.update(message)
|
||||
.digest('hex');
|
||||
};
|
||||
|
||||
export function serializeParams(params: object = {}, strict_validation = false): string {
|
||||
return Object.keys(params)
|
||||
.sort()
|
||||
.map(key => {
|
||||
const value = params[key];
|
||||
if (strict_validation === true && typeof value === 'undefined') {
|
||||
throw new Error('Failed to sign API request due to undefined parameter');
|
||||
}
|
||||
return `${key}=${value}`;
|
||||
})
|
||||
.join('&');
|
||||
};
|
||||
|
||||
export function getBaseRESTInverseUrl(useLivenet?: boolean, restInverseOptions?: RestClientInverseOptions) {
|
||||
const baseUrlsInverse = {
|
||||
livenet: 'https://api.bybit.com',
|
||||
testnet: 'https://api-testnet.bybit.com'
|
||||
};
|
||||
|
||||
if (restInverseOptions?.baseUrl) {
|
||||
return restInverseOptions.baseUrl;
|
||||
}
|
||||
|
||||
if (useLivenet === true) {
|
||||
return baseUrlsInverse.livenet;
|
||||
}
|
||||
return baseUrlsInverse.testnet;
|
||||
}
|
||||
193
src/util/requestWrapper.ts
Normal file
193
src/util/requestWrapper.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios';
|
||||
|
||||
import { signMessage, serializeParams, RestClientInverseOptions, GenericAPIResponse } from './requestUtils';
|
||||
|
||||
export default class RequestUtil {
|
||||
private timeOffset: number | null;
|
||||
private syncTimePromise: null | Promise<any>;
|
||||
private options: RestClientInverseOptions;
|
||||
private baseUrl: string;
|
||||
private globalRequestOptions: AxiosRequestConfig;
|
||||
private key: string | undefined;
|
||||
private secret: string | undefined;
|
||||
|
||||
constructor(
|
||||
key: string | undefined,
|
||||
secret: string | undefined,
|
||||
baseUrl: string,
|
||||
options: RestClientInverseOptions = {},
|
||||
requestOptions: AxiosRequestConfig = {}
|
||||
) {
|
||||
this.timeOffset = null;
|
||||
this.syncTimePromise = null;
|
||||
|
||||
this.options = {
|
||||
recv_window: 5000,
|
||||
// how often to sync time drift with bybit servers
|
||||
sync_interval_ms: 3600000,
|
||||
// if true, we'll throw errors if any params are undefined
|
||||
strict_param_validation: false,
|
||||
...options
|
||||
};
|
||||
|
||||
this.globalRequestOptions = {
|
||||
// in ms == 5 minutes by default
|
||||
timeout: 1000 * 60 * 5,
|
||||
// custom request options based on axios specs - see: https://github.com/axios/axios#request-config
|
||||
...requestOptions,
|
||||
headers: {
|
||||
'x-referer': 'bybitapinode'
|
||||
},
|
||||
};
|
||||
|
||||
this.baseUrl = baseUrl;
|
||||
|
||||
if (key && !secret) {
|
||||
throw new Error('API Key & Secret are both required for private enpoints')
|
||||
}
|
||||
|
||||
if (this.options.disable_time_sync !== true) {
|
||||
this.syncTime();
|
||||
setInterval(this.syncTime.bind(this), +this.options.sync_interval_ms!);
|
||||
}
|
||||
|
||||
this.key = key;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
// TODO: type check that endpoint never starts with forward slash??
|
||||
get(endpoint: string, params?: any): GenericAPIResponse {
|
||||
return this._call('GET', endpoint, params);
|
||||
}
|
||||
|
||||
post(endpoint: string, params?: any): GenericAPIResponse {
|
||||
return this._call('POST', endpoint, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private Make a HTTP request to a specific endpoint. Private endpoints are automatically signed.
|
||||
*/
|
||||
async _call(method: Method, endpoint: string, params?: any): GenericAPIResponse {
|
||||
const publicEndpoint = endpoint.startsWith('v2/public');
|
||||
|
||||
if (!publicEndpoint) {
|
||||
if (!this.key || !this.secret) {
|
||||
throw new Error('Private endpoints require api and private keys set');
|
||||
}
|
||||
|
||||
if (this.timeOffset === null) {
|
||||
await this.syncTime();
|
||||
}
|
||||
|
||||
params = this.signRequest(params);
|
||||
}
|
||||
|
||||
const options = {
|
||||
...this.globalRequestOptions,
|
||||
url: [this.baseUrl, endpoint].join('/'),
|
||||
method: method,
|
||||
json: true
|
||||
};
|
||||
|
||||
if (method === 'GET') {
|
||||
options.params = params;
|
||||
} else {
|
||||
options.data = params;
|
||||
}
|
||||
|
||||
return axios(options).then(response => {
|
||||
if (response.status == 200) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
throw response;
|
||||
}).catch(this.parseException);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private generic handler to parse request exceptions
|
||||
*/
|
||||
parseException(e: any): unknown {
|
||||
if (this.options.parse_exceptions === false) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
if (!e.response) {
|
||||
if (!e.request) {
|
||||
throw e.message;
|
||||
}
|
||||
|
||||
// request made but no response received
|
||||
throw e;
|
||||
}
|
||||
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
const response: AxiosResponse = e.response;
|
||||
throw {
|
||||
code: response.status,
|
||||
message: response.statusText,
|
||||
body: response.data,
|
||||
headers: response.headers,
|
||||
requestOptions: this.options
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private sign request and set recv window
|
||||
*/
|
||||
signRequest(data: any): any {
|
||||
const params = {
|
||||
...data,
|
||||
api_key: this.key,
|
||||
timestamp: Date.now() + (this.timeOffset || 0)
|
||||
};
|
||||
|
||||
// Optional, set to 5000 by default. Increase if timestamp/recv_window errors are seen.
|
||||
if (this.options.recv_window && !params.recv_window) {
|
||||
params.recv_window = this.options.recv_window;
|
||||
}
|
||||
|
||||
if (this.key && this.secret) {
|
||||
const serializedParams = serializeParams(params, this.options.strict_param_validation);
|
||||
params.sign = signMessage(serializedParams, this.secret);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private trigger time sync and store promise
|
||||
*/
|
||||
syncTime(): GenericAPIResponse {
|
||||
if (this.options.disable_time_sync === true) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (this.syncTimePromise !== null) {
|
||||
return this.syncTimePromise;
|
||||
}
|
||||
|
||||
this.syncTimePromise = this.getTimeOffset().then(offset => {
|
||||
this.timeOffset = offset;
|
||||
this.syncTimePromise = null;
|
||||
});
|
||||
|
||||
return this.syncTimePromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated move this somewhere else, because v2/public/time shouldn't be hardcoded here
|
||||
*
|
||||
* @returns {Promise<number>}
|
||||
* @memberof RequestUtil
|
||||
*/
|
||||
async getTimeOffset(): Promise<number> {
|
||||
const start = Date.now();
|
||||
const result = await this.get('v2/public/time');
|
||||
const end = Date.now();
|
||||
|
||||
return Math.ceil((result.time_now * 1000) - end + ((end - start) / 2));
|
||||
}
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const WebSocket = require('ws');
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const defaultLogger = require('./logger');
|
||||
const RestClient = require('./rest-client');
|
||||
const { signMessage, serializeParams } = require('./util/requestUtils');
|
||||
import { RestClient } from './rest-client';
|
||||
import { DefaultLogger } from './logger';
|
||||
import { signMessage, serializeParams } from './util/requestUtils';
|
||||
// import WebSocket from 'ws';
|
||||
import WebSocket from 'isomorphic-ws';
|
||||
|
||||
const wsUrls = {
|
||||
livenet: 'wss://stream.bybit.com/realtime',
|
||||
@@ -16,15 +17,39 @@ const READY_STATE_CONNECTED = 2;
|
||||
const READY_STATE_CLOSING = 3;
|
||||
const READY_STATE_RECONNECTING = 4;
|
||||
|
||||
module.exports = class WebsocketClient extends EventEmitter {
|
||||
constructor(options, logger) {
|
||||
export interface WebsocketClientOptions {
|
||||
key?: string;
|
||||
secret?: string;
|
||||
livenet?: boolean;
|
||||
|
||||
pongTimeout?: number;
|
||||
pingInterval?: number;
|
||||
reconnectTimeout?: number;
|
||||
restOptions?: any;
|
||||
requestOptions?: any;
|
||||
wsUrl?: string;
|
||||
};
|
||||
|
||||
type Logger = typeof DefaultLogger;
|
||||
|
||||
export class WebsocketClient extends EventEmitter {
|
||||
private logger: Logger;
|
||||
private readyState: number;
|
||||
private pingInterval?: number | undefined;
|
||||
private pongTimeout?: number | undefined;
|
||||
private client: RestClient;
|
||||
private _subscriptions: Set<unknown>;
|
||||
private ws: WebSocket;
|
||||
private options: WebsocketClientOptions;
|
||||
|
||||
constructor(options: WebsocketClientOptions, logger?: Logger) {
|
||||
super();
|
||||
|
||||
this.logger = logger || defaultLogger;
|
||||
this.logger = logger || DefaultLogger;
|
||||
|
||||
this.readyState = READY_STATE_INITIAL;
|
||||
this.pingInterval = null;
|
||||
this.pongTimeout = null;
|
||||
this.pingInterval = undefined;
|
||||
this.pongTimeout = undefined;
|
||||
|
||||
this.options = {
|
||||
livenet: false,
|
||||
@@ -34,7 +59,7 @@ module.exports = class WebsocketClient extends EventEmitter {
|
||||
...options
|
||||
};
|
||||
|
||||
this.client = new RestClient(null, null, this.options.livenet, this.options.restOptions, this.options.requestOptions);
|
||||
this.client = new RestClient(undefined, undefined, this.options.livenet, this.options.restOptions, this.options.requestOptions);
|
||||
this._subscriptions = new Set();
|
||||
|
||||
this._connect();
|
||||
@@ -78,14 +103,17 @@ module.exports = class WebsocketClient extends EventEmitter {
|
||||
const authParams = await this._authenticate();
|
||||
const url = this._getWsUrl() + authParams;
|
||||
|
||||
this.ws = new WebSocket(url);
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
ws.onopen = this._wsOpenHandler.bind(this);
|
||||
ws.onmessage = this._wsMessageHandler.bind(this);
|
||||
ws.onerror = this._wsOnErrorHandler.bind(this);
|
||||
ws.onclose = this._wsCloseHandler.bind(this);
|
||||
|
||||
this.ws = ws;
|
||||
|
||||
this.ws.on('open', this._wsOpenHandler.bind(this));
|
||||
this.ws.on('message', this._wsMessageHandler.bind(this));
|
||||
this.ws.on('error', this._wsOnErrorHandler.bind(this));
|
||||
this.ws.on('close', this._wsCloseHandler.bind(this));
|
||||
} catch (err) {
|
||||
this.logger.error('Connection failed', err);
|
||||
this.logger.error('Connection failed: ', err);
|
||||
this._reconnect(this.options.reconnectTimeout);
|
||||
}
|
||||
}
|
||||
@@ -96,7 +124,7 @@ module.exports = class WebsocketClient extends EventEmitter {
|
||||
|
||||
const timeOffset = await this.client.getTimeOffset();
|
||||
|
||||
const params = {
|
||||
const params: any = {
|
||||
api_key: this.options.key,
|
||||
expires: (Date.now() + timeOffset + 5000)
|
||||
};
|
||||
@@ -127,8 +155,8 @@ module.exports = class WebsocketClient extends EventEmitter {
|
||||
}
|
||||
|
||||
_ping() {
|
||||
clearTimeout(this.pongTimeout);
|
||||
this.pongTimeout = null;
|
||||
clearTimeout(this.pongTimeout!);
|
||||
delete this.pongTimeout;
|
||||
|
||||
this.logger.silly('Sending ping', { category: 'bybit-ws' });
|
||||
this.ws.send(JSON.stringify({op: 'ping'}));
|
||||
@@ -136,7 +164,9 @@ module.exports = class WebsocketClient extends EventEmitter {
|
||||
this.pongTimeout = setTimeout(() => {
|
||||
this.logger.info('Pong timeout', { category: 'bybit-ws' });
|
||||
this._teardown();
|
||||
this.ws.terminate();
|
||||
// this.ws.terminate();
|
||||
// TODO: does this work?
|
||||
this.ws.close();
|
||||
}, this.options.pongTimeout);
|
||||
}
|
||||
|
||||
@@ -144,8 +174,8 @@ module.exports = class WebsocketClient extends EventEmitter {
|
||||
if (this.pingInterval) clearInterval(this.pingInterval);
|
||||
if (this.pongTimeout) clearTimeout(this.pongTimeout);
|
||||
|
||||
this.pongTimeout = null;
|
||||
this.pingInterval = null;
|
||||
this.pongTimeout = undefined;
|
||||
this.pingInterval = undefined;
|
||||
}
|
||||
|
||||
_wsOpenHandler() {
|
||||
@@ -164,7 +194,7 @@ module.exports = class WebsocketClient extends EventEmitter {
|
||||
}
|
||||
|
||||
_wsMessageHandler(message) {
|
||||
const msg = JSON.parse(message);
|
||||
const msg = JSON.parse(message && message.data || message);
|
||||
|
||||
if ('success' in msg) {
|
||||
this._handleResponse(msg);
|
||||
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"removeComments": false,
|
||||
"noEmitOnError": true,
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["es2017","dom"],
|
||||
"baseUrl": ".",
|
||||
"outDir": "lib",
|
||||
"paths": {
|
||||
"@src/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*",
|
||||
"coverage",
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
69
webpack/webpack.config.js
Normal file
69
webpack/webpack.config.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
||||
function generateConfig(name) {
|
||||
var config = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist'),
|
||||
filename: name + '.js',
|
||||
sourceMapFilename: name + '.map',
|
||||
library: 'bybitapi',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
devtool: "source-map",
|
||||
mode: 'production',
|
||||
|
||||
resolve: {
|
||||
// Add '.ts' and '.tsx' as resolvable extensions.
|
||||
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"],
|
||||
fallback: {
|
||||
"crypto": require.resolve("crypto-browserify"),
|
||||
"buffer": require.resolve("buffer/"),
|
||||
"stream": require.resolve("stream-browserify")
|
||||
}
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
|
||||
{ test: /\.tsx?$/, loader: "ts-loader" },
|
||||
|
||||
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
|
||||
{ test: /\.js$/, loader: "source-map-loader" },
|
||||
|
||||
{
|
||||
test: /\.m?js$/,
|
||||
exclude: /(node_modules|bower_components|samples|lib|test|coverage)/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [['@babel/preset-env', {
|
||||
'targets': {
|
||||
'node': 'current'
|
||||
}
|
||||
}]]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
config.plugins = [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
|
||||
}),
|
||||
new BundleAnalyzerPlugin({
|
||||
defaultSizes: 'stat',
|
||||
analyzerMode: 'static',
|
||||
reportFilename: '../doc/bundleReport.html',
|
||||
openAnalyzer: false,
|
||||
})
|
||||
];
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = generateConfig('bybitapi');
|
||||
Reference in New Issue
Block a user