@taquito/http-utils 24.2.0 → 24.3.0-beta.1

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.
@@ -1,57 +1,75 @@
1
1
  import { NetworkError } from '@taquito/core';
2
2
 
3
- /******************************************************************************
4
- Copyright (c) Microsoft Corporation.
5
-
6
- Permission to use, copy, modify, and/or distribute this software for any
7
- purpose with or without fee is hereby granted.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
- PERFORMANCE OF THIS SOFTWARE.
16
- ***************************************************************************** */
17
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
18
-
19
-
20
- function __awaiter(thisArg, _arguments, P, generator) {
21
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
22
- return new (P || (P = Promise))(function (resolve, reject) {
23
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
24
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
25
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
26
- step((generator = generator.apply(thisArg, _arguments || [])).next());
27
- });
28
- }
29
-
30
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
31
- var e = new Error(message);
32
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
33
- };
34
-
3
+ const MAX_CAUSE_DEPTH$1 = 10;
4
+ /** Walk the `.cause` chain and return the deepest Error (or object-shaped cause). */
5
+ function deepestCause(err) {
6
+ let current = err;
7
+ let depth = 0;
8
+ for (;;) {
9
+ if (depth >= MAX_CAUSE_DEPTH$1)
10
+ break;
11
+ const next = current['cause'];
12
+ if (next instanceof Error) {
13
+ current = next;
14
+ depth++;
15
+ continue;
16
+ }
17
+ // Handle object-shaped causes (e.g. undici sometimes throws { message, code } objects)
18
+ if (typeof next === 'object' &&
19
+ next !== null &&
20
+ typeof next['message'] === 'string') {
21
+ const obj = next;
22
+ current = {
23
+ message: obj['message'],
24
+ code: typeof obj['code'] === 'string' ? obj['code'] : undefined,
25
+ };
26
+ break;
27
+ }
28
+ break;
29
+ }
30
+ return current;
31
+ }
35
32
  /**
36
33
  * @category Error
37
- * @description Error that indicates a general failure in making the HTTP request
34
+ * Error that indicates a general failure in making the HTTP request
38
35
  */
