UNPKG

5.49 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.createFetchConfigFromReq = exports.createFetchMiddleware = void 0;
4const json_rpc_engine_1 = require("json-rpc-engine");
5const eth_rpc_errors_1 = require("eth-rpc-errors");
6/* eslint-disable node/global-require,@typescript-eslint/no-require-imports */
7const fetch = global.fetch || require('node-fetch');
8const btoa = global.btoa || require('btoa');
9/* eslint-enable node/global-require,@typescript-eslint/no-require-imports */
10const RETRIABLE_ERRORS = [
11 // ignore server overload errors
12 'Gateway timeout',
13 'ETIMEDOUT',
14 // ignore server sent html error pages
15 // or truncated json responses
16 'failed to parse response body',
17 // ignore errors where http req failed to establish
18 'Failed to fetch',
19];
20function 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 // attempt request multiple times
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 // check for http errrors
34 checkForHttpErrors(fetchRes);
35 // parse response body
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 // set result and exit retry loop
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 // re-throw error if not retriable
53 if (!isRetriable) {
54 throw err;
55 }
56 }
57 // delay before retrying
58 await timeout(retryInterval);
59 }
60 });
61}
62exports.createFetchMiddleware = createFetchMiddleware;
63function checkForHttpErrors(fetchRes) {
64 // check for errors
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}
77function parseResponse(fetchRes, body) {
78 // check for error code
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 // check for rpc error
86 if (body.error) {
87 throw eth_rpc_errors_1.ethErrors.rpc.internal({
88 data: body.error,
89 });
90 }
91 // return successful result
92 return body.result;
93}
94function createFetchConfigFromReq({ req, rpcUrl, originHttpHeaderKey, }) {
95 const parsedUrl = new URL(rpcUrl);
96 const fetchUrl = normalizeUrlFromParsed(parsedUrl);
97 // prepare payload
98 // copy only canonical json rpc properties
99 const payload = {
100 id: req.id,
101 jsonrpc: req.jsonrpc,
102 method: req.method,
103 params: req.params,
104 };
105 // extract 'origin' parameter from request
106 const originDomain = req.origin;
107 // serialize request body
108 const serializedPayload = JSON.stringify(payload);
109 // configure fetch params
110 const fetchParams = {
111 method: 'POST',
112 headers: {
113 Accept: 'application/json',
114 'Content-Type': 'application/json',
115 },
116 body: serializedPayload,
117 };
118 // encoded auth details as header (not allowed in fetch url)
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 // optional: add request origin as header
125 if (originHttpHeaderKey && originDomain) {
126 fetchParams.headers[originHttpHeaderKey] = originDomain;
127 }
128 return { fetchUrl, fetchParams };
129}
130exports.createFetchConfigFromReq = createFetchConfigFromReq;
131function 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}
141function createRatelimitError() {
142 return eth_rpc_errors_1.ethErrors.rpc.internal({ message: `Request is being rate limited.` });
143}
144function 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}
149function timeout(duration) {
150 return new Promise((resolve) => setTimeout(resolve, duration));
151}
152//# sourceMappingURL=fetch.js.map
\No newline at end of file