diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 699f141..021f68f 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,10 +5,15 @@ module.exports = { tsconfigRootDir: __dirname, sourceType: 'module', }, - plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'], + plugins: [ + '@typescript-eslint/eslint-plugin', + 'simple-import-sort', + 'require-extensions', + ], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', + 'plugin:require-extensions/recommended', ], root: true, env: { diff --git a/.github/workflows/e2etest.yml b/.github/workflows/e2etest.yml index f1ce991..951afb3 100644 --- a/.github/workflows/e2etest.yml +++ b/.github/workflows/e2etest.yml @@ -27,7 +27,10 @@ jobs: - name: Install run: npm ci --ignore-scripts - - name: Build + - name: Check Lint + run: npx eslint src + + - name: Check Build run: npm run build - name: Test diff --git a/examples/spot/submitMarketOrder.ts b/examples/spot/submitMarketOrder.ts index 2c2f6a7..2fbaebc 100644 --- a/examples/spot/submitMarketOrder.ts +++ b/examples/spot/submitMarketOrder.ts @@ -1,4 +1,4 @@ -import { RestClient } from '../../src'; // For an easy demonstration, import from the src dir. Normally though, see below to import from the npm installed version instead. +import { RestClient } from '../../src/index.js'; // For an easy demonstration, import from the src dir. Normally though, see below to import from the npm installed version instead. // import { RestClient } from 'gateio-api'; // Import the RestClient from the published version of this SDK, installed via NPM (npm install gateio-api) // Define the account object with API key and secret @@ -9,8 +9,8 @@ const account = { // Initialize the RestClient with the API credentials const gateRestClient = new RestClient({ - apiKey: account.key, - apiSecret: account.secret, + apiKey: 'yourkeyhere', + apiSecret: 'yoursecrethere', }); async function submitSpotOrder() { diff --git a/package-lock.json b/package-lock.json index 2fa8f60..4bd4cea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gateio-api", - "version": "1.0.8", + "version": "1.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gateio-api", - "version": "1.0.8", + "version": "1.0.10", "license": "MIT", "dependencies": { "axios": "^1.6.6", @@ -22,6 +22,7 @@ "eslint": "^8.29.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-require-extensions": "^0.1.3", "eslint-plugin-simple-import-sort": "^12.0.0", "jest": "^29.7.0", "ts-jest": "^29.1.2", @@ -2440,6 +2441,18 @@ } } }, + "node_modules/eslint-plugin-require-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-require-extensions/-/eslint-plugin-require-extensions-0.1.3.tgz", + "integrity": "sha512-T3c1PZ9PIdI3hjV8LdunfYI8gj017UQjzAnCrxuo3wAjneDbTPHdE3oNWInOjMA+z/aBkUtlW5vC0YepYMZIug==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "eslint": "*" + } + }, "node_modules/eslint-plugin-simple-import-sort": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.0.tgz", diff --git a/package.json b/package.json index 9e046e5..50ce40e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gateio-api", - "version": "1.0.9", + "version": "1.0.10", "description": "Complete & robust Node.js SDK for Gate.io's REST APIs, WebSockets & WebSocket APIs, with TypeScript declarations.", "scripts": { "clean": "rm -rf dist/*", @@ -39,6 +39,7 @@ "eslint": "^8.29.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-require-extensions": "^0.1.3", "eslint-plugin-simple-import-sort": "^12.0.0", "jest": "^29.7.0", "ts-jest": "^29.1.2", diff --git a/src/WebsocketClient.ts b/src/WebsocketClient.ts index f0f7f5a..c65e35c 100644 --- a/src/WebsocketClient.ts +++ b/src/WebsocketClient.ts @@ -17,7 +17,7 @@ import { WsMarket, WsTopicRequest, } from './lib/websocket/websocket-util.js'; -import { DeferredPromise } from './lib/websocket/WsStore.types.js'; +import { WSConnectedResult } from './lib/websocket/WsStore.types.js'; import { WSAPIRequest, WsOperation, @@ -43,7 +43,7 @@ export class WebsocketClient extends BaseWebsocketClient { * * Returns array of promises that individually resolve when each connection is successfully opened. */ - public connectAll(): (DeferredPromise['promise'] | undefined)[] { + public connectAll(): Promise[] { return [ this.connect(WS_KEY_MAP.spotV4), this.connect(WS_KEY_MAP.perpFuturesUSDTV4), @@ -254,15 +254,11 @@ export class WebsocketClient extends BaseWebsocketClient { wsKey, }); if (!wsKey) { - throw new Error( - 'Cannot send PONG due to no known websocket for this wsKey', - ); + throw new Error('Cannot send PONG, no wsKey provided'); } const wsState = this.getWsStore().get(wsKey); if (!wsState || !wsState?.ws) { - throw new Error( - `${wsKey} socket not connected yet, call "connectAll()" first then try again when the "open" event arrives`, - ); + throw new Error(`Cannot send pong, ${wsKey} socket not connected yet`); } // Send a protocol layer pong @@ -422,6 +418,12 @@ export class WebsocketClient extends BaseWebsocketClient { `!! Unhandled string event type "${eventAction}. Defaulting to "update" channel...`, parsed, ); + } else { + // TODO: test meee + this.logger.error( + `!! Unhandled non-string event type "${eventAction}. Defaulting to "update" channel...`, + parsed, + ); } results.push({ diff --git a/src/lib/BaseWSClient.ts b/src/lib/BaseWSClient.ts index 2c9f39f..236c6ea 100644 --- a/src/lib/BaseWSClient.ts +++ b/src/lib/BaseWSClient.ts @@ -15,7 +15,7 @@ import { } from './websocket/websocket-util.js'; import { WsStore } from './websocket/WsStore.js'; import { - DeferredPromise, + WSConnectedResult, WsConnectionStateEnum, } from './websocket/WsStore.types.js'; @@ -169,7 +169,7 @@ export abstract class BaseWebsocketClient< /** * Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library */ - protected abstract connectAll(): (DeferredPromise['promise'] | undefined)[]; + protected abstract connectAll(): Promise[]; protected isPrivateWsKey(wsKey: TWSKey): boolean { return this.getPrivateWSKeys().includes(wsKey); @@ -221,10 +221,8 @@ export abstract class BaseWebsocketClient< WsConnectionStateEnum.CONNECTED, ); - // TODO: test this, spam loads of subscribe topic calls in intervals (and add a log line on this logic) const isConnectionInProgress = - this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTING) || - this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.RECONNECTING); + this.wsStore.isConnectionAttemptInProgress(wsKey); // start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect if (!isConnected && !isConnectionInProgress) { @@ -362,19 +360,17 @@ export abstract class BaseWebsocketClient< */ protected async connect( wsKey: TWSKey, - ): Promise { + ): Promise { try { if (this.wsStore.isWsOpen(wsKey)) { this.logger.error( 'Refused to connect to ws with existing active connection', { ...WS_LOGGER_CATEGORY, wsKey }, ); - return this.wsStore.getConnectionInProgressPromise(wsKey); + return { wsKey }; } - if ( - this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTING) - ) { + if (this.wsStore.isConnectionAttemptInProgress(wsKey)) { this.logger.error( 'Refused to connect to ws, connection attempt already active', { ...WS_LOGGER_CATEGORY, wsKey }, @@ -459,7 +455,6 @@ export abstract class BaseWebsocketClient< ...WS_LOGGER_CATEGORY, wsKey, }); - // TODO:! const request = await this.getWsAuthRequestEvent(wsKey); @@ -473,10 +468,7 @@ export abstract class BaseWebsocketClient< private reconnectWithDelay(wsKey: TWSKey, connectionDelayMs: number) { this.clearTimers(wsKey); - if ( - this.wsStore.getConnectionState(wsKey) !== - WsConnectionStateEnum.CONNECTING - ) { + if (!this.wsStore.isConnectionAttemptInProgress(wsKey)) { this.setWsState(wsKey, WsConnectionStateEnum.RECONNECTING); } diff --git a/src/lib/requestUtils.ts b/src/lib/requestUtils.ts index 1d1480e..aedfa2c 100644 --- a/src/lib/requestUtils.ts +++ b/src/lib/requestUtils.ts @@ -1,6 +1,6 @@ import WebSocket from 'isomorphic-ws'; -import { GateBaseUrlKey } from '../types/shared'; +import { GateBaseUrlKey } from '../types/shared.js'; export interface RestClientOptions { /** Your API key */ diff --git a/src/lib/websocket/WsStore.ts b/src/lib/websocket/WsStore.ts index 4389afe..d50d4e2 100644 --- a/src/lib/websocket/WsStore.ts +++ b/src/lib/websocket/WsStore.ts @@ -3,6 +3,7 @@ import WebSocket from 'isomorphic-ws'; import { DefaultLogger } from '../logger.js'; import { DeferredPromise, + WSConnectedResult, WsConnectionStateEnum, WsStoredState, } from './WsStore.types.js'; @@ -254,7 +255,7 @@ export class WsStore< /** Get promise designed to track a connection attempt in progress. Resolves once connected. */ getConnectionInProgressPromise( wsKey: WsKey, - ): DeferredPromise | undefined { + ): DeferredPromise | undefined { return this.getDeferredPromise( wsKey, DEFERRED_PROMISE_REF.CONNECTION_IN_PROGRESS, @@ -269,7 +270,7 @@ export class WsStore< createConnectionInProgressPromise( wsKey: WsKey, throwIfExists: boolean, - ): DeferredPromise { + ): DeferredPromise { return this.createDeferredPromise( wsKey, DEFERRED_PROMISE_REF.CONNECTION_IN_PROGRESS, @@ -307,6 +308,19 @@ export class WsStore< return this.getConnectionState(key) === state; } + /** + * Check if we're currently in the process of opening a connection for any reason. Safer than only checking "CONNECTING" as the state + * @param key + * @returns + */ + isConnectionAttemptInProgress(key: WsKey): boolean { + const isConnectionInProgress = + this.isConnectionState(key, WsConnectionStateEnum.CONNECTING) || + this.isConnectionState(key, WsConnectionStateEnum.RECONNECTING); + + return isConnectionInProgress; + } + /* subscribed topics */ getTopics(key: WsKey): Set { diff --git a/src/lib/websocket/WsStore.types.ts b/src/lib/websocket/WsStore.types.ts index 96df534..c48d22b 100644 --- a/src/lib/websocket/WsStore.types.ts +++ b/src/lib/websocket/WsStore.types.ts @@ -15,6 +15,10 @@ export interface DeferredPromise { promise?: Promise; } +export interface WSConnectedResult { + wsKey: string; +} + export interface WsStoredState { /** The currently active websocket connection */ ws?: WebSocket; diff --git a/src/lib/websocket/websocket-util.ts b/src/lib/websocket/websocket-util.ts index 644f153..182818e 100644 --- a/src/lib/websocket/websocket-util.ts +++ b/src/lib/websocket/websocket-util.ts @@ -1,8 +1,8 @@ -import { WSAPIRequest } from '../../types/websockets/requests'; +import { WSAPIRequest } from '../../types/websockets/requests.js'; import { FuturesWSAPITopic, SpotWSAPITopic, -} from '../../types/websockets/wsAPI'; +} from '../../types/websockets/wsAPI.js'; /** * Should be one WS key per unique URL. Some URLs may need a suffix. diff --git a/src/types/request/rebate.ts b/src/types/request/rebate.ts index 47a9e31..e70580e 100644 --- a/src/types/request/rebate.ts +++ b/src/types/request/rebate.ts @@ -37,6 +37,3 @@ export interface PartnerTransactionReq { limit?: number; offset?: number; } - - - diff --git a/src/types/websockets/requests.ts b/src/types/websockets/requests.ts index 1f57b1f..97bc39b 100644 --- a/src/types/websockets/requests.ts +++ b/src/types/websockets/requests.ts @@ -1,5 +1,5 @@ -import { CHANNEL_ID } from '../../lib/requestUtils'; -import { WSAPITopic } from './wsAPI'; +import { CHANNEL_ID } from '../../lib/requestUtils.js'; +import { WSAPITopic } from './wsAPI.js'; export type WsOperation = 'subscribe' | 'unsubscribe' | 'auth'; diff --git a/src/types/websockets/wsAPI.ts b/src/types/websockets/wsAPI.ts index 679f0f4..3096fa5 100644 --- a/src/types/websockets/wsAPI.ts +++ b/src/types/websockets/wsAPI.ts @@ -1,16 +1,16 @@ -import { WsKey } from '../../lib/websocket/websocket-util'; +import { WsKey } from '../../lib/websocket/websocket-util.js'; import { GetFuturesOrdersReq, SubmitFuturesOrderReq, UpdateFuturesOrderReq, -} from '../request/futures'; +} from '../request/futures.js'; import { CancelSpotBatchOrdersReq, DeleteSpotOrderReq, GetSpotOrderReq, SubmitSpotOrderReq, UpdateSpotOrderReq, -} from '../request/spot'; +} from '../request/spot.js'; export type SpotWSAPITopic = | 'spot.login'