diff --git a/README.md b/README.md index 8ba3f1e..a3063d7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [1]: https://www.npmjs.com/package/bitget-api -Node.js connector for the Bitget APIs and WebSockets: +Updated & performant JavaScript & Node.js SDK for the Bitget V2 REST APIs and WebSockets: - Complete integration with all Bitget APIs. - TypeScript support (with type declarations for most API requests & responses). @@ -45,7 +45,7 @@ Check out my related projects: Most methods pass values as-is into HTTP requests. These can be populated using parameters specified by Bitget's API documentation, or check the type definition in each class within this repository (see table below for convenient links to each class). -- [Bitget API Documentation](https://www.bitget.com/docs-v5/en/#rest-api). +- [Bitget API Documentation](https://www.bitget.com/api-doc/common/intro). ## Structure @@ -65,10 +65,12 @@ The version on npm is the output from the `build` command and can be used in pro Each REST API group has a dedicated REST client. To avoid confusion, here are the available REST clients and the corresponding API groups: | Class | Description | |:------------------------------------: |:---------------------------------------------------------------------------------------------: | -| [SpotClient](src/spot-client.ts) | [Spot APIs](https://bitgetlimited.github.io/apidoc/en/spot/#introduction) | -| [FuturesClient](src/futures-client.ts) | [Futures APIs](https://bitgetlimited.github.io/apidoc/en/mix/#introduction) | -| [BrokerClient](src/broker-client.ts) | [Broker APIs](https://bitgetlimited.github.io/apidoc/en/broker/#introduction) | -| [WebsocketClient](src/websocket-client.ts) | Universal client for all Bitget's Websockets | +| [RestClientV2](src/rest-client-v2.ts) | [V2 REST APIs](https://www.bitget.com/api-doc/common/intro) | +| [WebsocketClient](src/websocket-client-v2.ts) | Universal client for all Bitget's V2 Websockets | +| [~~SpotClient~~ (deprecated, use RestClientV2)](src/spot-client.ts) | [~~Spot APIs~~](https://bitgetlimited.github.io/apidoc/en/spot/#introduction) | +| [~~FuturesClient~~ (deprecated, use RestClientV2)](src/futures-client.ts) | [~~Futures APIs~~](https://bitgetlimited.github.io/apidoc/en/mix/#introduction) | +| [~~BrokerClient~~ (deprecated, use RestClientV2)](src/broker-client.ts) | [~~Broker APIs~~](https://bitgetlimited.github.io/apidoc/en/broker/#introduction) | +| [~~WebsocketClient~~ (deprecated, use WebsocketClientV2)](src/websocket-client.ts) | ~~Universal client for all Bitget's V1 Websockets~~ | Examples for using each client can be found in: @@ -81,54 +83,57 @@ If you're missing an example, you're welcome to request one. Priority will be gi First, create API credentials on Bitget's website. -All REST clients have can be used in a similar way. However, method names, parameters and responses may vary depending on the API category you're using! +All REST endpoints should be included in the [RestClientV2](src/rest-client-v2.ts) class. If any endpoints are missing or need improved types, pull requests are very welcome. You can also open an issue on this repo to request an improvement. Priority will be given to [github sponsors](https://github.com/sponsors/tiagosiebler). -Not sure which function to call or which parameters to use? Click the class name in the table above to look at all the function names (they are in the same order as the official API docs), and check the API docs for a list of endpoints/paramters/responses. +Not sure which function to call or which parameters to use? Click the class name in the table above to look at all the function names (they are in the same order as the official API docs), and check the API docs for a list of endpoints/parameters/responses. + +If you found the method you're looking for in the API docs, you can also search for the endpoint in the [RestClientV2](src/rest-client-v2.ts) class. ```javascript -const { - SpotClient, - FuturesClient, - BrokerClient, -} = require('bitget-api'); +const { RestClientV2 } = require('bitget-api'); const API_KEY = 'xxx'; const API_SECRET = 'yyy'; const API_PASS = 'zzz'; -const client = new SpotClient({ +const client = new RestClientV2( + { apiKey: API_KEY, apiSecret: API_SECRET, apiPass: API_PASS, -}, + }, // requestLibraryOptions ); // For public-only API calls, simply don't provide a key & secret or set them to undefined -// const client = new SpotClient(); +// const client = new RestClientV2(); - -client.getApiKeyInfo() - .then(result => { - console.log("getApiKeyInfo result: ", result); +client + .getSpotAccount() + .then((result) => { + console.log('getSpotAccount result: ', result); }) - .catch(err => { - console.error("getApiKeyInfo error: ", err); + .catch((err) => { + console.error('getSpotAccount error: ', err); }); -const symbol = 'BTCUSDT_SPBL'; -client.getCandles(symbol, '1min'); - .then(result => { - console.log("getCandles result: ", result); +client + .getSpotCandles({ + symbol: 'BTCUSDT', + granularity: '1min', + limit: '1000', }) - .catch(err => { - console.error("getCandles error: ", err); + .then((result) => { + console.log('getCandles result: ', result); + }) + .catch((err) => { + console.error('getCandles error: ', err); }); ``` #### WebSockets -For more examples, including how to use websockets with bitget, check the [examples](./examples/) and [test](./test/) folders. +For more examples, including how to use websockets with Bitget, check the [examples](./examples/) and [test](./test/) folders. --- @@ -141,16 +146,19 @@ Pass a custom logger which supports the log methods `silly`, `debug`, `notice`, ```javascript const { WebsocketClient, DefaultLogger } = require('bitget-api'); -// Disable all logging on the silly level -DefaultLogger.silly = () => {}; +// Disable all logging on the silly level (less console logs) +const customLogger = { + ...DefaultLogger, + silly: () => {}, +}; -const ws = new WebsocketClient( +const ws = new WebsocketClientV2( { apiKey: 'API_KEY', apiSecret: 'API_SECRET', apiPass: 'API_PASS', }, - DefaultLogger, + customLogger, ); ``` diff --git a/examples/deprecated-V1-REST/README.md b/examples/deprecated-V1-REST/README.md new file mode 100644 index 0000000..5a60185 --- /dev/null +++ b/examples/deprecated-V1-REST/README.md @@ -0,0 +1,8 @@ +# Deprecated V1 REST API Examples + +These examples are for Bitget's V1 APIs, the previous generation of their API offering. + +If you're building new functionality, you should look at using the V2 APIs via the RestClientV2 class in this SDK. This covers all the newer functionality offered by Bitget's APIs - with significant upgrades on all aspects of their APIs: +https://www.bitget.com/api-doc/common/release-note + +The V1 REST clients will remain function until Bitget formally deprecates their V1 API. diff --git a/examples/deprecated-V1-REST/rest-private-futures.ts b/examples/deprecated-V1-REST/rest-private-futures.ts new file mode 100644 index 0000000..b52717d --- /dev/null +++ b/examples/deprecated-V1-REST/rest-private-futures.ts @@ -0,0 +1,38 @@ +import { FuturesClient, WebsocketClient } from '../../src/index'; + +// or +// import { SpotClient } from 'bitget-api'; + +// read from environmental variables +const API_KEY = process.env.API_KEY_COM; +const API_SECRET = process.env.API_SECRET_COM; +const API_PASS = process.env.API_PASS_COM; + +const client = new FuturesClient({ + apiKey: API_KEY, + apiSecret: API_SECRET, + apiPass: API_PASS, + // apiKey: 'apiKeyHere', + // apiSecret: 'apiSecretHere', + // apiPass: 'apiPassHere', +}); + +/** This is a simple script wrapped in a immediately invoked function expression, designed to check for any available BTC balance and immediately sell the full amount for USDT */ +(async () => { + try { + const now = new Date(); + const toTime = new Date(now.getTime() - 24 * 60 * 60 * 1000); + + console.log( + await client.getAccountBill({ + symbol: 'BTCUSDT_UMCBL', + marginCoin: 'USDT', + startTime: now.getTime() + '', // should be sent as a string + endTime: toTime.getTime() + '', // should be sent as a string + pageSize: 100, + }), + ); + } catch (e) { + console.error('request failed: ', e); + } +})(); diff --git a/examples/deprecated-V1-REST/rest-private-spot.ts b/examples/deprecated-V1-REST/rest-private-spot.ts new file mode 100644 index 0000000..f897ada --- /dev/null +++ b/examples/deprecated-V1-REST/rest-private-spot.ts @@ -0,0 +1,27 @@ +import { SpotClient, WebsocketClient } from '../../src/index'; + +// or +// import { SpotClient } from 'bitget-api'; + +// read from environmental variables +const API_KEY = process.env.API_KEY_COM; +const API_SECRET = process.env.API_SECRET_COM; +const API_PASS = process.env.API_PASS_COM; + +const client = new SpotClient({ + apiKey: API_KEY, + apiSecret: API_SECRET, + apiPass: API_PASS, + // apiKey: 'apiKeyHere', + // apiSecret: 'apiSecretHere', + // apiPass: 'apiPassHere', +}); + +/** This is a simple script wrapped in a immediately invoked function expression, designed to check for any available BTC balance and immediately sell the full amount for USDT */ +(async () => { + try { + console.log(await client.getApiKeyInfo()); + } catch (e) { + console.error('request failed: ', e); + } +})(); diff --git a/examples/deprecated-V1-REST/rest-public-futures.ts b/examples/deprecated-V1-REST/rest-public-futures.ts new file mode 100644 index 0000000..423c6f9 --- /dev/null +++ b/examples/deprecated-V1-REST/rest-public-futures.ts @@ -0,0 +1,30 @@ +import { FuturesClient, WebsocketClient } from '../../src/index'; + +// or +// import { SpotClient } from 'bitget-api'; + +const futuresClient = new FuturesClient(); + +const symbol = 'BTCUSDT_UMCBL'; + +(async () => { + try { + // Fetch the last 1000 1min candles for a symbol + const timestampNow = Date.now(); + const msPerCandle = 60 * 1000; // 60 seconds x 1000 + const candlesToFetch = 1000; + const msFor1kCandles = candlesToFetch * msPerCandle; + const startTime = timestampNow - msFor1kCandles; + + const response = await futuresClient.getCandles( + symbol, + '1m', + startTime.toString(), + timestampNow.toString(), + candlesToFetch.toString(), + ); + console.log('getCandles returned ' + response.length + ' candles'); + } catch (e) { + console.error('request failed: ', e); + } +})(); diff --git a/examples/deprecated-V1-REST/rest-public-spot.ts b/examples/deprecated-V1-REST/rest-public-spot.ts new file mode 100644 index 0000000..2b83809 --- /dev/null +++ b/examples/deprecated-V1-REST/rest-public-spot.ts @@ -0,0 +1,19 @@ +import { SpotClient } from '../../src/index'; + +// or +// import { SpotClient } from 'bitget-api'; + +const spotClient = new SpotClient(); + +const symbol = 'BTCUSDT_SPBL'; + +(async () => { + try { + const response = await spotClient.getCandles(symbol, '1min', { + limit: '1000', + }); + console.log('getCandles: ', response.data.length); + } catch (e) { + console.error('request failed: ', e); + } +})(); diff --git a/examples/deprecated-V1-REST/rest-trade-futures.ts b/examples/deprecated-V1-REST/rest-trade-futures.ts new file mode 100644 index 0000000..bb6fb2c --- /dev/null +++ b/examples/deprecated-V1-REST/rest-trade-futures.ts @@ -0,0 +1,175 @@ +import { + FuturesClient, + isWsFuturesAccountSnapshotEvent, + isWsFuturesPositionsSnapshotEvent, + NewFuturesOrder, + WebsocketClient, +} from '../../src'; + +// or +// import { +// FuturesClient, +// isWsFuturesAccountSnapshotEvent, +// isWsFuturesPositionsSnapshotEvent, +// NewFuturesOrder, +// WebsocketClient, +// } from 'bitget-api'; + +// read from environmental variables +const API_KEY = process.env.API_KEY_COM; +const API_SECRET = process.env.API_SECRET_COM; +const API_PASS = process.env.API_PASS_COM; + +const client = new FuturesClient({ + apiKey: API_KEY, + apiSecret: API_SECRET, + apiPass: API_PASS, + // apiKey: 'apiKeyHere', + // apiSecret: 'apiSecretHere', + // apiPass: 'apiPassHere', +}); + +const wsClient = new WebsocketClient({ + apiKey: API_KEY, + apiSecret: API_SECRET, + apiPass: API_PASS, +}); + +function logWSEvent(type, data) { + console.log(new Date(), `WS ${type} event: `, data); +} + +// simple sleep function +function promiseSleep(milliseconds) { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); +} + +// WARNING: for sensitive math you should be using a library such as decimal.js! +function roundDown(value, decimals) { + return Number( + Math.floor(parseFloat(value + 'e' + decimals)) + 'e-' + decimals, + ); +} + +/** WS event handler that uses type guards to narrow down event type */ +async function handleWsUpdate(event) { + if (isWsFuturesAccountSnapshotEvent(event)) { + console.log(new Date(), 'ws update (account balance):', event); + return; + } + + if (isWsFuturesPositionsSnapshotEvent(event)) { + console.log(new Date(), 'ws update (positions):', event); + return; + } + + logWSEvent('update (unhandled)', event); +} + +/** + * This is a simple script wrapped in a immediately invoked function expression (to execute the below workflow immediately). + * + * It is designed to: + * - open a private websocket channel to log account events + * - check for any available USDT balance in the futures account + * - immediately open a minimum sized long position on BTCUSDT + * - check active positions + * - immediately send closing orders for any active futures positions + * - check positions again + * + * The corresponding UI for this is at https://www.bitget.com/en/mix/usdt/BTCUSDT_UMCBL + */ +(async () => { + try { + // Add event listeners to log websocket events on account + wsClient.on('update', (data) => handleWsUpdate(data)); + + wsClient.on('open', (data) => logWSEvent('open', data)); + wsClient.on('response', (data) => logWSEvent('response', data)); + wsClient.on('reconnect', (data) => logWSEvent('reconnect', data)); + wsClient.on('reconnected', (data) => logWSEvent('reconnected', data)); + wsClient.on('authenticated', (data) => logWSEvent('authenticated', data)); + wsClient.on('exception', (data) => logWSEvent('exception', data)); + + // Subscribe to private account topics + wsClient.subscribeTopic('UMCBL', 'account'); + // : position updates + wsClient.subscribeTopic('UMCBL', 'positions'); + // : order updates + wsClient.subscribeTopic('UMCBL', 'orders'); + + // wait briefly for ws to be ready (could also use the response or authenticated events, to make sure topics are subscribed to before starting) + await promiseSleep(2.5 * 1000); + + const symbol = 'BTCUSDT_UMCBL'; + const marginCoin = 'USDT'; + + const balanceResult = await client.getAccount(symbol, marginCoin); + const accountBalance = balanceResult.data; + // const balances = allBalances.filter((bal) => Number(bal.available) != 0); + const usdtAmount = accountBalance.available; + console.log('USDT balance: ', usdtAmount); + + if (!usdtAmount) { + console.error('No USDT to trade'); + return; + } + + const symbolRulesResult = await client.getSymbols('umcbl'); + const bitcoinUSDFuturesRule = symbolRulesResult.data.find( + (row) => row.symbol === symbol, + ); + + console.log('symbol rules: ', bitcoinUSDFuturesRule); + if (!bitcoinUSDFuturesRule) { + console.error('Failed to get trading rules for ' + symbol); + return; + } + + const order: NewFuturesOrder = { + marginCoin, + orderType: 'market', + side: 'open_long', + size: bitcoinUSDFuturesRule.minTradeNum, + symbol, + } as const; + + console.log('placing order: ', order); + + const result = await client.submitOrder(order); + + console.log('order result: ', result); + + const positionsResult = await client.getPositions('umcbl'); + const positionsToClose = positionsResult.data.filter( + (pos) => pos.total !== '0', + ); + + console.log('open positions to close: ', positionsToClose); + + // Loop through any active positions and send a closing market order on each position + for (const position of positionsToClose) { + const closingSide = + position.holdSide === 'long' ? 'close_long' : 'close_short'; + const closingOrder: NewFuturesOrder = { + marginCoin: position.marginCoin, + orderType: 'market', + side: closingSide, + size: position.available, + symbol: position.symbol, + }; + + console.log('closing position with market order: ', closingOrder); + + const result = await client.submitOrder(closingOrder); + console.log('position closing order result: ', result); + } + + console.log( + 'positions after closing all: ', + await client.getPositions('umcbl'), + ); + } catch (e) { + console.error('request failed: ', e); + } +})(); diff --git a/examples/deprecated-V1-REST/rest-trade-spot.ts b/examples/deprecated-V1-REST/rest-trade-spot.ts new file mode 100644 index 0000000..bfa171a --- /dev/null +++ b/examples/deprecated-V1-REST/rest-trade-spot.ts @@ -0,0 +1,104 @@ +import { SpotClient, WebsocketClient } from '../../src/index'; + +// or +// import { SpotClient } from 'bitget-api'; + +// read from environmental variables +const API_KEY = process.env.API_KEY_COM; +const API_SECRET = process.env.API_SECRET_COM; +const API_PASS = process.env.API_PASS_COM; + +const client = new SpotClient({ + apiKey: API_KEY, + apiSecret: API_SECRET, + apiPass: API_PASS, + // apiKey: 'apiKeyHere', + // apiSecret: 'apiSecretHere', + // apiPass: 'apiPassHere', +}); + +const wsClient = new WebsocketClient({ + apiKey: API_KEY, + apiSecret: API_SECRET, + apiPass: API_PASS, +}); + +function logWSEvent(type, data) { + console.log(new Date(), `WS ${type} event: `, data); +} + +// simple sleep function +function promiseSleep(milliseconds) { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); +} + +// WARNING: for sensitive math you should be using a library such as decimal.js! +function roundDown(value, decimals) { + return Number( + Math.floor(parseFloat(value + 'e' + decimals)) + 'e-' + decimals, + ); +} + +/** This is a simple script wrapped in a immediately invoked function expression, designed to check for any available BTC balance and immediately sell the full amount for USDT */ +(async () => { + try { + // Add event listeners to log websocket events on account + wsClient.on('update', (data) => logWSEvent('update', data)); + wsClient.on('open', (data) => logWSEvent('open', data)); + wsClient.on('response', (data) => logWSEvent('response', data)); + wsClient.on('reconnect', (data) => logWSEvent('reconnect', data)); + wsClient.on('reconnected', (data) => logWSEvent('reconnected', data)); + wsClient.on('authenticated', (data) => logWSEvent('authenticated', data)); + wsClient.on('exception', (data) => logWSEvent('exception', data)); + + // Subscribe to private account topics + wsClient.subscribeTopic('SPBL', 'account'); + wsClient.subscribeTopic('SPBL', 'orders'); + + // wait briefly for ws to be ready (could also use the response or authenticated events, to make sure topics are subscribed to before starting) + await promiseSleep(2.5 * 1000); + + const balanceResult = await client.getBalance(); + const allBalances = balanceResult.data; + // const balances = allBalances.filter((bal) => Number(bal.available) != 0); + const balanceBTC = allBalances.find((bal) => bal.coinName === 'BTC'); + const btcAmount = balanceBTC ? Number(balanceBTC.available) : 0; + // console.log('balance: ', JSON.stringify(balances, null, 2)); + console.log('BTC balance result: ', balanceBTC); + + if (!btcAmount) { + console.error('No BTC to trade'); + return; + } + + console.log(`BTC available: ${btcAmount}`); + const symbol = 'BTCUSDT_SPBL'; + + const symbolsResult = await client.getSymbols(); + const btcRules = symbolsResult.data.find((rule) => rule.symbol === symbol); + console.log('btc trading rules: ', btcRules); + if (!btcRules) { + return console.log('no rules found for trading ' + symbol); + } + + const quantityScale = Number(btcRules.quantityScale); + // const quantityRoundedDown = btcAmount - btcAmount % 0.01 + const quantity = roundDown(btcAmount, quantityScale); + + const order = { + symbol: symbol, + side: 'sell', + force: 'normal', + orderType: 'market', + quantity: String(quantity), + } as const; + + console.log('submitting order: ', order); + + const sellResult = await client.submitOrder(order); + + console.log('sell result: ', sellResult); + } catch (e) { + console.error('request failed: ', e); + } +})(); diff --git a/examples/deprecated-V1-Websockets/ws-private.ts b/examples/deprecated-V1-Websockets/ws-private.ts new file mode 100644 index 0000000..e5acc1a --- /dev/null +++ b/examples/deprecated-V1-Websockets/ws-private.ts @@ -0,0 +1,75 @@ +import { WebsocketClient, DefaultLogger } from '../../src'; + +// or +// import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from 'bitget-api'; + +(async () => { + const logger = { + ...DefaultLogger, + silly: (...params) => console.log('silly', ...params), + }; + + logger.info(`Starting private V1 websocket`); + + const API_KEY = process.env.API_KEY_COM; + const API_SECRET = process.env.API_SECRET_COM; + const API_PASS = process.env.API_PASS_COM; + + const wsClient = new WebsocketClient( + { + apiKey: API_KEY, + apiSecret: API_SECRET, + apiPass: API_PASS, + // restOptions: { + // optionally provide rest options, e.g. to pass through a proxy + // }, + }, + logger, + ); + + wsClient.on('update', (data) => { + console.log('WS raw message received ', data); + // console.log('WS raw message received ', JSON.stringify(data, null, 2)); + }); + + wsClient.on('open', (data) => { + console.log('WS connection opened:', data.wsKey); + }); + wsClient.on('response', (data) => { + console.log('WS response: ', JSON.stringify(data, null, 2)); + }); + wsClient.on('reconnect', ({ wsKey }) => { + console.log('WS automatically reconnecting.... ', wsKey); + }); + wsClient.on('reconnected', (data) => { + console.log('WS reconnected ', data?.wsKey); + }); + // auth happens async after the ws connection opens + wsClient.on('authenticated', (data) => { + console.log('WS authenticated', data); + // wsClient.subscribePublicSpotTickers(['BTCUSDT', 'LTCUSDT']); + }); + wsClient.on('exception', (data) => { + console.log('WS error', data); + }); + + /** + * Private account updates + */ + + // spot private + // : account updates + wsClient.subscribeTopic('SPBL', 'account'); + // : order updates + // wsClient.subscribeTopic('SPBL', 'orders'); + + // futures private + // : account updates + // wsClient.subscribeTopic('UMCBL', 'account'); + // // : position updates + // wsClient.subscribeTopic('UMCBL', 'positions'); + // // : order updates + // wsClient.subscribeTopic('UMCBL', 'orders'); + // // : plan order updates + // wsClient.subscribeTopic('UMCBL', 'ordersAlgo'); +})(); diff --git a/examples/deprecated-V1-Websockets/ws-public.ts b/examples/deprecated-V1-Websockets/ws-public.ts new file mode 100644 index 0000000..fb2af37 --- /dev/null +++ b/examples/deprecated-V1-Websockets/ws-public.ts @@ -0,0 +1,76 @@ +import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from '../../src'; + +// or +// import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from 'bitget-api'; + +(async () => { + const logger = { + ...DefaultLogger, + silly: (...params) => console.log('silly', ...params), + }; + + const wsClient = new WebsocketClient( + { + // restOptions: { + // optionally provide rest options, e.g. to pass through a proxy + // }, + }, + logger, + ); + + wsClient.on('update', (data) => { + console.log('WS raw message received ', data); + // console.log('WS raw message received ', JSON.stringify(data, null, 2)); + }); + + wsClient.on('open', (data) => { + console.log('WS connection opened:', data.wsKey); + }); + wsClient.on('response', (data) => { + console.log('WS response: ', JSON.stringify(data, null, 2)); + }); + wsClient.on('reconnect', ({ wsKey }) => { + console.log('WS automatically reconnecting.... ', wsKey); + }); + wsClient.on('reconnected', (data) => { + console.log('WS reconnected ', data?.wsKey); + }); + wsClient.on('exception', (data) => { + console.log('WS error', data); + }); + + /** + * Public events + */ + + const symbol = 'BTCUSDT'; + + // Spot public + // tickers + // wsClient.subscribeTopic('SP', 'ticker', symbol); + // // candles + // wsClient.subscribeTopic('SP', 'candle1m', symbol); + // // orderbook updates + wsClient.subscribeTopic('SP', 'books', symbol); + // // trades + // wsClient.subscribeTopic('SP', 'trade', symbol); + + // // Futures public + + // // tickers + // wsClient.subscribeTopic('MC', 'ticker', symbol); + // // candles + // wsClient.subscribeTopic('MC', 'candle1m', symbol); + // // orderbook updates + // wsClient.subscribeTopic('MC', 'books', symbol); + // // trades + // wsClient.subscribeTopic('MC', 'trade', symbol); + + // Topics are tracked per websocket type + // Get a list of subscribed topics (e.g. for spot topics) (after a 5 second delay) + setTimeout(() => { + const publicSpotTopics = wsClient.getWsStore().getTopics(WS_KEY_MAP.spotv1); + + console.log('public spot topics: ', publicSpotTopics); + }, 5 * 1000); +})(); diff --git a/examples/rest-private-futures.ts b/examples/rest-private-futures.ts index dcd1ac7..49a9b2f 100644 --- a/examples/rest-private-futures.ts +++ b/examples/rest-private-futures.ts @@ -1,14 +1,14 @@ -import { FuturesClient, WebsocketClient } from '../src/index'; +import { RestClientV2, WebsocketClient } from '../src/index'; // or -// import { SpotClient } from 'bitget-api'; +// import { RestClientV2 } from 'bitget-api'; // read from environmental variables const API_KEY = process.env.API_KEY_COM; const API_SECRET = process.env.API_SECRET_COM; const API_PASS = process.env.API_PASS_COM; -const client = new FuturesClient({ +const client = new RestClientV2({ apiKey: API_KEY, apiSecret: API_SECRET, apiPass: API_PASS, @@ -21,15 +21,14 @@ const client = new FuturesClient({ (async () => { try { const now = new Date(); - const toTime = new Date(now.getTime() - 24 * 60 * 60 * 1000); + const fromTime = new Date(now.getTime() - 24 * 60 * 60 * 1000); console.log( - await client.getAccountBill({ - symbol: 'BTCUSDT_UMCBL', - marginCoin: 'USDT', - startTime: now.getTime() + '', // should be sent as a string - endTime: toTime.getTime() + '', // should be sent as a string - pageSize: 100, + await client.getFuturesAccountBills({ + productType: 'USDT-FUTURES', + startTime: fromTime.getTime() + '', // should be sent as a string + endTime: now.getTime() + '', // should be sent as a string + limit: '100', }), ); } catch (e) { diff --git a/examples/rest-private-spot.ts b/examples/rest-private-spot.ts index e7da2e2..94f50db 100644 --- a/examples/rest-private-spot.ts +++ b/examples/rest-private-spot.ts @@ -1,14 +1,14 @@ -import { SpotClient, WebsocketClient } from '../src/index'; +import { RestClientV2, WebsocketClient } from '../src/index'; // or -// import { SpotClient } from 'bitget-api'; +// import { RestClientV2 } from 'bitget-api'; // read from environmental variables const API_KEY = process.env.API_KEY_COM; const API_SECRET = process.env.API_SECRET_COM; const API_PASS = process.env.API_PASS_COM; -const client = new SpotClient({ +const client = new RestClientV2({ apiKey: API_KEY, apiSecret: API_SECRET, apiPass: API_PASS, @@ -20,7 +20,7 @@ const client = new SpotClient({ /** This is a simple script wrapped in a immediately invoked function expression, designed to check for any available BTC balance and immediately sell the full amount for USDT */ (async () => { try { - console.log(await client.getApiKeyInfo()); + console.log(await client.getSpotAccount()); } catch (e) { console.error('request failed: ', e); } diff --git a/examples/rest-public-futures.ts b/examples/rest-public-futures.ts index 224bb7c..d9ef39b 100644 --- a/examples/rest-public-futures.ts +++ b/examples/rest-public-futures.ts @@ -1,11 +1,11 @@ -import { FuturesClient, SpotClient } from '../src/index'; +import { RestClientV2, SpotClient } from '../src/index'; // or // import { SpotClient } from 'bitget-api'; -const futuresClient = new FuturesClient(); +const restClient = new RestClientV2(); -const symbol = 'BTCUSDT_UMCBL'; +const symbol = 'BTCUSDT'; (async () => { try { @@ -16,14 +16,18 @@ const symbol = 'BTCUSDT_UMCBL'; const msFor1kCandles = candlesToFetch * msPerCandle; const startTime = timestampNow - msFor1kCandles; - const response = await futuresClient.getCandles( + const response = await restClient.getFuturesCandles({ symbol, - '1m', - startTime.toString(), - timestampNow.toString(), - candlesToFetch.toString(), - ); - console.log('getCandles returned ' + response.length + ' candles'); + productType: 'USDT-FUTURES', + granularity: '1m', + startTime: startTime.toString(), + endTime: timestampNow.toString(), + limit: candlesToFetch.toString(), + }); + + console.table(response.data); + + console.log('getCandles returned ' + response.data.length + ' candles'); } catch (e) { console.error('request failed: ', e); } diff --git a/examples/rest-public-spot.ts b/examples/rest-public-spot.ts index 02083ce..5e282eb 100644 --- a/examples/rest-public-spot.ts +++ b/examples/rest-public-spot.ts @@ -1,17 +1,19 @@ -import { SpotClient } from '../src/index'; +import { RestClientV2 } from '../src/index'; // or -// import { SpotClient } from 'bitget-api'; +// import { RestClientV2 } from 'bitget-api'; -const spotClient = new SpotClient(); - -const symbol = 'BTCUSDT_SPBL'; +const restClient = new RestClientV2(); (async () => { try { - const response = await spotClient.getCandles(symbol, '1min', { + const response = await restClient.getSpotCandles({ + symbol: 'BTCUSDT', + granularity: '1min', limit: '1000', }); + + console.table(response.data); console.log('getCandles: ', response.data.length); } catch (e) { console.error('request failed: ', e); diff --git a/examples/ws-private.ts b/examples/ws-private.ts index fa74781..f9975cf 100644 --- a/examples/ws-private.ts +++ b/examples/ws-private.ts @@ -1,7 +1,7 @@ -import { WebsocketClient, DefaultLogger } from '../src'; +import { WebsocketClientV2, DefaultLogger } from '../src'; // or -// import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from 'bitget-api'; +// import { DefaultLogger, WS_KEY_MAP, WebsocketClientV2 } from 'bitget-api'; (async () => { const logger = { @@ -13,7 +13,7 @@ import { WebsocketClient, DefaultLogger } from '../src'; const API_SECRET = process.env.API_SECRET_COM; const API_PASS = process.env.API_PASS_COM; - const wsClient = new WebsocketClient( + const wsClient = new WebsocketClientV2( { apiKey: API_KEY, apiSecret: API_SECRET, @@ -57,17 +57,21 @@ import { WebsocketClient, DefaultLogger } from '../src'; // spot private // : account updates - // wsClient.subscribeTopic('SPBL', 'account'); - // : order updates - // wsClient.subscribeTopic('SPBL', 'orders'); + // wsClient.subscribeTopic('SPOT', 'account'); + + // : order updates (note: symbol is required) + // wsClient.subscribeTopic('SPOT', 'orders', 'BTCUSDT'); // futures private // : account updates - // wsClient.subscribeTopic('UMCBL', 'account'); - // // : position updates - // wsClient.subscribeTopic('UMCBL', 'positions'); - // // : order updates - // wsClient.subscribeTopic('UMCBL', 'orders'); - // // : plan order updates - // wsClient.subscribeTopic('UMCBL', 'ordersAlgo'); + // wsClient.subscribeTopic('USDT-FUTURES', 'account'); + + // : position updates + // wsClient.subscribeTopic('USDT-FUTURES', 'positions'); + + // : order updates + // wsClient.subscribeTopic('USDT-FUTURES', 'orders'); + + // : plan order updates + wsClient.subscribeTopic('USDT-FUTURES', 'orders-algo'); })(); diff --git a/examples/ws-public.ts b/examples/ws-public.ts index 52a8701..19dc48e 100644 --- a/examples/ws-public.ts +++ b/examples/ws-public.ts @@ -1,7 +1,7 @@ -import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from '../src'; +import { DefaultLogger, WS_KEY_MAP, WebsocketClientV2 } from '../src'; // or -// import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from 'bitget-api'; +// import { DefaultLogger, WS_KEY_MAP, WebsocketClientV2 } from 'bitget-api'; (async () => { const logger = { @@ -9,7 +9,7 @@ import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from '../src'; silly: (...params) => console.log('silly', ...params), }; - const wsClient = new WebsocketClient( + const wsClient = new WebsocketClientV2( { // restOptions: { // optionally provide rest options, e.g. to pass through a proxy @@ -45,32 +45,39 @@ import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from '../src'; const symbol = 'BTCUSDT'; - // // Spot public - // // tickers - // wsClient.subscribeTopic('SP', 'ticker', symbol); - // // candles - // wsClient.subscribeTopic('SP', 'candle1m', symbol); - // // orderbook updates - wsClient.subscribeTopic('SP', 'books', symbol); - // // trades - // wsClient.subscribeTopic('SP', 'trade', symbol); + // Spot public - // // Futures public + // tickers + wsClient.subscribeTopic('SPOT', 'ticker', symbol); - // // tickers - // wsClient.subscribeTopic('MC', 'ticker', symbol); - // // candles - // wsClient.subscribeTopic('MC', 'candle1m', symbol); - // // orderbook updates - // wsClient.subscribeTopic('MC', 'books', symbol); - // // trades - // wsClient.subscribeTopic('MC', 'trade', symbol); + // candles + // wsClient.subscribeTopic('SPOT', 'candle1m', symbol); + + // orderbook updates + // wsClient.subscribeTopic('SPOT', 'books', symbol); + + // trades + // wsClient.subscribeTopic('SPOT', 'trade', symbol); + + // Futures public + + // tickers + // wsClient.subscribeTopic('USDT-FUTURES', 'ticker', symbol); + + // candles + // wsClient.subscribeTopic('USDT-FUTURES', 'candle1m', symbol); + + // orderbook updates + // wsClient.subscribeTopic('USDT-FUTURES', 'books', symbol); + + // trades + // wsClient.subscribeTopic('USDT-FUTURES', 'trade', symbol); // Topics are tracked per websocket type - // Get a list of subscribed topics (e.g. for spot topics) (after a 5 second delay) + // Get a list of subscribed topics (e.g. all public topics) (after a 5 second delay) setTimeout(() => { - const publicSpotTopics = wsClient.getWsStore().getTopics(WS_KEY_MAP.spotv1); + const publicTopics = wsClient.getWsStore().getTopics(WS_KEY_MAP.v2Public); - console.log('public spot topics: ', publicSpotTopics); + console.log('public topics: ', publicTopics); }, 5 * 1000); })(); diff --git a/jest.config.js b/jest.config.js index 32b968b..95f997f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,7 @@ module.exports = { __PROD__: false }, testEnvironment: 'node', - preset: "ts-jest", + preset: 'ts-jest', verbose: true, // report individual test bail: false, // enable to stop test when an error occur, detectOpenHandles: false, @@ -16,6 +16,7 @@ module.exports = { collectCoverageFrom: [ 'src/**/*.ts' ], + testTimeout: 10000, coverageThreshold: { // coverage strategy global: { @@ -25,4 +26,4 @@ module.exports = { statements: -10 } } -}; \ No newline at end of file +}; diff --git a/package-lock.json b/package-lock.json index 41daefd..6bdb8f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "bitget-api", - "version": "1.1.2", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bitget-api", - "version": "1.1.2", + "version": "2.0.0", "license": "MIT", "dependencies": { - "axios": "^0.27.2", + "axios": "^1.6.1", "isomorphic-ws": "^5.0.0", "ws": "^8.9.0" }, @@ -45,17 +45,89 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.19.3", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.3.tgz", @@ -96,13 +168,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.3.tgz", - "integrity": "sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dev": true, "dependencies": { - "@babel/types": "^7.19.3", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -142,34 +215,34 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -228,30 +301,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -281,13 +354,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -366,9 +439,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz", - "integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -555,33 +628,33 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.3.tgz", - "integrity": "sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.3", - "@babel/types": "^7.19.3", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -599,13 +672,13 @@ } }, "node_modules/@babel/types": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz", - "integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1112,9 +1185,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -1122,9 +1195,9 @@ } }, "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", @@ -1142,13 +1215,13 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@nodelib/fs.scandir": { @@ -1278,9 +1351,9 @@ } }, "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/graceful-fs": { @@ -1366,148 +1439,148 @@ "dev": true }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dev": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" } }, @@ -1566,9 +1639,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1578,9 +1651,9 @@ } }, "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -1714,12 +1787,13 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", + "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/babel-jest": { @@ -2206,9 +2280,9 @@ "dev": true }, "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -2240,9 +2314,9 @@ } }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, "node_modules/escalade": { @@ -3598,9 +3672,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4330,6 +4404,11 @@ "node": ">= 6" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -4524,9 +4603,9 @@ "dev": true }, "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -4542,18 +4621,18 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -4835,13 +4914,13 @@ } }, "node_modules/terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", "dev": true, "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -4853,16 +4932,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" }, "engines": { "node": ">= 10.13.0" @@ -5025,9 +5104,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5059,9 +5138,9 @@ } }, "node_modules/ts-loader/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5191,22 +5270,22 @@ } }, "node_modules/webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", + "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -5215,9 +5294,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", + "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -5412,9 +5491,9 @@ "dev": true }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5543,12 +5622,71 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/compat-data": { @@ -5581,13 +5719,14 @@ } }, "@babel/generator": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.3.tgz", - "integrity": "sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dev": true, "requires": { - "@babel/types": "^7.19.3", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "dependencies": { @@ -5617,28 +5756,28 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { @@ -5682,24 +5821,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -5720,13 +5859,13 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -5789,9 +5928,9 @@ } }, "@babel/parser": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz", - "integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -5921,30 +6060,30 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.3.tgz", - "integrity": "sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.3", - "@babel/types": "^7.19.3", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -5958,13 +6097,13 @@ } }, "@babel/types": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz", - "integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -6353,9 +6492,9 @@ "dev": true }, "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", @@ -6363,9 +6502,9 @@ }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "requires": { "@jridgewell/set-array": "^1.0.1", @@ -6382,13 +6521,13 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@nodelib/fs.scandir": { @@ -6509,9 +6648,9 @@ } }, "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "@types/graceful-fs": { @@ -6597,148 +6736,148 @@ "dev": true }, "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dev": true, "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", "dev": true }, "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" } }, @@ -6784,15 +6923,15 @@ "dev": true }, "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true }, "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true, "requires": {} }, @@ -6888,12 +7027,13 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", + "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "babel-jest": { @@ -7261,9 +7401,9 @@ "dev": true }, "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -7286,9 +7426,9 @@ } }, "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, "escalade": { @@ -8290,9 +8430,9 @@ }, "dependencies": { "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -8840,6 +8980,11 @@ "sisteransi": "^1.0.5" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -8965,9 +9110,9 @@ "dev": true }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "requires": { "@types/json-schema": "^7.0.8", @@ -8976,15 +9121,15 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -9189,13 +9334,13 @@ } }, "terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", "dev": true, "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -9213,16 +9358,16 @@ } }, "terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" }, "dependencies": { "jest-worker": { @@ -9308,9 +9453,9 @@ }, "dependencies": { "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -9331,9 +9476,9 @@ }, "dependencies": { "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -9418,22 +9563,22 @@ } }, "webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", + "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -9442,9 +9587,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", + "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -9559,9 +9704,9 @@ "dev": true }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wrap-ansi": { diff --git a/package.json b/package.json index f5b3cbb..ee04dfc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bitget-api", - "version": "1.1.4", - "description": "Node.js connector for Bitget REST APIs and WebSockets, with TypeScript & end-to-end tests.", + "version": "2.0.0", + "description": "Node.js & JavaScript SDK for Bitget REST APIs & WebSockets, with TypeScript & end-to-end tests.", "main": "lib/index.js", "types": "lib/index.d.ts", "files": [ @@ -22,7 +22,7 @@ "author": "Tiago Siebler (https://github.com/tiagosiebler)", "contributors": [], "dependencies": { - "axios": "^0.27.2", + "axios": "^1.6.1", "isomorphic-ws": "^5.0.0", "ws": "^8.9.0" }, diff --git a/src/broker-client.ts b/src/broker-client.ts index d96816b..bad3bdc 100644 --- a/src/broker-client.ts +++ b/src/broker-client.ts @@ -9,7 +9,10 @@ import { REST_CLIENT_TYPE_ENUM } from './util'; import BaseRestClient from './util/BaseRestClient'; /** - * REST API client for broker APIs + * REST API client for the V1 bitget Broker APIs. These are the previous generation of Bitget's APIs and should be considered deprecated. + * These will be removed in a future release, once Bitget formally deprecates them. + * + * @deprecated use RestClientV2 instead */ export class BrokerClient extends BaseRestClient { getClientType() { diff --git a/src/futures-client.ts b/src/futures-client.ts index f1a9a84..d6d82b4 100644 --- a/src/futures-client.ts +++ b/src/futures-client.ts @@ -31,7 +31,10 @@ import { REST_CLIENT_TYPE_ENUM } from './util'; import BaseRestClient from './util/BaseRestClient'; /** - * REST API client + * REST API client for the V1 bitget Futures APIs. These are the previous generation of Bitget's APIs and should be considered deprecated. + * These will be removed in a future release, once Bitget formally deprecates them. + * + * @deprecated use RestClientV2 instead */ export class FuturesClient extends BaseRestClient { getClientType() { diff --git a/src/index.ts b/src/index.ts index 73d9820..771b2dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,9 @@ +export * from './rest-client-v2'; export * from './broker-client'; export * from './futures-client'; export * from './spot-client'; export * from './websocket-client'; +export * from './websocket-client-v2'; export * from './util/logger'; export * from './util'; export * from './types'; diff --git a/src/rest-client-v2.ts b/src/rest-client-v2.ts new file mode 100644 index 0000000..0d3dc6d --- /dev/null +++ b/src/rest-client-v2.ts @@ -0,0 +1,876 @@ +import { + APIResponse, + MarginType, + FuturesAccountBillRequestV2, + FuturesCandlesRequestV2, + SpotCandlesRequestV2, +} from './types'; +import { REST_CLIENT_TYPE_ENUM, assertMarginType } from './util'; +import BaseRestClient from './util/BaseRestClient'; + +/** + * REST API client for all V2 endpoints + */ +export class RestClientV2 extends BaseRestClient { + getClientType() { + return REST_CLIENT_TYPE_ENUM.v2; + } + + async fetchServerTime(): Promise { + const res = await this.getServerTime(); + return Number(res.data); + } + + /** + * + * + * Common + * + * + */ + + /** + * + * * Common | Notice + * + */ + + getAnnouncements(): Promise> { + return this.get(`/api/v2/public/annoucements`); + } + + /** + * + * * Common | Public + * + */ + + getServerTime(): Promise> { + return this.get(`/api/v2/public/time`); + } + + getTradeRate(params: object): Promise> { + return this.getPrivate(`/api/v2/common/trade-rate`, params); + } + + /** + * + * * Common | Tax + * + */ + + getSpotTransactionRecords(params: object): Promise> { + return this.getPrivate(`/api/v2/tax/spot-record`, params); + } + + getFuturesTransactionRecords(params: object): Promise> { + return this.getPrivate(`/api/v2/tax/future-record`, params); + } + + getMarginTransactionRecords(params: object): Promise> { + return this.getPrivate(`/api/v2/tax/margin-record`, params); + } + + getP2PTransactionRecords(params: object): Promise> { + return this.getPrivate(`/api/v2/tax/p2p-record`, params); + } + + /** + * + * * Common | P2P + * + */ + + getP2PMerchantList(params?: object): Promise> { + return this.getPrivate(`/api/v2/p2p/merchantList`, params); + } + + getP2PMerchantInfo(): Promise> { + return this.getPrivate(`/api/v2/p2p/merchantInfo`); + } + + getP2PMerchantOrders(params: object): Promise> { + return this.getPrivate(`/api/v2/p2p/orderList`, params); + } + + getP2PMerchantAdvertisementList(params: object): Promise> { + return this.getPrivate(`/api/v2/p2p/advList`, params); + } + + /** + * + * * Common | Virtual Subaccount + * + */ + + createVirtualSubaccount(params: object): Promise> { + return this.postPrivate(`/api/v2/user/create-virtual-subaccount`, params); + } + + modifyVirtualSubaccount(params: object): Promise> { + return this.postPrivate(`/api/v2/user/modify-virtual-subaccount`, params); + } + + batchCreateVirtualSubaccountAndAPIKey( + params: object, + ): Promise> { + return this.postPrivate( + '/api/v2/user/batch-create-subaccount-and-apikey', + params, + ); + } + + getVirtualSubaccounts(params?: object): Promise> { + return this.getPrivate(`/api/v2/user/virtual-subaccount-list`, params); + } + + createVirtualSubaccountAPIKey(params: object): Promise> { + return this.postPrivate( + '/api/v2/user/create-virtual-subaccount-apikey', + params, + ); + } + + modifyVirtualSubaccountAPIKey(params: object): Promise> { + return this.postPrivate( + '/api/v2/user/modify-virtual-subaccount-apikey', + params, + ); + } + + getVirtualSubaccountAPIKeys(params: object): Promise> { + return this.getPrivate( + '/api/v2/user/virtual-subaccount-apikey-list', + params, + ); + } + + /** + * + * * Common | Convert + * + */ + + getConvertCoins(): Promise> { + return this.getPrivate(`/api/v2/convert/currencies`); + } + + getConvertQuotedPrice(params: object): Promise> { + return this.getPrivate(`/api/v2/convert/quoted-price`, params); + } + + convert(params: object): Promise> { + return this.postPrivate(`/api/v2/convert/trade`, params); + } + + getConvertHistory(params: object): Promise> { + return this.getPrivate(`/api/v2/convert/convert-record`, params); + } + + /** + * + * + * Spot + * + * + */ + + /** + * + * * Spot | Market + * + */ + + getSpotCoinInfo(params?: object): Promise> { + return this.getPrivate(`/api/v2/spot/public/coins`, params); + } + + getSpotSymbolInfo(params?: object): Promise> { + return this.getPrivate(`/api/v2/spot/public/symbols`, params); + } + + getSpotVIPFeeRate(): Promise> { + return this.getPrivate(`/api/v2/spot/market/vip-fee-rate`); + } + + getSpotTicker(params?: object): Promise> { + return this.getPrivate(`/api/v2/spot/market/tickers`, params); + } + + getSpotMergeDepth(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/market/merge-depth`, params); + } + + getSpotOrderBookDepth(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/market/orderbook`, params); + } + + getSpotCandles(params: SpotCandlesRequestV2): Promise> { + return this.getPrivate(`/api/v2/spot/market/candles`, params); + } + + getSpotHistoricCandles(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/market/history-candles`, params); + } + + getSpotRecentTrades(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/market/fills`, params); + } + + getSpotHistoricTrades(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/market/fills-history`, params); + } + + /** + * + * * Spot | Trade + * + */ + + spotSubmitOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/trade/place-order`, params); + } + + spotCancelOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/trade/cancel-order`, params); + } + + spotBatchSubmitOrders(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/trade/batch-orders`, params); + } + + spotBatchCancelOrders(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/trade/batch-cancel-order`, params); + } + + spotCancelSymbolOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/trade/cancel-symbol-order`, params); + } + + getSpotOrder(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/trade/orderInfo`, params); + } + + getSpotOpenOrders(params?: object): Promise> { + return this.getPrivate(`/api/v2/spot/trade/unfilled-orders`, params); + } + + getSpotHistoricOrders(params?: object): Promise> { + return this.getPrivate(`/api/v2/spot/trade/history-orders`, params); + } + + getSpotFills(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/trade/fills`, params); + } + + /** + * + * * Spot | Trigger Orders + * + */ + + spotSubmitPlanOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/trade/place-plan-order`, params); + } + + spotModifyPlanOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/trade/modify-plan-order`, params); + } + + spotCancelPlanOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/trade/cancel-plan-order`, params); + } + + getSpotCurrentPlanOrders(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/trade/current-plan-order`, params); + } + + getSpotHistoricPlanOrders(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/trade/history-plan-order`, params); + } + + spotCancelPlanOrders(params?: object): Promise> { + return this.postPrivate( + '/api/v2/spot/trade/batch-cancel-plan-order', + params, + ); + } + + /** + * + * * Spot | Account + * + */ + + getSpotAccount(): Promise> { + return this.getPrivate(`/api/v2/spot/account/info`); + } + + getSpotAccountAssets(params?: object): Promise> { + return this.getPrivate(`/api/v2/spot/account/assets`, params); + } + + getSpotSubAccountAssets(): Promise> { + return this.getPrivate(`/api/v2/spot/account/subaccount-assets`); + } + + getSpotAccountBills(params?: object): Promise> { + return this.getPrivate(`/api/v2/spot/account/bills`, params); + } + + spotTransfer(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/wallet/transfer`, params); + } + + spotSubTransfer(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/wallet/subaccount-transfer`, params); + } + + getSpotTransferHistory(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/account/transferRecords`, params); + } + + spotWithdraw(params: object): Promise> { + return this.postPrivate(`/api/v2/spot/wallet/withdrawal`, params); + } + + getSpotDepositAddress(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/wallet/deposit-address`, params); + } + + getSpotDepositHistory(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/wallet/deposit-records`, params); + } + + getSpotWithdrawalHistory(params: object): Promise> { + return this.getPrivate(`/api/v2/spot/wallet/withdrawal-records`, params); + } + + /** + * + * + * Futures + * + * + */ + + /** + * + * * Futures | Market + * + */ + + getFuturesVIPFeeRate(): Promise> { + return this.get(`/api/v2/mix/market/vip-fee-rate`); + } + + getFuturesTicker(params: object): Promise> { + return this.get(`/api/v2/mix/market/ticker`, params); + } + + getFuturesAllTickers(params: object): Promise> { + return this.get(`/api/v2/mix/market/tickers`, params); + } + + getFuturesMergeDepth(params: object): Promise> { + return this.get(`/api/v2/mix/market/merge-depth`, params); + } + + getFuturesCandles( + params: FuturesCandlesRequestV2, + ): Promise> { + return this.get(`/api/v2/mix/market/candles`, params); + } + + getFuturesHistoricCandles(params: object): Promise> { + return this.get(`/api/v2/mix/market/history-candles`, params); + } + + getFuturesHistoricIndexPriceCandles( + params: object, + ): Promise> { + return this.get(`/api/v2/mix/market/history-index-candles`, params); + } + + getFuturesHistoricMarkPriceCandles( + params: object, + ): Promise> { + return this.get(`/api/v2/mix/market/history-mark-candles`, params); + } + + getFuturesRecentTrades(params: object): Promise> { + return this.get(`/api/v2/mix/market/fills`, params); + } + + getFuturesHistoricTrades(params: object): Promise> { + return this.get(`/api/v2/mix/market/fills-history`, params); + } + + getFuturesOpenInterest(params: object): Promise> { + return this.get(`/api/v2/mix/market/open-interest`, params); + } + + getFuturesNextFundingTime(params: object): Promise> { + return this.get(`/api/v2/mix/market/funding-time`, params); + } + + getFuturesSymbolPrice(params: object): Promise> { + return this.get(`/api/v2/mix/market/symbol-price`, params); + } + + getFuturesHistoricFundingRates(params: object): Promise> { + return this.get(`/api/v2/mix/market/history-fund-rate`, params); + } + + getFuturesCurrentFundingRate(params: object): Promise> { + return this.get(`/api/v2/mix/market/current-fund-rate`, params); + } + + getFuturesContractConfig(params: object): Promise> { + return this.get(`/api/v2/mix/market/contracts`, params); + } + + /** + * + * * Futures | Account + * + */ + + getFuturesAccountAsset(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/account/account`, params); + } + + getFuturesAccountAssets(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/account/accounts`, params); + } + + getFuturesSubAccountAssets(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/account/sub-account-assets`, params); + } + + getFuturesOpenCount(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/account/open-count`, params); + } + + setFuturesLeverage(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/account/set-leverage`, params); + } + + setFuturesPositionMargin(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/account/set-margin`, params); + } + + setFuturesMarginMode(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/account/set-margin-mode`, params); + } + + setFuturesPositionMode(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/account/set-position-mode`, params); + } + + getFuturesAccountBills( + params: FuturesAccountBillRequestV2, + ): Promise> { + return this.getPrivate(`/api/v2/mix/account/bill`, params); + } + + /** + * + * * Futures | Position + * + */ + + getFuturesPositionTier(params: object): Promise> { + return this.get(`/api/v2/mix/market/query-position-lever`, params); + } + + getFuturesPosition(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/position/single-position`, params); + } + + getFuturesPositions(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/position/all-position`, params); + } + + getFuturesHistoricPositions(params?: object): Promise> { + return this.getPrivate(`/api/v2/mix/position/history-position`, params); + } + + /** + * + * * Futures | Trade + * + */ + + futuresSubmitOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/place-order`, params); + } + + futuresCancelOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/cancel-order`, params); + } + + futuresSubmitReversal(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/click-backhand`, params); + } + + futuresBatchSubmitOrders(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/batch-place-order`, params); + } + + futuresModifyOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/modify-order`, params); + } + + futuresBatchCancelOrders(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/batch-cancel-orders`, params); + } + + futuresFlashClosePositions(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/close-positions`, params); + } + + getFuturesOrder(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/order/detail`, params); + } + + getFuturesFills(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/order/fills`, params); + } + + getFuturesHistoricOrderFills(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/order/fill-history`, params); + } + + getFuturesOpenOrders(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/order/orders-pending`, params); + } + + getFuturesHistoricOrders(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/order/orders-history`, params); + } + + /** + * + * * Futures | Trigger Orders + * + */ + + futuresSubmitTPSLOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/place-tpsl-order`, params); + } + + futuresSubmitPlanOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/place-plan-order`, params); + } + + futuresModifyTPSLPOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/modify-tpsl-order`, params); + } + + futuresModifyPlanOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/modify-plan-order`, params); + } + + futuresCancelPlanOrder(params: object): Promise> { + return this.postPrivate(`/api/v2/mix/order/cancel-plan-order`, params); + } + + getFuturesPlanOrders(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/order/orders-plan-pending`, params); + } + + getFuturesHistoricPlanOrders(params: object): Promise> { + return this.getPrivate(`/api/v2/mix/order/orders-plan-history`, params); + } + + /** + * + * + * Broker + * + * + */ + + // TODO: not yet implemented + + /** + * + * + * Margin + * + * + */ + + /** + * + * * Margin | Common + * + */ + + getMarginCurrencies(): Promise> { + return this.get(`/api/v2/margin/currencies`); + } + + /** + * + * * Margin | Cross/Isolated | Order Record + * + */ + + getMarginBorrowHistory( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + `/api/v2/margin/${marginType}/borrow-history`, + params, + ); + } + + getMarginRepayHistory( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + `/api/v2/margin/${marginType}/repay-history`, + params, + ); + } + + getMarginInterestHistory( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + `/api/v2/margin/${marginType}/interest-history`, + params, + ); + } + + getMarginLiquidationHistory( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + `/api/v2/margin/${marginType}/liquidation-history`, + params, + ); + } + + getMarginFinancialHistory( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + `/api/v2/margin/${marginType}/financial-records`, + params, + ); + } + + /** + * + * * Margin | Cross/Isolated | Account + * + */ + + getMarginAccountAssets( + marginType: MarginType, + params?: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + `/api/v2/margin/${marginType}/account/assets`, + params, + ); + } + + marginBorrow( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.postPrivate( + `/api/v2/margin/${marginType}/account/borrow`, + params, + ); + } + + marginRepay( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.postPrivate( + `/api/v2/margin/${marginType}/account/repay`, + params, + ); + } + + getMarginRiskRate(marginType: MarginType): Promise> { + assertMarginType(marginType); + return this.getPrivate(`/api/v2/margin/${marginType}/account/risk-rate`); + } + + getMarginMaxBorrowable( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + '/api/v2/margin/${marginType}/account/max-borrowable-amount', + params, + ); + } + + getMarginMaxTransferable( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + '/api/v2/margin/${marginType}/account/max-transfer-out-amount', + params, + ); + } + + getMarginInterestRateAndMaxBorrowable( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + '/api/v2/margin/${marginType}/interest-rate-and-limit', + params, + ); + } + + getMarginTierConfiguration( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate(`/api/v2/margin/${marginType}/tier-data`, params); + } + + marginFlashRepay( + marginType: MarginType, + params?: object, + ): Promise> { + assertMarginType(marginType); + return this.postPrivate( + '/api/v2/margin/${marginType}/account/flash-repay', + params, + ); + } + + getMarginFlashRepayResult( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + '/api/v2/margin/${marginType}/account/query-flash-repay-status', + params, + ); + } + + /** + * + * * Margin | Cross/Isolated | Trade + * + */ + + marginSubmitOrder( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.postPrivate(`/api/v2/margin/${marginType}/place-order`, params); + } + + marginBatchSubmitOrders( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.postPrivate( + `/api/v2/margin/${marginType}/batch-place-order`, + params, + ); + } + + marginCancelOrder( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.postPrivate( + `/api/v2/margin/${marginType}/cancel-order`, + params, + ); + } + + marginBatchCancelOrders( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.postPrivate( + '/api/v2/margin/${marginType}/batch-cancel-order', + params, + ); + } + + getMarginOpenOrders( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate(`/api/v2/margin/${marginType}/open-orders`, params); + } + + getMarginHistoricOrders( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate( + `/api/v2/margin/${marginType}/history-orders`, + params, + ); + } + + getMarginHistoricOrderFills( + marginType: MarginType, + params: object, + ): Promise> { + assertMarginType(marginType); + return this.getPrivate(`/api/v2/margin/${marginType}/fills`, params); + } + + /** + * + * + * Copy Trading + * + * + */ + + // TODO: not yet implemented + + /** + * + * + * Earn + * + * + */ + + // TODO: not yet implemented +} diff --git a/src/spot-client.ts b/src/spot-client.ts index 97b6257..a500a26 100644 --- a/src/spot-client.ts +++ b/src/spot-client.ts @@ -26,7 +26,10 @@ import { REST_CLIENT_TYPE_ENUM } from './util'; import BaseRestClient from './util/BaseRestClient'; /** - * REST API client + * REST API client for the V1 bitget Spot APIs. These are the previous generation of Bitget's APIs and should be considered deprecated. + * These will be removed in a future release, once Bitget formally deprecates them. + * + * @deprecated use RestClientV2 instead */ export class SpotClient extends BaseRestClient { getClientType() { @@ -43,7 +46,6 @@ export class SpotClient extends BaseRestClient { * Public * */ - /** Get Server Time */ getServerTime(): Promise> { return this.get('/api/spot/v1/public/time'); diff --git a/src/types/request/index.ts b/src/types/request/index.ts index 8fd41c1..a092236 100644 --- a/src/types/request/index.ts +++ b/src/types/request/index.ts @@ -1,4 +1,6 @@ -export * from './broker'; -export * from './futures'; +export * from './v1/brokerV1'; +export * from './v1/futuresV1'; +export * from './v1/spotV1'; +export * from './v2/futures'; +export * from './v2/spot'; export * from './shared'; -export * from './spot'; diff --git a/src/types/request/shared.ts b/src/types/request/shared.ts index 3a63c26..6d21f7a 100644 --- a/src/types/request/shared.ts +++ b/src/types/request/shared.ts @@ -17,3 +17,8 @@ export interface GetHistoricTradesParams { startTime?: string; endTime?: string; } + +/** + * The margin type, used directly in building the endpoint URL + */ +export type MarginType = 'crossed' | 'isolated'; diff --git a/src/types/request/broker.ts b/src/types/request/v1/brokerV1.ts similarity index 100% rename from src/types/request/broker.ts rename to src/types/request/v1/brokerV1.ts diff --git a/src/types/request/futures.ts b/src/types/request/v1/futuresV1.ts similarity index 98% rename from src/types/request/futures.ts rename to src/types/request/v1/futuresV1.ts index ce316e6..e93fc0d 100644 --- a/src/types/request/futures.ts +++ b/src/types/request/v1/futuresV1.ts @@ -1,4 +1,4 @@ -import { OrderTimeInForce } from './shared'; +import { OrderTimeInForce } from '../shared'; export type FuturesProductType = | 'umcbl' diff --git a/src/types/request/spot.ts b/src/types/request/v1/spotV1.ts similarity index 98% rename from src/types/request/spot.ts rename to src/types/request/v1/spotV1.ts index aec63c4..79b758b 100644 --- a/src/types/request/spot.ts +++ b/src/types/request/v1/spotV1.ts @@ -1,4 +1,4 @@ -import { OrderTimeInForce } from './shared'; +import { OrderTimeInForce } from '../shared'; export type WalletType = 'spot' | 'mix_usdt' | 'mix_usd'; diff --git a/src/types/request/v2/futures.ts b/src/types/request/v2/futures.ts new file mode 100644 index 0000000..5499289 --- /dev/null +++ b/src/types/request/v2/futures.ts @@ -0,0 +1,32 @@ +import { FuturesKlineInterval } from '../v1/futuresV1'; + +export type FuturesProductTypeV2 = + | 'USDT-FUTURES' + | 'COIN-FUTURES' + | 'USDC-FUTURES' + | 'SUSDT-FUTURES' + | 'SCOIN-FUTURES' + | 'SUSDC-FUTURES'; + +export type FuturesKlineTypeV2 = 'MARKET' | 'MARK' | 'INDEX'; + +export interface FuturesAccountBillRequestV2 { + productType: FuturesProductTypeV2; + symbol?: string; + coin?: string; + businessType?: string; + idLessThan?: string; + startTime?: string; + endTime?: string; + limit?: string; +} + +export interface FuturesCandlesRequestV2 { + symbol: string; + productType: FuturesProductTypeV2; + granularity: FuturesKlineInterval; + startTime?: string; + endTime?: string; + kLineType?: FuturesKlineTypeV2; + limit?: string; +} diff --git a/src/types/request/v2/spot.ts b/src/types/request/v2/spot.ts new file mode 100644 index 0000000..6fdeaaa --- /dev/null +++ b/src/types/request/v2/spot.ts @@ -0,0 +1,9 @@ +import { SpotKlineInterval } from '../v1/spotV1'; + +export interface SpotCandlesRequestV2 { + symbol: string; + granularity: SpotKlineInterval; + startTime?: string; + endTime?: string; + limit?: string; +} diff --git a/src/types/response/shared.ts b/src/types/response/shared.ts index d99b11c..e59ec99 100644 --- a/src/types/response/shared.ts +++ b/src/types/response/shared.ts @@ -1,8 +1,8 @@ export interface APIResponse { code: string; - data: T; - msg: 'success' | string; requestTime: number; + msg: 'success' | string; + data: T; } export interface VIPFeeRate { diff --git a/src/types/websockets/client.ts b/src/types/websockets/client.ts index 1d52d3d..f1c414a 100644 --- a/src/types/websockets/client.ts +++ b/src/types/websockets/client.ts @@ -1,4 +1,9 @@ import { WS_KEY_MAP } from '../../util'; +import { FuturesProductTypeV2 } from '../request'; + +/** A "topic" is always a string */ +export type BitgetInstType = 'SP' | 'SPBL' | 'MC' | 'UMCBL' | 'DMCBL'; +export type BitgetInstTypeV2 = 'SPOT' | FuturesProductTypeV2; export type WsPublicSpotTopic = | 'ticker' @@ -29,9 +34,105 @@ export type WsPublicTopic = WsPublicSpotTopic | WsPublicFuturesTopic; export type WsPrivateTopic = WsPrivateSpotTopic | WsPrivateFuturesTopic; export type WsTopic = WsPublicTopic | WsPrivateTopic; -/** This is used to differentiate between each of the available websocket streams */ -export type WsKey = typeof WS_KEY_MAP[keyof typeof WS_KEY_MAP]; +export type WsPublicTopicV2 = + | 'index-price' // margin only + | 'ticker' + | 'candle1m' + | 'candle5m' + | 'candle15' + | 'candle30m' + | 'candle1H' + | 'candle4H' + | 'candle6H' + | 'candle12H' + | 'candle1D' + | 'candle3D' + | 'candle1W' + | 'candle1M' + | 'candle6Hutc' + | 'candle12Hutc' + | 'candle1Dutc' + | 'candle3Dutc' + | 'candle1Wutc' + | 'candle1Mutc' + | 'trade' + | 'books' + | 'books1' + | 'books5' + | 'books15'; +export type WSPrivateTopicFuturesV2 = + | 'positions' + | 'orders-algo' + | 'positions-history'; + +export type WSPrivateTopicMarginV2 = + | 'orders-crossed' + | 'account-crossed' + | 'account-isolated' + | 'orders-isolated'; + +export type WsPrivateTopicV2 = + | 'account' + | 'orders' + | WSPrivateTopicFuturesV2 + | WSPrivateTopicMarginV2; + +export type WsTopicV2 = WsPublicTopicV2 | WsPrivateTopicV2; + +/** This is used to differentiate between each of the available websocket streams */ +export type WsKey = (typeof WS_KEY_MAP)[keyof typeof WS_KEY_MAP]; + +/** + * Event args for subscribing/unsubscribing + */ + +export interface WsTopicSubscribeEventArgs { + instType: BitgetInstType; + channel: WsTopic; + instId?: string; +} + +export type WsTopicSubscribePublicArgsV2 = { + instType: BitgetInstTypeV2; + channel: WsPublicTopicV2; + /** The symbol, e.g. "BTCUSDT" */ + instId: string; +}; + +export type WsInstIdChannelsV2 = + | 'orders' + | WSPrivateTopicFuturesV2 + | 'orders-crossed' + | 'orders-isolated'; + +export type WsTopicSubscribePrivateInstIdArgsV2 = { + instType: BitgetInstTypeV2; + channel: WsInstIdChannelsV2; + /** The symbol, e.g. "BTCUSDT" */ + instId?: string; +}; + +export type WsCoinChannelsV2 = + | 'account' + | 'account-crossed' + | 'account-isolated'; + +export type WsTopicSubscribePrivateCoinArgsV2 = { + instType: BitgetInstTypeV2; + channel: WsCoinChannelsV2; + coin: 'default' | string; +}; + +export type WsTopicSubscribePrivateArgsV2 = + | WsTopicSubscribePrivateInstIdArgsV2 + | WsTopicSubscribePrivateCoinArgsV2; + +export type WsTopicSubscribeEventArgsV2 = + | WsTopicSubscribePublicArgsV2 + | WsTopicSubscribePrivateArgsV2; + +/** General configuration for the WebsocketClient */ export interface WSClientConfigurableOptions { /** Your API key */ apiKey?: string; diff --git a/src/util/BaseWSClient.ts b/src/util/BaseWSClient.ts new file mode 100644 index 0000000..66a30ae --- /dev/null +++ b/src/util/BaseWSClient.ts @@ -0,0 +1,630 @@ +import EventEmitter from 'events'; +import WebSocket from 'isomorphic-ws'; + +import { WebsocketClientOptions, WSClientConfigurableOptions } from '../types'; +import WsStore from './WsStore'; +import { WsConnectionStateEnum } from './WsStore.types'; +import { DefaultLogger } from './logger'; +import { isWsPong } from './requestUtils'; +import { getWsAuthSignature } from './websocket-util'; + +interface WSClientEventMap { + /** Connection opened. If this connection was previously opened and reconnected, expect the reconnected event instead */ + open: (evt: { wsKey: WsKey; event: any }) => void; + /** Reconnecting a dropped connection */ + reconnect: (evt: { wsKey: WsKey; event: any }) => void; + /** Successfully reconnected a connection that dropped */ + reconnected: (evt: { wsKey: WsKey; event: any }) => void; + /** Connection closed */ + close: (evt: { wsKey: WsKey; event: any }) => void; + /** Received reply to websocket command (e.g. after subscribing to topics) */ + response: (response: any & { wsKey: WsKey }) => void; + /** Received data for topic */ + update: (response: any & { wsKey: WsKey }) => void; + /** Exception from ws client OR custom listeners (e.g. if you throw inside your event handler) */ + exception: (response: any & { wsKey: WsKey }) => void; + /** Confirmation that a connection successfully authenticated */ + authenticated: (event: { wsKey: WsKey; event: any }) => void; +} + +// Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837 +export interface BaseWebsocketClient< + TWSKey extends string, + TWSTopicSubscribeEventArgs extends object, +> { + on>( + event: U, + listener: WSClientEventMap[U], + ): this; + + emit>( + event: U, + ...args: Parameters[U]> + ): boolean; +} + +export interface BaseWSClientImpl {} + +const LOGGER_CATEGORY = { category: 'bitget-ws' }; + +export abstract class BaseWebsocketClient< + TWSKey extends string, + TWSTopicSubscribeEventArgs extends object, +> extends EventEmitter { + private wsStore: WsStore; + + protected logger: typeof DefaultLogger; + protected options: WebsocketClientOptions; + + constructor( + options: WSClientConfigurableOptions, + logger?: typeof DefaultLogger, + ) { + super(); + + this.logger = logger || DefaultLogger; + this.wsStore = new WsStore(this.logger); + + this.options = { + pongTimeout: 1000, + pingInterval: 10000, + reconnectTimeout: 500, + recvWindow: 0, + ...options, + }; + } + + protected abstract getWsKeyForTopic( + subscribeEvent: TWSTopicSubscribeEventArgs, + isPrivate?: boolean, + ): TWSKey; + + protected abstract isPrivateChannel( + subscribeEvent: TWSTopicSubscribeEventArgs, + ): boolean; + + protected abstract shouldAuthOnConnect(wsKey: TWSKey): boolean; + protected abstract getWsUrl(wsKey: TWSKey): string; + protected abstract getMaxTopicsPerSubscribeEvent( + wsKey: TWSKey, + ): number | null; + + /** + * Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library + */ + abstract connectAll(): Promise[]; + + /** + * Subscribe to topics & track/persist them. They will be automatically resubscribed to if the connection drops/reconnects. + * @param wsTopics topic or list of topics + * @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet) + */ + public subscribe( + wsTopics: TWSTopicSubscribeEventArgs[] | TWSTopicSubscribeEventArgs, + isPrivateTopic?: boolean, + ) { + const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; + + topics.forEach((topic) => { + const wsKey = this.getWsKeyForTopic(topic, isPrivateTopic); + + // Persist this topic to the expected topics list + this.wsStore.addTopic(wsKey, topic); + + // TODO: tidy up unsubscribe too, also in other connectors + + // if connected, send subscription request + if ( + this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED) + ) { + // if not authenticated, dont sub to private topics yet. + // This'll happen automatically once authenticated + const isAuthenticated = this.wsStore.get(wsKey)?.isAuthenticated; + if (!isAuthenticated) { + return this.requestSubscribeTopics( + wsKey, + topics.filter((topic) => !this.isPrivateChannel(topic)), + ); + } + return this.requestSubscribeTopics(wsKey, topics); + } + + // start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect + if ( + !this.wsStore.isConnectionState( + wsKey, + WsConnectionStateEnum.CONNECTING, + ) && + !this.wsStore.isConnectionState( + wsKey, + WsConnectionStateEnum.RECONNECTING, + ) + ) { + return this.connect(wsKey); + } + }); + } + + /** + * Unsubscribe from topics & remove them from memory. They won't be re-subscribed to if the connection reconnects. + * @param wsTopics topic or list of topics + * @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet) + */ + public unsubscribe( + wsTopics: TWSTopicSubscribeEventArgs[] | TWSTopicSubscribeEventArgs, + isPrivateTopic?: boolean, + ) { + const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; + topics.forEach((topic) => { + this.wsStore.deleteTopic( + this.getWsKeyForTopic(topic, isPrivateTopic), + topic, + ); + + const wsKey = this.getWsKeyForTopic(topic, isPrivateTopic); + + // unsubscribe request only necessary if active connection exists + if ( + this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED) + ) { + this.requestUnsubscribeTopics(wsKey, [topic]); + } + }); + } + + /** Get the WsStore that tracks websockets & topics */ + public getWsStore(): WsStore { + return this.wsStore; + } + + public close(wsKey: TWSKey, force?: boolean) { + this.logger.info('Closing connection', { ...LOGGER_CATEGORY, wsKey }); + this.setWsState(wsKey, WsConnectionStateEnum.CLOSING); + this.clearTimers(wsKey); + + const ws = this.getWs(wsKey); + ws?.close(); + if (force) { + ws?.terminate(); + } + } + + public closeAll(force?: boolean) { + this.wsStore.getKeys().forEach((key: TWSKey) => { + this.close(key, force); + }); + } + + /** + * Request connection to a specific websocket, instead of waiting for automatic connection. + */ + protected async connect(wsKey: TWSKey): Promise { + try { + if (this.wsStore.isWsOpen(wsKey)) { + this.logger.error( + 'Refused to connect to ws with existing active connection', + { ...LOGGER_CATEGORY, wsKey }, + ); + return this.wsStore.getWs(wsKey); + } + + if ( + this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTING) + ) { + this.logger.error( + 'Refused to connect to ws, connection attempt already active', + { ...LOGGER_CATEGORY, wsKey }, + ); + return; + } + + if ( + !this.wsStore.getConnectionState(wsKey) || + this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.INITIAL) + ) { + this.setWsState(wsKey, WsConnectionStateEnum.CONNECTING); + } + + const url = this.getWsUrl(wsKey); // + authParams; + const ws = this.connectToWsUrl(url, wsKey); + + return this.wsStore.setWs(wsKey, ws); + } catch (err) { + this.parseWsError('Connection failed', err, wsKey); + this.reconnectWithDelay(wsKey, this.options.reconnectTimeout!); + } + } + + private parseWsError(context: string, error: any, wsKey: TWSKey) { + if (!error.message) { + this.logger.error(`${context} due to unexpected error: `, error); + this.emit('response', { ...error, wsKey }); + this.emit('exception', { ...error, wsKey }); + return; + } + + switch (error.message) { + case 'Unexpected server response: 401': + this.logger.error(`${context} due to 401 authorization failure.`, { + ...LOGGER_CATEGORY, + wsKey, + }); + break; + + default: + this.logger.error( + `${context} due to unexpected response error: "${ + error?.msg || error?.message || error + }"`, + { ...LOGGER_CATEGORY, wsKey, error }, + ); + break; + } + + this.emit('response', { ...error, wsKey }); + this.emit('exception', { ...error, wsKey }); + } + + /** Get a signature, build the auth request and send it */ + private async sendAuthRequest(wsKey: TWSKey): Promise { + try { + const { apiKey, apiSecret, apiPass, recvWindow } = this.options; + + const { signature, expiresAt } = await getWsAuthSignature( + apiKey, + apiSecret, + apiPass, + recvWindow, + ); + + this.logger.info(`Sending auth request...`, { + ...LOGGER_CATEGORY, + wsKey, + }); + + const request = { + op: 'login', + args: [ + { + apiKey: this.options.apiKey, + passphrase: this.options.apiPass, + timestamp: expiresAt, + sign: signature, + }, + ], + }; + // console.log('ws auth req', request); + + return this.tryWsSend(wsKey, JSON.stringify(request)); + } catch (e) { + this.logger.silly(e, { ...LOGGER_CATEGORY, wsKey }); + } + } + + private reconnectWithDelay(wsKey: TWSKey, connectionDelayMs: number) { + this.clearTimers(wsKey); + if ( + this.wsStore.getConnectionState(wsKey) !== + WsConnectionStateEnum.CONNECTING + ) { + this.setWsState(wsKey, WsConnectionStateEnum.RECONNECTING); + } + + this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => { + this.logger.info('Reconnecting to websocket', { + ...LOGGER_CATEGORY, + wsKey, + }); + this.connect(wsKey); + }, connectionDelayMs); + } + + private ping(wsKey: TWSKey) { + if (this.wsStore.get(wsKey, true).activePongTimer) { + return; + } + + this.clearPongTimer(wsKey); + + this.logger.silly('Sending ping', { ...LOGGER_CATEGORY, wsKey }); + this.tryWsSend(wsKey, 'ping'); + + this.wsStore.get(wsKey, true).activePongTimer = setTimeout(() => { + this.logger.info('Pong timeout - closing socket to reconnect', { + ...LOGGER_CATEGORY, + wsKey, + }); + this.getWs(wsKey)?.terminate(); + delete this.wsStore.get(wsKey, true).activePongTimer; + }, this.options.pongTimeout); + } + + private clearTimers(wsKey: TWSKey) { + this.clearPingTimer(wsKey); + this.clearPongTimer(wsKey); + const wsState = this.wsStore.get(wsKey); + if (wsState?.activeReconnectTimer) { + clearTimeout(wsState.activeReconnectTimer); + } + } + + // Send a ping at intervals + private clearPingTimer(wsKey: TWSKey) { + const wsState = this.wsStore.get(wsKey); + if (wsState?.activePingTimer) { + clearInterval(wsState.activePingTimer); + wsState.activePingTimer = undefined; + } + } + + // Expect a pong within a time limit + private clearPongTimer(wsKey: TWSKey) { + const wsState = this.wsStore.get(wsKey); + if (wsState?.activePongTimer) { + clearTimeout(wsState.activePongTimer); + wsState.activePongTimer = undefined; + } + } + + /** + * @private Use the `subscribe(topics)` method to subscribe to topics. Send WS message to subscribe to topics. + */ + private requestSubscribeTopics( + wsKey: TWSKey, + topics: TWSTopicSubscribeEventArgs[], + ) { + if (!topics.length) { + return; + } + + const maxTopicsPerEvent = this.getMaxTopicsPerSubscribeEvent(wsKey); + if (maxTopicsPerEvent && topics.length > maxTopicsPerEvent) { + this.logger.silly( + `Subscribing to topics in batches of ${maxTopicsPerEvent}`, + ); + for (var i = 0; i < topics.length; i += maxTopicsPerEvent) { + const batch = topics.slice(i, i + maxTopicsPerEvent); + this.logger.silly(`Subscribing to batch of ${batch.length}`); + this.requestSubscribeTopics(wsKey, batch); + } + this.logger.silly( + `Finished batch subscribing to ${topics.length} topics`, + ); + return; + } + + const wsMessage = JSON.stringify({ + op: 'subscribe', + args: topics, + }); + + this.tryWsSend(wsKey, wsMessage); + } + + /** + * @private Use the `unsubscribe(topics)` method to unsubscribe from topics. Send WS message to unsubscribe from topics. + */ + private requestUnsubscribeTopics( + wsKey: TWSKey, + topics: TWSTopicSubscribeEventArgs[], + ) { + if (!topics.length) { + return; + } + + const maxTopicsPerEvent = this.getMaxTopicsPerSubscribeEvent(wsKey); + if (maxTopicsPerEvent && topics.length > maxTopicsPerEvent) { + this.logger.silly( + `Unsubscribing to topics in batches of ${maxTopicsPerEvent}`, + ); + for (var i = 0; i < topics.length; i += maxTopicsPerEvent) { + const batch = topics.slice(i, i + maxTopicsPerEvent); + this.logger.silly(`Unsubscribing to batch of ${batch.length}`); + this.requestUnsubscribeTopics(wsKey, batch); + } + this.logger.silly( + `Finished batch unsubscribing to ${topics.length} topics`, + ); + return; + } + + const wsMessage = JSON.stringify({ + op: 'unsubscribe', + args: topics, + }); + + this.tryWsSend(wsKey, wsMessage); + } + + public tryWsSend(wsKey: TWSKey, wsMessage: string) { + try { + this.logger.silly(`Sending upstream ws message: `, { + ...LOGGER_CATEGORY, + wsMessage, + wsKey, + }); + if (!wsKey) { + throw new Error( + 'Cannot send message due to no known websocket for this wsKey', + ); + } + const ws = this.getWs(wsKey); + if (!ws) { + throw new Error( + `${wsKey} socket not connected yet, call "connectAll()" first then try again when the "open" event arrives`, + ); + } + ws.send(wsMessage); + } catch (e) { + this.logger.error(`Failed to send WS message`, { + ...LOGGER_CATEGORY, + wsMessage, + wsKey, + exception: e, + }); + } + } + + private connectToWsUrl(url: string, wsKey: TWSKey): WebSocket { + this.logger.silly(`Opening WS connection to URL: ${url}`, { + ...LOGGER_CATEGORY, + wsKey, + }); + + const agent = this.options.requestOptions?.agent; + const ws = new WebSocket(url, undefined, agent ? { agent } : undefined); + ws.onopen = (event) => this.onWsOpen(event, wsKey); + ws.onmessage = (event) => this.onWsMessage(event, wsKey); + ws.onerror = (event) => this.parseWsError('websocket error', event, wsKey); + ws.onclose = (event) => this.onWsClose(event, wsKey); + + return ws; + } + + private async onWsOpen(event, wsKey: TWSKey) { + if ( + this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTING) + ) { + this.logger.info('Websocket connected', { + ...LOGGER_CATEGORY, + wsKey, + }); + this.emit('open', { wsKey, event }); + } else if ( + this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.RECONNECTING) + ) { + this.logger.info('Websocket reconnected', { ...LOGGER_CATEGORY, wsKey }); + this.emit('reconnected', { wsKey, event }); + } + + this.setWsState(wsKey, WsConnectionStateEnum.CONNECTED); + + // Some websockets require an auth packet to be sent after opening the connection + if (this.shouldAuthOnConnect(wsKey)) { + await this.sendAuthRequest(wsKey); + } + + // Reconnect to topics known before it connected + // Private topics will be resubscribed to once reconnected + const topics = [...this.wsStore.getTopics(wsKey)]; + const publicTopics = topics.filter( + (topic) => !this.isPrivateChannel(topic), + ); + this.requestSubscribeTopics(wsKey, publicTopics); + + this.wsStore.get(wsKey, true)!.activePingTimer = setInterval( + () => this.ping(wsKey), + this.options.pingInterval, + ); + } + + /** Handle subscription to private topics _after_ authentication successfully completes asynchronously */ + private onWsAuthenticated(wsKey: TWSKey) { + const wsState = this.wsStore.get(wsKey, true); + wsState.isAuthenticated = true; + + const topics = [...this.wsStore.getTopics(wsKey)]; + const privateTopics = topics.filter((topic) => + this.isPrivateChannel(topic), + ); + + if (privateTopics.length) { + this.subscribe(privateTopics, true); + } + } + + private onWsMessage(event: unknown, wsKey: TWSKey) { + try { + // any message can clear the pong timer - wouldn't get a message if the ws wasn't working + this.clearPongTimer(wsKey); + + if (isWsPong(event)) { + this.logger.silly('Received pong', { ...LOGGER_CATEGORY, wsKey }); + return; + } + + const msg = JSON.parse((event && event['data']) || event); + const emittableEvent = { ...msg, wsKey }; + + if (typeof msg === 'object') { + if (typeof msg['code'] === 'number') { + if (msg.event === 'login' && msg.code === 0) { + this.logger.info(`Successfully authenticated WS client`, { + ...LOGGER_CATEGORY, + wsKey, + }); + this.emit('response', emittableEvent); + this.emit('authenticated', emittableEvent); + this.onWsAuthenticated(wsKey); + return; + } + } + + if (msg['event']) { + if (msg.event === 'error') { + this.logger.error(`WS Error received`, { + ...LOGGER_CATEGORY, + wsKey, + message: msg || 'no message', + // messageType: typeof msg, + // messageString: JSON.stringify(msg), + event, + }); + this.emit('exception', emittableEvent); + this.emit('response', emittableEvent); + return; + } + return this.emit('response', emittableEvent); + } + + if (msg['arg']) { + return this.emit('update', emittableEvent); + } + } + + this.logger.warning('Unhandled/unrecognised ws event message', { + ...LOGGER_CATEGORY, + message: msg || 'no message', + // messageType: typeof msg, + // messageString: JSON.stringify(msg), + event, + wsKey, + }); + + // fallback emit anyway + return this.emit('update', emittableEvent); + } catch (e) { + this.logger.error('Failed to parse ws event message', { + ...LOGGER_CATEGORY, + error: e, + event, + wsKey, + }); + } + } + + private onWsClose(event: unknown, wsKey: TWSKey) { + this.logger.info('Websocket connection closed', { + ...LOGGER_CATEGORY, + wsKey, + }); + + if ( + this.wsStore.getConnectionState(wsKey) !== WsConnectionStateEnum.CLOSING + ) { + this.reconnectWithDelay(wsKey, this.options.reconnectTimeout!); + this.emit('reconnect', { wsKey, event }); + } else { + this.setWsState(wsKey, WsConnectionStateEnum.INITIAL); + this.emit('close', { wsKey, event }); + } + } + + private getWs(wsKey: TWSKey) { + return this.wsStore.getWs(wsKey); + } + + private setWsState(wsKey: TWSKey, state: WsConnectionStateEnum) { + this.wsStore.setConnectionState(wsKey, state); + } +} diff --git a/src/util/WsStore.ts b/src/util/WsStore.ts index 1151dea..19f990d 100644 --- a/src/util/WsStore.ts +++ b/src/util/WsStore.ts @@ -1,50 +1,8 @@ import WebSocket from 'isomorphic-ws'; -import { WsPrivateTopic, WsTopic } from '../types'; import { DefaultLogger } from './logger'; +import { WsConnectionStateEnum, WsStoredState } from './WsStore.types'; -export enum WsConnectionStateEnum { - INITIAL = 0, - CONNECTING = 1, - CONNECTED = 2, - CLOSING = 3, - RECONNECTING = 4, - // ERROR = 5, -} -/** A "topic" is always a string */ - -export type BitgetInstType = 'SP' | 'SPBL' | 'MC' | 'UMCBL' | 'DMCBL'; - -// TODO: generalise so this can be made a reusable module for other clients -export interface WsTopicSubscribeEventArgs { - instType: BitgetInstType; - channel: WsTopic; - /** The symbol, e.g. "BTCUSDT" */ - instId: string; -} - -type WsTopicList = Set; - -interface WsStoredState { - /** The currently active websocket connection */ - ws?: WebSocket; - /** The current lifecycle state of the connection (enum) */ - connectionState?: WsConnectionStateEnum; - /** A timer that will send an upstream heartbeat (ping) when it expires */ - activePingTimer?: ReturnType | undefined; - /** A timer tracking that an upstream heartbeat was sent, expecting a reply before it expires */ - activePongTimer?: ReturnType | undefined; - /** If a reconnection is in progress, this will have the timer for the delayed reconnect */ - activeReconnectTimer?: ReturnType | undefined; - /** - * All the topics we are expected to be subscribed to (and we automatically resubscribe to if the connection drops) - * - * A "Set" and a deep object match are used to ensure we only subscribe to a topic once (tracking a list of unique topics we're expected to be connected to) - */ - subscribedTopics: WsTopicList; - isAuthenticated?: boolean; -} - -function isDeepObjectMatch(object1: any, object2: any) { +function isDeepObjectMatch(object1: object, object2: object) { for (const key in object1) { if (object1[key] !== object2[key]) { return false; @@ -53,8 +11,12 @@ function isDeepObjectMatch(object1: any, object2: any) { return true; } -export default class WsStore { - private wsState: Record = {}; +export default class WsStore< + WsKey extends string, + TWSTopicSubscribeEventArgs extends object, +> { + private wsState: Record> = + {}; private logger: typeof DefaultLogger; constructor(logger: typeof DefaultLogger) { @@ -62,9 +24,18 @@ export default class WsStore { } /** Get WS stored state for key, optionally create if missing */ - get(key: WsKey, createIfMissing?: true): WsStoredState; - get(key: WsKey, createIfMissing?: false): WsStoredState | undefined; - get(key: WsKey, createIfMissing?: boolean): WsStoredState | undefined { + get( + key: WsKey, + createIfMissing?: true, + ): WsStoredState; + get( + key: WsKey, + createIfMissing?: false, + ): WsStoredState | undefined; + get( + key: WsKey, + createIfMissing?: boolean, + ): WsStoredState | undefined { if (this.wsState[key]) { return this.wsState[key]; } @@ -78,7 +49,7 @@ export default class WsStore { return Object.keys(this.wsState) as WsKey[]; } - create(key: WsKey): WsStoredState | undefined { + create(key: WsKey): WsStoredState | undefined { if (this.hasExistingActiveConnection(key)) { this.logger.warning( 'WsStore setConnection() overwriting existing open connection: ', @@ -86,7 +57,7 @@ export default class WsStore { ); } this.wsState[key] = { - subscribedTopics: new Set(), + subscribedTopics: new Set(), connectionState: WsConnectionStateEnum.INITIAL, }; return this.get(key); @@ -151,11 +122,11 @@ export default class WsStore { /* subscribed topics */ - getTopics(key: WsKey): WsTopicList { + getTopics(key: WsKey): Set { return this.get(key, true).subscribedTopics; } - getTopicsByKey(): Record { + getTopicsByKey(): Record> { const result = {}; for (const refKey in this.wsState) { result[refKey] = this.getTopics(refKey as WsKey); @@ -164,7 +135,7 @@ export default class WsStore { } // Since topics are objects we can't rely on the set to detect duplicates - getMatchingTopic(key: WsKey, topic: WsTopicSubscribeEventArgs) { + getMatchingTopic(key: WsKey, topic: TWSTopicSubscribeEventArgs) { // if (typeof topic === 'string') { // return this.getMatchingTopic(key, { channel: topic }); // } @@ -177,7 +148,7 @@ export default class WsStore { } } - addTopic(key: WsKey, topic: WsTopicSubscribeEventArgs) { + addTopic(key: WsKey, topic: TWSTopicSubscribeEventArgs) { // if (typeof topic === 'string') { // return this.addTopic(key, { // instType: 'sp', @@ -193,7 +164,7 @@ export default class WsStore { return this.getTopics(key).add(topic); } - deleteTopic(key: WsKey, topic: WsTopicSubscribeEventArgs) { + deleteTopic(key: WsKey, topic: TWSTopicSubscribeEventArgs) { // Check if we're subscribed to a topic like this const storedTopic = this.getMatchingTopic(key, topic); if (storedTopic) { diff --git a/src/util/WsStore.types.ts b/src/util/WsStore.types.ts new file mode 100644 index 0000000..5eb95d6 --- /dev/null +++ b/src/util/WsStore.types.ts @@ -0,0 +1,28 @@ +export enum WsConnectionStateEnum { + INITIAL = 0, + CONNECTING = 1, + CONNECTED = 2, + CLOSING = 3, + RECONNECTING = 4, + // ERROR = 5, +} + +export interface WsStoredState { + /** The currently active websocket connection */ + ws?: WebSocket; + /** The current lifecycle state of the connection (enum) */ + connectionState?: WsConnectionStateEnum; + /** A timer that will send an upstream heartbeat (ping) when it expires */ + activePingTimer?: ReturnType | undefined; + /** A timer tracking that an upstream heartbeat was sent, expecting a reply before it expires */ + activePongTimer?: ReturnType | undefined; + /** If a reconnection is in progress, this will have the timer for the delayed reconnect */ + activeReconnectTimer?: ReturnType | undefined; + /** + * All the topics we are expected to be subscribed to on this connection (and we automatically resubscribe to if the connection drops) + * + * A "Set" and a deep-object-match are used to ensure we only subscribe to a topic once (tracking a list of unique topics we're expected to be connected to) + */ + subscribedTopics: Set; + isAuthenticated?: boolean; +} diff --git a/src/util/requestUtils.ts b/src/util/requestUtils.ts index d93f396..6f831f0 100644 --- a/src/util/requestUtils.ts +++ b/src/util/requestUtils.ts @@ -98,4 +98,5 @@ export const REST_CLIENT_TYPE_ENUM = { spot: 'spot', futures: 'futures', broker: 'broker', + v2: 'v2', } as const; diff --git a/src/util/type-guards.ts b/src/util/type-guards.ts index b74709a..fbf9967 100644 --- a/src/util/type-guards.ts +++ b/src/util/type-guards.ts @@ -1,4 +1,5 @@ import { + MarginType, WsAccountSnapshotUMCBL, WsBaseEvent, WSPositionSnapshotUMCBL, @@ -71,3 +72,13 @@ export function isWsFuturesPositionsSnapshotEvent( ): event is WSPositionSnapshotUMCBL { return isWsPositionsSnapshotEvent(event) && event.arg.instType === 'umcbl'; } + +/** + * Simple guard for non-TypeScript users, throw a runtime error if value doesn't match type + */ +export function assertMarginType(marginType: string): marginType is MarginType { + if (marginType !== 'isolated' && marginType !== 'crossed') { + throw new Error(`MarginType should be one of: crossed | isolated`); + } + return true; +} diff --git a/src/util/websocket-util.ts b/src/util/websocket-util.ts index 6b9645b..36ffc27 100644 --- a/src/util/websocket-util.ts +++ b/src/util/websocket-util.ts @@ -1,6 +1,11 @@ -import { WsKey } from '../types'; +import { + BitgetInstType, + WsKey, + WsPrivateTopicV2, + WsTopicSubscribeEventArgs, + WsTopicSubscribePublicArgsV2, +} from '../types'; import { signMessage } from './node-support'; -import { BitgetInstType, WsTopicSubscribeEventArgs } from './WsStore'; /** * Some exchanges have two livenet environments, some have test environments, some dont. This allows easy flexibility for different exchanges. @@ -31,18 +36,31 @@ export const WS_BASE_URL_MAP: Record< livenet: 'wss://ws.bitget.com/spot/v1/stream', }, }, + v2Public: { + all: { + livenet: 'wss://ws.bitget.com/v2/ws/public', + }, + }, + v2Private: { + all: { + livenet: 'wss://ws.bitget.com/v2/ws/private', + }, + }, }; /** Should be one WS key per unique URL */ export const WS_KEY_MAP = { spotv1: 'spotv1', mixv1: 'mixv1', + v2Public: 'v2Public', + v2Private: 'v2Private', } as const; /** Any WS keys in this list will trigger auth on connect, if credentials are available */ export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [ WS_KEY_MAP.spotv1, WS_KEY_MAP.mixv1, + WS_KEY_MAP.v2Private, ]; /** Any WS keys in this list will ALWAYS skip the authentication process, even if credentials are available */ @@ -54,10 +72,25 @@ export const PUBLIC_WS_KEYS = [] as WsKey[]; */ export const PRIVATE_TOPICS = ['account', 'orders', 'positions', 'ordersAlgo']; +export const PRIVATE_TOPICS_V2: WsPrivateTopicV2[] = [ + 'account', + 'orders', + 'positions', + 'orders-algo', + 'positions-history', + 'orders-crossed', + 'account-crossed', + 'account-isolated', + 'orders-isolated', +]; + export function isPrivateChannel( channel: TChannel, ): boolean { - return PRIVATE_TOPICS.includes(channel); + return ( + PRIVATE_TOPICS.includes(channel) || + PRIVATE_TOPICS_V2.includes(channel as any) + ); } export function getWsKeyForTopic( @@ -84,11 +117,22 @@ export function getWsKeyForTopic( } } +export function getWsKeyForTopicV2( + subscribeEvent: WsTopicSubscribePublicArgsV2, + isPrivate?: boolean, +): WsKey { + return isPrivate || isPrivateChannel(subscribeEvent.channel) + ? WS_KEY_MAP.v2Private + : WS_KEY_MAP.v2Public; +} + /** Force subscription requests to be sent in smaller batches, if a number is returned */ export function getMaxTopicsPerSubscribeEvent(wsKey: WsKey): number | null { switch (wsKey) { case 'mixv1': - case 'spotv1': { + case 'spotv1': + case 'v2Public': + case 'v2Private': { // Technically there doesn't seem to be a documented cap, but there is a size limit per request. Doesn't hurt to batch requests. return 15; } diff --git a/src/websocket-client-v2.ts b/src/websocket-client-v2.ts new file mode 100644 index 0000000..c124d28 --- /dev/null +++ b/src/websocket-client-v2.ts @@ -0,0 +1,167 @@ +import WebSocket from 'isomorphic-ws'; + +import { + BitgetInstTypeV2, + WebsocketClientOptions, + WsCoinChannelsV2, + WsInstIdChannelsV2, + WsKey, + WsPublicTopicV2, + WsTopicSubscribeEventArgsV2, + WsTopicSubscribePrivateCoinArgsV2, + WsTopicSubscribePrivateInstIdArgsV2, + WsTopicV2, +} from './types'; + +import { + WS_AUTH_ON_CONNECT_KEYS, + WS_KEY_MAP, + DefaultLogger, + WS_BASE_URL_MAP, + neverGuard, + getMaxTopicsPerSubscribeEvent, + isPrivateChannel, +} from './util'; + +import { BaseWebsocketClient } from './util/BaseWSClient'; + +const LOGGER_CATEGORY = { category: 'bitget-ws' }; + +const COIN_CHANNELS: WsTopicV2[] = [ + 'account', + 'account-crossed', + 'account-isolated', +]; + +export class WebsocketClientV2 extends BaseWebsocketClient< + WsKey, + WsTopicSubscribeEventArgsV2 +> { + protected logger: typeof DefaultLogger; + protected options: WebsocketClientOptions; + + protected getWsKeyForTopic( + subscribeEvent: WsTopicSubscribeEventArgsV2, + isPrivate?: boolean, + ): WsKey { + return isPrivate || isPrivateChannel(subscribeEvent.channel) + ? WS_KEY_MAP.v2Private + : WS_KEY_MAP.v2Public; + } + + protected isPrivateChannel( + subscribeEvent: WsTopicSubscribeEventArgsV2, + ): boolean { + return isPrivateChannel(subscribeEvent.channel); + } + + protected shouldAuthOnConnect(wsKey: WsKey): boolean { + return WS_AUTH_ON_CONNECT_KEYS.includes(wsKey as WsKey); + } + + protected getWsUrl(wsKey: WsKey): string { + if (this.options.wsUrl) { + return this.options.wsUrl; + } + + const networkKey = 'livenet'; + + switch (wsKey) { + case WS_KEY_MAP.spotv1: + case WS_KEY_MAP.mixv1: { + throw new Error( + `Use the WebsocketClient instead of WebsocketClientV2 for V1 websockets`, + ); + } + case WS_KEY_MAP.v2Private: { + return WS_BASE_URL_MAP.v2Private.all[networkKey]; + } + case WS_KEY_MAP.v2Public: { + return WS_BASE_URL_MAP.v2Public.all[networkKey]; + } + default: { + this.logger.error('getWsUrl(): Unhandled wsKey: ', { + ...LOGGER_CATEGORY, + wsKey, + }); + throw neverGuard(wsKey, `getWsUrl(): Unhandled wsKey`); + } + } + } + + protected getMaxTopicsPerSubscribeEvent(wsKey: WsKey): number | null { + return getMaxTopicsPerSubscribeEvent(wsKey); + } + + /** + * Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library + */ + public connectAll(): Promise[] { + return [ + this.connect(WS_KEY_MAP.v2Private), + this.connect(WS_KEY_MAP.v2Public), + ]; + } + + /** Some private channels use `coin` instead of `instId`. This method handles building the sub/unsub request */ + private getSubRequest( + instType: BitgetInstTypeV2, + topic: WsTopicV2, + coin: string = 'default', + ): WsTopicSubscribeEventArgsV2 { + if (isPrivateChannel(topic)) { + if (COIN_CHANNELS.includes(topic)) { + const subscribeRequest: WsTopicSubscribePrivateCoinArgsV2 = { + instType, + channel: topic as WsCoinChannelsV2, + coin, + }; + return subscribeRequest; + } + + const subscribeRequest: WsTopicSubscribePrivateInstIdArgsV2 = { + instType, + channel: topic as WsInstIdChannelsV2, + instId: coin, + }; + + return subscribeRequest; + } + + return { + instType, + channel: topic as WsPublicTopicV2, + instId: coin, + }; + } + + /** + * Subscribe to a PUBLIC topic + * @param instType instrument type (refer to API docs). + * @param topic topic name (e.g. "ticker"). + * @param instId instrument ID (e.g. "BTCUSDT"). Use "default" for private topics. + */ + public subscribeTopic( + instType: BitgetInstTypeV2, + topic: WsTopicV2, + coin: string = 'default', + ) { + const subRequest = this.getSubRequest(instType, topic, coin); + return this.subscribe(subRequest); + } + + /** + * Unsubscribe from a topic + * @param instType instrument type (refer to API docs). + * @param topic topic name (e.g. "ticker"). + * @param instId instrument ID (e.g. "BTCUSDT"). Use "default" for private topics to get all symbols. + */ + public unsubscribeTopic( + instType: BitgetInstTypeV2, + topic: WsTopicV2, + coin: string = 'default', + ) { + const subRequest = this.getSubRequest(instType, topic, coin); + return this.unsubscribe(subRequest); + } +} diff --git a/src/websocket-client.ts b/src/websocket-client.ts index 2ac02e6..cf7ab28 100644 --- a/src/websocket-client.ts +++ b/src/websocket-client.ts @@ -1,21 +1,19 @@ import { EventEmitter } from 'events'; import WebSocket from 'isomorphic-ws'; -import WsStore, { - BitgetInstType, - WsTopicSubscribeEventArgs, -} from './util/WsStore'; +import WsStore from './util/WsStore'; import { + BitgetInstType, WebsocketClientOptions, WSClientConfigurableOptions, WsKey, WsTopic, + WsTopicSubscribeEventArgs, } from './types'; import { isWsPong, - WsConnectionStateEnum, WS_AUTH_ON_CONNECT_KEYS, WS_KEY_MAP, DefaultLogger, @@ -26,6 +24,7 @@ import { isPrivateChannel, getWsAuthSignature, } from './util'; +import { WsConnectionStateEnum } from './util/WsStore.types'; const LOGGER_CATEGORY = { category: 'bitget-ws' }; @@ -70,10 +69,13 @@ export declare interface WebsocketClient { ): boolean; } +/** + * @deprecated use WebsocketClientV2 instead + */ export class WebsocketClient extends EventEmitter { private logger: typeof DefaultLogger; private options: WebsocketClientOptions; - private wsStore: WsStore; + private wsStore: WsStore; constructor( options: WSClientConfigurableOptions, @@ -143,7 +145,6 @@ export class WebsocketClient extends EventEmitter { } }); } - /** * Unsubscribe from topics & remove them from memory. They won't be re-subscribed to if the connection reconnects. * @param wsTopics topic or list of topics @@ -646,6 +647,10 @@ export class WebsocketClient extends EventEmitter { case WS_KEY_MAP.mixv1: { return WS_BASE_URL_MAP.mixv1.all[networkKey]; } + case WS_KEY_MAP.v2Private: + case WS_KEY_MAP.v2Public: { + throw new Error(`Use the WebsocketClientV2 for V2 websockets`); + } default: { this.logger.error('getWsUrl(): Unhandled wsKey: ', { ...LOGGER_CATEGORY, @@ -661,6 +666,8 @@ export class WebsocketClient extends EventEmitter { * @param instType instrument type (refer to API docs). * @param topic topic name (e.g. "ticker"). * @param instId instrument ID (e.g. "BTCUSDT"). Use "default" for private topics. + * + * @deprecated use WebsocketClientV2 instead */ public subscribeTopic( instType: BitgetInstType, @@ -679,6 +686,8 @@ export class WebsocketClient extends EventEmitter { * @param instType instrument type (refer to API docs). * @param topic topic name (e.g. "ticker"). * @param instId instrument ID (e.g. "BTCUSDT"). Use "default" for private topics to get all symbols. + * + * @deprecated use WebsocketClientV2 instead */ public unsubscribeTopic( instType: BitgetInstType, diff --git a/test/ws.private.test.ts b/test/ws.private.test.ts index 7354069..6c75ecd 100644 --- a/test/ws.private.test.ts +++ b/test/ws.private.test.ts @@ -4,9 +4,14 @@ import { WS_ERROR_ENUM, WS_KEY_MAP, } from '../src'; -import { getSilentLogger, waitForSocketEvent } from './ws.util'; +import { + getSilentLogger, + listenToSocketEvents, + logAllEvents, + waitForSocketEvent, +} from './ws.util'; -describe('Private Spot Websocket Client', () => { +describe.skip('Private Spot Websocket Client', () => { const API_KEY = process.env.API_KEY_COM; const API_SECRET = process.env.API_SECRET_COM; const API_PASS = process.env.API_PASS_COM; @@ -29,6 +34,7 @@ describe('Private Spot Websocket Client', () => { getSilentLogger('expect401'), ); + logAllEvents(badClient); // const wsOpenPromise = waitForSocketEvent(badClient, 'open'); const wsResponsePromise = waitForSocketEvent(badClient, 'response'); // const wsUpdatePromise = waitForSocketEvent(wsClient, 'update'); @@ -66,6 +72,7 @@ describe('Private Spot Websocket Client', () => { ); wsClient.connectAll(); // logAllEvents(wsClient); + logAllEvents(wsClient); }); afterAll(() => { diff --git a/test/ws.util.ts b/test/ws.util.ts index 1a83175..2e3e449 100644 --- a/test/ws.util.ts +++ b/test/ws.util.ts @@ -1,4 +1,4 @@ -import { WebsocketClient, WsClientEvent } from '../src'; +import { WebsocketClient } from '../src'; export function getSilentLogger(logHint?: string) { return { @@ -20,11 +20,20 @@ export const fullLogger = { error: (...params) => console.error('error', ...params), }; +type WsClientEvent = + | 'open' + | 'update' + | 'close' + | 'exception' + | 'reconnect' + | 'reconnected' + | 'response'; + /** Resolves a promise if an event is seen before a timeout (defaults to 4.5 seconds) */ export function waitForSocketEvent( wsClient: WebsocketClient, event: WsClientEvent, - timeoutMs: number = 4.5 * 1000, + timeoutMs: number = 10 * 1000, ) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => {