{"version":3,"file":"algo-http-client-with-retry.mjs","sources":["../../src/types/algo-http-client-with-retry.ts"],"sourcesContent":["import { IntDecoding, parseJSON, stringifyJSON } from 'algosdk'\nimport { BaseHTTPClientResponse, Query, URLTokenBaseHTTPClient } from 'algosdk/client'\nimport { Config } from '../config'\n\n/** A HTTP Client that wraps the Algorand SDK HTTP Client with retries */\nexport class AlgoHttpClientWithRetry extends URLTokenBaseHTTPClient {\n  private static readonly MAX_TRIES = 5\n  private static readonly MAX_BACKOFF_MS = 10000\n\n  // These lists come from https://visionmedia.github.io/superagent/#retrying-requests\n  // which is the underlying library used by algosdk - but the CloudFlare specific 52X status codes have been removed\n  private static readonly RETRY_STATUS_CODES = [408, 413, 429, 500, 502, 503, 504]\n  private static readonly RETRY_ERROR_CODES = [\n    'ETIMEDOUT',\n    'ECONNRESET',\n    'EADDRINUSE',\n    'ECONNREFUSED',\n    'EPIPE',\n    'ENOTFOUND',\n    'ENETUNREACH',\n    'EAI_AGAIN',\n    'EPROTO', // We get this intermittently with AlgoNode API\n  ]\n\n  private async callWithRetry(func: () => Promise<BaseHTTPClientResponse>): Promise<BaseHTTPClientResponse> {\n    let response: BaseHTTPClientResponse | undefined\n    let numTries = 1\n    do {\n      try {\n        response = await func()\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      } catch (err: any) {\n        if (numTries >= AlgoHttpClientWithRetry.MAX_TRIES) {\n          throw err\n        }\n        // Only retry for one of the hardcoded conditions\n        if (\n          !(\n            AlgoHttpClientWithRetry.RETRY_ERROR_CODES.includes(err.code) ||\n            AlgoHttpClientWithRetry.RETRY_STATUS_CODES.includes(Number(err.status)) ||\n            ('response' in err && AlgoHttpClientWithRetry.RETRY_STATUS_CODES.includes(Number(err.response.status)))\n          )\n        ) {\n          throw err\n        }\n        // Retry immediately the first time, then exponentially backoff.\n        const delayTimeMs = numTries == 1 ? 0 : Math.min(1000 * Math.pow(2, numTries - 1), AlgoHttpClientWithRetry.MAX_BACKOFF_MS)\n        if (delayTimeMs > 0) {\n          await new Promise((r) => setTimeout(r, delayTimeMs))\n        }\n        Config.logger.warn(`algosdk request failed ${numTries} times. Retrying in ${delayTimeMs}ms: ${err}`)\n      }\n    } while (!response && ++numTries <= AlgoHttpClientWithRetry.MAX_TRIES)\n    return response!\n  }\n\n  async get(relativePath: string, query?: Query<string>, requestHeaders: Record<string, string> = {}): Promise<BaseHTTPClientResponse> {\n    const response = await this.callWithRetry(() => super.get(relativePath, query, requestHeaders))\n    if (\n      relativePath.startsWith('/v2/accounts/') &&\n      relativePath.endsWith('/created-applications') &&\n      response.status === 200 &&\n      query?.['include-all']?.toString() === 'true'\n    ) {\n      // todo: Temporary hack\n      // Indexer get created applications by account returns approvalProgram and clearStateProgram as null, which breaks the algosdk@3 decoder\n      // instead we will detect this call and set them to empty byte arrays\n      try {\n        const json = parseJSON(Buffer.from(response.body).toString(), { intDecoding: IntDecoding.MIXED })\n        if (json.applications) {\n          for (const app of json.applications) {\n            if (app.params) {\n              if (app.params['approval-program'] === null) {\n                app.params['approval-program'] = ''\n              }\n              if (app.params['clear-state-program'] === null) {\n                app.params['clear-state-program'] = ''\n              }\n            }\n          }\n          response.body = Buffer.from(stringifyJSON(json))\n        }\n      } catch (e) {\n        // Make this hack resilient so we never break the app\n        Config.logger.warn('Failed to fix indexer response for created applications', e)\n      }\n    }\n    return response\n  }\n\n  async post(\n    relativePath: string,\n    data: Uint8Array,\n    query?: Query<string>,\n    requestHeaders: Record<string, string> = {},\n  ): Promise<BaseHTTPClientResponse> {\n    return await this.callWithRetry(() => super.post(relativePath, data, query, requestHeaders))\n  }\n\n  async delete(\n    relativePath: string,\n    data: Uint8Array,\n    query?: Query<string>,\n    requestHeaders: Record<string, string> = {},\n  ): Promise<BaseHTTPClientResponse> {\n    return await this.callWithRetry(() => super.delete(relativePath, data, query, requestHeaders))\n  }\n}\n"],"names":[],"mappings":"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"}