Merge pull request #54 from tiagosiebler/linter

feat(v2.2.0): bump build version to node LTS, fix linter, address linter conflicts, update gh action for linter check
This commit is contained in:
Tiago
2024-12-11 16:57:45 +00:00
committed by GitHub
45 changed files with 2039 additions and 1479 deletions

53
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,53 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.linting.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: [
'@typescript-eslint/eslint-plugin',
'simple-import-sort',
// 'require-extensions', // only once moved to ESM
],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
// 'plugin:require-extensions/recommended', // only once moved to ESM
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js', 'webpack.config.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/ban-types': 'off',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'array-bracket-spacing': ['error', 'never'],
'linebreak-style': ['error', 'unix'],
'lines-between-class-members': ['warn', 'always'],
semi: ['error', 'always'],
'new-cap': 'off',
'no-console': 'off',
'no-debugger': 'off',
'no-mixed-spaces-and-tabs': 2,
'no-use-before-define': [2, 'nofunc'],
'no-unreachable': ['warn'],
// 'no-unused-vars': ['warn'],
'no-extra-parens': ['off'],
'no-mixed-operators': ['off'],
quotes: [2, 'single', 'avoid-escape'],
'block-scoped-var': 2,
'brace-style': [2, '1tbs', { allowSingleLine: true }],
'computed-property-spacing': [2, 'never'],
'keyword-spacing': 2,
'space-unary-ops': 2,
},
};

View File

@@ -1,36 +0,0 @@
module.exports = {
env: {
es6: true,
node: true,
},
extends: ['eslint:recommended'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 9
},
plugins: [],
rules: {
'array-bracket-spacing': ['error', 'never'],
indent: ['warn', 2],
'linebreak-style': ['error', 'unix'],
'lines-between-class-members': ['warn', 'always'],
semi: ['error', 'always'],
'new-cap': 'off',
'no-console': 'off',
'no-debugger': 'off',
'no-mixed-spaces-and-tabs': 2,
'no-use-before-define': [2, 'nofunc'],
'no-unreachable': ['warn'],
'no-unused-vars': ['warn'],
'no-extra-parens': ['off'],
'no-mixed-operators': ['off'],
quotes: [2, 'single', 'avoid-escape'],
'block-scoped-var': 2,
'brace-style': [2, '1tbs', { allowSingleLine: true }],
'computed-property-spacing': [2, 'never'],
'keyword-spacing': 2,
'space-unary-ops': 2,
'max-len': ['warn', { 'code': 140 }]
}
};

View File

@@ -1,13 +1,9 @@
name: "Build & Test" name: "Build, Lint & Test"
on: [push] on:
push:
# on: pull_request:
# # pull_request: workflow_dispatch:
# # branches:
# # - "master"
# push:
# branches:
jobs: jobs:
build: build:
@@ -15,20 +11,27 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: "Checkout source code" - name: 'Checkout source code'
uses: actions/checkout@v3 uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: 16 node-version-file: '.nvmrc'
registry-url: 'https://registry.npmjs.org/'
cache: 'npm' cache: 'npm'
- name: Install - name: Install
run: npm ci --ignore-scripts run: npm ci --ignore-scripts
- name: Build - name: Clean
run: npm run clean
- name: Check Build
run: npm run build run: npm run build
- name: Check Lint
run: npm run lint
- name: Test - name: Test
run: npm run test run: npm run test
env: env:

1
.gitignore vendored
View File

@@ -27,3 +27,4 @@ restClientRegex.ts
localtest.sh localtest.sh
privaterepotracker privaterepotracker
testfile.ts testfile.ts
dist

2
.nvmrc
View File

@@ -1 +1 @@
v18.12.1 v22.11.0

