@metamask-previews/network-controller 36.0.0-preview-4f4c98e → 36.0.0-preview-5b293ee50

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 (158) hide show
  1. package/CHANGELOG.md +0 -5
  2. package/dist/NetworkController-method-action-types.cjs +7 -0
  3. package/dist/NetworkController-method-action-types.cjs.map +1 -0
  4. package/dist/{NetworkController-method-action-types.d.ts → NetworkController-method-action-types.d.cts} +2 -2
  5. package/dist/NetworkController-method-action-types.d.cts.map +1 -0
  6. package/dist/NetworkController-method-action-types.d.mts +268 -0
  7. package/dist/NetworkController-method-action-types.d.mts.map +1 -0
  8. package/dist/{NetworkController-method-action-types.js → NetworkController-method-action-types.mjs} +1 -1
  9. package/dist/NetworkController-method-action-types.mjs.map +1 -0
  10. package/dist/NetworkController.cjs +1599 -0
  11. package/dist/NetworkController.cjs.map +1 -0
  12. package/dist/{NetworkController.d.ts → NetworkController.d.cts} +29 -37
  13. package/dist/NetworkController.d.cts.map +1 -0
  14. package/dist/NetworkController.d.mts +956 -0
  15. package/dist/NetworkController.d.mts.map +1 -0
  16. package/dist/{NetworkController.js → NetworkController.mjs} +29 -20
  17. package/dist/NetworkController.mjs.map +1 -0
  18. package/dist/constants.cjs +46 -0
  19. package/dist/constants.cjs.map +1 -0
  20. package/dist/{constants.d.ts → constants.d.cts} +1 -1
  21. package/dist/constants.d.cts.map +1 -0
  22. package/dist/constants.d.mts +42 -0
  23. package/dist/constants.d.mts.map +1 -0
  24. package/dist/{constants.js → constants.mjs} +1 -1
  25. package/dist/constants.mjs.map +1 -0
  26. package/dist/create-auto-managed-network-client.cjs +149 -0
  27. package/dist/create-auto-managed-network-client.cjs.map +1 -0
  28. package/dist/{create-auto-managed-network-client.d.ts → create-auto-managed-network-client.d.cts} +7 -7
  29. package/dist/create-auto-managed-network-client.d.cts.map +1 -0
  30. package/dist/create-auto-managed-network-client.d.mts +74 -0
  31. package/dist/create-auto-managed-network-client.d.mts.map +1 -0
  32. package/dist/{create-auto-managed-network-client.js → create-auto-managed-network-client.mjs} +2 -2
  33. package/dist/create-auto-managed-network-client.mjs.map +1 -0
  34. package/dist/create-network-client.cjs +439 -0
  35. package/dist/create-network-client.cjs.map +1 -0
  36. package/dist/{create-network-client.d.ts → create-network-client.d.cts} +7 -7
  37. package/dist/create-network-client.d.cts.map +1 -0
  38. package/dist/create-network-client.d.mts +60 -0
  39. package/dist/create-network-client.d.mts.map +1 -0
  40. package/dist/{create-network-client.js → create-network-client.mjs} +14 -15
  41. package/dist/create-network-client.mjs.map +1 -0
  42. package/dist/index.cjs +31 -0
  43. package/dist/index.cjs.map +1 -0
  44. package/dist/{index.d.ts → index.d.cts} +16 -16
  45. package/dist/index.d.cts.map +1 -0
  46. package/dist/index.d.mts +16 -0
  47. package/dist/index.d.mts.map +1 -0
  48. package/dist/index.mjs +6 -0
  49. package/dist/index.mjs.map +1 -0
  50. package/dist/logger.cjs +7 -0
  51. package/dist/logger.cjs.map +1 -0
  52. package/dist/logger.d.cts +5 -0
  53. package/dist/logger.d.cts.map +1 -0
  54. package/dist/logger.d.mts +5 -0
  55. package/dist/logger.d.mts.map +1 -0
  56. package/dist/logger.mjs +4 -0
  57. package/dist/logger.mjs.map +1 -0
  58. package/dist/rpc-service/abstract-rpc-service.cjs +3 -0
  59. package/dist/rpc-service/abstract-rpc-service.cjs.map +1 -0
  60. package/dist/rpc-service/{abstract-rpc-service.d.ts → abstract-rpc-service.d.cts} +2 -2
  61. package/dist/rpc-service/abstract-rpc-service.d.cts.map +1 -0
  62. package/dist/rpc-service/abstract-rpc-service.d.mts +17 -0
  63. package/dist/rpc-service/abstract-rpc-service.d.mts.map +1 -0
  64. package/dist/rpc-service/abstract-rpc-service.mjs +2 -0
  65. package/dist/rpc-service/abstract-rpc-service.mjs.map +1 -0
  66. package/dist/rpc-service/rpc-service-chain.cjs +304 -0
  67. package/dist/rpc-service/rpc-service-chain.cjs.map +1 -0
  68. package/dist/rpc-service/{rpc-service-chain.d.ts → rpc-service-chain.d.cts} +6 -6
  69. package/dist/rpc-service/rpc-service-chain.d.cts.map +1 -0
  70. package/dist/rpc-service/rpc-service-chain.d.mts +169 -0
  71. package/dist/rpc-service/rpc-service-chain.d.mts.map +1 -0
  72. package/dist/rpc-service/{rpc-service-chain.js → rpc-service-chain.mjs} +4 -4
  73. package/dist/rpc-service/rpc-service-chain.mjs.map +1 -0
  74. package/dist/rpc-service/rpc-service-requestable.cjs +3 -0
  75. package/dist/rpc-service/rpc-service-requestable.cjs.map +1 -0
  76. package/dist/rpc-service/{rpc-service-requestable.d.ts → rpc-service-requestable.d.cts} +4 -4
  77. package/dist/rpc-service/rpc-service-requestable.d.cts.map +1 -0
  78. package/dist/rpc-service/rpc-service-requestable.d.mts +63 -0
  79. package/dist/rpc-service/rpc-service-requestable.d.mts.map +1 -0
  80. package/dist/rpc-service/rpc-service-requestable.mjs +2 -0
  81. package/dist/rpc-service/rpc-service-requestable.mjs.map +1 -0
  82. package/dist/rpc-service/rpc-service.cjs +540 -0
  83. package/dist/rpc-service/rpc-service.cjs.map +1 -0
  84. package/dist/rpc-service/{rpc-service.d.ts → rpc-service.d.cts} +6 -6
  85. package/dist/rpc-service/rpc-service.d.cts.map +1 -0
  86. package/dist/rpc-service/rpc-service.d.mts +247 -0
  87. package/dist/rpc-service/rpc-service.d.mts.map +1 -0
  88. package/dist/rpc-service/{rpc-service.js → rpc-service.mjs} +14 -6
  89. package/dist/rpc-service/rpc-service.mjs.map +1 -0
  90. package/dist/rpc-service/shared.cjs +3 -0
  91. package/dist/rpc-service/shared.cjs.map +1 -0
  92. package/dist/rpc-service/{shared.d.ts → shared.d.cts} +2 -2
  93. package/dist/rpc-service/shared.d.cts.map +1 -0
  94. package/dist/rpc-service/shared.d.mts +32 -0
  95. package/dist/rpc-service/shared.d.mts.map +1 -0
  96. package/dist/rpc-service/shared.mjs +2 -0
  97. package/dist/rpc-service/shared.mjs.map +1 -0
  98. package/dist/rpc-service-analytics.cjs +180 -0
  99. package/dist/rpc-service-analytics.cjs.map +1 -0
  100. package/dist/{rpc-service-analytics.d.ts → rpc-service-analytics.d.cts} +3 -3
  101. package/dist/rpc-service-analytics.d.cts.map +1 -0
  102. package/dist/rpc-service-analytics.d.mts +85 -0
  103. package/dist/rpc-service-analytics.d.mts.map +1 -0
  104. package/dist/{rpc-service-analytics.js → rpc-service-analytics.mjs} +4 -4
  105. package/dist/rpc-service-analytics.mjs.map +1 -0
  106. package/dist/selectors.cjs +16 -0
  107. package/dist/selectors.cjs.map +1 -0
  108. package/dist/{selectors.d.ts → selectors.d.cts} +2 -2
  109. package/dist/selectors.d.cts.map +1 -0
  110. package/dist/selectors.d.mts +22 -0
  111. package/dist/selectors.d.mts.map +1 -0
  112. package/dist/{selectors.js → selectors.mjs} +1 -1
  113. package/dist/selectors.mjs.map +1 -0
  114. package/dist/types.cjs +12 -0
  115. package/dist/types.cjs.map +1 -0
  116. package/dist/{types.d.ts → types.d.cts} +5 -5
  117. package/dist/types.d.cts.map +1 -0
  118. package/dist/types.d.mts +49 -0
  119. package/dist/types.d.mts.map +1 -0
  120. package/dist/{types.js → types.mjs} +1 -1
  121. package/dist/types.mjs.map +1 -0
  122. package/package.json +19 -15
  123. package/dist/NetworkController-method-action-types.d.ts.map +0 -1
  124. package/dist/NetworkController-method-action-types.js.map +0 -1
  125. package/dist/NetworkController.d.ts.map +0 -1
  126. package/dist/NetworkController.js.map +0 -1
  127. package/dist/constants.d.ts.map +0 -1
  128. package/dist/constants.js.map +0 -1
  129. package/dist/create-auto-managed-network-client.d.ts.map +0 -1
  130. package/dist/create-auto-managed-network-client.js.map +0 -1
  131. package/dist/create-network-client.d.ts.map +0 -1
  132. package/dist/create-network-client.js.map +0 -1
  133. package/dist/index.d.ts.map +0 -1
  134. package/dist/index.js +0 -6
  135. package/dist/index.js.map +0 -1
  136. package/dist/logger.d.ts +0 -4
  137. package/dist/logger.d.ts.map +0 -1
  138. package/dist/logger.js +0 -4
  139. package/dist/logger.js.map +0 -1
  140. package/dist/rpc-service/abstract-rpc-service.d.ts.map +0 -1
  141. package/dist/rpc-service/abstract-rpc-service.js +0 -2
  142. package/dist/rpc-service/abstract-rpc-service.js.map +0 -1
  143. package/dist/rpc-service/rpc-service-chain.d.ts.map +0 -1
  144. package/dist/rpc-service/rpc-service-chain.js.map +0 -1
  145. package/dist/rpc-service/rpc-service-requestable.d.ts.map +0 -1
  146. package/dist/rpc-service/rpc-service-requestable.js +0 -2
  147. package/dist/rpc-service/rpc-service-requestable.js.map +0 -1
  148. package/dist/rpc-service/rpc-service.d.ts.map +0 -1
  149. package/dist/rpc-service/rpc-service.js.map +0 -1
  150. package/dist/rpc-service/shared.d.ts.map +0 -1
  151. package/dist/rpc-service/shared.js +0 -2
  152. package/dist/rpc-service/shared.js.map +0 -1
  153. package/dist/rpc-service-analytics.d.ts.map +0 -1
  154. package/dist/rpc-service-analytics.js.map +0 -1
  155. package/dist/selectors.d.ts.map +0 -1
  156. package/dist/selectors.js.map +0 -1
  157. package/dist/types.d.ts.map +0 -1
  158. package/dist/types.js.map +0 -1
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ 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");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ 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");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _RpcServiceChain_onAvailableEventEmitter, _RpcServiceChain_onBreakEventEmitter, _RpcServiceChain_onDegradedEventEmitter, _RpcServiceChain_primaryService, _RpcServiceChain_services, _RpcServiceChain_status;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.RpcServiceChain = void 0;
16
+ const controller_utils_1 = require("@metamask/controller-utils");
17
+ const logger_js_1 = require("../logger.cjs");
18
+ const rpc_service_js_1 = require("./rpc-service.cjs");
19
+ const log = (0, logger_js_1.createModuleLogger)(logger_js_1.projectLogger, 'RpcServiceChain');
20
+ /**
21
+ * Statuses that the RPC service chain can be in.
22
+ */
23
+ const STATUSES = {
24
+ Available: 'available',
25
+ Degraded: 'degraded',
26
+ Unknown: 'unknown',
27
+ Unavailable: 'unavailable',
28
+ };
29
+ /**
30
+ * This class constructs and manages requests to a chain of RpcService objects
31
+ * which represent RPC endpoints with which to access a particular network. The
32
+ * first service in the chain is intended to be the primary way of hitting the
33
+ * network and the remaining services are used as failovers.
34
+ */
35
+ class RpcServiceChain {
36
+ /**
37
+ * Constructs a new RpcServiceChain object.
38
+ *
39
+ * @param rpcServiceConfigurations - The options for the RPC services
40
+ * that you want to construct. Each object in this array is the same as
41
+ * {@link RpcServiceOptions}.
42
+ */
43
+ constructor(rpcServiceConfigurations) {
44
+ /**
45
+ * The event emitter for the `onAvailable` event.
46
+ */
47
+ _RpcServiceChain_onAvailableEventEmitter.set(this, void 0);
48
+ /**
49
+ * The event emitter for the `onBreak` event.
50
+ */
51
+ _RpcServiceChain_onBreakEventEmitter.set(this, void 0);
52
+ /**
53
+ * The event emitter for the `onDegraded` event.
54
+ */
55
+ _RpcServiceChain_onDegradedEventEmitter.set(this, void 0);
56
+ /**
57
+ * The first RPC service that requests will be sent to.
58
+ */
59
+ _RpcServiceChain_primaryService.set(this, void 0);
60
+ /**
61
+ * The RPC services in the chain.
62
+ */
63
+ _RpcServiceChain_services.set(this, void 0);
64
+ /**
65
+ * The status of the RPC service chain.
66
+ */
67
+ _RpcServiceChain_status.set(this, void 0);
68
+ __classPrivateFieldSet(this, _RpcServiceChain_services, rpcServiceConfigurations.map((rpcServiceConfiguration) => new rpc_service_js_1.RpcService(rpcServiceConfiguration)), "f");
69
+ __classPrivateFieldSet(this, _RpcServiceChain_primaryService, __classPrivateFieldGet(this, _RpcServiceChain_services, "f")[0], "f");
70
+ __classPrivateFieldSet(this, _RpcServiceChain_status, STATUSES.Unknown, "f");
71
+ __classPrivateFieldSet(this, _RpcServiceChain_onBreakEventEmitter, new controller_utils_1.CockatielEventEmitter(), "f");
72
+ __classPrivateFieldSet(this, _RpcServiceChain_onDegradedEventEmitter, new controller_utils_1.CockatielEventEmitter(), "f");
73
+ for (const service of __classPrivateFieldGet(this, _RpcServiceChain_services, "f")) {
74
+ service.onDegraded((data) => {
75
+ if (__classPrivateFieldGet(this, _RpcServiceChain_status, "f") !== STATUSES.Degraded) {
76
+ log('Updating status to "degraded"', data);
77
+ __classPrivateFieldSet(this, _RpcServiceChain_status, STATUSES.Degraded, "f");
78
+ const { endpointUrl, ...rest } = data;
79
+ __classPrivateFieldGet(this, _RpcServiceChain_onDegradedEventEmitter, "f").emit(rest);
80
+ }
81
+ });
82
+ }
83
+ __classPrivateFieldSet(this, _RpcServiceChain_onAvailableEventEmitter, new controller_utils_1.CockatielEventEmitter(), "f");
84
+ for (const service of __classPrivateFieldGet(this, _RpcServiceChain_services, "f")) {
85
+ service.onAvailable((data) => {
86
+ if (__classPrivateFieldGet(this, _RpcServiceChain_status, "f") !== STATUSES.Available) {
87
+ log('Updating status to "available"', data);
88
+ __classPrivateFieldSet(this, _RpcServiceChain_status, STATUSES.Available, "f");
89
+ const { endpointUrl, ...rest } = data;
90
+ __classPrivateFieldGet(this, _RpcServiceChain_onAvailableEventEmitter, "f").emit(rest);
91
+ }
92
+ });
93
+ }
94
+ }
95
+ /**
96
+ * Calls the provided callback when any of the RPC services is retried.
97
+ *
98
+ * This is mainly useful for tests.
99
+ *
100
+ * @param listener - The callback to be called.
101
+ * @returns An object with a `dispose` method which can be used to unregister
102
+ * the event listener.
103
+ */
104
+ onServiceRetry(listener) {
105
+ const disposables = __classPrivateFieldGet(this, _RpcServiceChain_services, "f").map((service) => service.onRetry((data) => {
106
+ listener({
107
+ ...data,
108
+ primaryEndpointUrl: __classPrivateFieldGet(this, _RpcServiceChain_primaryService, "f").endpointUrl.toString(),
109
+ });
110
+ }));
111
+ return {
112
+ dispose() {
113
+ disposables.forEach((disposable) => disposable.dispose());
114
+ },
115
+ };
116
+ }
117
+ /**
118
+ * Calls the provided callback only when the maximum number of failed
119
+ * consecutive attempts to receive a 2xx response has been reached for all
120
+ * RPC services in the chain, and all services' underlying circuits have
121
+ * broken.
122
+ *
123
+ * The callback will not be called if a service's circuit breaks but its
124
+ * failover does not. Use `onServiceBreak` if you'd like a lower level of
125
+ * granularity.
126
+ *
127
+ * @param listener - The callback to be called.
128
+ * @returns An object with a `dispose` method which can be used to unregister
129
+ * the callback.
130
+ */
131
+ onBreak(listener) {
132
+ return __classPrivateFieldGet(this, _RpcServiceChain_onBreakEventEmitter, "f").addListener(listener);
133
+ }
134
+ /**
135
+ * Calls the provided callback each time when, for *any* of the RPC services
136
+ * in this chain, the maximum number of failed consecutive attempts to receive
137
+ * a 2xx response has been reached and the underlying circuit has broken. A
138
+ * more granular version of `onBreak`.
139
+ *
140
+ * @param listener - The callback to be called.
141
+ * @returns An object with a `dispose` method which can be used to unregister
142
+ * the callback.
143
+ */
144
+ onServiceBreak(listener) {
145
+ const disposables = __classPrivateFieldGet(this, _RpcServiceChain_services, "f").map((service) => service.onBreak((data) => {
146
+ listener({
147
+ ...data,
148
+ primaryEndpointUrl: __classPrivateFieldGet(this, _RpcServiceChain_primaryService, "f").endpointUrl.toString(),
149
+ });
150
+ }));
151
+ return {
152
+ dispose() {
153
+ disposables.forEach((disposable) => disposable.dispose());
154
+ },
155
+ };
156
+ }
157
+ /**
158
+ * Calls the provided callback if no requests have been initiated yet or
159
+ * all requests to RPC services in this chain have responded successfully in a
160
+ * timely fashion, and then one of the two conditions apply:
161
+ *
162
+ * 1. When a retriable error is encountered making a request to an RPC
163
+ * service, and the request is retried until a set maximum is reached.
164
+ * 2. When a RPC service responds successfully, but the request takes longer
165
+ * than a set number of seconds to complete.
166
+ *
167
+ * Note that the callback will be called even if there are local connectivity
168
+ * issues which prevent requests from being initiated. This is intentional.
169
+ *
170
+ * Also note this callback will only be called if the RPC service chain as a
171
+ * whole is in a "degraded" state, and will then only be called once (e.g., it
172
+ * will not be called if a failover service falls into a degraded state, then
173
+ * the primary comes back online, but it is slow). Use `onServiceDegraded` if
174
+ * you'd like a lower level of granularity.
175
+ *
176
+ * @param listener - The callback to be called.
177
+ * @returns An object with a `dispose` method which can be used to unregister
178
+ * the callback.
179
+ */
180
+ onDegraded(listener) {
181
+ return __classPrivateFieldGet(this, _RpcServiceChain_onDegradedEventEmitter, "f").addListener(listener);
182
+ }
183
+ /**
184
+ * Calls the provided callback each time one of the two conditions apply:
185
+ *
186
+ * 1. When a retriable error is encountered making a request to an RPC
187
+ * service, and the request is retried until a set maximum is reached.
188
+ * 2. When a RPC service responds successfully, but the request takes longer
189
+ * than a set number of seconds to complete.
190
+ *
191
+ * Note that the callback will be called even if there are local connectivity
192
+ * issues which prevent requests from being initiated. This is intentional.
193
+ *
194
+ * This is a more granular version of `onDegraded`. The callback will be
195
+ * called for each slow request to an RPC service. It may also be called again
196
+ * if a failover service falls into a degraded state, then the primary comes
197
+ * back online, but it is slow.
198
+ *
199
+ * @param listener - The callback to be called.
200
+ * @returns An object with a `dispose` method which can be used to unregister
201
+ * the callback.
202
+ */
203
+ onServiceDegraded(listener) {
204
+ const disposables = __classPrivateFieldGet(this, _RpcServiceChain_services, "f").map((service) => service.onDegraded((data) => {
205
+ listener({
206
+ ...data,
207
+ primaryEndpointUrl: __classPrivateFieldGet(this, _RpcServiceChain_primaryService, "f").endpointUrl.toString(),
208
+ });
209
+ }));
210
+ return {
211
+ dispose() {
212
+ disposables.forEach((disposable) => disposable.dispose());
213
+ },
214
+ };
215
+ }
216
+ /**
217
+ * Calls the provided callback in one of the following two conditions:
218
+ *
219
+ * 1. The first time that a 2xx request is made to any of the RPC services in
220
+ * this chain.
221
+ * 2. When requests to any the failover RPC services in this chain were
222
+ * failing such that they were degraded or their underyling circuits broke,
223
+ * but the first request to the primary succeeds again.
224
+ *
225
+ * Note this callback will only be called if the RPC service chain as a whole
226
+ * is in an "available" state.
227
+ *
228
+ * @param listener - The callback to be called.
229
+ * @returns An object with a `dispose` method which can be used to unregister
230
+ * the callback.
231
+ */
232
+ onAvailable(listener) {
233
+ return __classPrivateFieldGet(this, _RpcServiceChain_onAvailableEventEmitter, "f").addListener(listener);
234
+ }
235
+ async request(jsonRpcRequest, fetchOptions = {}) {
236
+ // Start with the primary (first) service and switch to failovers as the
237
+ // need arises. This is a bit confusing, so keep reading for more on how
238
+ // this works.
239
+ let availableServiceIndex;
240
+ let response;
241
+ for (const [i, service] of __classPrivateFieldGet(this, _RpcServiceChain_services, "f").entries()) {
242
+ log(`Trying service #${i + 1}...`);
243
+ const previousCircuitState = service.getCircuitState();
244
+ try {
245
+ // Try making the request through the service.
246
+ response = await service.request(jsonRpcRequest, fetchOptions);
247
+ log('Service successfully received request.');
248
+ availableServiceIndex = i;
249
+ break;
250
+ }
251
+ catch (error) {
252
+ // Oops, that didn't work.
253
+ // Capture this error so that we can handle it later.
254
+ const { lastError } = service;
255
+ const isCircuitOpen = service.getCircuitState() === controller_utils_1.CircuitState.Open;
256
+ log('Service failed! error =', error, 'lastError = ', lastError);
257
+ if (isCircuitOpen) {
258
+ if (i < __classPrivateFieldGet(this, _RpcServiceChain_services, "f").length - 1) {
259
+ log("This service's circuit is open. Proceeding to next service...");
260
+ continue;
261
+ }
262
+ if (previousCircuitState !== controller_utils_1.CircuitState.Open &&
263
+ __classPrivateFieldGet(this, _RpcServiceChain_status, "f") !== STATUSES.Unavailable &&
264
+ lastError !== undefined) {
265
+ // If the service's circuit just broke and it's the last one in the
266
+ // chain, then trigger the onBreak event. (But if for some reason we
267
+ // have already done this, then don't do it.)
268
+ log('This service\'s circuit just opened and it is the last service. Updating status to "unavailable" and triggering onBreak.');
269
+ __classPrivateFieldSet(this, _RpcServiceChain_status, STATUSES.Unavailable, "f");
270
+ __classPrivateFieldGet(this, _RpcServiceChain_onBreakEventEmitter, "f").emit({
271
+ error: lastError,
272
+ });
273
+ }
274
+ }
275
+ // The service failed, and we throw whatever the error is. The calling
276
+ // code can try again if it so desires.
277
+ log(`${isCircuitOpen ? '' : "This service's circuit is closed. "}Re-throwing error.`);
278
+ throw error;
279
+ }
280
+ }
281
+ if (response) {
282
+ // If one of the services is available, reset all of the circuits of the
283
+ // following services. If we didn't do this and the service became
284
+ // unavailable in the future, and any of the failovers' circuits were
285
+ // open (due to previous failures), we would receive a "circuit broken"
286
+ // error when we attempted to divert traffic to the failovers again.
287
+ //
288
+ if (availableServiceIndex !== undefined) {
289
+ for (const [i, service] of [...__classPrivateFieldGet(this, _RpcServiceChain_services, "f").entries()].slice(availableServiceIndex + 1)) {
290
+ log(`Resetting policy for service #${i + 1}.`);
291
+ service.resetPolicy();
292
+ }
293
+ }
294
+ return response;
295
+ }
296
+ // The only way we can end up here is if there are no services to loop over.
297
+ // That is not possible due to the types on the constructor, but TypeScript
298
+ // doesn't know this, so we have to appease it.
299
+ throw new Error('Nothing to return');
300
+ }
301
+ }
302
+ exports.RpcServiceChain = RpcServiceChain;
303
+ _RpcServiceChain_onAvailableEventEmitter = new WeakMap(), _RpcServiceChain_onBreakEventEmitter = new WeakMap(), _RpcServiceChain_onDegradedEventEmitter = new WeakMap(), _RpcServiceChain_primaryService = new WeakMap(), _RpcServiceChain_services = new WeakMap(), _RpcServiceChain_status = new WeakMap();
304
+ //# sourceMappingURL=rpc-service-chain.cjs.map
@@ -0,0 +1 @@
1
+ {"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.js';\nimport { RpcService } from './rpc-service.js';\nimport type { RpcServiceOptions } from './rpc-service.js';\nimport type {\n CockatielEventToEventListenerWithData,\n ExcludeCockatielEventData,\n ExtractCockatielEventData,\n FetchOptions,\n} from './shared.js';\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"]}
@@ -1,8 +1,8 @@
1
- import type { Json, JsonRpcParams, JsonRpcRequest, JsonRpcResponse } from '@metamask/utils';
2
- import { IDisposable } from 'cockatiel';
3
- import { RpcService } from './rpc-service.js';
4
- import type { RpcServiceOptions } from './rpc-service.js';
5
- import type { CockatielEventToEventListenerWithData, ExcludeCockatielEventData, ExtractCockatielEventData, FetchOptions } from './shared.js';
1
+ import type { Json, JsonRpcParams, JsonRpcRequest, JsonRpcResponse } from "@metamask/utils";
2
+ import { IDisposable } from "cockatiel";
3
+ import { RpcService } from "./rpc-service.cjs";
4
+ import type { RpcServiceOptions } from "./rpc-service.cjs";
5
+ import type { CockatielEventToEventListenerWithData, ExcludeCockatielEventData, ExtractCockatielEventData, FetchOptions } from "./shared.cjs";
6
6
  /**
7
7
  * This class constructs and manages requests to a chain of RpcService objects
8
8
  * which represent RPC endpoints with which to access a particular network. The
@@ -166,4 +166,4 @@ export declare class RpcServiceChain {
166
166
  */
