1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.createFetchConfigFromReq = exports.createFetchMiddleware = void 0;
|
4 | const json_rpc_engine_1 = require("json-rpc-engine");
|
5 | const eth_rpc_errors_1 = require("eth-rpc-errors");
|
6 |
|
7 | const fetch = global.fetch || require('node-fetch');
|
8 | const btoa = global.btoa || require('btoa');
|
9 |
|
10 | const RETRIABLE_ERRORS = [
|
11 |
|
12 | 'Gateway timeout',
|
13 | 'ETIMEDOUT',
|
14 |
|
15 |
|
16 | 'failed to parse response body',
|
17 |
|
18 | 'Failed to fetch',
|
19 | ];
|
20 | function createFetchMiddleware({ rpcUrl, originHttpHeaderKey, }) {
|
21 | return json_rpc_engine_1.createAsyncMiddleware(async (req, res, _next) => {
|
22 | const { fetchUrl, fetchParams } = createFetchConfigFromReq({
|
23 | req,
|
24 | rpcUrl,
|
25 | originHttpHeaderKey,
|
26 | });
|
27 |
|
28 | const maxAttempts = 5;
|
29 | const retryInterval = 1000;
|
30 | for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
31 | try {
|
32 | const fetchRes = await fetch(fetchUrl, fetchParams);
|
33 |
|
34 | checkForHttpErrors(fetchRes);
|
35 |
|
36 | const rawBody = await fetchRes.text();
|
37 | let fetchBody;
|
38 | try {
|
39 | fetchBody = JSON.parse(rawBody);
|
40 | }
|
41 | catch (_) {
|
42 | throw new Error(`FetchMiddleware - failed to parse response body: "${rawBody}"`);
|
43 | }
|
44 | const result = parseResponse(fetchRes, fetchBody);
|
45 |
|
46 | res.result = result;
|
47 | return;
|
48 | }
|
49 | catch (err) {
|
50 | const errMsg = err.toString();
|
51 | const isRetriable = RETRIABLE_ERRORS.some((phrase) => errMsg.includes(phrase));
|
52 |
|
53 | if (!isRetriable) {
|
54 | throw err;
|
55 | }
|
56 | }
|
57 |
|
58 | await timeout(retryInterval);
|
59 | }
|
60 | });
|
61 | }
|
62 | exports.createFetchMiddleware = createFetchMiddleware;
|
63 | function checkForHttpErrors(fetchRes) {
|
64 |
|
65 | switch (fetchRes.status) {
|
66 | case 405:
|
67 | throw eth_rpc_errors_1.ethErrors.rpc.methodNotFound();
|
68 | case 418:
|
69 | throw createRatelimitError();
|
70 | case 503:
|
71 | case 504:
|
72 | throw createTimeoutError();
|
73 | default:
|
74 | break;
|
75 | }
|
76 | }
|
77 | function parseResponse(fetchRes, body) {
|
78 |
|
79 | if (fetchRes.status !== 200) {
|
80 | throw eth_rpc_errors_1.ethErrors.rpc.internal({
|
81 | message: `Non-200 status code: '${fetchRes.status}'`,
|
82 | data: body,
|
83 | });
|
84 | }
|
85 |
|
86 | if (body.error) {
|
87 | throw eth_rpc_errors_1.ethErrors.rpc.internal({
|
88 | data: body.error,
|
89 | });
|
90 | }
|
91 |
|
92 | return body.result;
|
93 | }
|
94 | function createFetchConfigFromReq({ req, rpcUrl, originHttpHeaderKey, }) {
|
95 | const parsedUrl = new URL(rpcUrl);
|
96 | const fetchUrl = normalizeUrlFromParsed(parsedUrl);
|
97 |
|
98 |
|
99 | const payload = {
|
100 | id: req.id,
|
101 | jsonrpc: req.jsonrpc,
|
102 | method: req.method,
|
103 | params: req.params,
|
104 | };
|
105 |
|
106 | const originDomain = req.origin;
|
107 |
|
108 | const serializedPayload = JSON.stringify(payload);
|
109 |
|
110 | const fetchParams = {
|
111 | method: 'POST',
|
112 | headers: {
|
113 | Accept: 'application/json',
|
114 | 'Content-Type': 'application/json',
|
115 | },
|
116 | body: serializedPayload,
|
117 | };
|
118 |
|
119 | if (parsedUrl.username && parsedUrl.password) {
|
120 | const authString = `${parsedUrl.username}:${parsedUrl.password}`;
|
121 | const encodedAuth = btoa(authString);
|
122 | fetchParams.headers.Authorization = `Basic ${encodedAuth}`;
|
123 | }
|
124 |
|
125 | if (originHttpHeaderKey && originDomain) {
|
126 | fetchParams.headers[originHttpHeaderKey] = originDomain;
|
127 | }
|
128 | return { fetchUrl, fetchParams };
|
129 | }
|
130 | exports.createFetchConfigFromReq = createFetchConfigFromReq;
|
131 | function normalizeUrlFromParsed(parsedUrl) {
|
132 | let result = '';
|
133 | result += parsedUrl.protocol;
|
134 | result += `//${parsedUrl.hostname}`;
|
135 | if (parsedUrl.port) {
|
136 | result += `:${parsedUrl.port}`;
|
137 | }
|
138 | result += `${parsedUrl.pathname}`;
|
139 | return result;
|
140 | }
|
141 | function createRatelimitError() {
|
142 | return eth_rpc_errors_1.ethErrors.rpc.internal({ message: `Request is being rate limited.` });
|
143 | }
|
144 | function createTimeoutError() {
|
145 | let msg = `Gateway timeout. The request took too long to process. `;
|
146 | msg += `This can happen when querying logs over too wide a block range.`;
|
147 | return eth_rpc_errors_1.ethErrors.rpc.internal({ message: msg });
|
148 | }
|
149 | function timeout(duration) {
|
150 | return new Promise((resolve) => setTimeout(resolve, duration));
|
151 | }
|
152 |
|
\ | No newline at end of file |