@metamask/network-controller 22.1.1 → 22.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/dist/NetworkController.cjs +61 -10
  3. package/dist/NetworkController.cjs.map +1 -1
  4. package/dist/NetworkController.d.cts +61 -5
  5. package/dist/NetworkController.d.cts.map +1 -1
  6. package/dist/NetworkController.d.mts +61 -5
  7. package/dist/NetworkController.d.mts.map +1 -1
  8. package/dist/NetworkController.mjs +61 -9
  9. package/dist/NetworkController.mjs.map +1 -1
  10. package/dist/create-network-client.cjs +1 -4
  11. package/dist/create-network-client.cjs.map +1 -1
  12. package/dist/create-network-client.d.cts.map +1 -1
  13. package/dist/create-network-client.d.mts.map +1 -1
  14. package/dist/create-network-client.mjs +1 -4
  15. package/dist/create-network-client.mjs.map +1 -1
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +1 -0
  18. package/dist/index.d.cts.map +1 -1
  19. package/dist/index.d.mts +1 -0
  20. package/dist/index.d.mts.map +1 -1
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/rpc-service/abstract-rpc-service.cjs +3 -0
  23. package/dist/rpc-service/abstract-rpc-service.cjs.map +1 -0
  24. package/dist/rpc-service/abstract-rpc-service.d.cts +46 -0
  25. package/dist/rpc-service/abstract-rpc-service.d.cts.map +1 -0
  26. package/dist/rpc-service/abstract-rpc-service.d.mts +46 -0
  27. package/dist/rpc-service/abstract-rpc-service.d.mts.map +1 -0
  28. package/dist/rpc-service/abstract-rpc-service.mjs +2 -0
  29. package/dist/rpc-service/abstract-rpc-service.mjs.map +1 -0
  30. package/dist/rpc-service/rpc-service-chain.cjs +109 -0
  31. package/dist/rpc-service/rpc-service-chain.cjs.map +1 -0
  32. package/dist/rpc-service/rpc-service-chain.d.cts +116 -0
  33. package/dist/rpc-service/rpc-service-chain.d.cts.map +1 -0
  34. package/dist/rpc-service/rpc-service-chain.d.mts +116 -0
  35. package/dist/rpc-service/rpc-service-chain.d.mts.map +1 -0
  36. package/dist/rpc-service/rpc-service-chain.mjs +105 -0
  37. package/dist/rpc-service/rpc-service-chain.mjs.map +1 -0
  38. package/dist/rpc-service/rpc-service.cjs +279 -0
  39. package/dist/rpc-service/rpc-service.cjs.map +1 -0
  40. package/dist/rpc-service/rpc-service.d.cts +134 -0
  41. package/dist/rpc-service/rpc-service.d.cts.map +1 -0
  42. package/dist/rpc-service/rpc-service.d.mts +134 -0
  43. package/dist/rpc-service/rpc-service.d.mts.map +1 -0
  44. package/dist/rpc-service/rpc-service.mjs +278 -0
  45. package/dist/rpc-service/rpc-service.mjs.map +1 -0
  46. package/dist/rpc-service/shared.cjs +3 -0
  47. package/dist/rpc-service/shared.cjs.map +1 -0
  48. package/dist/rpc-service/shared.d.cts +12 -0
  49. package/dist/rpc-service/shared.d.cts.map +1 -0
  50. package/dist/rpc-service/shared.d.mts +12 -0
  51. package/dist/rpc-service/shared.d.mts.map +1 -0
  52. package/dist/rpc-service/shared.mjs +2 -0
  53. package/dist/rpc-service/shared.mjs.map +1 -0
  54. package/package.json +7 -7
