{"version":3,"file":"rpc-service.mjs","sourceRoot":"","sources":["../../src/rpc-service/rpc-service.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n  CreateServicePolicyOptions,\n  ServicePolicy,\n} from '@metamask/controller-utils';\nimport {\n  CircuitState,\n  createServicePolicy,\n  handleWhen,\n} from '@metamask/controller-utils';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type { JsonRpcRequest } from '@metamask/utils';\nimport {\n  hasProperty,\n  type Json,\n  type JsonRpcParams,\n  type JsonRpcResponse,\n} from '@metamask/utils';\nimport deepmerge from 'deepmerge';\n\nimport type { AbstractRpcService } from './abstract-rpc-service';\nimport type { AddToCockatielEventData, FetchOptions } from './shared';\n\n/**\n * Options for the RpcService constructor.\n */\nexport type RpcServiceOptions = {\n  /**\n   * A function that can be used to convert a binary string into a\n   * base64-encoded ASCII string. Used to encode authorization credentials.\n   */\n  btoa: typeof btoa;\n  /**\n   * The URL of the RPC endpoint to hit.\n   */\n  endpointUrl: URL | string;\n  /**\n   * An RPC service that represents a failover endpoint which will be invoked\n   * while the circuit for _this_ service is open.\n   */\n  failoverService?: AbstractRpcService;\n  /**\n   * A function that can be used to make an HTTP request. If your JavaScript\n   * environment supports `fetch` natively, you'll probably want to pass that;\n   * otherwise you can pass an equivalent (such as `fetch` via `node-fetch`).\n   */\n  fetch: typeof fetch;\n  /**\n   * A common set of options that will be used to make every request. Can be\n   * overridden on the request level (e.g. to add headers).\n   */\n  fetchOptions?: FetchOptions;\n  /**\n   * Options to pass to `createServicePolicy`. Note that `retryFilterPolicy` is\n   * not accepted, as it is overwritten. See {@link createServicePolicy}.\n   */\n  policyOptions?: Omit<CreateServicePolicyOptions, 'retryFilterPolicy'>;\n};\n\n/**\n * The maximum number of times that a failing service should be re-run before\n * giving up.\n */\nexport const DEFAULT_MAX_RETRIES = 4;\n\n/**\n * The maximum number of times that the service is allowed to fail before\n * pausing further retries. This is set to a value such that if given a\n * service that continually fails, the policy needs to be executed 3 times\n * before further retries are paused.\n */\nexport const DEFAULT_MAX_CONSECUTIVE_FAILURES = (1 + DEFAULT_MAX_RETRIES) * 3;\n\n/**\n * The list of error messages that represent a failure to connect to the network.\n *\n * This list was derived from Sindre Sorhus's `is-network-error` package:\n * <https://github.com/sindresorhus/is-network-error/blob/7bbfa8be9482ce1427a21fbff60e3ee1650dd091/index.js>\n */\nexport const CONNECTION_ERRORS = [\n  // Chrome\n  {\n    constructorName: 'TypeError',\n    pattern: /network error/u,\n  },\n  // Chrome\n  {\n    constructorName: 'TypeError',\n    pattern: /Failed to fetch/u,\n  },\n  // Firefox\n  {\n    constructorName: 'TypeError',\n    pattern: /NetworkError when attempting to fetch resource\\./u,\n  },\n  // Safari 16\n  {\n    constructorName: 'TypeError',\n    pattern: /The Internet connection appears to be offline\\./u,\n  },\n  // Safari 17+\n  {\n    constructorName: 'TypeError',\n    pattern: /Load failed/u,\n  },\n  // `cross-fetch`\n  {\n    constructorName: 'TypeError',\n    pattern: /Network request failed/u,\n  },\n  // `node-fetch`\n  {\n    constructorName: 'FetchError',\n    pattern: /request to (.+) failed/u,\n  },\n  // Undici (Node.js)\n  {\n    constructorName: 'TypeError',\n    pattern: /fetch failed/u,\n  },\n  // Undici (Node.js)\n  {\n    constructorName: 'TypeError',\n    pattern: /terminated/u,\n  },\n];\n\n/**\n * Determines whether the given error represents a failure to reach the network\n * after request parameters have been validated.\n *\n * This is somewhat difficult to verify because JavaScript engines (and in\n * some cases libraries) produce slightly different error messages for this\n * particular scenario, and we need to account for this.\n *\n * @param error - The error.\n * @returns True if the error indicates that the network cannot be connected to,\n * and false otherwise.\n */\nexport function isConnectionError(error: unknown) {\n  if (!(typeof error === 'object' && error !== null && 'message' in error)) {\n    return false;\n  }\n\n  const { message } = error;\n\n  return (\n    typeof message === 'string' &&\n    !isNockError(message) &&\n    CONNECTION_ERRORS.some(({ constructorName, pattern }) => {\n      return (\n        error.constructor.name === constructorName && pattern.test(message)\n      );\n    })\n  );\n}\n\n/**\n * Determines whether the given error message refers to a Nock error.\n *\n * It's important that if we failed to mock a request in a test, the resulting\n * error does not cause the request to be retried so that we can see it right\n * away.\n *\n * @param message - The error message to test.\n * @returns True if the message indicates a missing Nock mock, false otherwise.\n */\nfunction isNockError(message: string) {\n  return message.includes('Nock:');\n}\n\n/**\n * Guarantees a URL, even given a string. This is useful for checking components\n * of that URL.\n *\n * @param endpointUrlOrUrlString - Either a URL object or a string that\n * represents the URL of an endpoint.\n * @returns A URL object.\n */\nfunction getNormalizedEndpointUrl(endpointUrlOrUrlString: URL | string): URL {\n  return endpointUrlOrUrlString instanceof URL\n    ? endpointUrlOrUrlString\n    : new URL(endpointUrlOrUrlString);\n}\n\n/**\n * This class is responsible for making a request to an endpoint that implements\n * the JSON-RPC protocol. It is designed to gracefully handle network and server\n * failures, retrying requests using exponential backoff. It also offers a hook\n * which can used to respond to slow requests.\n */\nexport class RpcService implements AbstractRpcService {\n  /**\n   * The function used to make an HTTP request.\n   */\n  readonly #fetch: typeof fetch;\n\n  /**\n   * The URL of the RPC endpoint.\n   */\n  readonly endpointUrl: URL;\n\n  /**\n   * A common set of options that the request options will extend.\n   */\n  readonly #fetchOptions: FetchOptions;\n\n  /**\n   * An RPC service that represents a failover endpoint which will be invoked\n   * while the circuit for _this_ service is open.\n   */\n  readonly #failoverService: RpcServiceOptions['failoverService'];\n\n  /**\n   * The policy that wraps the request.\n   */\n  readonly #policy: ServicePolicy;\n\n  /**\n   * Constructs a new RpcService object.\n   *\n   * @param options - The options. See {@link RpcServiceOptions}.\n   */\n  constructor(options: RpcServiceOptions) {\n    const {\n      btoa: givenBtoa,\n      endpointUrl,\n      failoverService,\n      fetch: givenFetch,\n      fetchOptions = {},\n      policyOptions = {},\n    } = options;\n\n    this.#fetch = givenFetch;\n    this.endpointUrl = getNormalizedEndpointUrl(endpointUrl);\n    this.#fetchOptions = this.#getDefaultFetchOptions(\n      this.endpointUrl,\n      fetchOptions,\n      givenBtoa,\n    );\n    this.#failoverService = failoverService;\n\n    const policy = createServicePolicy({\n      maxRetries: DEFAULT_MAX_RETRIES,\n      maxConsecutiveFailures: DEFAULT_MAX_CONSECUTIVE_FAILURES,\n      ...policyOptions,\n      retryFilterPolicy: handleWhen((error) => {\n        return (\n          // Ignore errors where the request failed to establish\n          isConnectionError(error) ||\n          // Ignore server sent HTML error pages or truncated JSON responses\n          error.message.includes('not valid JSON') ||\n          // Ignore server overload errors\n          error.message.includes('Gateway timeout') ||\n          (hasProperty(error, 'code') &&\n            (error.code === 'ETIMEDOUT' || error.code === 'ECONNRESET'))\n        );\n      }),\n    });\n    this.#policy = policy;\n  }\n\n  /**\n   * Listens for when the RPC service retries the request.\n   *\n   * @param listener - The callback to be called when the retry occurs.\n   * @returns What {@link ServicePolicy.onRetry} returns.\n   * @see {@link createServicePolicy}\n   */\n  onRetry(\n    listener: AddToCockatielEventData<\n      Parameters<ServicePolicy['onRetry']>[0],\n      { endpointUrl: string }\n    >,\n  ) {\n    return this.#policy.onRetry((data) => {\n      listener({ ...data, endpointUrl: this.endpointUrl.toString() });\n    });\n  }\n\n  /**\n   * Listens for when the RPC service retries the request too many times in a\n   * row.\n   *\n   * @param listener - The callback to be called when the circuit is broken.\n   * @returns What {@link ServicePolicy.onBreak} returns.\n   * @see {@link createServicePolicy}\n   */\n  onBreak(\n    listener: AddToCockatielEventData<\n      Parameters<ServicePolicy['onBreak']>[0],\n      { endpointUrl: string; failoverEndpointUrl?: string }\n    >,\n  ) {\n    return this.#policy.onBreak((data) => {\n      listener({\n        ...data,\n        endpointUrl: this.endpointUrl.toString(),\n        failoverEndpointUrl: this.#failoverService\n          ? this.#failoverService.endpointUrl.toString()\n          : undefined,\n      });\n    });\n  }\n\n  /**\n   * Listens for when the policy underlying this RPC service detects a slow\n   * request.\n   *\n   * @param listener - The callback to be called when the request is slow.\n   * @returns What {@link ServicePolicy.onDegraded} returns.\n   * @see {@link createServicePolicy}\n   */\n  onDegraded(\n    listener: AddToCockatielEventData<\n      Parameters<ServicePolicy['onDegraded']>[0],\n      { endpointUrl: string }\n    >,\n  ) {\n    return this.#policy.onDegraded(() => {\n      listener({ endpointUrl: this.endpointUrl.toString() });\n    });\n  }\n\n  /**\n   * Makes a request to the RPC endpoint. If the circuit is open because this\n   * request has failed too many times, the request is forwarded to a failover\n   * service (if provided).\n   *\n   * This overload is specifically designed for `eth_getBlockByNumber`, which\n   * can return a `result` of `null` despite an expected `Result` being\n   * provided.\n   *\n   * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.\n   * @param fetchOptions - An options bag for {@link fetch} which further\n   * specifies the request.\n   * @returns The decoded JSON-RPC response from the endpoint.\n   * @throws A \"method not found\" error if the response status is 405.\n   * @throws A rate limiting error if the response HTTP status is 429.\n   * @throws A timeout error if the response HTTP status is 503 or 504.\n   * @throws A generic error if the response HTTP status is not 2xx but also not\n   * 405, 429, 503, or 504.\n   */\n  async request<Params extends JsonRpcParams, Result extends Json>(\n    jsonRpcRequest: JsonRpcRequest<Params> & { method: 'eth_getBlockByNumber' },\n    fetchOptions?: FetchOptions,\n  ): Promise<JsonRpcResponse<Result> | JsonRpcResponse<null>>;\n\n  /**\n   * Makes a request to the RPC endpoint. If the circuit is open because this\n   * request has failed too many times, the request is forwarded to a failover\n   * service (if provided).\n   *\n   * This overload is designed for all RPC methods except for\n   * `eth_getBlockByNumber`, which are expected to return a `result` of the\n   * expected `Result`.\n   *\n   * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.\n   * @param fetchOptions - An options bag for {@link fetch} which further\n   * specifies the request.\n   * @returns The decoded JSON-RPC response from the endpoint.\n   * @throws A \"method not found\" error if the response status is 405.\n   * @throws A rate limiting error if the response HTTP status is 429.\n   * @throws A timeout error if the response HTTP status is 503 or 504.\n   * @throws A generic error if the response HTTP status is not 2xx but also not\n   * 405, 429, 503, or 504.\n   */\n  async request<Params extends JsonRpcParams, Result extends Json>(\n    jsonRpcRequest: JsonRpcRequest<Params>,\n    fetchOptions?: FetchOptions,\n  ): Promise<JsonRpcResponse<Result>>;\n\n  async request<Params extends JsonRpcParams, Result extends Json>(\n    jsonRpcRequest: JsonRpcRequest<Params>,\n    fetchOptions: FetchOptions = {},\n  ): Promise<JsonRpcResponse<Result | null>> {\n    const completeFetchOptions = this.#getCompleteFetchOptions(\n      jsonRpcRequest,\n      fetchOptions,\n    );\n\n    try {\n      return await this.#executePolicy<Params, Result>(\n        jsonRpcRequest,\n        completeFetchOptions,\n      );\n    } catch (error) {\n      if (\n        this.#policy.circuitBreakerPolicy.state === CircuitState.Open &&\n        this.#failoverService !== undefined\n      ) {\n        return await this.#failoverService.request(\n          jsonRpcRequest,\n          completeFetchOptions,\n        );\n      }\n      throw error;\n    }\n  }\n\n  /**\n   * Constructs a default set of options to `fetch`.\n   *\n   * If a username and password are present in the URL, they are extracted to an\n   * Authorization header.\n   *\n   * @param endpointUrl - The endpoint URL.\n   * @param fetchOptions - The options to `fetch`.\n   * @param givenBtoa - An implementation of `btoa`.\n   * @returns The default fetch options.\n   */\n  #getDefaultFetchOptions(\n    endpointUrl: URL,\n    fetchOptions: FetchOptions,\n    givenBtoa: (stringToEncode: string) => string,\n  ): FetchOptions {\n    if (endpointUrl.username && endpointUrl.password) {\n      const authString = `${endpointUrl.username}:${endpointUrl.password}`;\n      const encodedCredentials = givenBtoa(authString);\n      return deepmerge(fetchOptions, {\n        headers: { Authorization: `Basic ${encodedCredentials}` },\n      });\n    }\n\n    return fetchOptions;\n  }\n\n  /**\n   * Constructs a final set of options to pass to `fetch`. Note that the method\n   * defaults to `post`, and the JSON-RPC request is automatically JSON-encoded.\n   *\n   * @param jsonRpcRequest - The JSON-RPC request.\n   * @param fetchOptions - Custom `fetch` options.\n   * @returns The complete set of `fetch` options.\n   */\n  #getCompleteFetchOptions<Params extends JsonRpcParams>(\n    jsonRpcRequest: JsonRpcRequest<Params>,\n    fetchOptions: FetchOptions,\n  ): FetchOptions {\n    const defaultOptions = {\n      method: 'POST',\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json',\n      },\n    };\n    const mergedOptions = deepmerge(\n      defaultOptions,\n      deepmerge(this.#fetchOptions, fetchOptions),\n    );\n\n    const { id, jsonrpc, method, params } = jsonRpcRequest;\n    const body = JSON.stringify({\n      id,\n      jsonrpc,\n      method,\n      params,\n    });\n\n    return { ...mergedOptions, body };\n  }\n\n  /**\n   * Makes the request using the Cockatiel policy that this service creates.\n   *\n   * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.\n   * @param fetchOptions - The options for `fetch`; will be combined with the\n   * fetch options passed to the constructor\n   * @returns The decoded JSON-RPC response from the endpoint.\n   * @throws A \"method not found\" error if the response status is 405.\n   * @throws A rate limiting error if the response HTTP status is 429.\n   * @throws A timeout error if the response HTTP status is 503 or 504.\n   * @throws A generic error if the response HTTP status is not 2xx but also not\n   * 405, 429, 503, or 504.\n   */\n  async #executePolicy<\n    Params extends JsonRpcParams,\n    Result extends Json,\n    Request extends JsonRpcRequest = JsonRpcRequest<Params>,\n  >(\n    jsonRpcRequest: Request,\n    fetchOptions: FetchOptions,\n  ): Promise<JsonRpcResponse<Result> | JsonRpcResponse<null>> {\n    return await this.#policy.execute(async () => {\n      const response = await this.#fetch(this.endpointUrl, fetchOptions);\n\n      if (response.status === 405) {\n        throw rpcErrors.methodNotFound();\n      }\n\n      if (response.status === 429) {\n        throw rpcErrors.internal({ message: 'Request is being rate limited.' });\n      }\n\n      if (response.status === 503 || response.status === 504) {\n        throw rpcErrors.internal({\n          message:\n            'Gateway timeout. The request took too long to process. This can happen when querying logs over too wide a block range.',\n        });\n      }\n\n      const text = await response.text();\n\n      if (\n        jsonRpcRequest.method === 'eth_getBlockByNumber' &&\n        text === 'Not Found'\n      ) {\n        return {\n          id: jsonRpcRequest.id,\n          jsonrpc: jsonRpcRequest.jsonrpc,\n          result: null,\n        };\n      }\n\n      // Type annotation: We assume that if this response is valid JSON, it's a\n      // valid JSON-RPC response.\n      let json: JsonRpcResponse<Result>;\n      try {\n        json = JSON.parse(text);\n      } catch (error) {\n        if (error instanceof SyntaxError) {\n          throw rpcErrors.internal({\n            message: 'Could not parse response as it is not valid JSON',\n            data: text,\n          });\n        } else {\n          throw error;\n        }\n      }\n\n      if (!response.ok) {\n        throw rpcErrors.internal({\n          message: `Non-200 status code: '${response.status}'`,\n          data: json,\n        });\n      }\n\n      return json;\n    });\n  }\n}\n"]}