index.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. "use strict";
  2. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  3. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  4. return new (P || (P = Promise))(function (resolve, reject) {
  5. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  6. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  7. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  8. step((generator = generator.apply(thisArg, _arguments || [])).next());
  9. });
  10. };
  11. var __generator = (this && this.__generator) || function (thisArg, body) {
  12. var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
  13. return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
  14. function verb(n) { return function (v) { return step([n, v]); }; }
  15. function step(op) {
  16. if (f) throw new TypeError("Generator is already executing.");
  17. while (_) try {
  18. if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
  19. if (y = 0, t) op = [op[0] & 2, t.value];
  20. switch (op[0]) {
  21. case 0: case 1: t = op; break;
  22. case 4: _.label++; return { value: op[1], done: false };
  23. case 5: _.label++; y = op[1]; op = [0]; continue;
  24. case 7: op = _.ops.pop(); _.trys.pop(); continue;
  25. default:
  26. if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
  27. if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
  28. if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
  29. if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
  30. if (t[2]) _.ops.pop();
  31. _.trys.pop(); continue;
  32. }
  33. op = body.call(thisArg, _);
  34. } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
  35. if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
  36. }
  37. };
  38. Object.defineProperty(exports, "__esModule", { value: true });
  39. exports.poll = exports.fetchJson = exports._fetchData = void 0;
  40. var base64_1 = require("@ethersproject/base64");
  41. var bytes_1 = require("@ethersproject/bytes");
  42. var properties_1 = require("@ethersproject/properties");
  43. var strings_1 = require("@ethersproject/strings");
  44. var logger_1 = require("@ethersproject/logger");
  45. var _version_1 = require("./_version");
  46. var logger = new logger_1.Logger(_version_1.version);
  47. var geturl_1 = require("./geturl");
  48. function staller(duration) {
  49. return new Promise(function (resolve) {
  50. setTimeout(resolve, duration);
  51. });
  52. }
  53. function bodyify(value, type) {
  54. if (value == null) {
  55. return null;
  56. }
  57. if (typeof (value) === "string") {
  58. return value;
  59. }
  60. if ((0, bytes_1.isBytesLike)(value)) {
  61. if (type && (type.split("/")[0] === "text" || type.split(";")[0].trim() === "application/json")) {
  62. try {
  63. return (0, strings_1.toUtf8String)(value);
  64. }
  65. catch (error) { }
  66. ;
  67. }
  68. return (0, bytes_1.hexlify)(value);
  69. }
  70. return value;
  71. }
  72. // This API is still a work in progress; the future changes will likely be:
  73. // - ConnectionInfo => FetchDataRequest<T = any>
  74. // - FetchDataRequest.body? = string | Uint8Array | { contentType: string, data: string | Uint8Array }
  75. // - If string => text/plain, Uint8Array => application/octet-stream (if content-type unspecified)
  76. // - FetchDataRequest.processFunc = (body: Uint8Array, response: FetchDataResponse) => T
  77. // For this reason, it should be considered internal until the API is finalized
  78. function _fetchData(connection, body, processFunc) {
  79. // How many times to retry in the event of a throttle
  80. var attemptLimit = (typeof (connection) === "object" && connection.throttleLimit != null) ? connection.throttleLimit : 12;
  81. logger.assertArgument((attemptLimit > 0 && (attemptLimit % 1) === 0), "invalid connection throttle limit", "connection.throttleLimit", attemptLimit);
  82. var throttleCallback = ((typeof (connection) === "object") ? connection.throttleCallback : null);
  83. var throttleSlotInterval = ((typeof (connection) === "object" && typeof (connection.throttleSlotInterval) === "number") ? connection.throttleSlotInterval : 100);
  84. logger.assertArgument((throttleSlotInterval > 0 && (throttleSlotInterval % 1) === 0), "invalid connection throttle slot interval", "connection.throttleSlotInterval", throttleSlotInterval);
  85. var errorPassThrough = ((typeof (connection) === "object") ? !!(connection.errorPassThrough) : false);
  86. var headers = {};
  87. var url = null;
  88. // @TODO: Allow ConnectionInfo to override some of these values
  89. var options = {
  90. method: "GET",
  91. };
  92. var allow304 = false;
  93. var timeout = 2 * 60 * 1000;
  94. if (typeof (connection) === "string") {
  95. url = connection;
  96. }
  97. else if (typeof (connection) === "object") {
  98. if (connection == null || connection.url == null) {
  99. logger.throwArgumentError("missing URL", "connection.url", connection);
  100. }
  101. url = connection.url;
  102. if (typeof (connection.timeout) === "number" && connection.timeout > 0) {
  103. timeout = connection.timeout;
  104. }
  105. if (connection.headers) {
  106. for (var key in connection.headers) {
  107. headers[key.toLowerCase()] = { key: key, value: String(connection.headers[key]) };
  108. if (["if-none-match", "if-modified-since"].indexOf(key.toLowerCase()) >= 0) {
  109. allow304 = true;
  110. }
  111. }
  112. }
  113. options.allowGzip = !!connection.allowGzip;
  114. if (connection.user != null && connection.password != null) {
  115. if (url.substring(0, 6) !== "https:" && connection.allowInsecureAuthentication !== true) {
  116. logger.throwError("basic authentication requires a secure https url", logger_1.Logger.errors.INVALID_ARGUMENT, { argument: "url", url: url, user: connection.user, password: "[REDACTED]" });
  117. }
  118. var authorization = connection.user + ":" + connection.password;
  119. headers["authorization"] = {
  120. key: "Authorization",
  121. value: "Basic " + (0, base64_1.encode)((0, strings_1.toUtf8Bytes)(authorization))
  122. };
  123. }
  124. if (connection.skipFetchSetup != null) {
  125. options.skipFetchSetup = !!connection.skipFetchSetup;
  126. }
  127. }
  128. var reData = new RegExp("^data:([a-z0-9-]+/[a-z0-9-]+);base64,(.*)$", "i");
  129. var dataMatch = ((url) ? url.match(reData) : null);
  130. if (dataMatch) {
  131. try {
  132. var response = {
  133. statusCode: 200,
  134. statusMessage: "OK",
  135. headers: { "content-type": dataMatch[1] },
  136. body: (0, base64_1.decode)(dataMatch[2])
  137. };
  138. var result = response.body;
  139. if (processFunc) {
  140. result = processFunc(response.body, response);
  141. }
  142. return Promise.resolve(result);
  143. }
  144. catch (error) {
  145. logger.throwError("processing response error", logger_1.Logger.errors.SERVER_ERROR, {
  146. body: bodyify(dataMatch[1], dataMatch[2]),
  147. error: error,
  148. requestBody: null,
  149. requestMethod: "GET",
  150. url: url
  151. });
  152. }
  153. }
  154. if (body) {
  155. options.method = "POST";
  156. options.body = body;
  157. if (headers["content-type"] == null) {
  158. headers["content-type"] = { key: "Content-Type", value: "application/octet-stream" };
  159. }
  160. if (headers["content-length"] == null) {
  161. headers["content-length"] = { key: "Content-Length", value: String(body.length) };
  162. }
  163. }
  164. var flatHeaders = {};
  165. Object.keys(headers).forEach(function (key) {
  166. var header = headers[key];
  167. flatHeaders[header.key] = header.value;
  168. });
  169. options.headers = flatHeaders;
  170. var runningTimeout = (function () {
  171. var timer = null;
  172. var promise = new Promise(function (resolve, reject) {
  173. if (timeout) {
  174. timer = setTimeout(function () {
  175. if (timer == null) {
  176. return;
  177. }
  178. timer = null;
  179. reject(logger.makeError("timeout", logger_1.Logger.errors.TIMEOUT, {
  180. requestBody: bodyify(options.body, flatHeaders["content-type"]),
  181. requestMethod: options.method,
  182. timeout: timeout,
  183. url: url
  184. }));
  185. }, timeout);
  186. }
  187. });
  188. var cancel = function () {
  189. if (timer == null) {
  190. return;
  191. }
  192. clearTimeout(timer);
  193. timer = null;
  194. };
  195. return { promise: promise, cancel: cancel };
  196. })();
  197. var runningFetch = (function () {
  198. return __awaiter(this, void 0, void 0, function () {
  199. var attempt, response, location_1, tryAgain, stall, retryAfter, error_1, body_1, result, error_2, tryAgain, timeout_1;
  200. return __generator(this, function (_a) {
  201. switch (_a.label) {
  202. case 0:
  203. attempt = 0;
  204. _a.label = 1;
  205. case 1:
  206. if (!(attempt < attemptLimit)) return [3 /*break*/, 20];
  207. response = null;
  208. _a.label = 2;
  209. case 2:
  210. _a.trys.push([2, 9, , 10]);
  211. return [4 /*yield*/, (0, geturl_1.getUrl)(url, options)];
  212. case 3:
  213. response = _a.sent();
  214. if (!(attempt < attemptLimit)) return [3 /*break*/, 8];
  215. if (!(response.statusCode === 301 || response.statusCode === 302)) return [3 /*break*/, 4];
  216. location_1 = response.headers.location || "";
  217. if (options.method === "GET" && location_1.match(/^https:/)) {
  218. url = response.headers.location;
  219. return [3 /*break*/, 19];
  220. }
  221. return [3 /*break*/, 8];
  222. case 4:
  223. if (!(response.statusCode === 429)) return [3 /*break*/, 8];
  224. tryAgain = true;
  225. if (!throttleCallback) return [3 /*break*/, 6];
  226. return [4 /*yield*/, throttleCallback(attempt, url)];
  227. case 5:
  228. tryAgain = _a.sent();
  229. _a.label = 6;
  230. case 6:
  231. if (!tryAgain) return [3 /*break*/, 8];
  232. stall = 0;
  233. retryAfter = response.headers["retry-after"];
  234. if (typeof (retryAfter) === "string" && retryAfter.match(/^[1-9][0-9]*$/)) {
  235. stall = parseInt(retryAfter) * 1000;
  236. }
  237. else {
  238. stall = throttleSlotInterval * parseInt(String(Math.random() * Math.pow(2, attempt)));
  239. }
  240. //console.log("Stalling 429");
  241. return [4 /*yield*/, staller(stall)];
  242. case 7:
  243. //console.log("Stalling 429");
  244. _a.sent();
  245. return [3 /*break*/, 19];
  246. case 8: return [3 /*break*/, 10];
  247. case 9:
  248. error_1 = _a.sent();
  249. response = error_1.response;
  250. if (response == null) {
  251. runningTimeout.cancel();
  252. logger.throwError("missing response", logger_1.Logger.errors.SERVER_ERROR, {
  253. requestBody: bodyify(options.body, flatHeaders["content-type"]),
  254. requestMethod: options.method,
  255. serverError: error_1,
  256. url: url
  257. });
  258. }
  259. return [3 /*break*/, 10];
  260. case 10:
  261. body_1 = response.body;
  262. if (allow304 && response.statusCode === 304) {
  263. body_1 = null;
  264. }
  265. else if (!errorPassThrough && (response.statusCode < 200 || response.statusCode >= 300)) {
  266. runningTimeout.cancel();
  267. logger.throwError("bad response", logger_1.Logger.errors.SERVER_ERROR, {
  268. status: response.statusCode,
  269. headers: response.headers,
  270. body: bodyify(body_1, ((response.headers) ? response.headers["content-type"] : null)),
  271. requestBody: bodyify(options.body, flatHeaders["content-type"]),
  272. requestMethod: options.method,
  273. url: url
  274. });
  275. }
  276. if (!processFunc) return [3 /*break*/, 18];
  277. _a.label = 11;
  278. case 11:
  279. _a.trys.push([11, 13, , 18]);
  280. return [4 /*yield*/, processFunc(body_1, response)];
  281. case 12:
  282. result = _a.sent();
  283. runningTimeout.cancel();
  284. return [2 /*return*/, result];
  285. case 13:
  286. error_2 = _a.sent();
  287. if (!(error_2.throttleRetry && attempt < attemptLimit)) return [3 /*break*/, 17];
  288. tryAgain = true;
  289. if (!throttleCallback) return [3 /*break*/, 15];
  290. return [4 /*yield*/, throttleCallback(attempt, url)];
  291. case 14:
  292. tryAgain = _a.sent();
  293. _a.label = 15;
  294. case 15:
  295. if (!tryAgain) return [3 /*break*/, 17];
  296. timeout_1 = throttleSlotInterval * parseInt(String(Math.random() * Math.pow(2, attempt)));
  297. //console.log("Stalling callback");
  298. return [4 /*yield*/, staller(timeout_1)];
  299. case 16:
  300. //console.log("Stalling callback");
  301. _a.sent();
  302. return [3 /*break*/, 19];
  303. case 17:
  304. runningTimeout.cancel();
  305. logger.throwError("processing response error", logger_1.Logger.errors.SERVER_ERROR, {
  306. body: bodyify(body_1, ((response.headers) ? response.headers["content-type"] : null)),
  307. error: error_2,
  308. requestBody: bodyify(options.body, flatHeaders["content-type"]),
  309. requestMethod: options.method,
  310. url: url
  311. });
  312. return [3 /*break*/, 18];
  313. case 18:
  314. runningTimeout.cancel();
  315. // If we had a processFunc, it either returned a T or threw above.
  316. // The "body" is now a Uint8Array.
  317. return [2 /*return*/, body_1];
  318. case 19:
  319. attempt++;
  320. return [3 /*break*/, 1];
  321. case 20: return [2 /*return*/, logger.throwError("failed response", logger_1.Logger.errors.SERVER_ERROR, {
  322. requestBody: bodyify(options.body, flatHeaders["content-type"]),
  323. requestMethod: options.method,
  324. url: url
  325. })];
  326. }
  327. });
  328. });
  329. })();
  330. return Promise.race([runningTimeout.promise, runningFetch]);
  331. }
  332. exports._fetchData = _fetchData;
  333. function fetchJson(connection, json, processFunc) {
  334. var processJsonFunc = function (value, response) {
  335. var result = null;
  336. if (value != null) {
  337. try {
  338. result = JSON.parse((0, strings_1.toUtf8String)(value));
  339. }
  340. catch (error) {
  341. logger.throwError("invalid JSON", logger_1.Logger.errors.SERVER_ERROR, {
  342. body: value,
  343. error: error
  344. });
  345. }
  346. }
  347. if (processFunc) {
  348. result = processFunc(result, response);
  349. }
  350. return result;
  351. };
  352. // If we have json to send, we must
  353. // - add content-type of application/json (unless already overridden)
  354. // - convert the json to bytes
  355. var body = null;
  356. if (json != null) {
  357. body = (0, strings_1.toUtf8Bytes)(json);
  358. // Create a connection with the content-type set for JSON
  359. var updated = (typeof (connection) === "string") ? ({ url: connection }) : (0, properties_1.shallowCopy)(connection);
  360. if (updated.headers) {
  361. var hasContentType = (Object.keys(updated.headers).filter(function (k) { return (k.toLowerCase() === "content-type"); }).length) !== 0;
  362. if (!hasContentType) {
  363. updated.headers = (0, properties_1.shallowCopy)(updated.headers);
  364. updated.headers["content-type"] = "application/json";
  365. }
  366. }
  367. else {
  368. updated.headers = { "content-type": "application/json" };
  369. }
  370. connection = updated;
  371. }
  372. return _fetchData(connection, body, processJsonFunc);
  373. }
  374. exports.fetchJson = fetchJson;
  375. function poll(func, options) {
  376. if (!options) {
  377. options = {};
  378. }
  379. options = (0, properties_1.shallowCopy)(options);
  380. if (options.floor == null) {
  381. options.floor = 0;
  382. }
  383. if (options.ceiling == null) {
  384. options.ceiling = 10000;
  385. }
  386. if (options.interval == null) {
  387. options.interval = 250;
  388. }
  389. return new Promise(function (resolve, reject) {
  390. var timer = null;
  391. var done = false;
  392. // Returns true if cancel was successful. Unsuccessful cancel means we're already done.
  393. var cancel = function () {
  394. if (done) {
  395. return false;
  396. }
  397. done = true;
  398. if (timer) {
  399. clearTimeout(timer);
  400. }
  401. return true;
  402. };
  403. if (options.timeout) {
  404. timer = setTimeout(function () {
  405. if (cancel()) {
  406. reject(new Error("timeout"));
  407. }
  408. }, options.timeout);
  409. }
  410. var retryLimit = options.retryLimit;
  411. var attempt = 0;
  412. function check() {
  413. return func().then(function (result) {
  414. // If we have a result, or are allowed null then we're done
  415. if (result !== undefined) {
  416. if (cancel()) {
  417. resolve(result);
  418. }
  419. }
  420. else if (options.oncePoll) {
  421. options.oncePoll.once("poll", check);
  422. }
  423. else if (options.onceBlock) {
  424. options.onceBlock.once("block", check);
  425. // Otherwise, exponential back-off (up to 10s) our next request
  426. }
  427. else if (!done) {
  428. attempt++;
  429. if (attempt > retryLimit) {
  430. if (cancel()) {
  431. reject(new Error("retry limit reached"));
  432. }
  433. return;
  434. }
  435. var timeout = options.interval * parseInt(String(Math.random() * Math.pow(2, attempt)));
  436. if (timeout < options.floor) {
  437. timeout = options.floor;
  438. }
  439. if (timeout > options.ceiling) {
  440. timeout = options.ceiling;
  441. }
  442. setTimeout(check, timeout);
  443. }
  444. return null;
  445. }, function (error) {
  446. if (cancel()) {
  447. reject(error);
  448. }
  449. });
  450. }
  451. check();
  452. });
  453. }
  454. exports.poll = poll;
  455. //# sourceMappingURL=index.js.map