1136
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "bitget-api", "name": "bitget-api",
"version": "2.1.0", "version": "2.2.0",
"description": "Node.js & JavaScript SDK for Bitget REST APIs & WebSockets, with TypeScript & end-to-end tests.", "description": "Node.js & JavaScript SDK for Bitget REST APIs & WebSockets, with TypeScript & end-to-end tests.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@@ -16,6 +16,7 @@
"build:clean": "npm run clean && npm run build", "build:clean": "npm run clean && npm run build",
"build:watch": "npm run clean && tsc --watch", "build:watch": "npm run clean && tsc --watch",
"pack": "webpack --config webpack/webpack.config.js", "pack": "webpack --config webpack/webpack.config.js",
"lint": "eslint src",
"prepublish": "npm run build:clean", "prepublish": "npm run build:clean",
"betapublish": "npm publish --tag beta" "betapublish": "npm publish --tag beta"
}, },
@@ -29,7 +30,12 @@
"devDependencies": { "devDependencies": {
"@types/jest": "^29.0.3", "@types/jest": "^29.0.3",
"@types/node": "^18.7.23", "@types/node": "^18.7.23",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"eslint": "^8.24.0", "eslint": "^8.24.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-require-extensions": "^0.1.3",
"eslint-plugin-simple-import-sort": "^12.1.1",
"jest": "^29.1.1", "jest": "^29.1.1",
"source-map-loader": "^4.0.0", "source-map-loader": "^4.0.0",
"ts-jest": "^29.0.2", "ts-jest": "^29.0.2",

View File

@@ -1,9 +1,9 @@
import { import {
APIResponse, APIResponse,
BrokerProductType, BrokerProductType,
BrokerSubWithdrawalRequest,
BrokerSubAPIKeyModifyRequest, BrokerSubAPIKeyModifyRequest,
BrokerSubListRequest, BrokerSubListRequest,
BrokerSubWithdrawalRequest,
} from './types'; } from './types';
import { REST_CLIENT_TYPE_ENUM } from './util'; import { REST_CLIENT_TYPE_ENUM } from './util';
import BaseRestClient from './util/BaseRestClient'; import BaseRestClient from './util/BaseRestClient';

View File

@@ -1,32 +1,32 @@
import { import {
APIResponse, APIResponse,
FuturesProductType, CancelFuturesPlanTPSL,
FuturesAccount,
FuturesAccountBillRequest, FuturesAccountBillRequest,
FuturesBusinessBillRequest, FuturesBusinessBillRequest,
NewFuturesOrder, FuturesCandleData,
NewBatchFuturesOrder, FuturesHistoricPositions,
FuturesKlineInterval,
FuturesMarginMode,
FuturesMarketTrade,
FuturesPagination, FuturesPagination,
NewFuturesPlanOrder, FuturesPlanType,
FuturesPosition,
FuturesProductType,
FuturesSymbolRule,
GetHistoricTradesParams,
HistoricPlanOrderTPSLRequest,
ModifyFuturesOrder,
ModifyFuturesPlanOrder, ModifyFuturesPlanOrder,
ModifyFuturesPlanOrderTPSL, ModifyFuturesPlanOrderTPSL,
NewFuturesPlanPositionTPSL,
ModifyFuturesPlanStopOrder, ModifyFuturesPlanStopOrder,
CancelFuturesPlanTPSL, NewBatchFuturesOrder,
HistoricPlanOrderTPSLRequest, NewFuturesOrder,
NewFuturesPlanOrder,
NewFuturesPlanPositionTPSL,
NewFuturesPlanStopOrder, NewFuturesPlanStopOrder,
FuturesAccount,
FuturesSymbolRule,
FuturesMarginMode,
FuturesPosition,
NewFuturesPlanTrailingStopOrder, NewFuturesPlanTrailingStopOrder,
VIPFeeRate, VIPFeeRate,
GetHistoricTradesParams,
FuturesMarketTrade,
FuturesPlanType,
FuturesKlineInterval,
FuturesHistoricPositions,
ModifyFuturesOrder,
FuturesCandleData,
} from './types'; } from './types';
import { REST_CLIENT_TYPE_ENUM } from './util'; import { REST_CLIENT_TYPE_ENUM } from './util';
import BaseRestClient from './util/BaseRestClient'; import BaseRestClient from './util/BaseRestClient';
@@ -107,7 +107,7 @@ export class FuturesClient extends BaseRestClient {
startTime: string, startTime: string,
endTime: string, endTime: string,
limit?: string, limit?: string,
kLineType?: 'market' | 'mark' | 'index' kLineType?: 'market' | 'mark' | 'index',
): Promise<APIResponse<FuturesCandleData[]>> { ): Promise<APIResponse<FuturesCandleData[]>> {
return this.get('/api/mix/v1/market/candles', { return this.get('/api/mix/v1/market/candles', {
symbol, symbol,

View File

@@ -1,10 +1,10 @@
export * from './rest-client-v2';
export * from './broker-client'; export * from './broker-client';
export * from './constants/enum';
export * from './futures-client'; export * from './futures-client';
export * from './rest-client-v2';
export * from './spot-client'; export * from './spot-client';
export * from './types';
export * from './util';
export * from './util/logger';
export * from './websocket-client'; export * from './websocket-client';
export * from './websocket-client-v2'; export * from './websocket-client-v2';
export * from './util/logger';
export * from './util';
export * from './types';
export * from './constants/enum';

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,27 @@
import { import {
APIResponse,
BatchCancelSpotOrderV2,
CancelSpotOrderV2,
CancelSpotPlanOrderParams,
CoinBalance,
GetHistoricPlanOrdersParams,
GetHistoricTradesParams,
GetSpotPlanOrdersParams,
ModifySpotPlanOrder,
NewBatchSpotOrder, NewBatchSpotOrder,
NewSpotOrder, NewSpotOrder,
NewWalletTransfer, NewSpotPlanOrder,
Pagination,
APIResponse,
CoinBalance,
SymbolRules,
NewSpotSubTransfer, NewSpotSubTransfer,
NewSpotWithdraw, NewSpotWithdraw,
CancelSpotOrderV2, NewWalletTransfer,
BatchCancelSpotOrderV2, Pagination,
SpotOrderResult,
NewSpotPlanOrder,
ModifySpotPlanOrder,
CancelSpotPlanOrderParams,
GetSpotPlanOrdersParams,
SpotPlanOrder,
GetHistoricPlanOrdersParams,
SpotMarketTrade,
GetHistoricTradesParams,
VIPFeeRate,
SpotKlineInterval,
SpotCandleData, SpotCandleData,
SpotKlineInterval,
SpotMarketTrade,
SpotOrderResult,
SpotPlanOrder,
SymbolRules,
VIPFeeRate,
} from './types'; } from './types';
import { REST_CLIENT_TYPE_ENUM } from './util'; import { REST_CLIENT_TYPE_ENUM } from './util';
import BaseRestClient from './util/BaseRestClient'; import BaseRestClient from './util/BaseRestClient';

View File

@@ -1,4 +1,4 @@
export * from './response';
export * from './request'; export * from './request';
export * from './response';
export * from './shared'; export * from './shared';
export * from './websockets'; export * from './websockets';

View File

@@ -1,11 +1,11 @@
export * from './shared';
export * from './v1/brokerV1'; export * from './v1/brokerV1';
export * from './v1/futuresV1'; export * from './v1/futuresV1';
export * from './v1/spotV1'; export * from './v1/spotV1';
export * from './v2/futures';
export * from './v2/spot';
export * from './v2/broker'; export * from './v2/broker';
export * from './v2/margin'; export * from './v2/common';
export * from './v2/copytrading'; export * from './v2/copytrading';
export * from './v2/earn'; export * from './v2/earn';
export * from './shared'; export * from './v2/futures';
export * from './v2/common'; export * from './v2/margin';
export * from './v2/spot';

View File

@@ -1,3 +1,3 @@
export * from './futures';
export * from './shared'; export * from './shared';
export * from './spot'; export * from './spot';
export * from './futures';

View File

@@ -27,4 +27,4 @@ export type KlineInterval =
| '1Mutc'; | '1Mutc';
export type RestClientType = export type RestClientType =
typeof REST_CLIENT_TYPE_ENUM[keyof typeof REST_CLIENT_TYPE_ENUM]; (typeof REST_CLIENT_TYPE_ENUM)[keyof typeof REST_CLIENT_TYPE_ENUM];

View File

@@ -28,15 +28,6 @@ export interface WsSnapshotPositionsEvent extends WsBaseEvent<'snapshot'> {
}; };
} }
export interface WsAccountSnapshotUMCBL extends WsBaseEvent<'snapshot'> {
arg: {
instType: 'umcbl';
channel: 'account';
instId: string;
};
data: WsAccountSnapshotDataUMCBL[];
}
export interface WsAccountSnapshotDataUMCBL { export interface WsAccountSnapshotDataUMCBL {
marginCoin: string; marginCoin: string;
locked: string; locked: string;
@@ -47,13 +38,13 @@ export interface WsAccountSnapshotDataUMCBL {
usdtEquity: string; usdtEquity: string;
} }
export interface WSPositionSnapshotUMCBL extends WsBaseEvent<'snapshot'> { export interface WsAccountSnapshotUMCBL extends WsBaseEvent<'snapshot'> {
arg: { arg: {
instType: 'umcbl'; instType: 'umcbl';
channel: 'positions'; channel: 'account';
instId: string; instId: string;
}; };
data: WsPositionSnapshotDataUMCBL[]; data: WsAccountSnapshotDataUMCBL[];
} }
export interface WsPositionSnapshotDataUMCBL { export interface WsPositionSnapshotDataUMCBL {
@@ -80,3 +71,12 @@ export interface WsPositionSnapshotDataUMCBL {
uTime: string; uTime: string;
markPrice: string; markPrice: string;
} }
export interface WSPositionSnapshotUMCBL extends WsBaseEvent<'snapshot'> {
arg: {
instType: 'umcbl';
channel: 'positions';
instId: string;
};
data: WsPositionSnapshotDataUMCBL[];
}

View File

@@ -1,15 +1,15 @@
import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios'; import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios';
import { RestClientType } from '../types';
import { RestClientType } from '../types';
import { signMessage } from './node-support'; import { signMessage } from './node-support';
import { import {
getRestBaseUrl,
RestClientOptions, RestClientOptions,
serializeParams, serializeParams,
getRestBaseUrl,
} from './requestUtils'; } from './requestUtils';
import { neverGuard } from './websocket-util'; import { neverGuard } from './websocket-util';
interface SignedRequest<T extends object | undefined = {}> { interface SignedRequest<T extends object | undefined = object> {
originalParams: T; originalParams: T;
paramsWithSign?: T & { sign: string }; paramsWithSign?: T & { sign: string };
serializedParams: string; serializedParams: string;
@@ -19,7 +19,7 @@ interface SignedRequest<T extends object | undefined = {}> {
recvWindow: number; recvWindow: number;
} }
interface UnsignedRequest<T extends object | undefined = {}> { interface UnsignedRequest<T extends object | undefined = object> {
originalParams: T; originalParams: T;
paramsWithSign: T; paramsWithSign: T;
} }
@@ -70,10 +70,15 @@ if (ENABLE_HTTP_TRACE) {
export default abstract class BaseRestClient { export default abstract class BaseRestClient {
private options: RestClientOptions; private options: RestClientOptions;
private baseUrl: string; private baseUrl: string;
private globalRequestOptions: AxiosRequestConfig; private globalRequestOptions: AxiosRequestConfig;
private apiKey: string | undefined; private apiKey: string | undefined;
private apiSecret: string | undefined; private apiSecret: string | undefined;
private apiPass: string | undefined; private apiPass: string | undefined;
/** Defines the client type (affecting how requests & signatures behave) */ /** Defines the client type (affecting how requests & signatures behave) */
@@ -229,7 +234,7 @@ export default abstract class BaseRestClient {
/** /**
* @private sign request and set recv window * @private sign request and set recv window
*/ */
private async signRequest<T extends object | undefined = {}>( private async signRequest<T extends object | undefined = object>(
data: T, data: T,
endpoint: string, endpoint: string,
method: Method, method: Method,
@@ -292,6 +297,7 @@ export default abstract class BaseRestClient {
params?: TParams, params?: TParams,
isPublicApi?: true, isPublicApi?: true,
): Promise<UnsignedRequest<TParams>>; ): Promise<UnsignedRequest<TParams>>;
private async prepareSignParams<TParams extends object | undefined>( private async prepareSignParams<TParams extends object | undefined>(
method: Method, method: Method,
endpoint: string, endpoint: string,
@@ -299,6 +305,7 @@ export default abstract class BaseRestClient {
params?: TParams, params?: TParams,
isPublicApi?: false | undefined, isPublicApi?: false | undefined,
): Promise<SignedRequest<TParams>>; ): Promise<SignedRequest<TParams>>;
private async prepareSignParams<TParams extends object | undefined>( private async prepareSignParams<TParams extends object | undefined>(
method: Method, method: Method,
endpoint: string, endpoint: string,

View File

@@ -1,12 +1,16 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
import EventEmitter from 'events'; import EventEmitter from 'events';
import WebSocket from 'isomorphic-ws'; import WebSocket from 'isomorphic-ws';
import { WebsocketClientOptions, WSClientConfigurableOptions } from '../types'; import {
import WsStore from './WsStore'; WebsocketClientOptions,
import { WsConnectionStateEnum } from './WsStore.types'; WSClientConfigurableOptions,
} from '../types/index';
import { DefaultLogger } from './logger'; import { DefaultLogger } from './logger';
import { isWsPong } from './requestUtils'; import { isWsPong } from './requestUtils';
import { getWsAuthSignature } from './websocket-util'; import { getWsAuthSignature } from './websocket-util';
import WsStore from './WsStore';
import { WsConnectionStateEnum } from './WsStore.types';
interface WSClientEventMap<WsKey extends string> { interface WSClientEventMap<WsKey extends string> {
/** Connection opened. If this connection was previously opened and reconnected, expect the reconnected event instead */ /** Connection opened. If this connection was previously opened and reconnected, expect the reconnected event instead */
@@ -30,6 +34,7 @@ interface WSClientEventMap<WsKey extends string> {
// Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837 // Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837
export interface BaseWebsocketClient< export interface BaseWebsocketClient<
TWSKey extends string, TWSKey extends string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
TWSTopicSubscribeEventArgs extends object, TWSTopicSubscribeEventArgs extends object,
> { > {
on<U extends keyof WSClientEventMap<TWSKey>>( on<U extends keyof WSClientEventMap<TWSKey>>(
@@ -43,8 +48,6 @@ export interface BaseWebsocketClient<
): boolean; ): boolean;
} }
export interface BaseWSClientImpl {}
const LOGGER_CATEGORY = { category: 'bitget-ws' }; const LOGGER_CATEGORY = { category: 'bitget-ws' };
export abstract class BaseWebsocketClient< export abstract class BaseWebsocketClient<
@@ -54,6 +57,7 @@ export abstract class BaseWebsocketClient<
private wsStore: WsStore<TWSKey, TWSTopicSubscribeEventArgs>; private wsStore: WsStore<TWSKey, TWSTopicSubscribeEventArgs>;
protected logger: typeof DefaultLogger; protected logger: typeof DefaultLogger;
protected options: WebsocketClientOptions; protected options: WebsocketClientOptions;
constructor( constructor(
@@ -84,7 +88,9 @@ export abstract class BaseWebsocketClient<
): boolean; ): boolean;
protected abstract shouldAuthOnConnect(wsKey: TWSKey): boolean; protected abstract shouldAuthOnConnect(wsKey: TWSKey): boolean;
protected abstract getWsUrl(wsKey: TWSKey): string; protected abstract getWsUrl(wsKey: TWSKey): string;
protected abstract getMaxTopicsPerSubscribeEvent( protected abstract getMaxTopicsPerSubscribeEvent(
wsKey: TWSKey, wsKey: TWSKey,
): number | null; ): number | null;
@@ -277,7 +283,7 @@ export abstract class BaseWebsocketClient<
recvWindow, recvWindow,
); );
this.logger.info(`Sending auth request...`, { this.logger.info('Sending auth request...', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsKey, wsKey,
}); });
@@ -382,7 +388,7 @@ export abstract class BaseWebsocketClient<
this.logger.silly( this.logger.silly(
`Subscribing to topics in batches of ${maxTopicsPerEvent}`, `Subscribing to topics in batches of ${maxTopicsPerEvent}`,
); );
for (var i = 0; i < topics.length; i += maxTopicsPerEvent) { for (let i = 0; i < topics.length; i += maxTopicsPerEvent) {
const batch = topics.slice(i, i + maxTopicsPerEvent); const batch = topics.slice(i, i + maxTopicsPerEvent);
this.logger.silly(`Subscribing to batch of ${batch.length}`); this.logger.silly(`Subscribing to batch of ${batch.length}`);
this.requestSubscribeTopics(wsKey, batch); this.requestSubscribeTopics(wsKey, batch);
@@ -417,7 +423,7 @@ export abstract class BaseWebsocketClient<
this.logger.silly( this.logger.silly(
`Unsubscribing to topics in batches of ${maxTopicsPerEvent}`, `Unsubscribing to topics in batches of ${maxTopicsPerEvent}`,
); );
for (var i = 0; i < topics.length; i += maxTopicsPerEvent) { for (let i = 0; i < topics.length; i += maxTopicsPerEvent) {
const batch = topics.slice(i, i + maxTopicsPerEvent); const batch = topics.slice(i, i + maxTopicsPerEvent);
this.logger.silly(`Unsubscribing to batch of ${batch.length}`); this.logger.silly(`Unsubscribing to batch of ${batch.length}`);
this.requestUnsubscribeTopics(wsKey, batch); this.requestUnsubscribeTopics(wsKey, batch);
@@ -438,7 +444,7 @@ export abstract class BaseWebsocketClient<
public tryWsSend(wsKey: TWSKey, wsMessage: string) { public tryWsSend(wsKey: TWSKey, wsMessage: string) {
try { try {
this.logger.silly(`Sending upstream ws message: `, { this.logger.silly('Sending upstream ws message: ', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsMessage, wsMessage,
wsKey, wsKey,
@@ -456,7 +462,7 @@ export abstract class BaseWebsocketClient<
} }
ws.send(wsMessage); ws.send(wsMessage);
} catch (e) { } catch (e) {
this.logger.error(`Failed to send WS message`, { this.logger.error('Failed to send WS message', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsMessage, wsMessage,
wsKey, wsKey,
@@ -549,7 +555,7 @@ export abstract class BaseWebsocketClient<
if (typeof msg === 'object') { if (typeof msg === 'object') {
if (typeof msg['code'] === 'number') { if (typeof msg['code'] === 'number') {
if (msg.event === 'login' && msg.code === 0) { if (msg.event === 'login' && msg.code === 0) {
this.logger.info(`Successfully authenticated WS client`, { this.logger.info('Successfully authenticated WS client', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsKey, wsKey,
}); });
@@ -562,7 +568,7 @@ export abstract class BaseWebsocketClient<
if (msg['event']) { if (msg['event']) {
if (msg.event === 'error') { if (msg.event === 'error') {
this.logger.error(`WS Error received`, { this.logger.error('WS Error received', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsKey, wsKey,
message: msg || 'no message', message: msg || 'no message',

View File

@@ -1,4 +1,5 @@
import WebSocket from 'isomorphic-ws'; import WebSocket from 'isomorphic-ws';
import { DefaultLogger } from './logger'; import { DefaultLogger } from './logger';
import { WsConnectionStateEnum, WsStoredState } from './WsStore.types'; import { WsConnectionStateEnum, WsStoredState } from './WsStore.types';
@@ -17,6 +18,7 @@ export default class WsStore<
> { > {
private wsState: Record<string, WsStoredState<TWSTopicSubscribeEventArgs>> = private wsState: Record<string, WsStoredState<TWSTopicSubscribeEventArgs>> =
{}; {};
private logger: typeof DefaultLogger; private logger: typeof DefaultLogger;
constructor(logger: typeof DefaultLogger) { constructor(logger: typeof DefaultLogger) {
@@ -28,10 +30,12 @@ export default class WsStore<
key: WsKey, key: WsKey,
createIfMissing?: true, createIfMissing?: true,
): WsStoredState<TWSTopicSubscribeEventArgs>; ): WsStoredState<TWSTopicSubscribeEventArgs>;
get( get(
key: WsKey, key: WsKey,
createIfMissing?: false, createIfMissing?: false,
): WsStoredState<TWSTopicSubscribeEventArgs> | undefined; ): WsStoredState<TWSTopicSubscribeEventArgs> | undefined;
get( get(
key: WsKey, key: WsKey,
createIfMissing?: boolean, createIfMissing?: boolean,

View File

@@ -40,6 +40,7 @@ export async function signMessage(
return _arrayBufferToBase64(signature); return _arrayBufferToBase64(signature);
} }
default: { default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
((x: never) => {})(method); ((x: never) => {})(method);
throw new Error(`Unhandled sign method: ${method}`); throw new Error(`Unhandled sign method: ${method}`);
} }

View File

@@ -1,6 +1,6 @@
export * from './BaseRestClient'; export * from './BaseRestClient';
export * from './requestUtils';
export * from './WsStore';
export * from './logger'; export * from './logger';
export * from './requestUtils';
export * from './type-guards'; export * from './type-guards';
export * from './websocket-util'; export * from './websocket-util';
export * from './WsStore';

View File

@@ -1,6 +1,7 @@
export type LogParams = null | any; export type LogParams = null | any;
export const DefaultLogger = { export const DefaultLogger = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
silly: (...params: LogParams): void => { silly: (...params: LogParams): void => {
// console.log(params); // console.log(params);
}, },

View File

@@ -16,6 +16,7 @@ export async function signMessage(
return hmac.digest().toString('base64'); return hmac.digest().toString('base64');
} }
default: { default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
((x: never) => {})(method); ((x: never) => {})(method);
throw new Error(`Unhandled sign method: ${method}`); throw new Error(`Unhandled sign method: ${method}`);
} }

View File

@@ -34,7 +34,7 @@ export interface RestClientOptions {
parseExceptions?: boolean; parseExceptions?: boolean;
} }
export function serializeParams<T extends object | undefined = {}>( export function serializeParams<T extends object | undefined = object>(
params: T, params: T,
strict_validation = false, strict_validation = false,
encodeValues: boolean = true, encodeValues: boolean = true,

View File

@@ -78,7 +78,7 @@ export function isWsFuturesPositionsSnapshotEvent(
*/ */
export function assertMarginType(marginType: string): marginType is MarginType { export function assertMarginType(marginType: string): marginType is MarginType {
if (marginType !== 'isolated' && marginType !== 'crossed') { if (marginType !== 'isolated' && marginType !== 'crossed') {
throw new Error(`MarginType should be one of: crossed | isolated`); throw new Error('MarginType should be one of: crossed | isolated');
} }
return true; return true;
} }

View File

@@ -95,6 +95,7 @@ export function isPrivateChannel<TChannel extends string>(
export function getWsKeyForTopic( export function getWsKeyForTopic(
subscribeEvent: WsTopicSubscribeEventArgs, subscribeEvent: WsTopicSubscribeEventArgs,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isPrivate?: boolean, isPrivate?: boolean,
): WsKey { ): WsKey {
const instType = subscribeEvent.instType.toUpperCase() as BitgetInstType; const instType = subscribeEvent.instType.toUpperCase() as BitgetInstType;
@@ -137,7 +138,7 @@ export function getMaxTopicsPerSubscribeEvent(wsKey: WsKey): number | null {
return 15; return 15;
} }
default: { default: {
throw neverGuard(wsKey, `getWsKeyForTopic(): Unhandled wsKey`); throw neverGuard(wsKey, 'getWsKeyForTopic(): Unhandled wsKey');
} }
} }
} }
@@ -161,7 +162,7 @@ export async function getWsAuthSignature(
}> { }> {
if (!apiKey || !apiSecret || !apiPass) { if (!apiKey || !apiSecret || !apiPass) {
throw new Error( throw new Error(
`Cannot auth - missing api key, secret or passcode in config`, 'Cannot auth - missing api key, secret or passcode in config',
); );
} }
const signatureExpiresAt = ((Date.now() + recvWindow) / 1000).toFixed(0); const signatureExpiresAt = ((Date.now() + recvWindow) / 1000).toFixed(0);

View File

@@ -12,17 +12,15 @@ import {
WsTopicSubscribePrivateInstIdArgsV2, WsTopicSubscribePrivateInstIdArgsV2,
WsTopicV2, WsTopicV2,
} from './types'; } from './types';
import { import {
WS_AUTH_ON_CONNECT_KEYS,
WS_KEY_MAP,
DefaultLogger, DefaultLogger,
WS_BASE_URL_MAP,
neverGuard,
getMaxTopicsPerSubscribeEvent, getMaxTopicsPerSubscribeEvent,
isPrivateChannel, isPrivateChannel,
neverGuard,
WS_AUTH_ON_CONNECT_KEYS,
WS_BASE_URL_MAP,
WS_KEY_MAP,
} from './util'; } from './util';
import { BaseWebsocketClient } from './util/BaseWSClient'; import { BaseWebsocketClient } from './util/BaseWSClient';
const LOGGER_CATEGORY = { category: 'bitget-ws' }; const LOGGER_CATEGORY = { category: 'bitget-ws' };
@@ -38,6 +36,7 @@ export class WebsocketClientV2 extends BaseWebsocketClient<
WsTopicSubscribeEventArgsV2 WsTopicSubscribeEventArgsV2
> { > {
protected logger: typeof DefaultLogger; protected logger: typeof DefaultLogger;
protected options: WebsocketClientOptions; protected options: WebsocketClientOptions;
protected getWsKeyForTopic( protected getWsKeyForTopic(
@@ -70,7 +69,7 @@ export class WebsocketClientV2 extends BaseWebsocketClient<
case WS_KEY_MAP.spotv1: case WS_KEY_MAP.spotv1:
case WS_KEY_MAP.mixv1: { case WS_KEY_MAP.mixv1: {
throw new Error( throw new Error(
`Use the WebsocketClient instead of WebsocketClientV2 for V1 websockets`, 'Use the WebsocketClient instead of WebsocketClientV2 for V1 websockets',
); );
} }
case WS_KEY_MAP.v2Private: { case WS_KEY_MAP.v2Private: {
@@ -84,7 +83,7 @@ export class WebsocketClientV2 extends BaseWebsocketClient<
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsKey, wsKey,
}); });
throw neverGuard(wsKey, `getWsUrl(): Unhandled wsKey`); throw neverGuard(wsKey, 'getWsUrl(): Unhandled wsKey');
} }
} }
} }

View File

@@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import WebSocket from 'isomorphic-ws'; import WebSocket from 'isomorphic-ws';
import WsStore from './util/WsStore';
import { import {
BitgetInstType, BitgetInstType,
WebsocketClientOptions, WebsocketClientOptions,
@@ -11,19 +10,19 @@ import {
WsTopic, WsTopic,
WsTopicSubscribeEventArgs, WsTopicSubscribeEventArgs,
} from './types'; } from './types';
import { import {
isWsPong,
WS_AUTH_ON_CONNECT_KEYS,
WS_KEY_MAP,
DefaultLogger, DefaultLogger,
WS_BASE_URL_MAP,
getWsKeyForTopic,
neverGuard,
getMaxTopicsPerSubscribeEvent, getMaxTopicsPerSubscribeEvent,
isPrivateChannel,
getWsAuthSignature, getWsAuthSignature,
getWsKeyForTopic,
isPrivateChannel,
isWsPong,
neverGuard,
WS_AUTH_ON_CONNECT_KEYS,
WS_BASE_URL_MAP,
WS_KEY_MAP,
} from './util'; } from './util';
import WsStore from './util/WsStore';
import { WsConnectionStateEnum } from './util/WsStore.types'; import { WsConnectionStateEnum } from './util/WsStore.types';
const LOGGER_CATEGORY = { category: 'bitget-ws' }; const LOGGER_CATEGORY = { category: 'bitget-ws' };
@@ -74,7 +73,9 @@ export declare interface WebsocketClient {
*/ */
export class WebsocketClient extends EventEmitter { export class WebsocketClient extends EventEmitter {
private logger: typeof DefaultLogger; private logger: typeof DefaultLogger;
private options: WebsocketClientOptions; private options: WebsocketClientOptions;
private wsStore: WsStore<WsKey, WsTopicSubscribeEventArgs>; private wsStore: WsStore<WsKey, WsTopicSubscribeEventArgs>;
constructor( constructor(
@@ -145,6 +146,7 @@ export class WebsocketClient extends EventEmitter {
} }
}); });
} }
/** /**
* Unsubscribe from topics & remove them from memory. They won't be re-subscribed to if the connection reconnects. * 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 wsTopics topic or list of topics
@@ -282,7 +284,7 @@ export class WebsocketClient extends EventEmitter {
recvWindow, recvWindow,
); );
this.logger.info(`Sending auth request...`, { this.logger.info('Sending auth request...', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsKey, wsKey,
}); });
@@ -387,7 +389,7 @@ export class WebsocketClient extends EventEmitter {
this.logger.silly( this.logger.silly(
`Subscribing to topics in batches of ${maxTopicsPerEvent}`, `Subscribing to topics in batches of ${maxTopicsPerEvent}`,
); );
for (var i = 0; i < topics.length; i += maxTopicsPerEvent) { for (let i = 0; i < topics.length; i += maxTopicsPerEvent) {
const batch = topics.slice(i, i + maxTopicsPerEvent); const batch = topics.slice(i, i + maxTopicsPerEvent);
this.logger.silly(`Subscribing to batch of ${batch.length}`); this.logger.silly(`Subscribing to batch of ${batch.length}`);
this.requestSubscribeTopics(wsKey, batch); this.requestSubscribeTopics(wsKey, batch);
@@ -422,7 +424,7 @@ export class WebsocketClient extends EventEmitter {
this.logger.silly( this.logger.silly(
`Unsubscribing to topics in batches of ${maxTopicsPerEvent}`, `Unsubscribing to topics in batches of ${maxTopicsPerEvent}`,
); );
for (var i = 0; i < topics.length; i += maxTopicsPerEvent) { for (let i = 0; i < topics.length; i += maxTopicsPerEvent) {
const batch = topics.slice(i, i + maxTopicsPerEvent); const batch = topics.slice(i, i + maxTopicsPerEvent);
this.logger.silly(`Unsubscribing to batch of ${batch.length}`); this.logger.silly(`Unsubscribing to batch of ${batch.length}`);
this.requestUnsubscribeTopics(wsKey, batch); this.requestUnsubscribeTopics(wsKey, batch);
@@ -443,7 +445,7 @@ export class WebsocketClient extends EventEmitter {
public tryWsSend(wsKey: WsKey, wsMessage: string) { public tryWsSend(wsKey: WsKey, wsMessage: string) {
try { try {
this.logger.silly(`Sending upstream ws message: `, { this.logger.silly('Sending upstream ws message: ', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsMessage, wsMessage,
wsKey, wsKey,
@@ -461,7 +463,7 @@ export class WebsocketClient extends EventEmitter {
} }
ws.send(wsMessage); ws.send(wsMessage);
} catch (e) { } catch (e) {
this.logger.error(`Failed to send WS message`, { this.logger.error('Failed to send WS message', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsMessage, wsMessage,
wsKey, wsKey,
@@ -554,7 +556,7 @@ export class WebsocketClient extends EventEmitter {
if (typeof msg === 'object') { if (typeof msg === 'object') {
if (typeof msg['code'] === 'number') { if (typeof msg['code'] === 'number') {
if (msg.event === 'login' && msg.code === 0) { if (msg.event === 'login' && msg.code === 0) {
this.logger.info(`Successfully authenticated WS client`, { this.logger.info('Successfully authenticated WS client', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsKey, wsKey,
}); });
@@ -567,7 +569,7 @@ export class WebsocketClient extends EventEmitter {
if (msg['event']) { if (msg['event']) {
if (msg.event === 'error') { if (msg.event === 'error') {
this.logger.error(`WS Error received`, { this.logger.error('WS Error received', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsKey, wsKey,
message: msg || 'no message', message: msg || 'no message',
@@ -649,14 +651,14 @@ export class WebsocketClient extends EventEmitter {
} }
case WS_KEY_MAP.v2Private: case WS_KEY_MAP.v2Private:
case WS_KEY_MAP.v2Public: { case WS_KEY_MAP.v2Public: {
throw new Error(`Use the WebsocketClientV2 for V2 websockets`); throw new Error('Use the WebsocketClientV2 for V2 websockets');
} }
default: { default: {
this.logger.error('getWsUrl(): Unhandled wsKey: ', { this.logger.error('getWsUrl(): Unhandled wsKey: ', {
...LOGGER_CATEGORY, ...LOGGER_CATEGORY,
wsKey, wsKey,
}); });
throw neverGuard(wsKey, `getWsUrl(): Unhandled wsKey`); throw neverGuard(wsKey, 'getWsUrl(): Unhandled wsKey');
} }
} }
} }

View File

@@ -20,9 +20,9 @@ describe('Private Broker REST API GET Endpoints', () => {
const coin = 'BTC'; const coin = 'BTC';
const subUid = '123456'; const subUid = '123456';
const timestampOneHourAgo = new Date().getTime() - 1000 * 60 * 60; // const timestampOneHourAgo = new Date().getTime() - 1000 * 60 * 60;
const from = timestampOneHourAgo.toFixed(0); // const from = timestampOneHourAgo.toFixed(0);
const to = String(Number(from) + 1000 * 60 * 30); // 30 minutes // const to = String(Number(from) + 1000 * 60 * 30); // 30 minutes
it('getBrokerInfo()', async () => { it('getBrokerInfo()', async () => {
try { try {

View File

@@ -18,11 +18,11 @@ describe('Private Broker REST API POST Endpoints', () => {
apiPass: API_PASS, apiPass: API_PASS,
}); });
const coin = 'BTC'; // const coin = 'BTC';
const subUid = '123456'; const subUid = '123456';
const timestampOneHourAgo = new Date().getTime() - 1000 * 60 * 60; // const timestampOneHourAgo = new Date().getTime() - 1000 * 60 * 60;
const from = timestampOneHourAgo.toFixed(0); // const from = timestampOneHourAgo.toFixed(0);
const to = String(Number(from) + 1000 * 60 * 30); // 30 minutes // const to = String(Number(from) + 1000 * 60 * 30); // 30 minutes
it('createSubAccount()', async () => { it('createSubAccount()', async () => {
try { try {

View File

@@ -22,9 +22,9 @@ describe('Private Futures REST API POST Endpoints', () => {
const symbol = 'BTCUSDT_UMCBL'; const symbol = 'BTCUSDT_UMCBL';
const marginCoin = 'USDT'; const marginCoin = 'USDT';
const timestampOneHourAgo = new Date().getTime() - 1000 * 60 * 60; // const timestampOneHourAgo = new Date().getTime() - 1000 * 60 * 60;
const from = timestampOneHourAgo.toFixed(0); // const from = timestampOneHourAgo.toFixed(0);
const to = String(Number(from) + 1000 * 60 * 30); // 30 minutes // const to = String(Number(from) + 1000 * 60 * 30); // 30 minutes
it('setLeverage()', async () => { it('setLeverage()', async () => {
try { try {

View File

@@ -1,9 +1,5 @@
import { API_ERROR_CODE, FuturesClient } from '../../src'; import { FuturesClient } from '../../src';
import { import { sucessEmptyResponseObject } from '../response.util';
notAuthenticatedError,
successResponseString,
sucessEmptyResponseObject,
} from '../response.util';
describe('Public Spot REST API Endpoints', () => { describe('Public Spot REST API Endpoints', () => {
const api = new FuturesClient(); const api = new FuturesClient();

View File

@@ -1,4 +1,4 @@
import { API_ERROR_CODE, SpotClient } from '../../src'; import { SpotClient } from '../../src';
import { sucessEmptyResponseObject } from '../response.util'; import { sucessEmptyResponseObject } from '../response.util';
describe('Private Spot REST API GET Endpoints', () => { describe('Private Spot REST API GET Endpoints', () => {

View File

@@ -1,6 +1,5 @@
import { API_ERROR_CODE, SpotClient } from '../../src'; import { SpotClient } from '../../src';
import { import {
notAuthenticatedError,
successResponseString, successResponseString,
sucessEmptyResponseObject, sucessEmptyResponseObject,
} from '../response.util'; } from '../response.util';
@@ -9,8 +8,6 @@ describe('Public Spot REST API Endpoints', () => {
const api = new SpotClient(); const api = new SpotClient();
const symbol = 'BTCUSDT_SPBL'; const symbol = 'BTCUSDT_SPBL';
const timestampOneHourAgo = new Date().getTime() / 1000 - 1000 * 60 * 60;
const from = Number(timestampOneHourAgo.toFixed(0));
// it('should throw for unauthenticated private calls', async () => { // it('should throw for unauthenticated private calls', async () => {
// expect(() => api.getOpenOrders()).rejects.toMatchObject( // expect(() => api.getOpenOrders()).rejects.toMatchObject(

View File

@@ -1,15 +1,10 @@
import { import {
WebsocketClient, WebsocketClient,
WSClientConfigurableOptions,
WS_ERROR_ENUM, WS_ERROR_ENUM,
WS_KEY_MAP, WS_KEY_MAP,
WSClientConfigurableOptions,
} from '../src'; } from '../src';
import { import { getSilentLogger, logAllEvents, waitForSocketEvent } from './ws.util';
getSilentLogger,
listenToSocketEvents,
logAllEvents,
waitForSocketEvent,
} from './ws.util';
describe.skip('Private Spot Websocket Client', () => { describe.skip('Private Spot Websocket Client', () => {
const API_KEY = process.env.API_KEY_COM; const API_KEY = process.env.API_KEY_COM;
@@ -50,7 +45,7 @@ describe.skip('Private Spot Websocket Client', () => {
try { try {
await Promise.all([wsResponsePromise]); await Promise.all([wsResponsePromise]);
} catch (e) { } catch (e) {
// console.error() console.error(e);
} }
badClient.closeAll(); badClient.closeAll();
}); });

View File

@@ -1,9 +1,9 @@
import { import {
WebsocketClient, WebsocketClient,
WSClientConfigurableOptions,
WS_KEY_MAP, WS_KEY_MAP,
WSClientConfigurableOptions,
} from '../src'; } from '../src';
import { logAllEvents, getSilentLogger, waitForSocketEvent } from './ws.util'; import { getSilentLogger, logAllEvents, waitForSocketEvent } from './ws.util';
describe('Public Spot Websocket Client', () => { describe('Public Spot Websocket Client', () => {
let wsClient: WebsocketClient; let wsClient: WebsocketClient;

View File

@@ -1,5 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { WebsocketClient } from '../src'; import { WebsocketClient } from '../src';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function getSilentLogger(logHint?: string) { export function getSilentLogger(logHint?: string) {
return { return {
silly: () => {}, silly: () => {},

18
tsconfig.linting.json Normal file
View File

@@ -0,0 +1,18 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "dist/cjs",
"target": "esnext",
"rootDir": "../",
"allowJs": true
},
"include": [
"src/**/*.*",
"test/**/*.*",
"examples/**/*.*",
".eslintrc.cjs",
"jest.config.ts",
"webpack/webpack.config.ts"
]
}

View File

@@ -1,6 +1,6 @@
const webpack = require('webpack');
const path = require('path'); const path = require('path');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
function generateConfig(name) { function generateConfig(name) {
var config = { var config = {
@@ -10,27 +10,29 @@ function generateConfig(name) {
filename: name + '.js', filename: name + '.js',
sourceMapFilename: name + '.map', sourceMapFilename: name + '.map',
library: name, library: name,
libraryTarget: 'umd' libraryTarget: 'umd',
}, },
devtool: "source-map", devtool: 'source-map',
mode: 'production', mode: 'production',
resolve: { resolve: {
// Add '.ts' and '.tsx' as resolvable extensions. // Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"], extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js'],
alias: { alias: {
[path.resolve(__dirname, "../lib/util/node-support.js")]: [path.resolve(__dirname, '../lib/util/node-support.js')]: path.resolve(
path.resolve(__dirname, "../lib/util/browser-support.js"), __dirname,
} '../lib/util/browser-support.js',
),
},
}, },
module: { module: {
rules: [ rules: [
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'. // All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
{ test: /\.tsx?$/, loader: "ts-loader" }, { test: /\.tsx?$/, loader: 'ts-loader' },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ test: /\.js$/, loader: "source-map-loader" }, { test: /\.js$/, loader: 'source-map-loader' },
{ {
test: /\.m?js$/, test: /\.m?js$/,
@@ -38,28 +40,30 @@ function generateConfig(name) {
use: { use: {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: [['@babel/preset-env', { presets: [
'targets': { [
'node': 'current' '@babel/preset-env',
} {
}]] targets: {
} node: 'current',
} },
} },
] ],
} ],
},
},
},
],
},
}; };
config.plugins = [ config.plugins = [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}),
new BundleAnalyzerPlugin({ new BundleAnalyzerPlugin({
defaultSizes: 'stat', defaultSizes: 'stat',
analyzerMode: 'static', analyzerMode: 'static',
reportFilename: '../doc/bundleReport.html', reportFilename: '../doc/bundleReport.html',
openAnalyzer: false, openAnalyzer: false,
}) }),
]; ];
return config; return config;