167
167
  request<Params extends JsonRpcParams, Result extends Json>(jsonRpcRequest: Readonly<JsonRpcRequest<Params>>, fetchOptions?: FetchOptions): Promise<JsonRpcResponse<Result>>;
168
168
  }
169
- //# sourceMappingURL=rpc-service-chain.d.ts.map
169
+ //# sourceMappingURL=rpc-service-chain.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-service-chain.d.cts","sourceRoot":"","sources":["../../src/rpc-service/rpc-service-chain.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,IAAI,EACJ,aAAa,EACb,cAAc,EACd,eAAe,EAChB,wBAAwB;AACzB,OAAO,EAAE,WAAW,EAAE,kBAAkB;AAGxC,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAAyB;AAC1D,OAAO,KAAK,EACV,qCAAqC,EACrC,yBAAyB,EACzB,yBAAyB,EACzB,YAAY,EACb,qBAAoB;AAmBrB;;;;;GAKG;AACH,qBAAa,eAAe;;IA8C1B;;;;;;OAMG;gBAED,wBAAwB,EAAE,CAAC,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAkDvE;;;;;;;;OAQG;IACH,cAAc,CACZ,QAAQ,EAAE,qCAAqC,CAC7C,UAAU,CAAC,SAAS,CAAC,EACrB;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAC/B,GACA;QAAE,OAAO,IAAI,IAAI,CAAA;KAAE;IAiBtB;;;;;;;;;;;;;OAaG;IACH,OAAO,CACL,QAAQ,EAAE,CACR,IAAI,EAAE,yBAAyB,CAC7B,yBAAyB,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAChD,aAAa,CACd,KACE,IAAI,GACR,WAAW;IAId;;;;;;;;;OASG;IACH,cAAc,CACZ,QAAQ,EAAE,qCAAqC,CAC7C,UAAU,CAAC,SAAS,CAAC,EACrB;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAC/B,GACA,WAAW;IAiBd;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,UAAU,CACR,QAAQ,EAAE,CACR,IAAI,EAAE,yBAAyB,CAC7B,yBAAyB,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,EACnD,aAAa,CACd,KACE,IAAI,GACR,WAAW;IAId;;;;;;;;;;;;;;;;;;;OAmBG;IACH,iBAAiB,CACf,QAAQ,EAAE,qCAAqC,CAC7C,UAAU,CAAC,YAAY,CAAC,EACxB;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAC/B,GACA,WAAW;IAiBd;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CACT,QAAQ,EAAE,CACR,IAAI,EAAE,yBAAyB,CAC7B,yBAAyB,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,EACpD,aAAa,CACd,KACE,IAAI,GACR,WAAW;IAId;;;;;;;;;;;;;;;;;OAiBG;IACG,OAAO,CAAC,MAAM,SAAS,aAAa,EAAE,MAAM,SAAS,IAAI,EAC7D,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,GAAG;QACjD,MAAM,EAAE,sBAAsB,CAAC;KAChC,EACD,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAE3D;;;;;;;;;;;;;;;;;OAiBG;IACG,OAAO,CAAC,MAAM,SAAS,aAAa,EAAE,MAAM,SAAS,IAAI,EAC7D,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,EAChD,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;CA8FpC"}
@@ -0,0 +1,169 @@
1
+ import type { Json, JsonRpcParams, JsonRpcRequest, JsonRpcResponse } from "@metamask/utils";
2
+ import { IDisposable } from "cockatiel";
3
+ import { RpcService } from "./rpc-service.mjs";
4
+ import type { RpcServiceOptions } from "./rpc-service.mjs";
5
+ import type { CockatielEventToEventListenerWithData, ExcludeCockatielEventData, ExtractCockatielEventData, FetchOptions } from "./shared.mjs";
6
+ /**
7
+ * This class constructs and manages requests to a chain of RpcService objects
8
+ * which represent RPC endpoints with which to access a particular network. The
9
+ * first service in the chain is intended to be the primary way of hitting the
10
+ * network and the remaining services are used as failovers.
11
+ */
12
+ export declare class RpcServiceChain {
13
+ #private;
14
+ /**
15
+ * Constructs a new RpcServiceChain object.
16
+ *
17
+ * @param rpcServiceConfigurations - The options for the RPC services
18
+ * that you want to construct. Each object in this array is the same as
19
+ * {@link RpcServiceOptions}.
20
+ */
21
+ constructor(rpcServiceConfigurations: [RpcServiceOptions, ...RpcServiceOptions[]]);
22
+ /**
23
+ * Calls the provided callback when any of the RPC services is retried.
24
+ *
25
+ * This is mainly useful for tests.
26
+ *
27
+ * @param listener - The callback to be called.
28
+ * @returns An object with a `dispose` method which can be used to unregister
29
+ * the event listener.
30
+ */
31
+ onServiceRetry(listener: CockatielEventToEventListenerWithData<RpcService['onRetry'], {
32
+ primaryEndpointUrl: string;
33
+ }>): {
34
+ dispose(): void;
35
+ };
36
+ /**
37
+ * Calls the provided callback only when the maximum number of failed
38
+ * consecutive attempts to receive a 2xx response has been reached for all
39
+ * RPC services in the chain, and all services' underlying circuits have
40
+ * broken.
41
+ *
42
+ * The callback will not be called if a service's circuit breaks but its
43
+ * failover does not. Use `onServiceBreak` if you'd like a lower level of
44
+ * granularity.
45
+ *
46
+ * @param listener - The callback to be called.
47
+ * @returns An object with a `dispose` method which can be used to unregister
48
+ * the callback.
49
+ */
50
+ onBreak(listener: (data: ExcludeCockatielEventData<ExtractCockatielEventData<RpcService['onBreak']>, 'endpointUrl'>) => void): IDisposable;
51
+ /**
52
+ * Calls the provided callback each time when, for *any* of the RPC services
53
+ * in this chain, the maximum number of failed consecutive attempts to receive
54
+ * a 2xx response has been reached and the underlying circuit has broken. A
55
+ * more granular version of `onBreak`.
56
+ *
57
+ * @param listener - The callback to be called.
58
+ * @returns An object with a `dispose` method which can be used to unregister
59
+ * the callback.
60
+ */
61
+ onServiceBreak(listener: CockatielEventToEventListenerWithData<RpcService['onBreak'], {
62
+ primaryEndpointUrl: string;
63
+ }>): IDisposable;
64
+ /**
65
+ * Calls the provided callback if no requests have been initiated yet or
66
+ * all requests to RPC services in this chain have responded successfully in a
67
+ * timely fashion, and then one of the two conditions apply:
68
+ *
69
+ * 1. When a retriable error is encountered making a request to an RPC
70
+ * service, and the request is retried until a set maximum is reached.
71
+ * 2. When a RPC service responds successfully, but the request takes longer
72
+ * than a set number of seconds to complete.
73
+ *
74
+ * Note that the callback will be called even if there are local connectivity
75
+ * issues which prevent requests from being initiated. This is intentional.
76
+ *
77
+ * Also note this callback will only be called if the RPC service chain as a
78
+ * whole is in a "degraded" state, and will then only be called once (e.g., it
79
+ * will not be called if a failover service falls into a degraded state, then
80
+ * the primary comes back online, but it is slow). Use `onServiceDegraded` if
81
+ * you'd like a lower level of granularity.
82
+ *
83
+ * @param listener - The callback to be called.
84
+ * @returns An object with a `dispose` method which can be used to unregister
85
+ * the callback.
86
+ */
87
+ onDegraded(listener: (data: ExcludeCockatielEventData<ExtractCockatielEventData<RpcService['onDegraded']>, 'endpointUrl'>) => void): IDisposable;
88
+ /**
89
+ * Calls the provided callback each time one of the two conditions apply:
90
+ *
91
+ * 1. When a retriable error is encountered making a request to an RPC
92
+ * service, and the request is retried until a set maximum is reached.
93
+ * 2. When a RPC service responds successfully, but the request takes longer
94
+ * than a set number of seconds to complete.
95
+ *
96
+ * Note that the callback will be called even if there are local connectivity
97
+ * issues which prevent requests from being initiated. This is intentional.
98
+ *
99
+ * This is a more granular version of `onDegraded`. The callback will be
100
+ * called for each slow request to an RPC service. It may also be called again
101
+ * if a failover service falls into a degraded state, then the primary comes
102
+ * back online, but it is slow.
103
+ *
104
+ * @param listener - The callback to be called.
105
+ * @returns An object with a `dispose` method which can be used to unregister
106
+ * the callback.
107
+ */
108
+ onServiceDegraded(listener: CockatielEventToEventListenerWithData<RpcService['onDegraded'], {
109
+ primaryEndpointUrl: string;
110
+ }>): IDisposable;
111
+ /**
112
+ * Calls the provided callback in one of the following two conditions:
113
+ *
114
+ * 1. The first time that a 2xx request is made to any of the RPC services in
115
+ * this chain.
116
+ * 2. When requests to any the failover RPC services in this chain were
117
+ * failing such that they were degraded or their underyling circuits broke,
118
+ * but the first request to the primary succeeds again.
119
+ *
120
+ * Note this callback will only be called if the RPC service chain as a whole
121
+ * is in an "available" state.
122
+ *
123
+ * @param listener - The callback to be called.
124
+ * @returns An object with a `dispose` method which can be used to unregister
125
+ * the callback.
126
+ */
127
+ onAvailable(listener: (data: ExcludeCockatielEventData<ExtractCockatielEventData<RpcService['onAvailable']>, 'endpointUrl'>) => void): IDisposable;
128
+ /**
129
+ * Uses the RPC services in the chain to make a request, using each service
130
+ * after the first as a fallback to the previous one as necessary.
131
+ *
132
+ * This overload is specifically designed for `eth_getBlockByNumber`, which
133
+ * can return a `result` of `null` despite an expected `Result` being
134
+ * provided.
135
+ *
136
+ * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.
137
+ * @param fetchOptions - An options bag for {@link fetch} which further
138
+ * specifies the request.
139
+ * @returns The decoded JSON-RPC response from the endpoint.
140
+ * @throws A 401 error if the response status is 401.
141
+ * @throws A "rate limiting" error if the response HTTP status is 429.
142
+ * @throws A "resource unavailable" error if the response status is 402, 404, or any 5xx.
143
+ * @throws A generic HTTP client error (-32100) for any other 4xx status codes.
144
+ * @throws A "parse" error if the response is not valid JSON.
145
+ */
146
+ request<Params extends JsonRpcParams, Result extends Json>(jsonRpcRequest: Readonly<JsonRpcRequest<Params>> & {
147
+ method: 'eth_getBlockByNumber';
148
+ }, fetchOptions?: FetchOptions): Promise<JsonRpcResponse<Result> | JsonRpcResponse<null>>;
149
+ /**
150
+ * Uses the RPC services in the chain to make a request, using each service
151
+ * after the first as a fallback to the previous one as necessary.
152
+ *
153
+ * This overload is designed for all RPC methods except for
154
+ * `eth_getBlockByNumber`, which are expected to return a `result` of the
155
+ * expected `Result`.
156
+ *
157
+ * @param jsonRpcRequest - The JSON-RPC request to send to the endpoint.
158
+ * @param fetchOptions - An options bag for {@link fetch} which further
159
+ * specifies the request.
160
+ * @returns The decoded JSON-RPC response from the endpoint.
161
+ * @throws A 401 error if the response status is 401.
162
+ * @throws A "rate limiting" error if the response HTTP status is 429.
163
+ * @throws A "resource unavailable" error if the response status is 402, 404, or any 5xx.
164
+ * @throws A generic HTTP client error (-32100) for any other 4xx status codes.
165
+ * @throws A "parse" error if the response is not valid JSON.
166
+ */
167
+ request<Params extends JsonRpcParams, Result extends Json>(jsonRpcRequest: Readonly<JsonRpcRequest<Params>>, fetchOptions?: FetchOptions): Promise<JsonRpcResponse<Result>>;
168
+ }
169
+ //# sourceMappingURL=rpc-service-chain.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-service-chain.d.mts","sourceRoot":"","sources":["../../src/rpc-service/rpc-service-chain.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,IAAI,EACJ,aAAa,EACb,cAAc,EACd,eAAe,EAChB,wBAAwB;AACzB,OAAO,EAAE,WAAW,EAAE,kBAAkB;AAGxC,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAAyB;AAC1D,OAAO,KAAK,EACV,qCAAqC,EACrC,yBAAyB,EACzB,yBAAyB,EACzB,YAAY,EACb,qBAAoB;AAmBrB;;;;;GAKG;AACH,qBAAa,eAAe;;IA8C1B;;;;;;OAMG;gBAED,wBAAwB,EAAE,CAAC,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAkDvE;;;;;;;;OAQG;IACH,cAAc,CACZ,QAAQ,EAAE,qCAAqC,CAC7C,UAAU,CAAC,SAAS,CAAC,EACrB;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAC/B,GACA;QAAE,OAAO,IAAI,IAAI,CAAA;KAAE;IAiBtB;;;;;;;;;;;;;OAaG;IACH,OAAO,CACL,QAAQ,EAAE,CACR,IAAI,EAAE,yBAAyB,CAC7B,yBAAyB,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAChD,aAAa,CACd,KACE,IAAI,GACR,WAAW;IAId;;;;;;;;;OASG;IACH,cAAc,CACZ,QAAQ,EAAE,qCAAqC,CAC7C,UAAU,CAAC,SAAS,CAAC,EACrB;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAC/B,GACA,WAAW;IAiBd;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,UAAU,CACR,QAAQ,EAAE,CACR,IAAI,EAAE,yBAAyB,CAC7B,yBAAyB,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,EACnD,aAAa,CACd,KACE,IAAI,GACR,WAAW;IAId;;;;;;;;;;;;;;;;;;;OAmBG;IACH,iBAAiB,CACf,QAAQ,EAAE,qCAAqC,CAC7C,UAAU,CAAC,YAAY,CAAC,EACxB;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAC/B,GACA,WAAW;IAiBd;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CACT,QAAQ,EAAE,CACR,IAAI,EAAE,yBAAyB,CAC7B,yBAAyB,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,EACpD,aAAa,CACd,KACE,IAAI,GACR,WAAW;IAId;;;;;;;;;;;;;;;;;OAiBG;IACG,OAAO,CAAC,MAAM,SAAS,aAAa,EAAE,MAAM,SAAS,IAAI,EAC7D,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,GAAG;QACjD,MAAM,EAAE,sBAAsB,CAAC;KAChC,EACD,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAE3D;;;;;;;;;;;;;;;;;OAiBG;IACG,OAAO,CAAC,MAAM,SAAS,aAAa,EAAE,MAAM,SAAS,IAAI,EAC7D,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,EAChD,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;CA8FpC"}
@@ -10,9 +10,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
12
  var _RpcServiceChain_onAvailableEventEmitter, _RpcServiceChain_onBreakEventEmitter, _RpcServiceChain_onDegradedEventEmitter, _RpcServiceChain_primaryService, _RpcServiceChain_services, _RpcServiceChain_status;