@@ -0,0 +1,134 @@
1
+ import type { ServicePolicy } from "@metamask/controller-utils";
2
+ import type { JsonRpcRequest } from "@metamask/utils";
3
+ import { type Json, type JsonRpcParams, type JsonRpcResponse } from "@metamask/utils";
4
+ import type { AbstractRpcService } from "./abstract-rpc-service.cjs";
5
+ import type { AddToCockatielEventData, FetchOptions } from "./shared.cjs";
6
+ /**
7
+ * The list of error messages that represent a failure to reach the network.
8
+ *
9
+ * This list was derived from Sindre Sorhus's `is-network-error` package:
10
+ * <https://github.com/sindresorhus/is-network-error/blob/7bbfa8be9482ce1427a21fbff60e3ee1650dd091/index.js>
11
+ */
12
+ export declare const NETWORK_UNREACHABLE_ERRORS: Set<string>;
13
+ /**
14
+ * Determines whether the given error represents a failure to reach the network
15
+ * after request parameters have been validated.
16
+ *
17
+ * This is somewhat difficult to verify because JavaScript engines (and in
18
+ * some cases libraries) produce slightly different error messages for this
19
+ * particular scenario, and we need to account for this.
20
+ *
21
+ * @param error - The error.
22
+ * @returns True if the error indicates that the network is unreachable, and
23
+ * false otherwise.
24
+ */
25
+ export default function isNetworkUnreachableError(error: unknown): boolean;
26
+ /**
27
+ * This class is responsible for making a request to an endpoint that implements
28
+ * the JSON-RPC protocol. It is designed to gracefully handle network and server
29
+ * failures, retrying requests using exponential backoff. It also offers a hook
30
+ * which can used to respond to slow requests.
31
+ */
32
+ export declare class RpcService implements AbstractRpcService {
33
+ #private;
34
+ /**
35
+ * Constructs a new RpcService object.
36
+ *
37
+ * @param args - The arguments.
38
+ * @param args.fetch - A function that can be used to make an HTTP request.
39
+ * If your JavaScript environment supports `fetch` natively, you'll probably
40
+ * want to pass that; otherwise you can pass an equivalent (such as `fetch`
41
+ * via `node-fetch`).
42
+ * @param args.btoa - A function that can be used to convert a binary string
43
+ * into base-64. Used to encode authorization credentials.
44
+ * @param args.endpointUrl - The URL of the RPC endpoint.
45
+ * @param args.fetchOptions - A common set of options that will be used to
46
+ * make every request. Can be overridden on the request level (e.g. to add
47
+ * headers).
48
+ * @param args.failoverService - An RPC service that represents a failover
49
+ * endpoint which will be invoked while the circuit for _this_ service is
50
+ * open.
51
+ */
52
+ constructor({ fetch: givenFetch, btoa: givenBtoa, endpointUrl, fetchOptions, failoverService, }: {
53
+ fetch: typeof fetch;
54
+ btoa: typeof btoa;
55
+ endpointUrl: URL | string;
56
+ fetchOptions?: FetchOptions;
57
+ failoverService?: AbstractRpcService;
58
+ });
59
+ /**
60
+ * Listens for when the RPC service retries the request.
61
+ *
62
+ * @param listener - The callback to be called when the retry occurs.
63
+ * @returns What {@link ServicePolicy.onRetry} returns.
64
+ * @see {@link createServicePolicy}
65
+ */
66
+ onRetry(listener: AddToCockatielEventData<Parameters<ServicePolicy['onRetry']>[0], {
67
+ endpointUrl: string;
68
+ }>): import("cockatiel").IDisposable;
69
+ /**
70
+ * Listens for when the RPC service retries the request too many times in a
71
+ * row.
72
+ *
73
+ * @param listener - The callback to be called when the circuit is broken.
74
+ * @returns What {@link ServicePolicy.onBreak} returns.
75
+ * @see {@link createServicePolicy}
76
+ */
77
+ onBreak(listener: AddToCockatielEventData<Parameters<ServicePolicy['onBreak']>[0], {
78
+ endpointUrl: string;
79
+ }>): import("cockatiel").IDisposable;
80
+ /**
81
+ * Listens for when the policy underlying this RPC service detects a slow
82
+ * request.
83
+ *
84
+ * @param listener - The callback to be called when the request is slow.
85
+ * @returns What {@link ServicePolicy.onDegraded} returns.
86
+ * @see {@link createServicePolicy}
87
+ */
88
+ onDegraded(listener: AddToCockatielEventData<Parameters<ServicePolicy['onDegraded']>[0], {
89
+ endpointUrl: string;
90
+ }>): import("cockatiel").IDisposable;
91
+ /**
92
+ * Makes a request to the RPC endpoint. If the circuit is open because this
93
+ * request has failed too many times, the request is forwarded to a failover
94
+ * service (if provided).
95
+ *
96
+ * This overload is specifically designed for `eth_getBlockByNumber`, which
97
+ * can return a `result` of `null` despite an expected `Result` being
98
+ * provided.
99
+ *
100
+ * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.
101
+ * @param fetchOptions - An options bag for {@link fetch} which further
102
+ * specifies the request.
103
+ * @returns The decoded JSON-RPC response from the endpoint.
104
+ * @throws A "method not found" error if the response status is 405.
105
+ * @throws A rate limiting error if the response HTTP status is 429.
106
+ * @throws A timeout error if the response HTTP status is 503 or 504.
107
+ * @throws A generic error if the response HTTP status is not 2xx but also not
108
+ * 405, 429, 503, or 504.
109
+ */
110
+ request<Params extends JsonRpcParams, Result extends Json>(jsonRpcRequest: JsonRpcRequest<Params> & {
111
+ method: 'eth_getBlockByNumber';
112
+ }, fetchOptions?: FetchOptions): Promise<JsonRpcResponse<Result> | JsonRpcResponse<null>>;
113
+ /**
114
+ * Makes a request to the RPC endpoint. If the circuit is open because this
115
+ * request has failed too many times, the request is forwarded to a failover
116
+ * service (if provided).
117
+ *
118
+ * This overload is designed for all RPC methods except for
119
+ * `eth_getBlockByNumber`, which are expected to return a `result` of the
120
+ * expected `Result`.
121
+ *
122
+ * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.
123
+ * @param fetchOptions - An options bag for {@link fetch} which further
124
+ * specifies the request.
125
+ * @returns The decoded JSON-RPC response from the endpoint.
126
+ * @throws A "method not found" error if the response status is 405.
127
+ * @throws A rate limiting error if the response HTTP status is 429.
128
+ * @throws A timeout error if the response HTTP status is 503 or 504.
129
+ * @throws A generic error if the response HTTP status is not 2xx but also not
130
+ * 405, 429, 503, or 504.
131
+ */
132
+ request<Params extends JsonRpcParams, Result extends Json>(jsonRpcRequest: JsonRpcRequest<Params>, fetchOptions?: FetchOptions): Promise<JsonRpcResponse<Result>>;
133
+ }
134
+ //# sourceMappingURL=rpc-service.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-service.d.cts","sourceRoot":"","sources":["../../src/rpc-service/rpc-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAOhE,OAAO,KAAK,EAAE,cAAc,EAAE,wBAAwB;AACtD,OAAO,EAEL,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,eAAe,EACrB,wBAAwB;AAGzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,mCAA+B;AACjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,YAAY,EAAE,qBAAiB;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,aASrC,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,KAAK,EAAE,OAAO,WAI/D;AAgBD;;;;;GAKG;AACH,qBAAa,UAAW,YAAW,kBAAkB;;IA2BnD;;;;;;;;;;;;;;;;;OAiBG;gBACS,EACV,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,SAAS,EACf,WAAW,EACX,YAAiB,EACjB,eAAe,GAChB,EAAE;QACD,KAAK,EAAE,OAAO,KAAK,CAAC;QACpB,IAAI,EAAE,OAAO,IAAI,CAAC;QAClB,WAAW,EAAE,GAAG,GAAG,MAAM,CAAC;QAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;QAC5B,eAAe,CAAC,EAAE,kBAAkB,CAAC;KACtC;IA6BD;;;;;;OAMG;IACH,OAAO,CACL,QAAQ,EAAE,uBAAuB,CAC/B,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EACvC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CACxB;IAOH;;;;;;;OAOG;IACH,OAAO,CACL,QAAQ,EAAE,uBAAuB,CAC/B,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EACvC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CACxB;IAOH;;;;;;;OAOG;IACH,UAAU,CACR,QAAQ,EAAE,uBAAuB,CAC/B,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,EAC1C;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CACxB;IAOH;;;;;;;;;;;;;;;;;;OAkBG;IACG,OAAO,CAAC,MAAM,SAAS,aAAa,EAAE,MAAM,SAAS,IAAI,EAC7D,cAAc,EAAE,cAAc,CAAC,MAAM,CAAC,GAAG;QAAE,MAAM,EAAE,sBAAsB,CAAA;KAAE,EAC3E,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAE3D;;;;;;;;;;;;;;;;;;OAkBG;IACG,OAAO,CAAC,MAAM,SAAS,aAAa,EAAE,MAAM,SAAS,IAAI,EAC7D,cAAc,EAAE,cAAc,CAAC,MAAM,CAAC,EACtC,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;CA0KpC"}
@@ -0,0 +1,134 @@
1
+ import type { ServicePolicy } from "@metamask/controller-utils";
2
+ import type { JsonRpcRequest } from "@metamask/utils";
3
+ import { type Json, type JsonRpcParams, type JsonRpcResponse } from "@metamask/utils";
4
+ import type { AbstractRpcService } from "./abstract-rpc-service.mjs";
5
+ import type { AddToCockatielEventData, FetchOptions } from "./shared.mjs";
6
+ /**
7
+ * The list of error messages that represent a failure to reach the network.
8
+ *
9
+ * This list was derived from Sindre Sorhus's `is-network-error` package:
10
+ * <https://github.com/sindresorhus/is-network-error/blob/7bbfa8be9482ce1427a21fbff60e3ee1650dd091/index.js>
11
+ */
12
+ export declare const NETWORK_UNREACHABLE_ERRORS: Set<string>;
13
+ /**
14
+ * Determines whether the given error represents a failure to reach the network
15
+ * after request parameters have been validated.
16
+ *
17
+ * This is somewhat difficult to verify because JavaScript engines (and in
18
+ * some cases libraries) produce slightly different error messages for this
19
+ * particular scenario, and we need to account for this.
20
+ *
21
+ * @param error - The error.
22
+ * @returns True if the error indicates that the network is unreachable, and
23
+ * false otherwise.
24
+ */
25
+ export default function isNetworkUnreachableError(error: unknown): boolean;
26
+ /**
27
+ * This class is responsible for making a request to an endpoint that implements
28
+ * the JSON-RPC protocol. It is designed to gracefully handle network and server
29
+ * failures, retrying requests using exponential backoff. It also offers a hook
30
+ * which can used to respond to slow requests.
31
+ */
32
+ export declare class RpcService implements AbstractRpcService {
33
+ #private;
34
+ /**
35
+ * Constructs a new RpcService object.
36
+ *
37
+ * @param args - The arguments.
38
+ * @param args.fetch - A function that can be used to make an HTTP request.
39
+ * If your JavaScript environment supports `fetch` natively, you'll probably
40
+ * want to pass that; otherwise you can pass an equivalent (such as `fetch`
41
+ * via `node-fetch`).
42
+ * @param args.btoa - A function that can be used to convert a binary string
43
+ * into base-64. Used to encode authorization credentials.
44
+ * @param args.endpointUrl - The URL of the RPC endpoint.
45
+ * @param args.fetchOptions - A common set of options that will be used to
46
+ * make every request. Can be overridden on the request level (e.g. to add
47
+ * headers).
48
+ * @param args.failoverService - An RPC service that represents a failover
49
+ * endpoint which will be invoked while the circuit for _this_ service is
50
+ * open.
51
+ */
52
+ constructor({ fetch: givenFetch, btoa: givenBtoa, endpointUrl, fetchOptions, failoverService, }: {
53
+ fetch: typeof fetch;
54
+ btoa: typeof btoa;
55
+ endpointUrl: URL | string;
56
+ fetchOptions?: FetchOptions;
57
+ failoverService?: AbstractRpcService;
58
+ });
59
+ /**
60
+ * Listens for when the RPC service retries the request.
61
+ *
62
+ * @param listener - The callback to be called when the retry occurs.
63
+ * @returns What {@link ServicePolicy.onRetry} returns.
64
+ * @see {@link createServicePolicy}
65
+ */
66
+ onRetry(listener: AddToCockatielEventData<Parameters<ServicePolicy['onRetry']>[0], {
67
+ endpointUrl: string;
68
+ }>): import("cockatiel").IDisposable;
69
+ /**
70
+ * Listens for when the RPC service retries the request too many times in a
71
+ * row.
72
+ *
73
+ * @param listener - The callback to be called when the circuit is broken.
74
+ * @returns What {@link ServicePolicy.onBreak} returns.
75
+ * @see {@link createServicePolicy}
76
+ */
77
+ onBreak(listener: AddToCockatielEventData<Parameters<ServicePolicy['onBreak']>[0], {
78
+ endpointUrl: string;
79
+ }>): import("cockatiel").IDisposable;
80
+ /**
81
+ * Listens for when the policy underlying this RPC service detects a slow
82
+ * request.
83
+ *
84
+ * @param listener - The callback to be called when the request is slow.
85
+ * @returns What {@link ServicePolicy.onDegraded} returns.
86
+ * @see {@link createServicePolicy}
87
+ */
88
+ onDegraded(listener: AddToCockatielEventData<Parameters<ServicePolicy['onDegraded']>[0], {
89
+ endpointUrl: string;
90
+ }>): import("cockatiel").IDisposable;
91
+ /**
92
+ * Makes a request to the RPC endpoint. If the circuit is open because this
93
+ * request has failed too many times, the request is forwarded to a failover
94
+ * service (if provided).
95
+ *
96
+ * This overload is specifically designed for `eth_getBlockByNumber`, which
97
+ * can return a `result` of `null` despite an expected `Result` being
98
+ * provided.
99
+ *
100
+ * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.
101
+ * @param fetchOptions - An options bag for {@link fetch} which further
102
+ * specifies the request.
103
+ * @returns The decoded JSON-RPC response from the endpoint.
104
+ * @throws A "method not found" error if the response status is 405.
105
+ * @throws A rate limiting error if the response HTTP status is 429.
106
+ * @throws A timeout error if the response HTTP status is 503 or 504.
107
+ * @throws A generic error if the response HTTP status is not 2xx but also not
108
+ * 405, 429, 503, or 504.
109
+ */
110
+ request<Params extends JsonRpcParams, Result extends Json>(jsonRpcRequest: JsonRpcRequest<Params> & {
111
+ method: 'eth_getBlockByNumber';
112
+ }, fetchOptions?: FetchOptions): Promise<JsonRpcResponse<Result> | JsonRpcResponse<null>>;
113
+ /**
114
+ * Makes a request to the RPC endpoint. If the circuit is open because this
115
+ * request has failed too many times, the request is forwarded to a failover
116
+ * service (if provided).
117
+ *
118
+ * This overload is designed for all RPC methods except for
119
+ * `eth_getBlockByNumber`, which are expected to return a `result` of the
120
+ * expected `Result`.
121
+ *
122
+ * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.
123
+ * @param fetchOptions - An options bag for {@link fetch} which further
124
+ * specifies the request.
125
+ * @returns The decoded JSON-RPC response from the endpoint.
126
+ * @throws A "method not found" error if the response status is 405.
127
+ * @throws A rate limiting error if the response HTTP status is 429.
128
+ * @throws A timeout error if the response HTTP status is 503 or 504.
129
+ * @throws A generic error if the response HTTP status is not 2xx but also not
130
+ * 405, 429, 503, or 504.
131
+ */
132
+ request<Params extends JsonRpcParams, Result extends Json>(jsonRpcRequest: JsonRpcRequest<Params>, fetchOptions?: FetchOptions): Promise<JsonRpcResponse<Result>>;
133
+ }
134
+ //# sourceMappingURL=rpc-service.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-service.d.mts","sourceRoot":"","sources":["../../src/rpc-service/rpc-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAOhE,OAAO,KAAK,EAAE,cAAc,EAAE,wBAAwB;AACtD,OAAO,EAEL,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,eAAe,EACrB,wBAAwB;AAGzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,mCAA+B;AACjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,YAAY,EAAE,qBAAiB;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,aASrC,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,KAAK,EAAE,OAAO,WAI/D;AAgBD;;;;;GAKG;AACH,qBAAa,UAAW,YAAW,kBAAkB;;IA2BnD;;;;;;;;;;;;;;;;;OAiBG;gBACS,EACV,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,SAAS,EACf,WAAW,EACX,YAAiB,EACjB,eAAe,GAChB,EAAE;QACD,KAAK,EAAE,OAAO,KAAK,CAAC;QACpB,IAAI,EAAE,OAAO,IAAI,CAAC;QAClB,WAAW,EAAE,GAAG,GAAG,MAAM,CAAC;QAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;QAC5B,eAAe,CAAC,EAAE,kBAAkB,CAAC;KACtC;IA6BD;;;;;;OAMG;IACH,OAAO,CACL,QAAQ,EAAE,uBAAuB,CAC/B,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EACvC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CACxB;IAOH;;;;;;;OAOG;IACH,OAAO,CACL,QAAQ,EAAE,uBAAuB,CAC/B,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EACvC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CACxB;IAOH;;;;;;;OAOG;IACH,UAAU,CACR,QAAQ,EAAE,uBAAuB,CAC/B,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,EAC1C;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CACxB;IAOH;;;;;;;;;;;;;;;;;;OAkBG;IACG,OAAO,CAAC,MAAM,SAAS,aAAa,EAAE,MAAM,SAAS,IAAI,EAC7D,cAAc,EAAE,cAAc,CAAC,MAAM,CAAC,GAAG;QAAE,MAAM,EAAE,sBAAsB,CAAA;KAAE,EAC3E,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAE3D;;;;;;;;;;;;;;;;;;OAkBG;IACG,OAAO,CAAC,MAAM,SAAS,aAAa,EAAE,MAAM,SAAS,IAAI,EAC7D,cAAc,EAAE,cAAc,CAAC,MAAM,CAAC,EACtC,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;CA0KpC"}
@@ -0,0 +1,278 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _RpcService_instances, _RpcService_fetch, _RpcService_endpointUrl, _RpcService_fetchOptions, _RpcService_failoverService, _RpcService_policy, _RpcService_getDefaultFetchOptions, _RpcService_getCompleteFetchOptions, _RpcService_executePolicy;
13
+ function $importDefault(module) {
14
+ if (module?.__esModule) {
15
+ return module.default;
16
+ }
17
+ return module;
18
+ }
19
+ import { CircuitState, createServicePolicy, handleWhen } from "@metamask/controller-utils";
20
+ import { rpcErrors } from "@metamask/rpc-errors";
21
+ import { hasProperty } from "@metamask/utils";
22
+ import $deepmerge from "deepmerge";
23
+ const deepmerge = $importDefault($deepmerge);
24
+ /**
25
+ * The list of error messages that represent a failure to reach the network.
26
+ *
27
+ * This list was derived from Sindre Sorhus's `is-network-error` package:
28
+ * <https://github.com/sindresorhus/is-network-error/blob/7bbfa8be9482ce1427a21fbff60e3ee1650dd091/index.js>
29
+ */
30
+ export const NETWORK_UNREACHABLE_ERRORS = new Set([
31
+ 'network error',
32
+ 'Failed to fetch',
33
+ 'NetworkError when attempting to fetch resource.',
34
+ 'The Internet connection appears to be offline.',
35
+ 'Load failed',
36
+ 'Network request failed',
37
+ 'fetch failed',
38
+ 'terminated', // Undici (Node.js)
39
+ ]);
40
+ /**
41
+ * Determines whether the given error represents a failure to reach the network
42
+ * after request parameters have been validated.
43
+ *
44
+ * This is somewhat difficult to verify because JavaScript engines (and in
45
+ * some cases libraries) produce slightly different error messages for this
46
+ * particular scenario, and we need to account for this.
47
+ *
48
+ * @param error - The error.
49
+ * @returns True if the error indicates that the network is unreachable, and
50
+ * false otherwise.
51
+ */
52
+ export default function isNetworkUnreachableError(error) {
53
+ return (error instanceof TypeError && NETWORK_UNREACHABLE_ERRORS.has(error.message));
54
+ }
55
+ /**
56
+ * Guarantees a URL, even given a string. This is useful for checking components
57
+ * of that URL.
58
+ *
59
+ * @param endpointUrlOrUrlString - Either a URL object or a string that
60
+ * represents the URL of an endpoint.
61
+ * @returns A URL object.
62
+ */
63
+ function getNormalizedEndpointUrl(endpointUrlOrUrlString) {
64
+ return endpointUrlOrUrlString instanceof URL
65
+ ? endpointUrlOrUrlString
66
+ : new URL(endpointUrlOrUrlString);
67
+ }
68
+ /**
69
+ * This class is responsible for making a request to an endpoint that implements
70
+ * the JSON-RPC protocol. It is designed to gracefully handle network and server
71
+ * failures, retrying requests using exponential backoff. It also offers a hook
72
+ * which can used to respond to slow requests.
73
+ */
74
+ export class RpcService {
75
+ /**
76
+ * Constructs a new RpcService object.
77
+ *
78
+ * @param args - The arguments.
79
+ * @param args.fetch - A function that can be used to make an HTTP request.
80
+ * If your JavaScript environment supports `fetch` natively, you'll probably
81
+ * want to pass that; otherwise you can pass an equivalent (such as `fetch`
82
+ * via `node-fetch`).
83
+ * @param args.btoa - A function that can be used to convert a binary string
84
+ * into base-64. Used to encode authorization credentials.
85
+ * @param args.endpointUrl - The URL of the RPC endpoint.
86
+ * @param args.fetchOptions - A common set of options that will be used to
87
+ * make every request. Can be overridden on the request level (e.g. to add
88
+ * headers).
89
+ * @param args.failoverService - An RPC service that represents a failover
90
+ * endpoint which will be invoked while the circuit for _this_ service is
91
+ * open.
92
+ */
93
+ constructor({ fetch: givenFetch, btoa: givenBtoa, endpointUrl, fetchOptions = {}, failoverService, }) {
94
+ _RpcService_instances.add(this);
95
+ /**
96
+ * The function used to make an HTTP request.
97
+ */
98
+ _RpcService_fetch.set(this, void 0);
99
+ /**
100
+ * The URL of the RPC endpoint.
101
+ */
102
+ _RpcService_endpointUrl.set(this, void 0);
103
+ /**
104
+ * A common set of options that the request options will extend.
105
+ */
106
+ _RpcService_fetchOptions.set(this, void 0);
107
+ /**
108
+ * An RPC service that represents a failover endpoint which will be invoked
109
+ * while the circuit for _this_ service is open.
110
+ */
111
+ _RpcService_failoverService.set(this, void 0);
112
+ /**
113
+ * The policy that wraps the request.
114
+ */
115
+ _RpcService_policy.set(this, void 0);
116
+ __classPrivateFieldSet(this, _RpcService_fetch, givenFetch, "f");
117
+ __classPrivateFieldSet(this, _RpcService_endpointUrl, getNormalizedEndpointUrl(endpointUrl), "f");
118
+ __classPrivateFieldSet(this, _RpcService_fetchOptions, __classPrivateFieldGet(this, _RpcService_instances, "m", _RpcService_getDefaultFetchOptions).call(this, __classPrivateFieldGet(this, _RpcService_endpointUrl, "f"), fetchOptions, givenBtoa), "f");
119
+ __classPrivateFieldSet(this, _RpcService_failoverService, failoverService, "f");
120
+ const policy = createServicePolicy({
121
+ maxRetries: 4,
122
+ maxConsecutiveFailures: 15,
123
+ retryFilterPolicy: handleWhen((error) => {
124
+ return (
125
+ // Ignore errors where the request failed to establish
126
+ isNetworkUnreachableError(error) ||
127
+ // Ignore server sent HTML error pages or truncated JSON responses
128
+ error.message.includes('not valid JSON') ||
129
+ // Ignore server overload errors
130
+ error.message.includes('Gateway timeout') ||
131
+ (hasProperty(error, 'code') &&
132
+ (error.code === 'ETIMEDOUT' || error.code === 'ECONNRESET')));
133
+ }),
134
+ });
135
+ __classPrivateFieldSet(this, _RpcService_policy, policy, "f");
136
+ }
137
+ /**
138
+ * Listens for when the RPC service retries the request.
139
+ *
140
+ * @param listener - The callback to be called when the retry occurs.
141
+ * @returns What {@link ServicePolicy.onRetry} returns.
142
+ * @see {@link createServicePolicy}
143
+ */
144
+ onRetry(listener) {
145
+ return __classPrivateFieldGet(this, _RpcService_policy, "f").onRetry((data) => {
146
+ listener({ ...data, endpointUrl: __classPrivateFieldGet(this, _RpcService_endpointUrl, "f").toString() });
147
+ });
148
+ }
149
+ /**
150
+ * Listens for when the RPC service retries the request too many times in a
151
+ * row.
152
+ *
153
+ * @param listener - The callback to be called when the circuit is broken.
154
+ * @returns What {@link ServicePolicy.onBreak} returns.
155
+ * @see {@link createServicePolicy}
156
+ */
157
+ onBreak(listener) {
158
+ return __classPrivateFieldGet(this, _RpcService_policy, "f").onBreak((data) => {
159
+ listener({ ...data, endpointUrl: __classPrivateFieldGet(this, _RpcService_endpointUrl, "f").toString() });
160
+ });
161
+ }
162
+ /**
163
+ * Listens for when the policy underlying this RPC service detects a slow
164
+ * request.
165
+ *
166
+ * @param listener - The callback to be called when the request is slow.
167
+ * @returns What {@link ServicePolicy.onDegraded} returns.
168
+ * @see {@link createServicePolicy}
169
+ */
170
+ onDegraded(listener) {
171
+ return __classPrivateFieldGet(this, _RpcService_policy, "f").onDegraded(() => {
172
+ listener({ endpointUrl: __classPrivateFieldGet(this, _RpcService_endpointUrl, "f").toString() });
173
+ });
174
+ }
175
+ async request(jsonRpcRequest, fetchOptions = {}) {
176
+ const completeFetchOptions = __classPrivateFieldGet(this, _RpcService_instances, "m", _RpcService_getCompleteFetchOptions).call(this, jsonRpcRequest, fetchOptions);
177
+ try {
178
+ return await __classPrivateFieldGet(this, _RpcService_instances, "m", _RpcService_executePolicy).call(this, jsonRpcRequest, completeFetchOptions);
179
+ }
180
+ catch (error) {
181
+ if (__classPrivateFieldGet(this, _RpcService_policy, "f").circuitBreakerPolicy.state === CircuitState.Open &&
182
+ __classPrivateFieldGet(this, _RpcService_failoverService, "f") !== undefined) {
183
+ return await __classPrivateFieldGet(this, _RpcService_failoverService, "f").request(jsonRpcRequest, completeFetchOptions);
184
+ }
185
+ throw error;
186
+ }
187
+ }
188
+ }
189
+ _RpcService_fetch = new WeakMap(), _RpcService_endpointUrl = new WeakMap(), _RpcService_fetchOptions = new WeakMap(), _RpcService_failoverService = new WeakMap(), _RpcService_policy = new WeakMap(), _RpcService_instances = new WeakSet(), _RpcService_getDefaultFetchOptions = function _RpcService_getDefaultFetchOptions(endpointUrl, fetchOptions, givenBtoa) {
190
+ if (endpointUrl.username && endpointUrl.password) {
191
+ const authString = `${endpointUrl.username}:${endpointUrl.password}`;
192
+ const encodedCredentials = givenBtoa(authString);
193
+ return deepmerge(fetchOptions, {
194
+ headers: { Authorization: `Basic ${encodedCredentials}` },
195
+ });
196
+ }
197
+ return fetchOptions;
198
+ }, _RpcService_getCompleteFetchOptions = function _RpcService_getCompleteFetchOptions(jsonRpcRequest, fetchOptions) {
199
+ const defaultOptions = {
200
+ method: 'POST',
201
+ headers: {
202
+ Accept: 'application/json',
203
+ 'Content-Type': 'application/json',
204
+ },
205
+ };
206
+ const mergedOptions = deepmerge(defaultOptions, deepmerge(__classPrivateFieldGet(this, _RpcService_fetchOptions, "f"), fetchOptions));
207
+ const { id, jsonrpc, method, params } = jsonRpcRequest;
208
+ const body = JSON.stringify({
209
+ id,
210
+ jsonrpc,
211
+ method,
212
+ params,
213
+ });
214
+ return { ...mergedOptions, body };
215
+ }, _RpcService_executePolicy =
216
+ /**
217
+ * Makes the request using the Cockatiel policy that this service creates.
218
+ *
219
+ * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.
220
+ * @param fetchOptions - The options for `fetch`; will be combined with the
221
+ * fetch options passed to the constructor
222
+ * @returns The decoded JSON-RPC response from the endpoint.
223
+ * @throws A "method not found" error if the response status is 405.
224
+ * @throws A rate limiting error if the response HTTP status is 429.
225
+ * @throws A timeout error if the response HTTP status is 503 or 504.
226
+ * @throws A generic error if the response HTTP status is not 2xx but also not
227
+ * 405, 429, 503, or 504.
228
+ */
229
+ async function _RpcService_executePolicy(jsonRpcRequest, fetchOptions) {
230
+ return await __classPrivateFieldGet(this, _RpcService_policy, "f").execute(async () => {
231
+ const response = await __classPrivateFieldGet(this, _RpcService_fetch, "f").call(this, __classPrivateFieldGet(this, _RpcService_endpointUrl, "f"), fetchOptions);
232
+ if (response.status === 405) {
233
+ throw rpcErrors.methodNotFound();
234
+ }
235
+ if (response.status === 429) {
236
+ throw rpcErrors.internal({ message: 'Request is being rate limited.' });
237
+ }
238
+ if (response.status === 503 || response.status === 504) {
239
+ throw rpcErrors.internal({
240
+ message: 'Gateway timeout. The request took too long to process. This can happen when querying logs over too wide a block range.',
241
+ });
242
+ }
243
+ const text = await response.text();
244
+ if (jsonRpcRequest.method === 'eth_getBlockByNumber' &&
245
+ text === 'Not Found') {
246
+ return {
247
+ id: jsonRpcRequest.id,
248
+ jsonrpc: jsonRpcRequest.jsonrpc,
249
+ result: null,
250
+ };
251
+ }
252
+ // Type annotation: We assume that if this response is valid JSON, it's a
253
+ // valid JSON-RPC response.
254
+ let json;
255
+ try {
256
+ json = JSON.parse(text);
257
+ }
258
+ catch (error) {
259
+ if (error instanceof SyntaxError) {
260
+ throw rpcErrors.internal({
261
+ message: 'Could not parse response as it is not valid JSON',
262
+ data: text,
263
+ });
264
+ }
265
+ else {
266
+ throw error;
267
+ }
268
+ }
269
+ if (!response.ok) {
270
+ throw rpcErrors.internal({
271
+ message: `Non-200 status code: '${response.status}'`,
272
+ data: json,
273
+ });
274
+ }
275
+ return json;
276
+ });
277
+ };
278
+ //# sourceMappingURL=rpc-service.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-service.mjs","sourceRoot":"","sources":["../../src/rpc-service/rpc-service.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServicePolicy } 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 * The list of error messages that represent a failure to reach 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 NETWORK_UNREACHABLE_ERRORS = new Set([\n 'network error', // Chrome\n 'Failed to fetch', // Chrome\n 'NetworkError when attempting to fetch resource.', // Firefox\n 'The Internet connection appears to be offline.', // Safari 16\n 'Load failed', // Safari 17+\n 'Network request failed', // `cross-fetch`\n 'fetch failed', // Undici (Node.js)\n 'terminated', // Undici (Node.js)\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 is unreachable, and\n * false otherwise.\n */\nexport default function isNetworkUnreachableError(error: unknown) {\n return (\n error instanceof TypeError && NETWORK_UNREACHABLE_ERRORS.has(error.message)\n );\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: AbstractRpcService | undefined;\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 args - The arguments.\n * @param args.fetch - A function that can be used to make an HTTP request.\n * If your JavaScript environment supports `fetch` natively, you'll probably\n * want to pass that; otherwise you can pass an equivalent (such as `fetch`\n * via `node-fetch`).\n * @param args.btoa - A function that can be used to convert a binary string\n * into base-64. Used to encode authorization credentials.\n * @param args.endpointUrl - The URL of the RPC endpoint.\n * @param args.fetchOptions - A common set of options that will be used to\n * make every request. Can be overridden on the request level (e.g. to add\n * headers).\n * @param args.failoverService - An RPC service that represents a failover\n * endpoint which will be invoked while the circuit for _this_ service is\n * open.\n */\n constructor({\n fetch: givenFetch,\n btoa: givenBtoa,\n endpointUrl,\n fetchOptions = {},\n failoverService,\n }: {\n fetch: typeof fetch;\n btoa: typeof btoa;\n endpointUrl: URL | string;\n fetchOptions?: FetchOptions;\n failoverService?: AbstractRpcService;\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: 4,\n maxConsecutiveFailures: 15,\n retryFilterPolicy: handleWhen((error) => {\n return (\n // Ignore errors where the request failed to establish\n isNetworkUnreachableError(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 }\n >,\n ) {\n return this.#policy.onBreak((data) => {\n listener({ ...data, endpointUrl: this.#endpointUrl.toString() });\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"]}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=shared.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.cjs","sourceRoot":"","sources":["../../src/rpc-service/shared.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Equivalent to the built-in `FetchOptions` type, but renamed for clarity.\n */\nexport type FetchOptions = RequestInit;\n\n/**\n * Extends an event listener that Cockatiel uses so that when it is called, more\n * data can be supplied in the event object.\n */\nexport type AddToCockatielEventData<EventListener, AdditionalData> =\n EventListener extends (data: infer Data) => void\n ? // Prevent Data from being split if it's a type union\n [Data] extends [void]\n ? (data: AdditionalData) => void\n : (data: Data & AdditionalData) => void\n : never;\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Equivalent to the built-in `FetchOptions` type, but renamed for clarity.
3
+ */
4
+ export type FetchOptions = RequestInit;
5
+ /**
6
+ * Extends an event listener that Cockatiel uses so that when it is called, more
7
+ * data can be supplied in the event object.
8
+ */
9
+ export type AddToCockatielEventData<EventListener, AdditionalData> = EventListener extends (data: infer Data) => void ? [
10
+ Data
11
+ ] extends [void] ? (data: AdditionalData) => void : (data: Data & AdditionalData) => void : never;
12
+ //# sourceMappingURL=shared.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.cts","sourceRoot":"","sources":["../../src/rpc-service/shared.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC;AAEvC;;;GAGG;AACH,MAAM,MAAM,uBAAuB,CAAC,aAAa,EAAE,cAAc,IAC/D,aAAa,SAAS,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,GAE5C;IAAC,IAAI;CAAC,SAAS,CAAC,IAAI,CAAC,GACnB,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,GAC9B,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,KAAK,IAAI,GACvC,KAAK,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Equivalent to the built-in `FetchOptions` type, but renamed for clarity.
3
+ */
4
+ export type FetchOptions = RequestInit;
5
+ /**
6
+ * Extends an event listener that Cockatiel uses so that when it is called, more
7
+ * data can be supplied in the event object.
8
+ */
9
+ export type AddToCockatielEventData<EventListener, AdditionalData> = EventListener extends (data: infer Data) => void ? [
10
+ Data
11
+ ] extends [void] ? (data: AdditionalData) => void : (data: Data & AdditionalData) => void : never;
12
+ //# sourceMappingURL=shared.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.mts","sourceRoot":"","sources":["../../src/rpc-service/shared.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC;AAEvC;;;GAGG;AACH,MAAM,MAAM,uBAAuB,CAAC,aAAa,EAAE,cAAc,IAC/D,aAAa,SAAS,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,GAE5C;IAAC,IAAI;CAAC,SAAS,CAAC,IAAI,CAAC,GACnB,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,GAC9B,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,KAAK,IAAI,GACvC,KAAK,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=shared.mjs.map