{"version":3,"file":"rpc-service-chain.cjs","sourceRoot":"","sources":["../../src/rpc-service/rpc-service-chain.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n  CircuitState,\n  CockatielEventEmitter,\n} from '@metamask/controller-utils';\nimport type {\n  Json,\n  JsonRpcParams,\n  JsonRpcRequest,\n  JsonRpcResponse,\n} from '@metamask/utils';\nimport { IDisposable } from 'cockatiel';\n\nimport { projectLogger, createModuleLogger } from '../logger';\nimport { RpcService } from './rpc-service';\nimport type { RpcServiceOptions } from './rpc-service';\nimport type {\n  CockatielEventToEventListenerWithData,\n  ExcludeCockatielEventData,\n  ExtractCockatielEventData,\n  FetchOptions,\n} from './shared';\n\nconst log = createModuleLogger(projectLogger, 'RpcServiceChain');\n\n/**\n * Statuses that the RPC service chain can be in.\n */\nconst STATUSES = {\n  Available: 'available',\n  Degraded: 'degraded',\n  Unknown: 'unknown',\n  Unavailable: 'unavailable',\n} as const;\n\n/**\n * Statuses that the RPC service chain can be in.\n */\ntype Status = (typeof STATUSES)[keyof typeof STATUSES];\n\n/**\n * This class constructs and manages requests to a chain of RpcService objects\n * which represent RPC endpoints with which to access a particular network. The\n * first service in the chain is intended to be the primary way of hitting the\n * network and the remaining services are used as failovers.\n */\nexport class RpcServiceChain {\n  /**\n   * The event emitter for the `onAvailable` event.\n   */\n  readonly #onAvailableEventEmitter: CockatielEventEmitter<\n    ExcludeCockatielEventData<\n      ExtractCockatielEventData<RpcService['onAvailable']>,\n      'endpointUrl'\n    >\n  >;\n\n  /**\n   * The event emitter for the `onBreak` event.\n   */\n  readonly #onBreakEventEmitter: CockatielEventEmitter<\n    ExcludeCockatielEventData<\n      ExtractCockatielEventData<RpcService['onBreak']>,\n      'endpointUrl'\n    >\n  >;\n\n  /**\n   * The event emitter for the `onDegraded` event.\n   */\n  readonly #onDegradedEventEmitter: CockatielEventEmitter<\n    ExcludeCockatielEventData<\n      ExtractCockatielEventData<RpcService['onDegraded']>,\n      'endpointUrl'\n    >\n  >;\n\n  /**\n   * The first RPC service that requests will be sent to.\n   */\n  readonly #primaryService: RpcService;\n\n  /**\n   * The RPC services in the chain.\n   */\n  readonly #services: RpcService[];\n\n  /**\n   * The status of the RPC service chain.\n   */\n  #status: Status;\n\n  /**\n   * Constructs a new RpcServiceChain object.\n   *\n   * @param rpcServiceConfigurations - The options for the RPC services\n   * that you want to construct. Each object in this array is the same as\n   * {@link RpcServiceOptions}.\n   */\n  constructor(\n    rpcServiceConfigurations: [RpcServiceOptions, ...RpcServiceOptions[]],\n  ) {\n    this.#services = rpcServiceConfigurations.map(\n      (rpcServiceConfiguration) => new RpcService(rpcServiceConfiguration),\n    );\n    this.#primaryService = this.#services[0];\n\n    this.#status = STATUSES.Unknown;\n    this.#onBreakEventEmitter = new CockatielEventEmitter<\n      ExcludeCockatielEventData<\n        ExtractCockatielEventData<RpcService['onBreak']>,\n        'endpointUrl'\n      >\n    >();\n\n    this.#onDegradedEventEmitter = new CockatielEventEmitter<\n      ExcludeCockatielEventData<\n        ExtractCockatielEventData<RpcService['onDegraded']>,\n        'endpointUrl'\n      >\n    >();\n    for (const service of this.#services) {\n      service.onDegraded((data) => {\n        if (this.#status !== STATUSES.Degraded) {\n          log('Updating status to \"degraded\"', data);\n          this.#status = STATUSES.Degraded;\n          const { endpointUrl, ...rest } = data;\n          this.#onDegradedEventEmitter.emit(rest);\n        }\n      });\n    }\n\n    this.#onAvailableEventEmitter = new CockatielEventEmitter<\n      ExcludeCockatielEventData<\n        ExtractCockatielEventData<RpcService['onAvailable']>,\n        'endpointUrl'\n      >\n    >();\n    for (const service of this.#services) {\n      service.onAvailable((data) => {\n        if (this.#status !== STATUSES.Available) {\n          log('Updating status to \"available\"', data);\n          this.#status = STATUSES.Available;\n          const { endpointUrl, ...rest } = data;\n          this.#onAvailableEventEmitter.emit(rest);\n        }\n      });\n    }\n  }\n\n  /**\n   * Calls the provided callback when any of the RPC services is retried.\n   *\n   * This is mainly useful for tests.\n   *\n   * @param listener - The callback to be called.\n   * @returns An object with a `dispose` method which can be used to unregister\n   * the event listener.\n   */\n  onServiceRetry(\n    listener: CockatielEventToEventListenerWithData<\n      RpcService['onRetry'],\n      { primaryEndpointUrl: string }\n    >,\n  ): { dispose(): void } {\n    const disposables = this.#services.map((service) =>\n      service.onRetry((data) => {\n        listener({\n          ...data,\n          primaryEndpointUrl: this.#primaryService.endpointUrl.toString(),\n        });\n      }),\n    );\n\n    return {\n      dispose(): void {\n        disposables.forEach((disposable) => disposable.dispose());\n      },\n    };\n  }\n\n  /**\n   * Calls the provided callback only when the maximum number of failed\n   * consecutive attempts to receive a 2xx response has been reached for all\n   * RPC services in the chain, and all services' underlying circuits have\n   * broken.\n   *\n   * The callback will not be called if a service's circuit breaks but its\n   * failover does not. Use `onServiceBreak` if you'd like a lower level of\n   * granularity.\n   *\n   * @param listener - The callback to be called.\n   * @returns An object with a `dispose` method which can be used to unregister\n   * the callback.\n   */\n  onBreak(\n    listener: (\n      data: ExcludeCockatielEventData<\n        ExtractCockatielEventData<RpcService['onBreak']>,\n        'endpointUrl'\n      >,\n    ) => void,\n  ): IDisposable {\n    return this.#onBreakEventEmitter.addListener(listener);\n  }\n\n  /**\n   * Calls the provided callback each time when, for *any* of the RPC services\n   * in this chain, the maximum number of failed consecutive attempts to receive\n   * a 2xx response has been reached and the underlying circuit has broken. A\n   * more granular version of `onBreak`.\n   *\n   * @param listener - The callback to be called.\n   * @returns An object with a `dispose` method which can be used to unregister\n   * the callback.\n   */\n  onServiceBreak(\n    listener: CockatielEventToEventListenerWithData<\n      RpcService['onBreak'],\n      { primaryEndpointUrl: string }\n    >,\n  ): IDisposable {\n    const disposables = this.#services.map((service) =>\n      service.onBreak((data) => {\n        listener({\n          ...data,\n          primaryEndpointUrl: this.#primaryService.endpointUrl.toString(),\n        });\n      }),\n    );\n\n    return {\n      dispose(): void {\n        disposables.forEach((disposable) => disposable.dispose());\n      },\n    };\n  }\n\n  /**\n   * Calls the provided callback if no requests have been initiated yet or\n   * all requests to RPC services in this chain have responded successfully in a\n   * timely fashion, and then one of the two conditions apply:\n   *\n   * 1. When a retriable error is encountered making a request to an RPC\n   * service, and the request is retried until a set maximum is reached.\n   * 2. When a RPC service responds successfully, but the request takes longer\n   * than a set number of seconds to complete.\n   *\n   * Note that the callback will be called even if there are local connectivity\n   * issues which prevent requests from being initiated. This is intentional.\n   *\n   * Also note this callback will only be called if the RPC service chain as a\n   * whole is in a \"degraded\" state, and will then only be called once (e.g., it\n   * will not be called if a failover service falls into a degraded state, then\n   * the primary comes back online, but it is slow). Use `onServiceDegraded` if\n   * you'd like a lower level of granularity.\n   *\n   * @param listener - The callback to be called.\n   * @returns An object with a `dispose` method which can be used to unregister\n   * the callback.\n   */\n  onDegraded(\n    listener: (\n      data: ExcludeCockatielEventData<\n        ExtractCockatielEventData<RpcService['onDegraded']>,\n        'endpointUrl'\n      >,\n    ) => void,\n  ): IDisposable {\n    return this.#onDegradedEventEmitter.addListener(listener);\n  }\n\n  /**\n   * Calls the provided callback each time one of the two conditions apply:\n   *\n   * 1. When a retriable error is encountered making a request to an RPC\n   * service, and the request is retried until a set maximum is reached.\n   * 2. When a RPC service responds successfully, but the request takes longer\n   * than a set number of seconds to complete.\n   *\n   * Note that the callback will be called even if there are local connectivity\n   * issues which prevent requests from being initiated. This is intentional.\n   *\n   * This is a more granular version of `onDegraded`. The callback will be\n   * called for each slow request to an RPC service. It may also be called again\n   * if a failover service falls into a degraded state, then the primary comes\n   * back online, but it is slow.\n   *\n   * @param listener - The callback to be called.\n   * @returns An object with a `dispose` method which can be used to unregister\n   * the callback.\n   */\n  onServiceDegraded(\n    listener: CockatielEventToEventListenerWithData<\n      RpcService['onDegraded'],\n      { primaryEndpointUrl: string }\n    >,\n  ): IDisposable {\n    const disposables = this.#services.map((service) =>\n      service.onDegraded((data) => {\n        listener({\n          ...data,\n          primaryEndpointUrl: this.#primaryService.endpointUrl.toString(),\n        });\n      }),\n    );\n\n    return {\n      dispose(): void {\n        disposables.forEach((disposable) => disposable.dispose());\n      },\n    };\n  }\n\n  /**\n   * Calls the provided callback in one of the following two conditions:\n   *\n   * 1. The first time that a 2xx request is made to any of the RPC services in\n   * this chain.\n   * 2. When requests to any the failover RPC services in this chain were\n   * failing such that they were degraded or their underyling circuits broke,\n   * but the first request to the primary succeeds again.\n   *\n   * Note this callback will only be called if the RPC service chain as a whole\n   * is in an \"available\" state.\n   *\n   * @param listener - The callback to be called.\n   * @returns An object with a `dispose` method which can be used to unregister\n   * the callback.\n   */\n  onAvailable(\n    listener: (\n      data: ExcludeCockatielEventData<\n        ExtractCockatielEventData<RpcService['onAvailable']>,\n        'endpointUrl'\n      >,\n    ) => void,\n  ): IDisposable {\n    return this.#onAvailableEventEmitter.addListener(listener);\n  }\n\n  /**\n   * Uses the RPC services in the chain to make a request, using each service\n   * after the first as a fallback to the previous one as necessary.\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 401 error if the response status is 401.\n   * @throws A \"rate limiting\" error if the response HTTP status is 429.\n   * @throws A \"resource unavailable\" error if the response status is 402, 404, or any 5xx.\n   * @throws A generic HTTP client error (-32100) for any other 4xx status codes.\n   * @throws A \"parse\" error if the response is not valid JSON.\n   */\n  async request<Params extends JsonRpcParams, Result extends Json>(\n    jsonRpcRequest: Readonly<JsonRpcRequest<Params>> & {\n      method: 'eth_getBlockByNumber';\n    },\n    fetchOptions?: FetchOptions,\n  ): Promise<JsonRpcResponse<Result> | JsonRpcResponse<null>>;\n\n  /**\n   * Uses the RPC services in the chain to make a request, using each service\n   * after the first as a fallback to the previous one as necessary.\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 401 error if the response status is 401.\n   * @throws A \"rate limiting\" error if the response HTTP status is 429.\n   * @throws A \"resource unavailable\" error if the response status is 402, 404, or any 5xx.\n   * @throws A generic HTTP client error (-32100) for any other 4xx status codes.\n   * @throws A \"parse\" error if the response is not valid JSON.\n   */\n  async request<Params extends JsonRpcParams, Result extends Json>(\n    jsonRpcRequest: Readonly<JsonRpcRequest<Params>>,\n    fetchOptions?: FetchOptions,\n  ): Promise<JsonRpcResponse<Result>>;\n\n  async request<Params extends JsonRpcParams, Result extends Json>(\n    jsonRpcRequest: Readonly<JsonRpcRequest<Params>>,\n    fetchOptions: FetchOptions = {},\n  ): Promise<JsonRpcResponse<Result | null>> {\n    // Start with the primary (first) service and switch to failovers as the\n    // need arises. This is a bit confusing, so keep reading for more on how\n    // this works.\n\n    let availableServiceIndex: number | undefined;\n    let response: JsonRpcResponse<Result> | undefined;\n\n    for (const [i, service] of this.#services.entries()) {\n      log(`Trying service #${i + 1}...`);\n      const previousCircuitState = service.getCircuitState();\n\n      try {\n        // Try making the request through the service.\n        response = await service.request<Params, Result>(\n          jsonRpcRequest,\n          fetchOptions,\n        );\n        log('Service successfully received request.');\n        availableServiceIndex = i;\n        break;\n      } catch (error) {\n        // Oops, that didn't work.\n        // Capture this error so that we can handle it later.\n\n        const { lastError } = service;\n        const isCircuitOpen = service.getCircuitState() === CircuitState.Open;\n\n        log('Service failed! error =', error, 'lastError = ', lastError);\n\n        if (isCircuitOpen) {\n          if (i < this.#services.length - 1) {\n            log(\n              \"This service's circuit is open. Proceeding to next service...\",\n            );\n            continue;\n          }\n\n          if (\n            previousCircuitState !== CircuitState.Open &&\n            this.#status !== STATUSES.Unavailable &&\n            lastError !== undefined\n          ) {\n            // If the service's circuit just broke and it's the last one in the\n            // chain, then trigger the onBreak event. (But if for some reason we\n            // have already done this, then don't do it.)\n            log(\n              'This service\\'s circuit just opened and it is the last service. Updating status to \"unavailable\" and triggering onBreak.',\n            );\n            this.#status = STATUSES.Unavailable;\n            this.#onBreakEventEmitter.emit({\n              error: lastError,\n            });\n          }\n        }\n\n        // The service failed, and we throw whatever the error is. The calling\n        // code can try again if it so desires.\n        log(\n          `${isCircuitOpen ? '' : \"This service's circuit is closed. \"}Re-throwing error.`,\n        );\n        throw error;\n      }\n    }\n\n    if (response) {\n      // If one of the services is available, reset all of the circuits of the\n      // following services. If we didn't do this and the service became\n      // unavailable in the future, and any of the failovers' circuits were\n      // open (due to previous failures), we would receive a \"circuit broken\"\n      // error when we attempted to divert traffic to the failovers again.\n      //\n      if (availableServiceIndex !== undefined) {\n        for (const [i, service] of [...this.#services.entries()].slice(\n          availableServiceIndex + 1,\n        )) {\n          log(`Resetting policy for service #${i + 1}.`);\n          service.resetPolicy();\n        }\n      }\n\n      return response;\n    }\n\n    // The only way we can end up here is if there are no services to loop over.\n    // That is not possible due to the types on the constructor, but TypeScript\n    // doesn't know this, so we have to appease it.\n    throw new Error('Nothing to return');\n  }\n}\n"]}