13
- import { CircuitState, CockatielEventEmitter, } from '@metamask/controller-utils';
14
- import { projectLogger, createModuleLogger } from '../logger.js';
15
- import { RpcService } from './rpc-service.js';
13
+ import { CircuitState, CockatielEventEmitter } from "@metamask/controller-utils";
14
+ import { projectLogger, createModuleLogger } from "../logger.mjs";
15
+ import { RpcService } from "./rpc-service.mjs";
16
16
  const log = createModuleLogger(projectLogger, 'RpcServiceChain');
17
17
  /**
18
18
  * Statuses that the RPC service chain can be in.
@@ -297,4 +297,4 @@ export class RpcServiceChain {
297
297
  }
298
298
  }
299
299
  _RpcServiceChain_onAvailableEventEmitter = new WeakMap(), _RpcServiceChain_onBreakEventEmitter = new WeakMap(), _RpcServiceChain_onDegradedEventEmitter = new WeakMap(), _RpcServiceChain_primaryService = new WeakMap(), _RpcServiceChain_services = new WeakMap(), _RpcServiceChain_status = new WeakMap();
300
- //# sourceMappingURL=rpc-service-chain.js.map
300
+ //# sourceMappingURL=rpc-service-chain.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-service-chain.mjs","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.js';\nimport { RpcService } from './rpc-service.js';\nimport type { RpcServiceOptions } from './rpc-service.js';\nimport type {\n CockatielEventToEventListenerWithData,\n ExcludeCockatielEventData,\n ExtractCockatielEventData,\n FetchOptions,\n} from './shared.js';\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"]}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=rpc-service-requestable.cjs.map