39
36
  class HttpRequestFailed extends NetworkError {
40
- constructor(method, url, cause) {
37
+ constructor(
38
+ /** The HTTP method that was attempted. */
39
+ method,
40
+ /** The URL that was requested. */
41
+ url,
42
+ /** The underlying error that caused the request to fail. */
43
+ cause, transportError) {
41
44
  super();
42
45
  this.method = method;
43
46
  this.url = url;
44
47
  this.cause = cause;
45
48
  this.name = 'HttpRequestFailed';
46
- this.message = `${method} ${url} ${String(cause)}`;
49
+ const rootCause = deepestCause(cause);
50
+ const rootCode = rootCause.code;
51
+ const detail = rootCause !== cause
52
+ ? `${rootCause.message}${rootCode ? ` [${rootCode}]` : ''}`
53
+ : cause.message;
54
+ const kindLabel = transportError ? ` (${transportError.kind})` : '';
55
+ this.message = `${method} ${url}${kindLabel}: ${detail}`;
56
+ this.transportError = transportError;
47
57
  }
48
58
  }
49
59
  /**
50
60
  * @category Error
51
- * @description Error thrown when the endpoint returns an HTTP error to the client
61
+ * Error thrown when the endpoint returns an HTTP error to the client
52
62
  */
53
63
  class HttpResponseError extends NetworkError {
54
- constructor(message, status, statusText, body, url) {
64
+ constructor(message,
65
+ /** The HTTP status code (e.g. 404, 500). */
66
+ status,
67
+ /** The HTTP status text (e.g. "Not Found"). */
68
+ statusText,
69
+ /** The raw response body text. */
70
+ body,
71
+ /** The URL that was requested. */
72
+ url) {
55
73
  super();
56
74
  this.message = message;
57
75
  this.status = status;
@@ -63,10 +81,14 @@ class HttpResponseError extends NetworkError {
63
81
  }
64
82
  /**
65
83
  * @category Error
66
- * @description Error
84
+ * Error thrown when an HTTP request exceeds its configured timeout duration.
67
85
  */
68
86
  class HttpTimeoutError extends NetworkError {
69
- constructor(timeout, url) {
87
+ constructor(
88
+ /** The timeout duration in milliseconds that was exceeded. */
89
+ timeout,
90
+ /** The URL that was requested. */
91
+ url) {
70
92
  super();
71
93
  this.timeout = timeout;
72
94
  this.url = url;
@@ -75,6 +97,145 @@ class HttpTimeoutError extends NetworkError {
75
97
  }
76
98
  }
77
99
 
100
+ /**
101
+ * Cross-runtime transport error classifier.
102
+ *
103
+ * Inspects structural properties first (err.code, err.cause.code, err.name),
104
+ * falls back to message substring matching only when structure isn't enough.
105
+ * Handles node-fetch FetchError, native fetch/undici TypeError + cause chain,
106
+ * browser TypeError, Deno, and Bun error shapes.
107
+ */
108
+ const MAY_HAVE_REACHED = {
109
+ abort: false,
110
+ dns: false,
111
+ connect: false,
112
+ socket: true,
113
+ tls: false,
114
+ timeout: true,
115
+ network: true,
116
+ };
117
+ function extractCode(err) {
118
+ if (typeof err !== 'object' || err === null)
119
+ return undefined;
120
+ const code = err['code'];
121
+ return typeof code === 'string' ? code : undefined;
122
+ }
123
+ function extractCause(err) {
124
+ if (typeof err !== 'object' || err === null)
125
+ return undefined;
126
+ return err['cause'];
127
+ }
128
+ function extractCauseCode(err) {
129
+ return extractCode(extractCause(err));
130
+ }
131
+ function extractCauseMessage(err) {
132
+ const cause = extractCause(err);
133
+ if (typeof cause === 'object' && cause !== null) {
134
+ const msg = cause['message'];
135
+ if (typeof msg === 'string')
136
+ return msg.toLowerCase();
137
+ }
138
+ return '';
139
+ }
140
+ function classify(kind, err) {
141
+ return { kind, mayHaveReachedServer: MAY_HAVE_REACHED[kind], original: err };
142
+ }
143
+ /**
144
+ * Classify a thrown value as a transport-level error.
145
+ *
146
+ * Inspects structural properties first (err.code, err.cause.code, err.name),
147
+ * then falls back to message substring matching. Handles node-fetch FetchError,
148
+ * native fetch/undici TypeError + cause chain, browser TypeError, Deno, and Bun.
149
+ *
150
+ * @returns the classification, or `undefined` if the error is not transport-related.
151
+ */
152
+ function classifyTransportError(err) {
153
+ if (!(err instanceof Error))
154
+ return undefined;
155
+ const code = extractCode(err);
156
+ const causeCode = extractCauseCode(err);
157
+ const msg = err.message.toLowerCase();
158
+ const causeMsg = extractCauseMessage(err);
159
+ // 1. AbortError (our timeout or caller cancel)
160
+ if (err.name === 'AbortError') {
161
+ return classify('abort', err);
162
+ }
163
+ // 2. DNS resolution failure
164
+ if (code === 'ENOTFOUND' ||
165
+ code === 'EAI_AGAIN' ||
166
+ causeCode === 'ENOTFOUND' ||
167
+ causeCode === 'EAI_AGAIN' ||
168
+ msg.includes('enotfound') ||
169
+ msg.includes('eai_again')) {
170
+ return classify('dns', err);
171
+ }
172
+ // 3. Connection refused / connect timeout
173
+ if (code === 'ECONNREFUSED' ||
174
+ causeCode === 'ECONNREFUSED' ||
175
+ causeCode === 'UND_ERR_CONNECT_TIMEOUT' ||
176
+ msg.includes('econnrefused')) {
177
+ return classify('connect', err);
178
+ }
179
+ // 4. TLS errors (check before socket/timeout since TLS errors can also set ECONNRESET)
180
+ // Also inspect cause.message for native fetch which wraps TLS errors in TypeError("fetch failed")
181
+ if (msg.includes('certificate') ||
182
+ msg.includes('self signed') ||
183
+ msg.includes('ssl') ||
184
+ msg.includes('handshake') ||
185
+ causeMsg.includes('certificate') ||
186
+ causeMsg.includes('self signed') ||
187
+ causeMsg.includes('ssl') ||
188
+ causeMsg.includes('handshake')) {
189
+ return classify('tls', err);
190
+ }
191
+ // 5. TCP/connect timeout and undici header/body timeouts
192
+ if (code === 'ETIMEDOUT' ||
193
+ causeCode === 'ETIMEDOUT' ||
194
+ causeCode === 'UND_ERR_HEADERS_TIMEOUT' ||
195
+ causeCode === 'UND_ERR_BODY_TIMEOUT' ||
196
+ msg.includes('etimedout') ||
197
+ msg.includes('timed out')) {
198
+ return classify('timeout', err);
199
+ }
200
+ // 6. Socket-level errors (connection was established or partially established)
201
+ if (code === 'ECONNRESET' ||
202
+ causeCode === 'ECONNRESET' ||
203
+ causeCode === 'UND_ERR_SOCKET' ||
204
+ msg.includes('econnreset') ||
205
+ msg.includes('socket hang up') ||
206
+ msg.includes('other side closed') ||
207
+ msg.includes('closed unexpectedly') ||
208
+ msg.includes('connection reset')) {
209
+ return classify('socket', err);
210
+ }
211
+ // 7. Generic fetch failure (browser TypeError, undici wrapper)
212
+ // "terminated" is undici's wrapper for body-read failures (e.g. response.json() after socket drop)
213
+ if (err instanceof TypeError &&
214
+ (msg.includes('fetch failed') ||
215
+ msg.includes('failed to fetch') ||
216
+ msg.includes('network error') ||
217
+ msg.includes('error sending request') ||
218
+ msg === 'terminated')) {
219
+ return classify('network', err);
220
+ }
221
+ // 8. Remaining node-fetch FetchError.
222
+ // Transport types: 'system' (OS-level), 'request-timeout', 'body-timeout'.
223
+ // Non-transport types: 'invalid-json', 'no-redirect', 'max-size'.
224
+ if (err.name === 'FetchError') {
225
+ const fetchType = err['type'];
226
+ if (fetchType === 'request-timeout' || fetchType === 'body-timeout') {
227
+ return classify('timeout', err);
228
+ }
229
+ if (fetchType === 'system') {
230
+ return classify('network', err);
231
+ }
232
+ // Not a transport error (e.g. JSON parse failure on a 200 response)
233
+ return undefined;
234
+ }
235
+ // Not a transport error
236
+ return undefined;
237
+ }
238
+
78
239
  /**
79
240
  * Hypertext Transfer Protocol (HTTP) response status codes.
80
241
  * @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
@@ -397,48 +558,117 @@ var STATUS_CODE;
397
558
 
398
559
  // IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT!
399
560
  const VERSION = {
400
- "commitHash": "105a7b15cfb862a0732c204e0e9741098d697775",
401
- "version": "24.2.0"
561
+ "commitHash": "05df48fee92f846cba793920d6fa829afd6a1847",
562
+ "version": "24.3.0-beta.1"
402
563
  };
403
564
 
404
565
  /**
405
566
  * @packageDocumentation
406
567
  * @module @taquito/http-utils
407
568
  */
408
- var _a;
409
- let fetch = globalThis === null || globalThis === void 0 ? void 0 : globalThis.fetch;
410
- let createAgent;
411
- let useNodeFetchAgent = false;
412
- const isNode = typeof process !== 'undefined' && !!((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
413
- const isBrowserLike = typeof window !== 'undefined';
414
- // Use native fetch in browser-like environments (they have reliable native fetch)
415
- // Use node-fetch in pure Node.js CLI for better compatibility and keepAlive control
416
- if (isNode && !isBrowserLike) {
417
- // Handle both ESM and CJS default export patterns for webpack compatibility
418
- // eslint-disable-next-line @typescript-eslint/no-var-requires
419
- const nodeFetch = require('node-fetch');
420
- // eslint-disable-next-line @typescript-eslint/no-var-requires
421
- const https = require('https');
422
- // eslint-disable-next-line @typescript-eslint/no-var-requires
423
- const http = require('http');
424
- fetch = nodeFetch.default || nodeFetch;
425
- useNodeFetchAgent = true;
426
- if (Number(process.versions.node.split('.')[0]) >= 19) {
427
- // we need agent with keepalive false for node 19 and above
428
- createAgent = (url) => {
429
- return url.startsWith('https')
430
- ? new https.Agent({ keepAlive: false })
431
- : new http.Agent({ keepAlive: false });
432
- };
433
- }
434
- }
435
- else if (typeof fetch !== 'function') {
436
- throw new Error('No fetch implementation available');
569
+ if (typeof globalThis.fetch !== 'function') {
570
+ throw new Error('No fetch implementation available. Requires Node.js >= 22 or a browser environment.');
437
571
  }
572
+ const httpTraceEnabled = /^(1|true)$/i.test(process?.env?.TAQUITO_HTTP_TRACE ?? '') || process?.env?.RUNNER_DEBUG === '1';
573
+ const parsedHttpRetryCount = Number(process?.env?.TAQUITO_HTTP_RETRY_COUNT ?? '1');
574
+ const httpRetryCount = Number.isFinite(parsedHttpRetryCount) && parsedHttpRetryCount >= 0
575
+ ? Math.floor(parsedHttpRetryCount)
576
+ : 1;
577
+ const parsedHttpRetryBaseMs = Number(process?.env?.TAQUITO_HTTP_RETRY_BASE_MS ?? '100');
578
+ const httpRetryBaseMs = Number.isFinite(parsedHttpRetryBaseMs) && parsedHttpRetryBaseMs >= 0
579
+ ? parsedHttpRetryBaseMs
580
+ : 100;
581
+ const normalizeTraceUrl = (url) => {
582
+ try {
583
+ const parsedUrl = new URL(url);
584
+ return `${parsedUrl.origin}${parsedUrl.pathname}`;
585
+ }
586
+ catch {
587
+ return url.split('?')[0];
588
+ }
589
+ };
590
+ const traceHttp = (payload) => {
591
+ if (!httpTraceEnabled) {
592
+ return;
593
+ }
594
+ // JSON logs make CI-side grepping and aggregation much easier.
595
+ console.log(`[taquito:http-trace] ${JSON.stringify(payload)}`);
596
+ };
597
+ const getCause = (err) => err['cause'];
598
+ const getCode = (err) => {
599
+ const code = err['code'];
600
+ return typeof code === 'string' ? code : undefined;
601
+ };
602
+ const MAX_CAUSE_DEPTH = 10;
603
+ const toErrorMessage = (error) => {
604
+ if (!(error instanceof Error)) {
605
+ return String(error);
606
+ }
607
+ const parts = [`${error.name}: ${error.message}`];
608
+ let current = getCause(error);
609
+ let depth = 0;
610
+ while (depth < MAX_CAUSE_DEPTH) {
611
+ if (current instanceof Error) {
612
+ const code = getCode(current);
613
+ const codeSuffix = code ? ` [${code}]` : '';
614
+ parts.push(`${current.name}: ${current.message}${codeSuffix}`);
615
+ current = getCause(current);
616
+ depth++;
617
+ continue;
618
+ }
619
+ // Handle object-shaped causes (e.g. undici { message, code } objects)
620
+ if (typeof current === 'object' &&
621
+ current !== null &&
622
+ typeof current['message'] === 'string') {
623
+ const obj = current;
624
+ const code = typeof obj['code'] === 'string' ? obj['code'] : undefined;
625
+ const codeSuffix = code ? ` [${code}]` : '';
626
+ parts.push(`${obj['message']}${codeSuffix}`);
627
+ }
628
+ break;
629
+ }
630
+ return parts.join(' → ');
631
+ };
632
+ const isRetriableRequest = (method, url) => {
633
+ const normalizedMethod = method.toUpperCase();
634
+ if (normalizedMethod === 'GET') {
635
+ return true;
636
+ }
637
+ if (normalizedMethod !== 'POST') {
638
+ return false;
639
+ }
640
+ const normalizedUrl = normalizeTraceUrl(url);
641
+ return (normalizedUrl.endsWith('/helpers/forge/operations') ||
642
+ normalizedUrl.endsWith('/helpers/preapply/operations') ||
643
+ normalizedUrl.endsWith('/helpers/scripts/simulate_operation') ||
644
+ normalizedUrl.endsWith('/helpers/scripts/run_operation') ||
645
+ // Safe to retry: ops are content-addressed, mempool deduplicates identical bytes,
646
+ // and counter prevents replay. If the first request secretly succeeded but the
647
+ // connection dropped, the retry may get a 500 (async_injection_failed) which
648
+ // propagates to the caller via HttpResponseError (not retried). No silent corruption,
649
+ // but the caller may see an error despite the op being on-chain.
650
+ normalizedUrl.endsWith('/injection/operation'));
651
+ };
652
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
653
+ /**
654
+ * HTTP client used by Taquito to communicate with Tezos RPC nodes.
655
+ *
656
+ * Uses `globalThis.fetch` (Node.js >= 22 built-in or browser native).
657
+ * Retries retriable transport errors (socket resets, DNS, timeouts) with
658
+ * exponential backoff and jitter. Configure via environment variables:
659
+ *
660
+ * - `TAQUITO_HTTP_RETRY_COUNT` - max retries (default `1`)
661
+ * - `TAQUITO_HTTP_RETRY_BASE_MS` - base delay in ms (default `100`)
662
+ * - `TAQUITO_HTTP_TRACE` - emit JSON request logs when `true` or `1`
663
+ */
438
664
  class HttpBackend {
665
+ /**
666
+ * @param timeout - Default request timeout in milliseconds (default `30000`).
667
+ */
439
668
  constructor(timeout = 30000) {
440
669
  this.timeout = timeout;
441
670
  }
671
+ /** Serialize an object into a URL query string (including the leading `?`). */
442
672
  serialize(obj) {
443
673
  if (!obj) {
444
674
  return '';
@@ -447,9 +677,14 @@ class HttpBackend {
447
677
  for (const p in obj) {
448
678
  // eslint-disable-next-line no-prototype-builtins
449
679
  if (obj.hasOwnProperty(p) && typeof obj[p] !== 'undefined') {
450
- const prop = typeof obj[p].toJSON === 'function' ? obj[p].toJSON() : obj[p];
680
+ const val = obj[p];
451
681
  // query arguments can have no value so we need some way of handling that
452
682
  // example https://domain.com/query?all
683
+ if (val === null) {
684
+ str.push(encodeURIComponent(p));
685
+ continue;
686
+ }
687
+ const prop = typeof val.toJSON === 'function' ? val.toJSON() : val;
453
688
  if (prop === null) {
454
689
  str.push(encodeURIComponent(p));
455
690
  continue;
@@ -474,33 +709,64 @@ class HttpBackend {
474
709
  }
475
710
  }
476
711
  /**
712
+ * Send an HTTP request to the given URL, with automatic retries on transport errors.
477
713
  *
478
- * @param options contains options to be passed for the HTTP request (url, method and timeout)
479
- * @throws {@link HttpRequestFailed} | {@link HttpResponseError} | {@link HttpTimeoutError}
480
- */
481
- createRequest(_a, data_1) {
482
- return __awaiter(this, arguments, void 0, function* ({ url, method, timeout = this.timeout, query, headers = {}, json = true }, data) {
483
- // Serializes query params
484
- const urlWithQuery = url + this.serialize(query);
485
- // Adds default header entry if there aren't any Content-Type header
486
- if (!headers['Content-Type']) {
487
- headers['Content-Type'] = 'application/json';
488
- }
714
+ * @param options - Request configuration (URL, method, timeout, headers, etc.).
715
+ * @param data - Request body, serialized to JSON via `JSON.stringify`.
716
+ * @returns The parsed JSON response (or raw text when `json: false`).
717
+ * @throws {@link HttpResponseError} when the server returns HTTP status >= 400.
718
+ * @throws {@link HttpTimeoutError} when the request exceeds the configured timeout.
719
+ * @throws {@link HttpRequestFailed} for transport-level failures (network, DNS, socket, etc.).
720
+ */
721
+ async createRequest({ url, method, timeout = this.timeout, query, headers = {}, json = true }, data) {
722
+ // Serializes query params
723
+ const urlWithQuery = url + this.serialize(query);
724
+ const methodValue = String(method ?? 'GET');
725
+ // Adds default header entry if there aren't any Content-Type header
726
+ if (!headers['Content-Type']) {
727
+ headers['Content-Type'] = 'application/json';
728
+ }
729
+ // Serialized once on first attempt, reused on retries for byte-stable requests.
730
+ let body;
731
+ let bodySerialized = false;
732
+ for (let attempt = 0; attempt <= httpRetryCount; attempt++) {
489
733
  // Creates a new AbortController instance to handle timeouts
490
734
  const controller = new AbortController();
491
735
  const t = setTimeout(() => controller.abort(), timeout);
736
+ const requestStartedAt = Date.now();
492
737
  try {
493
- const response = yield fetch(urlWithQuery, Object.assign({ keepalive: false, // Disable keepalive (keepalive defaults to true starting from Node 19 & 20)
738
+ if (!bodySerialized) {
739
+ body = JSON.stringify(data);
740
+ bodySerialized = true;
741
+ }
742
+ const response = await globalThis.fetch(urlWithQuery, {
494
743
  method,
495
- headers, body: JSON.stringify(data), signal: controller.signal }, (useNodeFetchAgent && createAgent ? { agent: createAgent(urlWithQuery) } : {})));
744
+ headers,
745
+ body,
746
+ signal: controller.signal,
747
+ });
496
748
  if (typeof response === 'undefined') {
497
749
  throw new Error('Response is undefined');
498
750
  }
499
751
  // Handle responses with status code >= 400
500
752
  if (response.status >= 400) {
501
- const errorData = yield response.text();
753
+ const errorData = await response.text();
754
+ traceHttp({
755
+ stage: 'response-error',
756
+ method: methodValue,
757
+ url: normalizeTraceUrl(urlWithQuery),
758
+ status: response.status,
759
+ elapsedMs: Date.now() - requestStartedAt,
760
+ });
502
761
  throw new HttpResponseError(`Http error response: (${response.status}) ${errorData}`, response.status, response.statusText, errorData, urlWithQuery);
503
762
  }
763
+ traceHttp({
764
+ stage: 'response-ok',
765
+ method: methodValue,
766
+ url: normalizeTraceUrl(urlWithQuery),
767
+ status: response.status,
768
+ elapsedMs: Date.now() - requestStartedAt,
769
+ });
504
770
  if (json) {
505
771
  return response.json();
506
772
  }
@@ -509,22 +775,72 @@ class HttpBackend {
509
775
  }
510
776
  }
511
777
  catch (e) {
512
- if (e instanceof Error && e.name === 'AbortError') {
513
- throw new HttpTimeoutError(timeout, urlWithQuery);
514
- }
515
- else if (e instanceof HttpResponseError) {
778
+ // HttpResponseError is an application-level error (status >= 400), not a transport error.
779
+ // Short-circuit before classification to prevent RPC body text (which may contain
780
+ // strings like "connection reset") from being misclassified as a transport error.
781
+ if (e instanceof HttpResponseError) {
782
+ traceHttp({
783
+ stage: 'http-response-error',
784
+ method: methodValue,
785
+ url: normalizeTraceUrl(urlWithQuery),
786
+ elapsedMs: Date.now() - requestStartedAt,
787
+ status: e.status,
788
+ });
516
789
  throw e;
517
790
  }
791
+ const classified = e instanceof Error ? classifyTransportError(e) : undefined;
792
+ const isRetriableTransport = classified !== undefined && classified.kind !== 'abort' && classified.kind !== 'tls';
793
+ const shouldRetry = attempt < httpRetryCount &&
794
+ isRetriableRequest(methodValue, urlWithQuery) &&
795
+ isRetriableTransport;
796
+ if (shouldRetry) {
797
+ const exponential = httpRetryBaseMs * Math.pow(2, attempt);
798
+ const jitter = Math.floor(Math.random() * httpRetryBaseMs);
799
+ const retryDelayMs = exponential + jitter;
800
+ traceHttp({
801
+ stage: 'request-retry',
802
+ method: methodValue,
803
+ url: normalizeTraceUrl(urlWithQuery),
804
+ elapsedMs: Date.now() - requestStartedAt,
805
+ attempt: attempt + 1,
806
+ maxAttempts: httpRetryCount + 1,
807
+ retryDelayMs,
808
+ error: toErrorMessage(e),
809
+ transportKind: classified?.kind,
810
+ });
811
+ await sleep(retryDelayMs);
812
+ continue;
813
+ }
814
+ if (classified?.kind === 'abort') {
815
+ traceHttp({
816
+ stage: 'timeout',
817
+ method: methodValue,
818
+ url: normalizeTraceUrl(urlWithQuery),
819
+ elapsedMs: Date.now() - requestStartedAt,
820
+ timeoutMs: timeout,
821
+ });
822
+ throw new HttpTimeoutError(timeout, urlWithQuery);
823
+ }
518
824
  else {
519
- throw new HttpRequestFailed(String(method), urlWithQuery, e);
825
+ traceHttp({
826
+ stage: 'request-failed',
827
+ method: methodValue,
828
+ url: normalizeTraceUrl(urlWithQuery),
829
+ elapsedMs: Date.now() - requestStartedAt,
830
+ error: toErrorMessage(e),
831
+ transportKind: classified?.kind,
832
+ });
833
+ const cause = e instanceof Error ? e : new Error(String(e));
834
+ throw new HttpRequestFailed(methodValue, urlWithQuery, cause, classified);
520
835
  }
521
836
  }
522
837
  finally {
523
838
  clearTimeout(t);
524
839
  }
525
- });
840
+ }
841
+ throw new Error('Unexpected request retry flow');
526
842
  }
527
843
  }
528
844
 
529
- export { HttpBackend, HttpRequestFailed, HttpResponseError, HttpTimeoutError, STATUS_CODE, VERSION };
845
+ export { HttpBackend, HttpRequestFailed, HttpResponseError, HttpTimeoutError, STATUS_CODE, VERSION, classifyTransportError };
530
846
  //# sourceMappingURL=taquito-http-utils.es6.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"taquito-http-utils.es6.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"taquito-http-utils.es6.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}