From 74cde6eb46179637218adbfdf0a3b1daa484275a Mon Sep 17 00:00:00 2001 From: ZeroWave022 <36341766+ZeroWave022@users.noreply.github.com> Date: Fri, 21 Apr 2023 21:11:54 +0200 Subject: [PATCH 1/4] build: Make tsconfig.json include all files in src --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 6255b06..cbc0edd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,6 @@ "outDir": "./dist", "strict": true }, - "include": ["src", ".eslintrc.js"], + "include": ["src/**/*", ".eslintrc.js"], "exclude": ["node_modules"] } From 6cc066f15a249d14962b6f55c5ba0069b4e01b18 Mon Sep 17 00:00:00 2001 From: ZeroWave022 <36341766+ZeroWave022@users.noreply.github.com> Date: Fri, 21 Apr 2023 21:15:27 +0200 Subject: [PATCH 2/4] feat: Add max polling retries to client --- src/index.ts | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index f8f01b5..a9d8c3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,10 +36,14 @@ export default class TipccClient extends EventEmitter { public pollingInterval = 10000; + public maxRetries = 5; + private polling = new Set(); private pollingTimeout: NodeJS.Timeout | null = null; + private pollingRetries = 0; + private lastPoll = new Date(); constructor( @@ -47,6 +51,7 @@ export default class TipccClient extends EventEmitter { options: { baseUrl?: string; pollingInterval?: number; + maxRetries?: number; } = {}, ) { super(); @@ -60,6 +65,7 @@ export default class TipccClient extends EventEmitter { }); if (options.pollingInterval) this.pollingInterval = options.pollingInterval; + if (options.maxRetries) this.maxRetries = options.maxRetries; Promise.all([ updateCurrenciesCache(this), @@ -72,15 +78,32 @@ export default class TipccClient extends EventEmitter { private async _poll(): Promise { const now = new Date(); - const { transactions } = (await this.REST.request( - 'GET', - '/account/transactions', - { + let transactions; + + // Retry until a successful reponse is received or max retries are reached + while (transactions === undefined) { + try { + transactions = ( + (await this.REST.request('GET', '/account/transactions', { types: [...this.polling], since: this.lastPoll.toISOString(), until: now.toISOString(), - }, - )) as APIRESTGetAccountTransactions; + })) as APIRESTGetAccountTransactions + ).transactions; + + break; + } catch { + this.pollingRetries += 1; + + if (this.pollingRetries >= this.maxRetries) + throw new Error( + `Failed ${this.pollingRetries} consecutive API polls. Is the API responding?`, + ); + } + } + + // Reset pollingRetries, as it should only increment if multiple consecutive requests don't succeed + if (this.pollingRetries > 0) this.pollingRetries = 0; for (const transaction of transactions) { if (!getCachedCryptoCurrency(transaction.amount.currency)) From 58895e661268a14ab6999d0d8ab15ed0fe3548f1 Mon Sep 17 00:00:00 2001 From: ZeroWave022 <36341766+ZeroWave022@users.noreply.github.com> Date: Fri, 21 Apr 2023 21:16:21 +0200 Subject: [PATCH 3/4] docs: Document client with JSDocs --- src/index.ts | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index a9d8c3b..6fa6ba6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,6 +46,11 @@ export default class TipccClient extends EventEmitter { private lastPoll = new Date(); + /** + * Create a tip.cc client. + * @param token The tip.cc API token to use + * @param options Optional options + */ constructor( token: string, options: { @@ -76,6 +81,9 @@ export default class TipccClient extends EventEmitter { }); } + /** + * Poll the tip.cc API for new data. + */ private async _poll(): Promise { const now = new Date(); let transactions; @@ -85,9 +93,9 @@ export default class TipccClient extends EventEmitter { try { transactions = ( (await this.REST.request('GET', '/account/transactions', { - types: [...this.polling], - since: this.lastPoll.toISOString(), - until: now.toISOString(), + types: [...this.polling], + since: this.lastPoll.toISOString(), + until: now.toISOString(), })) as APIRESTGetAccountTransactions ).transactions; @@ -140,6 +148,10 @@ export default class TipccClient extends EventEmitter { return this; } + /** + * Get a list of cryptocurrencies. + * @param cache Whether to use the cache (`true` by default) + */ public async getCryptoCurrencies(cache = true): Promise { const currencies = getCachedCryptoCurrencies(); if (currencies.length > 0 && cache) return currencies; @@ -147,6 +159,10 @@ export default class TipccClient extends EventEmitter { return getCachedCryptoCurrencies(); } + /** + * Get a list of fiat currencies. + * @param cache Whether to use the cache (`true` by default) + */ public async getFiatCurrencies(cache = true): Promise { const currencies = getCachedCryptoCurrencies(); if (currencies.length > 0 && cache) return currencies; @@ -154,6 +170,10 @@ export default class TipccClient extends EventEmitter { return getCachedCryptoCurrencies(); } + /** + * Get a list of transactions based on options. + * @param options Which options to use when requesting transactions + */ public async getTransactions( options: { types?: string[]; @@ -171,6 +191,9 @@ export default class TipccClient extends EventEmitter { return transactions.map((t) => new Transaction(t)); } + /** + * Get a list of exchange rates. + */ public async getExchangeRates(): Promise { const { rates } = (await this.REST.request( 'GET', @@ -179,6 +202,10 @@ export default class TipccClient extends EventEmitter { return rates.map((r) => new ExchangeRate(r)); } + /** + * Get a single transaction. + * @param id The transaction id + */ public async getTransaction(id: string): Promise { const { transaction } = (await this.REST.request( 'GET', @@ -188,6 +215,10 @@ export default class TipccClient extends EventEmitter { return new Transaction(transaction); } + /** + * Post a new tip. + * @param payload The post tip payload + */ public async postTip( payload: APIRESTPostTipPayload, ): Promise { @@ -198,6 +229,11 @@ export default class TipccClient extends EventEmitter { )) as APIRESTPostTips; } + /** + * Get a single wallet. + * @param currency The wallet currency + * @param fallback Whether to create an empty wallet if there's no API response + */ public async getWallet( currency: string, fallback = true, @@ -223,6 +259,9 @@ export default class TipccClient extends EventEmitter { ); } + /** + * Get all wallets. + */ public async getWallets(): Promise { const { wallets } = (await this.REST.request( 'GET', From 1daeb1f9df68dec30a568a8a9e42339c0d6250f2 Mon Sep 17 00:00:00 2001 From: Walledgarden Date: Fri, 21 Apr 2023 21:29:50 +0200 Subject: [PATCH 4/4] feat: Replace loop by do-while and optimize null check --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6fa6ba6..6c41b92 100644 --- a/src/index.ts +++ b/src/index.ts @@ -89,7 +89,7 @@ export default class TipccClient extends EventEmitter { let transactions; // Retry until a successful reponse is received or max retries are reached - while (transactions === undefined) { + do { try { transactions = ( (await this.REST.request('GET', '/account/transactions', { @@ -108,7 +108,7 @@ export default class TipccClient extends EventEmitter { `Failed ${this.pollingRetries} consecutive API polls. Is the API responding?`, ); } - } + } while(!transactions); // Reset pollingRetries, as it should only increment if multiple consecutive requests don't succeed if (this.pollingRetries > 0) this.pollingRetries = 0;