@openclaw/discord 2026.6.33 → 2026.6.35
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.
- package/dist/action-runtime-api.js +1 -1
- package/dist/{api-CjnGBhM3.js → api-1GXlSrYP.js} +42 -21
- package/dist/api.js +14 -14
- package/dist/{approval-handler.runtime-BWPQrzpP.js → approval-handler.runtime-Dv4p7REU.js} +3 -3
- package/dist/{audit-BwJ_2OjL.js → audit-DEVi9nIs.js} +3 -3
- package/dist/{channel-Ddnonoy4.js → channel-CQIRiosn.js} +15 -15
- package/dist/{channel-actions-CwmIqnYN.js → channel-actions-JziPEcBU.js} +2 -2
- package/dist/{channel-actions.runtime-DvpTfaJ4.js → channel-actions.runtime-D7L47LY3.js} +5 -5
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-glZ5Y98a.js → channel.setup-oMeR19Cc.js} +2 -2
- package/dist/{components-j6lTZBsT.js → components-_s5UPJqU.js} +1 -1
- package/dist/{conversation-identity-36FFBwm5.js → conversation-identity-B0k94-bz.js} +2 -2
- package/dist/{directory-config-BYMFzL29.js → directory-config-CD1svkyN.js} +1 -1
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-9JtJCiu8.js → directory-live-DBTsSRxl.js} +5 -5
- package/dist/{handle-action.guild-admin-xxGYrp-I.js → handle-action.guild-admin-CYIFMxh-.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{manager.runtime-BK6cf2Zu.js → manager.runtime-BZVdGX2d.js} +17 -9
- package/dist/{message-handler-BqpJIavO.js → message-handler-DZd9Bpq8.js} +7 -6
- package/dist/{message-handler.preflight-C90MxCcv.js → message-handler.preflight-Axv9J9EO.js} +12 -11
- package/dist/{message-handler.process-CeQ7Bd_3.js → message-handler.process-CG0zFS-Z.js} +10 -10
- package/dist/{message-utils-CJeAoISH.js → message-utils-Dbuw27gd.js} +1 -1
- package/dist/{outbound-adapter-Dxs6iKEq.js → outbound-adapter-CKC_kmUN.js} +6 -6
- package/dist/pluralkit-CfTReLl3.js +40 -0
- package/dist/{probe-esE_Oqdm.js → probe-DcgZIpyF.js} +3 -2
- package/dist/{probe.runtime-C3Atgpvr.js → probe.runtime-CVwM9L5E.js} +1 -1
- package/dist/{provider-C7376XFH.js → provider-CwwSXIIj.js} +102 -37
- package/dist/{provider-session.runtime-CCdqogGf.js → provider-session.runtime-reBWkqHZ.js} +3 -3
- package/dist/provider.runtime-BsmTLYS_.js +2 -0
- package/dist/{resolve-allowlist-common-3gB2Gpmm.js → resolve-allowlist-common-D7bZqAw3.js} +3 -3
- package/dist/{resolve-channels-B3gQf8QL.js → resolve-channels--oE-RnO6.js} +6 -6
- package/dist/{resolve-users-BO10y5fJ.js → resolve-users-QXo8CXBU.js} +5 -5
- package/dist/{runtime-CFlR3L9X.js → runtime-CF7ramAs.js} +7 -7
- package/dist/runtime-api.actions.js +2 -2
- package/dist/runtime-api.js +19 -19
- package/dist/runtime-api.lookup.js +5 -5
- package/dist/runtime-api.monitor-CzF8MUl_.js +6 -0
- package/dist/runtime-api.monitor.js +5 -5
- package/dist/runtime-api.send.js +5 -5
- package/dist/runtime-api.threads.js +3 -3
- package/dist/{send-JHxfT4Pk.js → send-Bb295iHT.js} +5 -5
- package/dist/{send.components-CN35E2Ck.js → send.components-COkDUO3B.js} +4 -4
- package/dist/{send.outbound-B-cA1Esv.js → send.outbound-B_sk0hNe.js} +3 -3
- package/dist/{send.receipt-CGecJDin.js → send.receipt-Cli6_9Sn.js} +19 -7
- package/dist/{send.shared-vcbfThdy.js → send.shared-BCG7MWnP.js} +2 -2
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{subagent-hooks-20wXhVzZ.js → subagent-hooks-CtqoWK4x.js} +2 -2
- package/dist/subagent-hooks-api.js +1 -1
- package/dist/{system-events-DR27TE7p.js → system-events-BxGNk_wE.js} +1 -1
- package/dist/{target-resolver-CDi1gI90.js → target-resolver-BcoK6b0g.js} +2 -2
- package/dist/targets-Q-ZoP7K4.js +3 -0
- package/dist/{thread-bindings-DU4T6nDo.js → thread-bindings-DJ_HT6GJ.js} +4 -4
- package/dist/{thread-bindings.discord-api-CYkr-3Gj.js → thread-bindings.discord-api-OPyjvgyz.js} +4 -4
- package/dist/{thread-bindings.manager-BqWs1UE2.js → thread-bindings.manager-Cd2HAVw_.js} +3 -3
- package/dist/{transcripts-source-D-lBI5ZD.js → transcripts-source-CPf5gURy.js} +1 -1
- package/dist/transcripts-source-api.js +1 -1
- package/dist/{typing-xr05bL-r.js → typing-DY0cwLdg.js} +1 -1
- package/node_modules/undici/README.md +8 -2
- package/node_modules/undici/docs/docs/api/Agent.md +248 -44
- package/node_modules/undici/docs/docs/api/BalancedPool.md +246 -48
- package/node_modules/undici/docs/docs/api/CacheStorage.md +171 -13
- package/node_modules/undici/docs/docs/api/CacheStore.md +294 -98
- package/node_modules/undici/docs/docs/api/Client.md +386 -179
- package/node_modules/undici/docs/docs/api/ClientStats.md +80 -14
- package/node_modules/undici/docs/docs/api/Connector.md +118 -36
- package/node_modules/undici/docs/docs/api/ContentType.md +65 -25
- package/node_modules/undici/docs/docs/api/Cookies.md +122 -71
- package/node_modules/undici/docs/docs/api/Debug.md +34 -18
- package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +364 -164
- package/node_modules/undici/docs/docs/api/Dispatcher.md +485 -1102
- package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +102 -87
- package/node_modules/undici/docs/docs/api/Errors.md +503 -40
- package/node_modules/undici/docs/docs/api/EventSource.md +298 -32
- package/node_modules/undici/docs/docs/api/Fetch.md +680 -31
- package/node_modules/undici/docs/docs/api/GlobalInstallation.md +78 -98
- package/node_modules/undici/docs/docs/api/H2CClient.md +298 -176
- package/node_modules/undici/docs/docs/api/Interceptors.md +418 -0
- package/node_modules/undici/docs/docs/api/MockAgent.md +333 -314
- package/node_modules/undici/docs/docs/api/MockCallHistory.md +272 -98
- package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +189 -26
- package/node_modules/undici/docs/docs/api/MockClient.md +151 -33
- package/node_modules/undici/docs/docs/api/MockErrors.md +56 -5
- package/node_modules/undici/docs/docs/api/MockPool.md +321 -258
- package/node_modules/undici/docs/docs/api/Pool.md +235 -43
- package/node_modules/undici/docs/docs/api/PoolStats.md +119 -13
- package/node_modules/undici/docs/docs/api/ProxyAgent.md +179 -160
- package/node_modules/undici/docs/docs/api/RedirectHandler.md +238 -63
- package/node_modules/undici/docs/docs/api/RetryAgent.md +113 -26
- package/node_modules/undici/docs/docs/api/RetryHandler.md +161 -77
- package/node_modules/undici/docs/docs/api/RoundRobinPool.md +166 -72
- package/node_modules/undici/docs/docs/api/SnapshotAgent.md +264 -542
- package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +177 -196
- package/node_modules/undici/docs/docs/api/Util.md +68 -11
- package/node_modules/undici/docs/docs/api/WebSocket.md +475 -80
- package/node_modules/undici/docs/docs/api/api-lifecycle.md +100 -32
- package/node_modules/undici/docs/docs/best-practices/client-certificate.md +2 -0
- package/node_modules/undici/docs/docs/best-practices/crawling.md +3 -1
- package/node_modules/undici/docs/docs/best-practices/migrating-from-v7-to-v8.md +6 -4
- package/node_modules/undici/docs/docs/best-practices/mocking-request.md +9 -7
- package/node_modules/undici/docs/docs/best-practices/proxy.md +3 -1
- package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +10 -8
- package/node_modules/undici/docs/docs/best-practices/writing-tests.md +2 -0
- package/node_modules/undici/docs/docs/{GettingStarted.md → getting-started.md} +22 -19
- package/node_modules/undici/docs/docs/index.md +784 -0
- package/node_modules/undici/docs/docs/site.json +125 -0
- package/node_modules/undici/docs/docs/type-map.json +79 -0
- package/node_modules/undici/lib/api/api-request.js +7 -1
- package/node_modules/undici/lib/api/readable.js +42 -7
- package/node_modules/undici/lib/cache/memory-cache-store.js +56 -11
- package/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
- package/node_modules/undici/lib/core/connect.js +16 -2
- package/node_modules/undici/lib/core/errors.js +20 -0
- package/node_modules/undici/lib/core/request.js +14 -3
- package/node_modules/undici/lib/core/symbols.js +3 -0
- package/node_modules/undici/lib/core/util.js +30 -2
- package/node_modules/undici/lib/dispatcher/agent.js +10 -5
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +7 -4
- package/node_modules/undici/lib/dispatcher/client-h1.js +74 -9
- package/node_modules/undici/lib/dispatcher/client-h2.js +483 -151
- package/node_modules/undici/lib/dispatcher/client.js +76 -30
- package/node_modules/undici/lib/dispatcher/dispatcher-base.js +13 -1
- package/node_modules/undici/lib/dispatcher/dispatcher.js +15 -1
- package/node_modules/undici/lib/dispatcher/dispatcher1-wrapper.js +3 -0
- package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +37 -8
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +58 -7
- package/node_modules/undici/lib/dispatcher/retry-agent.js +3 -0
- package/node_modules/undici/lib/dispatcher/round-robin-pool.js +1 -1
- package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +106 -22
- package/node_modules/undici/lib/global.js +36 -16
- package/node_modules/undici/lib/handler/cache-handler.js +297 -65
- package/node_modules/undici/lib/handler/cache-revalidation-handler.js +13 -3
- package/node_modules/undici/lib/handler/decorator-handler.js +7 -1
- package/node_modules/undici/lib/handler/deduplication-handler.js +12 -6
- package/node_modules/undici/lib/handler/redirect-handler.js +26 -5
- package/node_modules/undici/lib/handler/retry-handler.js +238 -51
- package/node_modules/undici/lib/interceptor/cache.js +221 -58
- package/node_modules/undici/lib/interceptor/decompress.js +124 -15
- package/node_modules/undici/lib/interceptor/deduplicate.js +5 -4
- package/node_modules/undici/lib/interceptor/dns.js +7 -0
- package/node_modules/undici/lib/interceptor/dump.js +10 -23
- package/node_modules/undici/lib/mock/mock-agent.js +13 -1
- package/node_modules/undici/lib/mock/mock-interceptor.js +25 -7
- package/node_modules/undici/lib/mock/mock-utils.js +246 -27
- package/node_modules/undici/lib/util/cache.js +415 -71
- package/node_modules/undici/lib/util/date.js +23 -6
- package/node_modules/undici/lib/web/cookies/parse.js +150 -146
- package/node_modules/undici/lib/web/cookies/util.js +79 -9
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +28 -1
- package/node_modules/undici/lib/web/eventsource/eventsource.js +13 -3
- package/node_modules/undici/lib/web/eventsource/util.js +1 -1
- package/node_modules/undici/lib/web/fetch/body.js +2 -1
- package/node_modules/undici/lib/web/fetch/constants.js +1 -1
- package/node_modules/undici/lib/web/fetch/index.js +17 -2
- package/node_modules/undici/lib/web/fetch/request.js +2 -3
- package/node_modules/undici/lib/web/fetch/response.js +1 -3
- package/node_modules/undici/lib/web/fetch/util.js +10 -2
- package/node_modules/undici/lib/web/webidl/index.js +3 -3
- package/node_modules/undici/lib/web/websocket/connection.js +1 -1
- package/node_modules/undici/lib/web/websocket/permessage-deflate.js +5 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +8 -10
- package/node_modules/undici/lib/web/websocket/websocket.js +22 -0
- package/node_modules/undici/package.json +4 -4
- package/node_modules/undici/types/client.d.ts +53 -3
- package/node_modules/undici/types/cookies.d.ts +1 -1
- package/node_modules/undici/types/dispatcher.d.ts +3 -3
- package/node_modules/undici/types/errors.d.ts +10 -0
- package/node_modules/undici/types/handlers.d.ts +2 -0
- package/node_modules/undici/types/interceptors.d.ts +2 -0
- package/node_modules/undici/types/mock-agent.d.ts +11 -10
- package/node_modules/undici/types/mock-interceptor.d.ts +5 -1
- package/node_modules/undici/types/proxy-agent.d.ts +7 -0
- package/node_modules/undici/types/socks5-proxy-agent.d.ts +2 -0
- package/npm-shrinkwrap.json +7 -7
- package/package.json +6 -5
- package/dist/pluralkit-DI6RYaVj.js +0 -24
- package/dist/provider.runtime-D1m66kDS.js +0 -2
- package/dist/runtime-api.monitor-4oc58TSk.js +0 -6
- package/dist/targets-C6yd3y8R.js +0 -3
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const util = require('../core/util')
|
|
4
4
|
const assert = require('node:assert')
|
|
5
5
|
const { InvalidArgumentError } = require('../core/errors')
|
|
6
|
+
const { kRequestOrigin } = require('../core/symbols')
|
|
6
7
|
|
|
7
8
|
const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
|
|
8
9
|
|
|
@@ -43,6 +44,14 @@ class RedirectHandler {
|
|
|
43
44
|
this.handler.onRequestStart?.(controller, { ...context, history: this.history })
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
onBodySent (chunk) {
|
|
48
|
+
this.handler.onBodySent?.(chunk)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onRequestSent () {
|
|
52
|
+
this.handler.onRequestSent?.()
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
47
56
|
this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
48
57
|
}
|
|
@@ -52,15 +61,19 @@ class RedirectHandler {
|
|
|
52
61
|
throw new Error('max redirects')
|
|
53
62
|
}
|
|
54
63
|
|
|
64
|
+
let removeContentHeaders = statusCode === 303
|
|
65
|
+
|
|
55
66
|
// https://tools.ietf.org/html/rfc7231#section-6.4.2
|
|
56
67
|
// https://fetch.spec.whatwg.org/#http-redirect-fetch
|
|
57
68
|
// In case of HTTP 301 or 302 with POST, change the method to GET
|
|
69
|
+
// QUERY is safe (RFC 10008) and should not change method like GET.
|
|
58
70
|
if ((statusCode === 301 || statusCode === 302) && this.opts.method === 'POST') {
|
|
59
71
|
this.opts.method = 'GET'
|
|
60
72
|
if (util.isStream(this.opts.body)) {
|
|
61
73
|
util.destroy(this.opts.body.on('error', noop))
|
|
62
74
|
}
|
|
63
75
|
this.opts.body = null
|
|
76
|
+
removeContentHeaders = true
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
|
@@ -77,8 +90,12 @@ class RedirectHandler {
|
|
|
77
90
|
? null
|
|
78
91
|
: headers.location
|
|
79
92
|
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
const requestOrigin = this.opts[kRequestOrigin] === undefined
|
|
94
|
+
? this.opts.origin
|
|
95
|
+
: this.opts[kRequestOrigin]
|
|
96
|
+
|
|
97
|
+
if (requestOrigin) {
|
|
98
|
+
this.history.push(new URL(this.opts.path, requestOrigin))
|
|
82
99
|
}
|
|
83
100
|
|
|
84
101
|
if (!this.location) {
|
|
@@ -86,7 +103,10 @@ class RedirectHandler {
|
|
|
86
103
|
return
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
const
|
|
106
|
+
const baseUrl = requestOrigin
|
|
107
|
+
? new URL(this.opts.path, requestOrigin)
|
|
108
|
+
: undefined
|
|
109
|
+
const { origin, pathname, search } = util.parseURL(new URL(this.location, baseUrl))
|
|
90
110
|
const path = search ? `${pathname}${search}` : pathname
|
|
91
111
|
|
|
92
112
|
// Check for redirect loops by seeing if we've already visited this URL in our history
|
|
@@ -100,11 +120,12 @@ class RedirectHandler {
|
|
|
100
120
|
}
|
|
101
121
|
|
|
102
122
|
// Remove headers referring to the original URL.
|
|
103
|
-
// By default it is Host only
|
|
123
|
+
// By default it is Host only. A 303 or a 301/302 POST-to-GET redirect also removes all Content-* headers.
|
|
104
124
|
// https://tools.ietf.org/html/rfc7231#section-6.4
|
|
105
|
-
this.opts.headers = cleanRequestHeaders(this.opts.headers,
|
|
125
|
+
this.opts.headers = cleanRequestHeaders(this.opts.headers, removeContentHeaders, requestOrigin !== origin, this.stripHeadersOnRedirect, this.stripHeadersOnCrossOriginRedirect)
|
|
106
126
|
this.opts.path = path
|
|
107
127
|
this.opts.origin = origin
|
|
128
|
+
this.opts[kRequestOrigin] = origin
|
|
108
129
|
this.opts.query = null
|
|
109
130
|
}
|
|
110
131
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const assert = require('node:assert')
|
|
3
3
|
|
|
4
4
|
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
|
|
5
|
-
const { RequestRetryError } = require('../core/errors')
|
|
5
|
+
const { RequestRetryError, RequestAbortedError } = require('../core/errors')
|
|
6
6
|
const {
|
|
7
7
|
isDisturbed,
|
|
8
8
|
parseRangeHeader,
|
|
@@ -11,7 +11,72 @@ const {
|
|
|
11
11
|
|
|
12
12
|
function calculateRetryAfterHeader (retryAfter) {
|
|
13
13
|
const retryTime = new Date(retryAfter).getTime()
|
|
14
|
-
return isNaN(retryTime) ?
|
|
14
|
+
return isNaN(retryTime) ? null : retryTime - Date.now()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function validatePartialResponseContentLength (headers, range, statusCode, retryCount) {
|
|
18
|
+
const contentLength = headers['content-length']
|
|
19
|
+
if (contentLength == null) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const length = Number(contentLength)
|
|
28
|
+
const expectedLength = range.end - range.start + 1
|
|
29
|
+
if (!Number.isFinite(length) || length !== expectedLength) {
|
|
30
|
+
throw new RequestRetryError('Content-Length mismatch', statusCode, {
|
|
31
|
+
headers,
|
|
32
|
+
data: { count: retryCount }
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A stable controller handed to the downstream handler for the lifetime of the
|
|
38
|
+
// request. Each transparent retry/resume is a *separate* dispatch with its
|
|
39
|
+
// *own* connection controller. Without a stable proxy the downstream body keeps
|
|
40
|
+
// flow-controlling the original (now-dead) controller while data flows on the
|
|
41
|
+
// new one: backpressure pauses the new connection's controller, but the
|
|
42
|
+
// consumer's resume() targets the old one, so the resumed body stalls forever.
|
|
43
|
+
// The proxy always forwards to the controller of the currently active connection.
|
|
44
|
+
// An abort is additionally reported to the handler so it can cancel a pending
|
|
45
|
+
// retry backoff instead of letting the request hang until the backoff elapses.
|
|
46
|
+
// The notification is a private callback the handler hands over on construction,
|
|
47
|
+
// so nothing outside the handler can trigger it.
|
|
48
|
+
class RetryController {
|
|
49
|
+
#onAbort
|
|
50
|
+
|
|
51
|
+
constructor (onAbort) {
|
|
52
|
+
this.#onAbort = onAbort
|
|
53
|
+
this.target = null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pause () { this.target?.pause() }
|
|
57
|
+
resume () { this.target?.resume() }
|
|
58
|
+
|
|
59
|
+
abort (reason) {
|
|
60
|
+
this.target?.abort(reason)
|
|
61
|
+
this.#onAbort(reason)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get paused () { return this.target?.paused ?? false }
|
|
65
|
+
get aborted () { return this.target?.aborted ?? false }
|
|
66
|
+
get reason () { return this.target?.reason ?? null }
|
|
67
|
+
get rawHeaders () { return this.target?.rawHeaders ?? null }
|
|
68
|
+
set rawHeaders (value) {
|
|
69
|
+
if (this.target) {
|
|
70
|
+
this.target.rawHeaders = value
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get rawTrailers () { return this.target?.rawTrailers ?? null }
|
|
75
|
+
set rawTrailers (value) {
|
|
76
|
+
if (this.target) {
|
|
77
|
+
this.target.rawTrailers = value
|
|
78
|
+
}
|
|
79
|
+
}
|
|
15
80
|
}
|
|
16
81
|
|
|
17
82
|
class RetryHandler {
|
|
@@ -45,7 +110,7 @@ class RetryHandler {
|
|
|
45
110
|
timeoutFactor: timeoutFactor ?? 2,
|
|
46
111
|
maxRetries: maxRetries ?? 5,
|
|
47
112
|
// What errors we should retry
|
|
48
|
-
methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'],
|
|
113
|
+
methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE', 'QUERY'],
|
|
49
114
|
// Indicates which errors to retry
|
|
50
115
|
statusCodes: statusCodes ?? [500, 502, 503, 504, 429],
|
|
51
116
|
// List of errors to retry
|
|
@@ -70,14 +135,32 @@ class RetryHandler {
|
|
|
70
135
|
this.etag = null
|
|
71
136
|
this.statusCode = null
|
|
72
137
|
this.headers = null
|
|
138
|
+
this.controllerProxy = new RetryController(reason => this.#onAbort(reason))
|
|
139
|
+
// A retry decision is in flight (the policy may be holding a backoff
|
|
140
|
+
// timer). While pending, a consumer abort cancels the wait.
|
|
141
|
+
this.retryPending = false
|
|
142
|
+
// Backoff timer returned by the retry policy, so #onAbort can cancel it.
|
|
143
|
+
// Null for custom policies that do not return their timer.
|
|
144
|
+
this.retryTimer = null
|
|
145
|
+
// Set once an abort during the backoff delivered the terminal error
|
|
146
|
+
// downstream; late policy callbacks and connection errors are then moot.
|
|
147
|
+
this.aborted = false
|
|
73
148
|
}
|
|
74
149
|
|
|
75
150
|
onResponseStartWithRetry (controller, statusCode, headers, statusMessage, err) {
|
|
76
151
|
if (this.retryOpts.throwOnError) {
|
|
77
152
|
// Preserve old behavior for status codes that are not eligible for retry
|
|
78
153
|
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
79
|
-
this.headersSent
|
|
80
|
-
|
|
154
|
+
if (this.headersSent) {
|
|
155
|
+
// The downstream handler already received the response from an
|
|
156
|
+
// earlier attempt. Forwarding this response would replace the
|
|
157
|
+
// downstream body and leave the original body pending forever.
|
|
158
|
+
this.handler.onResponseError?.(this.controllerProxy, err)
|
|
159
|
+
} else {
|
|
160
|
+
this.headersSent = true
|
|
161
|
+
this.checkpointResponseEnd(headers)
|
|
162
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
163
|
+
}
|
|
81
164
|
} else {
|
|
82
165
|
this.error = err
|
|
83
166
|
}
|
|
@@ -87,14 +170,30 @@ class RetryHandler {
|
|
|
87
170
|
|
|
88
171
|
if (isDisturbed(this.opts.body)) {
|
|
89
172
|
this.headersSent = true
|
|
90
|
-
this.
|
|
173
|
+
this.checkpointResponseEnd(headers)
|
|
174
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
91
175
|
return
|
|
92
176
|
}
|
|
93
177
|
|
|
94
178
|
function shouldRetry (passedErr) {
|
|
179
|
+
if (this.aborted) {
|
|
180
|
+
// Aborted while the policy was deciding; the decision is moot.
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
this.retryPending = false
|
|
184
|
+
this.retryTimer = null
|
|
185
|
+
|
|
95
186
|
if (passedErr) {
|
|
96
|
-
this.headersSent
|
|
97
|
-
|
|
187
|
+
if (this.headersSent) {
|
|
188
|
+
// The downstream handler already received the response from an
|
|
189
|
+
// earlier attempt. Forwarding this response would replace the
|
|
190
|
+
// downstream body and leave the original body pending forever.
|
|
191
|
+
this.handler.onResponseError?.(this.controllerProxy, passedErr)
|
|
192
|
+
} else {
|
|
193
|
+
this.headersSent = true
|
|
194
|
+
this.checkpointResponseEnd(headers)
|
|
195
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
196
|
+
}
|
|
98
197
|
controller.resume()
|
|
99
198
|
return
|
|
100
199
|
}
|
|
@@ -103,25 +202,63 @@ class RetryHandler {
|
|
|
103
202
|
controller.resume()
|
|
104
203
|
}
|
|
105
204
|
|
|
205
|
+
// The pause()/resume() pair (here and in shouldRetry) acts on THIS
|
|
206
|
+
// connection's controller -- never the downstream proxy. We hold this exact
|
|
207
|
+
// connection while the retry policy decides (possibly after a timeout) and
|
|
208
|
+
// must resume the same one. Routing this through controllerProxy would risk
|
|
209
|
+
// resuming a different connection if a later dispatch re-points the proxy in
|
|
210
|
+
// between, leaving this one paused forever -- the very stall the proxy exists
|
|
211
|
+
// to prevent.
|
|
106
212
|
controller.pause()
|
|
107
|
-
|
|
213
|
+
// The default policy returns its backoff timer so an abort can cancel it;
|
|
214
|
+
// a custom policy may return anything (or nothing), which is ignored.
|
|
215
|
+
this.retryPending = true
|
|
216
|
+
this.retryTimer = this.retryOpts.retry(
|
|
108
217
|
err,
|
|
109
218
|
{
|
|
110
219
|
state: { counter: this.retryCount },
|
|
111
220
|
opts: { retryOptions: this.retryOpts, ...this.opts }
|
|
112
221
|
},
|
|
113
222
|
shouldRetry.bind(this)
|
|
114
|
-
)
|
|
223
|
+
) ?? null
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
checkpointResponseEnd (headers) {
|
|
227
|
+
if (this.end == null && this.opts.method !== 'HEAD') {
|
|
228
|
+
const contentLength = headers['content-length']
|
|
229
|
+
this.end = contentLength != null ? Number(contentLength) - 1 : null
|
|
230
|
+
|
|
231
|
+
assert(
|
|
232
|
+
this.end == null || Number.isFinite(this.end),
|
|
233
|
+
'invalid content-length'
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
this.resume = this.end != null
|
|
237
|
+
}
|
|
115
238
|
}
|
|
116
239
|
|
|
117
240
|
onRequestStart (controller, context) {
|
|
241
|
+
// request.js creates a fresh RequestController per dispatch and passes that
|
|
242
|
+
// same instance to every later callback of the dispatch. onRequestStart is
|
|
243
|
+
// the first callback (it is where the controller is created), so re-pointing
|
|
244
|
+
// the proxy here is enough to keep it on the active connection across every
|
|
245
|
+
// transparent retry/resume.
|
|
246
|
+
this.controllerProxy.target = controller
|
|
118
247
|
if (!this.headersSent) {
|
|
119
|
-
this.handler.onRequestStart?.(
|
|
248
|
+
this.handler.onRequestStart?.(this.controllerProxy, context)
|
|
120
249
|
}
|
|
121
250
|
}
|
|
122
251
|
|
|
123
|
-
|
|
124
|
-
this.handler.
|
|
252
|
+
onBodySent (chunk) {
|
|
253
|
+
this.handler.onBodySent?.(chunk)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
onRequestSent () {
|
|
257
|
+
this.handler.onRequestSent?.()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
onRequestUpgrade (_controller, statusCode, headers, socket) {
|
|
261
|
+
this.handler.onRequestUpgrade?.(this.controllerProxy, statusCode, headers, socket)
|
|
125
262
|
}
|
|
126
263
|
|
|
127
264
|
static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
|
|
@@ -134,7 +271,8 @@ class RetryHandler {
|
|
|
134
271
|
timeoutFactor,
|
|
135
272
|
statusCodes,
|
|
136
273
|
errorCodes,
|
|
137
|
-
methods
|
|
274
|
+
methods,
|
|
275
|
+
retryAfter
|
|
138
276
|
} = retryOptions
|
|
139
277
|
const { counter } = state
|
|
140
278
|
|
|
@@ -166,7 +304,7 @@ class RetryHandler {
|
|
|
166
304
|
return
|
|
167
305
|
}
|
|
168
306
|
|
|
169
|
-
let retryAfterHeader = headers?.['retry-after']
|
|
307
|
+
let retryAfterHeader = retryAfter === false ? undefined : headers?.['retry-after']
|
|
170
308
|
if (retryAfterHeader) {
|
|
171
309
|
retryAfterHeader = Number(retryAfterHeader)
|
|
172
310
|
retryAfterHeader = Number.isNaN(retryAfterHeader)
|
|
@@ -175,31 +313,28 @@ class RetryHandler {
|
|
|
175
313
|
}
|
|
176
314
|
|
|
177
315
|
const retryTimeout =
|
|
178
|
-
retryAfterHeader
|
|
179
|
-
?
|
|
180
|
-
:
|
|
181
|
-
|
|
182
|
-
|
|
316
|
+
retryAfterHeader === 0
|
|
317
|
+
? 0
|
|
318
|
+
: retryAfterHeader > 0
|
|
319
|
+
? Math.min(retryAfterHeader, maxTimeout)
|
|
320
|
+
: Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
|
|
321
|
+
|
|
322
|
+
// Return the backoff timer so the handler can cancel it when the
|
|
323
|
+
// consumer aborts while the retry decision is pending.
|
|
324
|
+
return setTimeout(() => cb(null), retryTimeout)
|
|
183
325
|
}
|
|
184
326
|
|
|
185
327
|
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
328
|
+
if (statusCode < 200) {
|
|
329
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
|
|
186
333
|
this.error = null
|
|
187
334
|
this.retryCount += 1
|
|
188
335
|
this.statusCode = statusCode
|
|
189
336
|
this.headers = headers
|
|
190
337
|
|
|
191
|
-
if (statusCode >= 300) {
|
|
192
|
-
const err = new RequestRetryError('Request failed', statusCode, {
|
|
193
|
-
headers,
|
|
194
|
-
data: {
|
|
195
|
-
count: this.retryCount
|
|
196
|
-
}
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
this.onResponseStartWithRetry(controller, statusCode, headers, statusMessage, err)
|
|
200
|
-
return
|
|
201
|
-
}
|
|
202
|
-
|
|
203
338
|
// Checkpoint for resume from where we left it
|
|
204
339
|
if (this.headersSent) {
|
|
205
340
|
// Only Partial Content 206 supposed to provide Content-Range,
|
|
@@ -232,23 +367,41 @@ class RetryHandler {
|
|
|
232
367
|
})
|
|
233
368
|
}
|
|
234
369
|
|
|
370
|
+
validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount)
|
|
371
|
+
|
|
235
372
|
const { start, size, end = size ? size - 1 : null } = contentRange
|
|
236
373
|
|
|
237
|
-
|
|
238
|
-
|
|
374
|
+
if (this.start !== start || (this.end != null && this.end !== end)) {
|
|
375
|
+
throw new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
376
|
+
headers,
|
|
377
|
+
data: { count: this.retryCount }
|
|
378
|
+
})
|
|
379
|
+
}
|
|
239
380
|
|
|
240
381
|
return
|
|
241
382
|
}
|
|
242
383
|
|
|
384
|
+
if (statusCode >= 300) {
|
|
385
|
+
const err = new RequestRetryError('Request failed', statusCode, {
|
|
386
|
+
headers,
|
|
387
|
+
data: {
|
|
388
|
+
count: this.retryCount
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
this.onResponseStartWithRetry(controller, statusCode, headers, statusMessage, err)
|
|
393
|
+
return
|
|
394
|
+
}
|
|
395
|
+
|
|
243
396
|
if (this.end == null) {
|
|
244
397
|
if (statusCode === 206) {
|
|
245
398
|
// First time we receive 206
|
|
246
399
|
const range = parseRangeHeader(headers['content-range'])
|
|
247
400
|
|
|
248
|
-
if (range == null) {
|
|
401
|
+
if (range == null || range.end == null) {
|
|
249
402
|
this.headersSent = true
|
|
250
403
|
this.handler.onResponseStart?.(
|
|
251
|
-
|
|
404
|
+
this.controllerProxy,
|
|
252
405
|
statusCode,
|
|
253
406
|
headers,
|
|
254
407
|
statusMessage
|
|
@@ -256,6 +409,8 @@ class RetryHandler {
|
|
|
256
409
|
return
|
|
257
410
|
}
|
|
258
411
|
|
|
412
|
+
validatePartialResponseContentLength(headers, range, statusCode, this.retryCount)
|
|
413
|
+
|
|
259
414
|
const { start, size, end = size ? size - 1 : null } = range
|
|
260
415
|
assert(
|
|
261
416
|
start != null && Number.isFinite(start),
|
|
@@ -268,7 +423,7 @@ class RetryHandler {
|
|
|
268
423
|
}
|
|
269
424
|
|
|
270
425
|
// We make our best to checkpoint the body for further range headers
|
|
271
|
-
if (this.end == null) {
|
|
426
|
+
if (this.end == null && this.opts.method !== 'HEAD') {
|
|
272
427
|
const contentLength = headers['content-length']
|
|
273
428
|
this.end = contentLength != null ? Number(contentLength) - 1 : null
|
|
274
429
|
}
|
|
@@ -295,7 +450,7 @@ class RetryHandler {
|
|
|
295
450
|
|
|
296
451
|
this.headersSent = true
|
|
297
452
|
this.handler.onResponseStart?.(
|
|
298
|
-
|
|
453
|
+
this.controllerProxy,
|
|
299
454
|
statusCode,
|
|
300
455
|
headers,
|
|
301
456
|
statusMessage
|
|
@@ -308,17 +463,17 @@ class RetryHandler {
|
|
|
308
463
|
}
|
|
309
464
|
}
|
|
310
465
|
|
|
311
|
-
onResponseData (
|
|
466
|
+
onResponseData (_controller, chunk) {
|
|
312
467
|
if (this.error) {
|
|
313
468
|
return
|
|
314
469
|
}
|
|
315
470
|
|
|
316
471
|
this.start += chunk.length
|
|
317
472
|
|
|
318
|
-
this.handler.onResponseData?.(
|
|
473
|
+
this.handler.onResponseData?.(this.controllerProxy, chunk)
|
|
319
474
|
}
|
|
320
475
|
|
|
321
|
-
onResponseEnd (
|
|
476
|
+
onResponseEnd (_controller, trailers) {
|
|
322
477
|
if (this.error && this.retryOpts.throwOnError) {
|
|
323
478
|
throw this.error
|
|
324
479
|
}
|
|
@@ -335,13 +490,13 @@ class RetryHandler {
|
|
|
335
490
|
}
|
|
336
491
|
}
|
|
337
492
|
this.retryCount = 0
|
|
338
|
-
return this.handler.onResponseEnd?.(
|
|
493
|
+
return this.handler.onResponseEnd?.(this.controllerProxy, trailers)
|
|
339
494
|
}
|
|
340
495
|
|
|
341
|
-
this.retry(
|
|
496
|
+
this.retry()
|
|
342
497
|
}
|
|
343
498
|
|
|
344
|
-
retry (
|
|
499
|
+
retry () {
|
|
345
500
|
if (this.start !== 0) {
|
|
346
501
|
const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
|
|
347
502
|
|
|
@@ -363,23 +518,38 @@ class RetryHandler {
|
|
|
363
518
|
this.retryCountCheckpoint = this.retryCount
|
|
364
519
|
this.dispatch(this.opts, this)
|
|
365
520
|
} catch (err) {
|
|
366
|
-
this.handler.onResponseError?.(
|
|
521
|
+
this.handler.onResponseError?.(this.controllerProxy, err)
|
|
367
522
|
}
|
|
368
523
|
}
|
|
369
524
|
|
|
370
525
|
onResponseError (controller, err) {
|
|
371
|
-
if (
|
|
372
|
-
|
|
526
|
+
if (this.aborted) {
|
|
527
|
+
// #onAbort already delivered the terminal error downstream; the late
|
|
528
|
+
// error of the torn-down connection must not be forwarded twice.
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// controller is THIS failed connection (not the proxy): we inspect whether
|
|
533
|
+
// the consumer aborted it to decide retry-vs-propagate.
|
|
534
|
+
if (controller?.aborted || isDisturbed(this.opts.body) || (this.headersSent && !this.resume)) {
|
|
535
|
+
this.handler.onResponseError?.(this.controllerProxy, err)
|
|
373
536
|
return
|
|
374
537
|
}
|
|
375
538
|
|
|
376
539
|
function shouldRetry (returnedErr) {
|
|
540
|
+
if (this.aborted) {
|
|
541
|
+
// Aborted while the policy was deciding; the decision is moot.
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
this.retryPending = false
|
|
545
|
+
this.retryTimer = null
|
|
546
|
+
|
|
377
547
|
if (!returnedErr) {
|
|
378
|
-
this.retry(
|
|
548
|
+
this.retry()
|
|
379
549
|
return
|
|
380
550
|
}
|
|
381
551
|
|
|
382
|
-
this.handler?.onResponseError?.(
|
|
552
|
+
this.handler?.onResponseError?.(this.controllerProxy, returnedErr)
|
|
383
553
|
}
|
|
384
554
|
|
|
385
555
|
// We reconcile in case of a mix between network errors
|
|
@@ -393,14 +563,31 @@ class RetryHandler {
|
|
|
393
563
|
this.retryCount += 1
|
|
394
564
|
}
|
|
395
565
|
|
|
396
|
-
this.
|
|
566
|
+
this.retryPending = true
|
|
567
|
+
this.retryTimer = this.retryOpts.retry(
|
|
397
568
|
err,
|
|
398
569
|
{
|
|
399
570
|
state: { counter: this.retryCount },
|
|
400
571
|
opts: { retryOptions: this.retryOpts, ...this.opts }
|
|
401
572
|
},
|
|
402
573
|
shouldRetry.bind(this)
|
|
403
|
-
)
|
|
574
|
+
) ?? null
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
#onAbort (reason) {
|
|
578
|
+
// A consumer abort lands on the controller proxy. If the retry policy is
|
|
579
|
+
// still deciding (typically holding a backoff timer), cancel the wait and
|
|
580
|
+
// surface the abort immediately instead of letting the request hang until
|
|
581
|
+
// the backoff elapses.
|
|
582
|
+
if (!this.retryPending) {
|
|
583
|
+
return
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
this.aborted = true
|
|
587
|
+
this.retryPending = false
|
|
588
|
+
clearTimeout(this.retryTimer)
|
|
589
|
+
this.retryTimer = null
|
|
590
|
+
this.handler.onResponseError?.(this.controllerProxy, reason ?? new RequestAbortedError())
|
|
404
591
|
}
|
|
405
592
|
}
|
|
406
593
|
|