@openclaw/discord 2026.6.11 → 2026.6.34
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-4GFMoWu0.js → api-CjnGBhM3.js} +4 -1
- package/dist/api.js +14 -14
- package/dist/{approval-handler.runtime-SsFtZQA6.js → approval-handler.runtime-BWPQrzpP.js} +9 -4
- package/dist/{audit-CAnmyf2A.js → audit-BwJ_2OjL.js} +3 -3
- package/dist/{channel-YA7AbYP3.js → channel-DU4jnQgl.js} +15 -15
- package/dist/{channel-actions-BJ3n5Z1l.js → channel-actions-CwmIqnYN.js} +2 -2
- package/dist/{channel-actions.runtime-D2QTRgZ9.js → channel-actions.runtime-DvpTfaJ4.js} +5 -5
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-B1zDEAR7.js → channel.setup-glZ5Y98a.js} +2 -2
- package/dist/{components-Dm4c_ce6.js → components-j6lTZBsT.js} +1 -1
- package/dist/{conversation-identity-DZ0BD7xO.js → conversation-identity-36FFBwm5.js} +2 -2
- package/dist/{directory-config-BK1OhHa3.js → directory-config-BYMFzL29.js} +1 -1
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-DP3CmwGf.js → directory-live-9JtJCiu8.js} +2 -2
- package/dist/{handle-action.guild-admin-B2cOoU2D.js → handle-action.guild-admin-xxGYrp-I.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{manager.runtime-CPHxr9B3.js → manager.runtime-NxCd-PVt.js} +3 -3
- package/dist/{message-handler-CKaTgBIV.js → message-handler-cizxbaDc.js} +7 -6
- package/dist/{message-handler.preflight--eAiaewz.js → message-handler.preflight-CbD0u7U7.js} +10 -10
- package/dist/{message-handler.process-PhKAmrEd.js → message-handler.process-Do4EM591.js} +10 -10
- package/dist/{message-utils-O1vK3kPu.js → message-utils-CJeAoISH.js} +1 -1
- package/dist/{outbound-adapter-fO_7hbTh.js → outbound-adapter-Dxs6iKEq.js} +62 -131
- package/dist/{pluralkit-B3MzPaPU.js → pluralkit-DI6RYaVj.js} +1 -1
- package/dist/presence-cache-z1N8MbJ3.js +45 -0
- package/dist/{probe-CxnDikWm.js → probe-esE_Oqdm.js} +1 -1
- package/dist/{probe.runtime-DLFkSDVB.js → probe.runtime-C3Atgpvr.js} +1 -1
- package/dist/{provider-BJaJ20MY.js → provider-B-s93q9s.js} +76 -26
- package/dist/{provider-session.runtime-D6xc-60s.js → provider-session.runtime-BKMOqHpd.js} +3 -3
- package/dist/provider.runtime-CaTJrpnl.js +2 -0
- package/dist/{resolve-allowlist-common-BDpBb8dF.js → resolve-allowlist-common-3gB2Gpmm.js} +1 -1
- package/dist/{resolve-channels-iJIjYfRw.js → resolve-channels-B3gQf8QL.js} +3 -3
- package/dist/{resolve-users-_fv1mm1f.js → resolve-users-BO10y5fJ.js} +3 -3
- package/dist/{runtime-ywXByivr.js → runtime-CFlR3L9X.js} +8 -8
- package/dist/runtime-api.actions.js +2 -2
- package/dist/runtime-api.js +20 -20
- package/dist/runtime-api.lookup.js +5 -5
- package/dist/runtime-api.monitor-CRiBIQEI.js +6 -0
- package/dist/runtime-api.monitor.js +6 -5
- package/dist/runtime-api.send.js +5 -5
- package/dist/runtime-api.threads.js +3 -3
- package/dist/{send-Dc5uQX5H.js → send-JHxfT4Pk.js} +6 -4
- package/dist/{send.components-D4bl20xr.js → send.components-CN35E2Ck.js} +11 -6
- package/dist/{send.outbound-5axvwLkl.js → send.outbound-B-cA1Esv.js} +15 -8
- package/dist/{send.receipt-B21MspvT.js → send.receipt-CGecJDin.js} +10 -1
- package/dist/{send.shared-9082A2sO.js → send.shared-vcbfThdy.js} +107 -16
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{subagent-hooks-NTv6B4up.js → subagent-hooks-20wXhVzZ.js} +2 -2
- package/dist/subagent-hooks-api.js +1 -1
- package/dist/{system-events-BzaI1WpN.js → system-events-DR27TE7p.js} +1 -1
- package/dist/{target-resolver-agnOrtOU.js → target-resolver-CDi1gI90.js} +2 -2
- package/dist/targets-C6yd3y8R.js +3 -0
- package/dist/{thread-bindings-DNitaHVh.js → thread-bindings-DU4T6nDo.js} +4 -4
- package/dist/{thread-bindings.discord-api-CjYNuvZa.js → thread-bindings.discord-api-CYkr-3Gj.js} +4 -4
- package/dist/{thread-bindings.manager-PZvJ4TJx.js → thread-bindings.manager-BqWs1UE2.js} +3 -3
- package/dist/{transcripts-source-Bi8N6h5X.js → transcripts-source-D-lBI5ZD.js} +1 -1
- package/dist/transcripts-source-api.js +1 -1
- package/dist/{typing-BdRqXUN3.js → typing-xr05bL-r.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 +369 -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 +101 -87
- package/node_modules/undici/docs/docs/api/Errors.md +503 -40
- package/node_modules/undici/docs/docs/api/EventSource.md +294 -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 +414 -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 +162 -187
- 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 +47 -2
- package/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
- package/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
- 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/util.js +30 -2
- package/node_modules/undici/lib/dispatcher/agent.js +3 -3
- package/node_modules/undici/lib/dispatcher/client-h1.js +65 -8
- package/node_modules/undici/lib/dispatcher/client-h2.js +265 -142
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +40 -6
- package/node_modules/undici/lib/global.js +36 -16
- package/node_modules/undici/lib/handler/cache-handler.js +269 -61
- package/node_modules/undici/lib/handler/cache-revalidation-handler.js +13 -3
- package/node_modules/undici/lib/handler/redirect-handler.js +6 -2
- package/node_modules/undici/lib/handler/retry-handler.js +87 -23
- package/node_modules/undici/lib/interceptor/cache.js +159 -52
- package/node_modules/undici/lib/interceptor/decompress.js +9 -2
- package/node_modules/undici/lib/mock/mock-interceptor.js +25 -7
- package/node_modules/undici/lib/mock/mock-utils.js +39 -10
- package/node_modules/undici/lib/util/cache.js +372 -68
- package/node_modules/undici/lib/util/date.js +23 -6
- package/node_modules/undici/lib/web/cookies/parse.js +3 -2
- package/node_modules/undici/lib/web/cookies/util.js +79 -9
- 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 +11 -2
- package/node_modules/undici/lib/web/fetch/response.js +1 -3
- package/node_modules/undici/lib/web/fetch/util.js +4 -1
- package/node_modules/undici/package.json +2 -2
- package/node_modules/undici/types/client.d.ts +3 -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/mock-interceptor.d.ts +5 -1
- package/node_modules/undici/types/proxy-agent.d.ts +7 -0
- package/npm-shrinkwrap.json +7 -7
- package/package.json +5 -5
- package/dist/gateway-registry-DPxmW0Db.js +0 -74
- package/dist/provider.runtime-jNlHxUSW.js +0 -2
- package/dist/runtime-api.monitor-Bb5AaRzg.js +0 -6
- package/dist/targets-ClUPljCU.js +0 -3
|
@@ -11,7 +11,49 @@ 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
|
+
class RetryController {
|
|
45
|
+
constructor () {
|
|
46
|
+
this.target = null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pause () { this.target?.pause() }
|
|
50
|
+
resume () { this.target?.resume() }
|
|
51
|
+
abort (reason) { this.target?.abort(reason) }
|
|
52
|
+
get paused () { return this.target?.paused ?? false }
|
|
53
|
+
get aborted () { return this.target?.aborted ?? false }
|
|
54
|
+
get reason () { return this.target?.reason ?? null }
|
|
55
|
+
get rawHeaders () { return this.target?.rawHeaders ?? null }
|
|
56
|
+
get rawTrailers () { return this.target?.rawTrailers ?? null }
|
|
15
57
|
}
|
|
16
58
|
|
|
17
59
|
class RetryHandler {
|
|
@@ -45,7 +87,7 @@ class RetryHandler {
|
|
|
45
87
|
timeoutFactor: timeoutFactor ?? 2,
|
|
46
88
|
maxRetries: maxRetries ?? 5,
|
|
47
89
|
// What errors we should retry
|
|
48
|
-
methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'],
|
|
90
|
+
methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE', 'QUERY'],
|
|
49
91
|
// Indicates which errors to retry
|
|
50
92
|
statusCodes: statusCodes ?? [500, 502, 503, 504, 429],
|
|
51
93
|
// List of errors to retry
|
|
@@ -70,6 +112,7 @@ class RetryHandler {
|
|
|
70
112
|
this.etag = null
|
|
71
113
|
this.statusCode = null
|
|
72
114
|
this.headers = null
|
|
115
|
+
this.controllerProxy = new RetryController()
|
|
73
116
|
}
|
|
74
117
|
|
|
75
118
|
onResponseStartWithRetry (controller, statusCode, headers, statusMessage, err) {
|
|
@@ -77,7 +120,7 @@ class RetryHandler {
|
|
|
77
120
|
// Preserve old behavior for status codes that are not eligible for retry
|
|
78
121
|
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
79
122
|
this.headersSent = true
|
|
80
|
-
this.handler.onResponseStart?.(
|
|
123
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
81
124
|
} else {
|
|
82
125
|
this.error = err
|
|
83
126
|
}
|
|
@@ -87,14 +130,14 @@ class RetryHandler {
|
|
|
87
130
|
|
|
88
131
|
if (isDisturbed(this.opts.body)) {
|
|
89
132
|
this.headersSent = true
|
|
90
|
-
this.handler.onResponseStart?.(
|
|
133
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
91
134
|
return
|
|
92
135
|
}
|
|
93
136
|
|
|
94
137
|
function shouldRetry (passedErr) {
|
|
95
138
|
if (passedErr) {
|
|
96
139
|
this.headersSent = true
|
|
97
|
-
this.handler.onResponseStart?.(
|
|
140
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
98
141
|
controller.resume()
|
|
99
142
|
return
|
|
100
143
|
}
|
|
@@ -103,6 +146,13 @@ class RetryHandler {
|
|
|
103
146
|
controller.resume()
|
|
104
147
|
}
|
|
105
148
|
|
|
149
|
+
// The pause()/resume() pair (here and in shouldRetry) acts on THIS
|
|
150
|
+
// connection's controller -- never the downstream proxy. We hold this exact
|
|
151
|
+
// connection while the retry policy decides (possibly after a timeout) and
|
|
152
|
+
// must resume the same one. Routing this through controllerProxy would risk
|
|
153
|
+
// resuming a different connection if a later dispatch re-points the proxy in
|
|
154
|
+
// between, leaving this one paused forever -- the very stall the proxy exists
|
|
155
|
+
// to prevent.
|
|
106
156
|
controller.pause()
|
|
107
157
|
this.retryOpts.retry(
|
|
108
158
|
err,
|
|
@@ -115,13 +165,19 @@ class RetryHandler {
|
|
|
115
165
|
}
|
|
116
166
|
|
|
117
167
|
onRequestStart (controller, context) {
|
|
168
|
+
// request.js creates a fresh RequestController per dispatch and passes that
|
|
169
|
+
// same instance to every later callback of the dispatch. onRequestStart is
|
|
170
|
+
// the first callback (it is where the controller is created), so re-pointing
|
|
171
|
+
// the proxy here is enough to keep it on the active connection across every
|
|
172
|
+
// transparent retry/resume.
|
|
173
|
+
this.controllerProxy.target = controller
|
|
118
174
|
if (!this.headersSent) {
|
|
119
|
-
this.handler.onRequestStart?.(
|
|
175
|
+
this.handler.onRequestStart?.(this.controllerProxy, context)
|
|
120
176
|
}
|
|
121
177
|
}
|
|
122
178
|
|
|
123
|
-
onRequestUpgrade (
|
|
124
|
-
this.handler.onRequestUpgrade?.(
|
|
179
|
+
onRequestUpgrade (_controller, statusCode, headers, socket) {
|
|
180
|
+
this.handler.onRequestUpgrade?.(this.controllerProxy, statusCode, headers, socket)
|
|
125
181
|
}
|
|
126
182
|
|
|
127
183
|
static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
|
|
@@ -175,9 +231,11 @@ class RetryHandler {
|
|
|
175
231
|
}
|
|
176
232
|
|
|
177
233
|
const retryTimeout =
|
|
178
|
-
retryAfterHeader
|
|
179
|
-
?
|
|
180
|
-
:
|
|
234
|
+
retryAfterHeader === 0
|
|
235
|
+
? 0
|
|
236
|
+
: retryAfterHeader > 0
|
|
237
|
+
? Math.min(retryAfterHeader, maxTimeout)
|
|
238
|
+
: Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
|
|
181
239
|
|
|
182
240
|
setTimeout(() => cb(null), retryTimeout)
|
|
183
241
|
}
|
|
@@ -232,6 +290,8 @@ class RetryHandler {
|
|
|
232
290
|
})
|
|
233
291
|
}
|
|
234
292
|
|
|
293
|
+
validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount)
|
|
294
|
+
|
|
235
295
|
const { start, size, end = size ? size - 1 : null } = contentRange
|
|
236
296
|
|
|
237
297
|
assert(this.start === start, 'content-range mismatch')
|
|
@@ -248,7 +308,7 @@ class RetryHandler {
|
|
|
248
308
|
if (range == null) {
|
|
249
309
|
this.headersSent = true
|
|
250
310
|
this.handler.onResponseStart?.(
|
|
251
|
-
|
|
311
|
+
this.controllerProxy,
|
|
252
312
|
statusCode,
|
|
253
313
|
headers,
|
|
254
314
|
statusMessage
|
|
@@ -256,6 +316,8 @@ class RetryHandler {
|
|
|
256
316
|
return
|
|
257
317
|
}
|
|
258
318
|
|
|
319
|
+
validatePartialResponseContentLength(headers, range, statusCode, this.retryCount)
|
|
320
|
+
|
|
259
321
|
const { start, size, end = size ? size - 1 : null } = range
|
|
260
322
|
assert(
|
|
261
323
|
start != null && Number.isFinite(start),
|
|
@@ -295,7 +357,7 @@ class RetryHandler {
|
|
|
295
357
|
|
|
296
358
|
this.headersSent = true
|
|
297
359
|
this.handler.onResponseStart?.(
|
|
298
|
-
|
|
360
|
+
this.controllerProxy,
|
|
299
361
|
statusCode,
|
|
300
362
|
headers,
|
|
301
363
|
statusMessage
|
|
@@ -308,17 +370,17 @@ class RetryHandler {
|
|
|
308
370
|
}
|
|
309
371
|
}
|
|
310
372
|
|
|
311
|
-
onResponseData (
|
|
373
|
+
onResponseData (_controller, chunk) {
|
|
312
374
|
if (this.error) {
|
|
313
375
|
return
|
|
314
376
|
}
|
|
315
377
|
|
|
316
378
|
this.start += chunk.length
|
|
317
379
|
|
|
318
|
-
this.handler.onResponseData?.(
|
|
380
|
+
this.handler.onResponseData?.(this.controllerProxy, chunk)
|
|
319
381
|
}
|
|
320
382
|
|
|
321
|
-
onResponseEnd (
|
|
383
|
+
onResponseEnd (_controller, trailers) {
|
|
322
384
|
if (this.error && this.retryOpts.throwOnError) {
|
|
323
385
|
throw this.error
|
|
324
386
|
}
|
|
@@ -335,13 +397,13 @@ class RetryHandler {
|
|
|
335
397
|
}
|
|
336
398
|
}
|
|
337
399
|
this.retryCount = 0
|
|
338
|
-
return this.handler.onResponseEnd?.(
|
|
400
|
+
return this.handler.onResponseEnd?.(this.controllerProxy, trailers)
|
|
339
401
|
}
|
|
340
402
|
|
|
341
|
-
this.retry(
|
|
403
|
+
this.retry()
|
|
342
404
|
}
|
|
343
405
|
|
|
344
|
-
retry (
|
|
406
|
+
retry () {
|
|
345
407
|
if (this.start !== 0) {
|
|
346
408
|
const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
|
|
347
409
|
|
|
@@ -363,23 +425,25 @@ class RetryHandler {
|
|
|
363
425
|
this.retryCountCheckpoint = this.retryCount
|
|
364
426
|
this.dispatch(this.opts, this)
|
|
365
427
|
} catch (err) {
|
|
366
|
-
this.handler.onResponseError?.(
|
|
428
|
+
this.handler.onResponseError?.(this.controllerProxy, err)
|
|
367
429
|
}
|
|
368
430
|
}
|
|
369
431
|
|
|
370
432
|
onResponseError (controller, err) {
|
|
433
|
+
// controller is THIS failed connection (not the proxy): we inspect whether
|
|
434
|
+
// the consumer aborted it to decide retry-vs-propagate.
|
|
371
435
|
if (controller?.aborted || isDisturbed(this.opts.body)) {
|
|
372
|
-
this.handler.onResponseError?.(
|
|
436
|
+
this.handler.onResponseError?.(this.controllerProxy, err)
|
|
373
437
|
return
|
|
374
438
|
}
|
|
375
439
|
|
|
376
440
|
function shouldRetry (returnedErr) {
|
|
377
441
|
if (!returnedErr) {
|
|
378
|
-
this.retry(
|
|
442
|
+
this.retry()
|
|
379
443
|
return
|
|
380
444
|
}
|
|
381
445
|
|
|
382
|
-
this.handler?.onResponseError?.(
|
|
446
|
+
this.handler?.onResponseError?.(this.controllerProxy, returnedErr)
|
|
383
447
|
}
|
|
384
448
|
|
|
385
449
|
// We reconcile in case of a mix between network errors
|
|
@@ -6,8 +6,9 @@ const util = require('../core/util')
|
|
|
6
6
|
const CacheHandler = require('../handler/cache-handler')
|
|
7
7
|
const MemoryCacheStore = require('../cache/memory-cache-store')
|
|
8
8
|
const CacheRevalidationHandler = require('../handler/cache-revalidation-handler')
|
|
9
|
-
const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader } = require('../util/cache.js')
|
|
9
|
+
const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader, isInvalidOrWildcardVaryHeader } = require('../util/cache.js')
|
|
10
10
|
const { AbortError } = require('../core/errors.js')
|
|
11
|
+
const { parseHttpDate } = require('../util/date.js')
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @param {(string | RegExp)[] | undefined} origins
|
|
@@ -28,6 +29,44 @@ function assertCacheOrigins (origins, name) {
|
|
|
28
29
|
|
|
29
30
|
const nop = () => {}
|
|
30
31
|
|
|
32
|
+
function trimOWS (value) {
|
|
33
|
+
return value.replace(/^[\t ]+|[\t ]+$/g, '')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function arrayIncludes (array, value) {
|
|
37
|
+
for (let i = 0; i < array.length; i++) {
|
|
38
|
+
if (array[i] === value) {
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hasPragmaNoCache (headers) {
|
|
47
|
+
const pragma = headers?.pragma
|
|
48
|
+
if (!pragma) {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const values = Array.isArray(pragma) ? pragma : [pragma]
|
|
53
|
+
for (let i = 0; i < values.length; i++) {
|
|
54
|
+
const value = values[i]
|
|
55
|
+
if (typeof value !== 'string') {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const directives = value.split(',')
|
|
60
|
+
for (let j = 0; j < directives.length; j++) {
|
|
61
|
+
if (trimOWS(directives[j]).toLowerCase() === 'no-cache') {
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
31
70
|
/**
|
|
32
71
|
* @typedef {(options: import('../../types/dispatcher.d.ts').default.DispatchOptions, handler: import('../../types/dispatcher.d.ts').default.DispatchHandler) => void} DispatchFn
|
|
33
72
|
*/
|
|
@@ -57,16 +96,92 @@ function needsRevalidation (result, cacheControlDirectives, { headers = {} }) {
|
|
|
57
96
|
return false
|
|
58
97
|
}
|
|
59
98
|
|
|
99
|
+
/**
|
|
100
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
|
|
101
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
102
|
+
* @returns {boolean}
|
|
103
|
+
*/
|
|
104
|
+
function staleResponseRequiresRevalidation (result, cacheType) {
|
|
105
|
+
return result.cacheControlDirectives?.['must-revalidate'] === true ||
|
|
106
|
+
(cacheType === 'shared' && (
|
|
107
|
+
result.cacheControlDirectives?.['proxy-revalidate'] === true ||
|
|
108
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10
|
|
109
|
+
// s-maxage implies proxy-revalidate for shared caches.
|
|
110
|
+
result.cacheControlDirectives?.['s-maxage'] !== undefined
|
|
111
|
+
))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
116
|
+
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} headers
|
|
117
|
+
* @returns {boolean}
|
|
118
|
+
*/
|
|
119
|
+
function revalidationResponseDisallowsCachedReuse (cacheType, headers) {
|
|
120
|
+
if (headers.vary && isInvalidOrWildcardVaryHeader(headers.vary)) {
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const cacheControl = headers['cache-control']
|
|
125
|
+
if (!cacheControl) {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const cacheControlDirectives = parseCacheControlHeader(cacheControl)
|
|
130
|
+
return cacheControlDirectives['no-store'] === true ||
|
|
131
|
+
(cacheType === 'shared' && cacheControlDirectives.private === true)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function revalidationResponseUpdatesCacheControl (headers) {
|
|
135
|
+
return headers['cache-control'] !== undefined
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function deleteCachedValue (store, cacheKey) {
|
|
139
|
+
try {
|
|
140
|
+
store.delete(cacheKey)?.catch?.(nop)
|
|
141
|
+
} catch {
|
|
142
|
+
// Fail silently
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getUsableLastModified (headers) {
|
|
147
|
+
const lastModified = headers?.['last-modified']
|
|
148
|
+
if (typeof lastModified === 'string' && parseHttpDate(lastModified)) {
|
|
149
|
+
return lastModified
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function makeRevalidationHeaders (opts, result) {
|
|
154
|
+
const headers = {
|
|
155
|
+
...opts.headers,
|
|
156
|
+
'if-modified-since': getUsableLastModified(result.headers) ?? new Date(result.cachedAt).toUTCString()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (result.etag) {
|
|
160
|
+
headers['if-none-match'] = result.etag
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (result.vary) {
|
|
164
|
+
for (const key in result.vary) {
|
|
165
|
+
if (result.vary[key] != null) {
|
|
166
|
+
headers[key] = result.vary[key]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return headers
|
|
172
|
+
}
|
|
173
|
+
|
|
60
174
|
/**
|
|
61
175
|
* @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
|
|
62
176
|
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} cacheControlDirectives
|
|
177
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
63
178
|
* @returns {boolean}
|
|
64
179
|
*/
|
|
65
|
-
function isStale (result, cacheControlDirectives) {
|
|
180
|
+
function isStale (result, cacheControlDirectives, cacheType) {
|
|
66
181
|
const now = Date.now()
|
|
67
182
|
if (now > result.staleAt) {
|
|
68
183
|
// Response is stale
|
|
69
|
-
if (cacheControlDirectives?.['max-stale']) {
|
|
184
|
+
if (!staleResponseRequiresRevalidation(result, cacheType) && cacheControlDirectives?.['max-stale']) {
|
|
70
185
|
// There's a threshold where we can serve stale responses, let's see if
|
|
71
186
|
// we're in it
|
|
72
187
|
// https://www.rfc-editor.org/rfc/rfc9111.html#name-max-stale
|
|
@@ -93,11 +208,12 @@ function isStale (result, cacheControlDirectives) {
|
|
|
93
208
|
/**
|
|
94
209
|
* Check if we're within the stale-while-revalidate window for a stale response
|
|
95
210
|
* @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
|
|
211
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
96
212
|
* @returns {boolean}
|
|
97
213
|
*/
|
|
98
|
-
function withinStaleWhileRevalidateWindow (result) {
|
|
214
|
+
function withinStaleWhileRevalidateWindow (result, cacheType) {
|
|
99
215
|
const staleWhileRevalidate = result.cacheControlDirectives?.['stale-while-revalidate']
|
|
100
|
-
if (!staleWhileRevalidate) {
|
|
216
|
+
if (!staleWhileRevalidate || staleResponseRequiresRevalidation(result, cacheType)) {
|
|
101
217
|
return false
|
|
102
218
|
}
|
|
103
219
|
|
|
@@ -280,14 +396,10 @@ function handleResult (
|
|
|
280
396
|
}
|
|
281
397
|
|
|
282
398
|
const age = Math.round((now - result.cachedAt) / 1000)
|
|
283
|
-
|
|
284
|
-
// Response is considered expired for this specific request
|
|
285
|
-
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1
|
|
286
|
-
return dispatch(opts, handler)
|
|
287
|
-
}
|
|
399
|
+
const requestMaxAgeExpired = reqCacheControl?.['max-age'] !== undefined && age >= reqCacheControl['max-age']
|
|
288
400
|
|
|
289
|
-
const stale = isStale(result, reqCacheControl)
|
|
290
|
-
const revalidate = needsRevalidation(result, reqCacheControl, opts)
|
|
401
|
+
const stale = requestMaxAgeExpired || isStale(result, reqCacheControl, globalOpts.type)
|
|
402
|
+
const revalidate = requestMaxAgeExpired || needsRevalidation(result, reqCacheControl, opts)
|
|
291
403
|
|
|
292
404
|
// Check if the response is stale
|
|
293
405
|
if (stale || revalidate) {
|
|
@@ -299,28 +411,13 @@ function handleResult (
|
|
|
299
411
|
|
|
300
412
|
// RFC 5861: If we're within stale-while-revalidate window, serve stale immediately
|
|
301
413
|
// and revalidate in background, unless immediate revalidation is necessary
|
|
302
|
-
if (!revalidate && withinStaleWhileRevalidateWindow(result)) {
|
|
414
|
+
if (!revalidate && withinStaleWhileRevalidateWindow(result, globalOpts.type)) {
|
|
303
415
|
// Serve stale response immediately
|
|
304
416
|
sendCachedValue(handler, opts, result, age, null, true)
|
|
305
417
|
|
|
306
418
|
// Start background revalidation (fire-and-forget)
|
|
307
419
|
queueMicrotask(() => {
|
|
308
|
-
const headers =
|
|
309
|
-
...opts.headers,
|
|
310
|
-
'if-modified-since': new Date(result.cachedAt).toUTCString()
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (result.etag) {
|
|
314
|
-
headers['if-none-match'] = result.etag
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (result.vary) {
|
|
318
|
-
for (const key in result.vary) {
|
|
319
|
-
if (result.vary[key] != null) {
|
|
320
|
-
headers[key] = result.vary[key]
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
420
|
+
const headers = makeRevalidationHeaders(opts, result)
|
|
324
421
|
|
|
325
422
|
// Background revalidation - update cache if we get new data
|
|
326
423
|
dispatch(
|
|
@@ -344,28 +441,15 @@ function handleResult (
|
|
|
344
441
|
}
|
|
345
442
|
|
|
346
443
|
let withinStaleIfErrorThreshold = false
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const headers = {
|
|
353
|
-
...opts.headers,
|
|
354
|
-
'if-modified-since': new Date(result.cachedAt).toUTCString()
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (result.etag) {
|
|
358
|
-
headers['if-none-match'] = result.etag
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (result.vary) {
|
|
362
|
-
for (const key in result.vary) {
|
|
363
|
-
if (result.vary[key] != null) {
|
|
364
|
-
headers[key] = result.vary[key]
|
|
365
|
-
}
|
|
444
|
+
if (!staleResponseRequiresRevalidation(result, globalOpts.type)) {
|
|
445
|
+
const staleIfErrorExpiry = result.cacheControlDirectives['stale-if-error'] ?? reqCacheControl?.['stale-if-error']
|
|
446
|
+
if (staleIfErrorExpiry) {
|
|
447
|
+
withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000))
|
|
366
448
|
}
|
|
367
449
|
}
|
|
368
450
|
|
|
451
|
+
const headers = makeRevalidationHeaders(opts, result)
|
|
452
|
+
|
|
369
453
|
// We need to revalidate the response
|
|
370
454
|
return dispatch(
|
|
371
455
|
{
|
|
@@ -373,8 +457,23 @@ function handleResult (
|
|
|
373
457
|
headers
|
|
374
458
|
},
|
|
375
459
|
new CacheRevalidationHandler(
|
|
376
|
-
(success, context) => {
|
|
460
|
+
(success, context, statusCode, headers) => {
|
|
377
461
|
if (success) {
|
|
462
|
+
if (statusCode === 304) {
|
|
463
|
+
if (revalidationResponseDisallowsCachedReuse(globalOpts.type, headers)) {
|
|
464
|
+
if (util.isStream(result.body)) {
|
|
465
|
+
result.body.on('error', nop).destroy()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
deleteCachedValue(globalOpts.store, cacheKey)
|
|
469
|
+
return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (revalidationResponseUpdatesCacheControl(headers)) {
|
|
473
|
+
deleteCachedValue(globalOpts.store, cacheKey)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
378
477
|
// TODO: successful revalidation should be considered fresh (not give stale warning).
|
|
379
478
|
sendCachedValue(handler, opts, result, age, context, stale)
|
|
380
479
|
} else if (util.isStream(result.body)) {
|
|
@@ -431,11 +530,17 @@ module.exports = (opts = {}) => {
|
|
|
431
530
|
type
|
|
432
531
|
}
|
|
433
532
|
|
|
434
|
-
const safeMethodsToNotCache =
|
|
533
|
+
const safeMethodsToNotCache = []
|
|
534
|
+
for (let i = 0; i < util.safeHTTPMethods.length; i++) {
|
|
535
|
+
const method = util.safeHTTPMethods[i]
|
|
536
|
+
if (!arrayIncludes(methods, method)) {
|
|
537
|
+
safeMethodsToNotCache.push(method)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
435
540
|
|
|
436
541
|
return dispatch => {
|
|
437
542
|
return (opts, handler) => {
|
|
438
|
-
if (!opts.origin || safeMethodsToNotCache
|
|
543
|
+
if (!opts.origin || arrayIncludes(safeMethodsToNotCache, opts.method)) {
|
|
439
544
|
// Not a method we want to cache or we don't have the origin, skip
|
|
440
545
|
return dispatch(opts, handler)
|
|
441
546
|
}
|
|
@@ -470,7 +575,9 @@ module.exports = (opts = {}) => {
|
|
|
470
575
|
|
|
471
576
|
const reqCacheControl = opts.headers?.['cache-control']
|
|
472
577
|
? parseCacheControlHeader(opts.headers['cache-control'])
|
|
473
|
-
:
|
|
578
|
+
: hasPragmaNoCache(opts.headers)
|
|
579
|
+
? { 'no-cache': true }
|
|
580
|
+
: undefined
|
|
474
581
|
|
|
475
582
|
if (reqCacheControl?.['no-store']) {
|
|
476
583
|
return dispatch(opts, handler)
|
|
@@ -32,6 +32,8 @@ let warningEmitted = /** @type {boolean} */ (false)
|
|
|
32
32
|
class DecompressHandler extends DecoratorHandler {
|
|
33
33
|
/** @type {Transform[]} */
|
|
34
34
|
#decompressors = []
|
|
35
|
+
/** @type {Record<string, string | string[]> | undefined} */
|
|
36
|
+
#trailers
|
|
35
37
|
/** @type {Readonly<number[]>} */
|
|
36
38
|
#skipStatusCodes
|
|
37
39
|
/** @type {boolean} */
|
|
@@ -123,7 +125,7 @@ class DecompressHandler extends DecoratorHandler {
|
|
|
123
125
|
this.#setupDecompressorEvents(decompressor, controller)
|
|
124
126
|
|
|
125
127
|
decompressor.on('end', () => {
|
|
126
|
-
super.onResponseEnd(controller,
|
|
128
|
+
super.onResponseEnd(controller, this.#trailers)
|
|
127
129
|
})
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -141,7 +143,7 @@ class DecompressHandler extends DecoratorHandler {
|
|
|
141
143
|
super.onResponseError(controller, err)
|
|
142
144
|
return
|
|
143
145
|
}
|
|
144
|
-
super.onResponseEnd(controller,
|
|
146
|
+
super.onResponseEnd(controller, this.#trailers)
|
|
145
147
|
})
|
|
146
148
|
}
|
|
147
149
|
|
|
@@ -236,6 +238,7 @@ class DecompressHandler extends DecoratorHandler {
|
|
|
236
238
|
*/
|
|
237
239
|
onResponseEnd (controller, trailers) {
|
|
238
240
|
if (this.#decompressors.length > 0) {
|
|
241
|
+
this.#trailers = trailers
|
|
239
242
|
this.#decompressors[0].end()
|
|
240
243
|
this.#cleanupDecompressors()
|
|
241
244
|
return
|
|
@@ -276,6 +279,10 @@ function createDecompressInterceptor (options = {}) {
|
|
|
276
279
|
|
|
277
280
|
return (dispatch) => {
|
|
278
281
|
return (opts, handler) => {
|
|
282
|
+
if (opts.method === 'HEAD') {
|
|
283
|
+
return dispatch(opts, handler)
|
|
284
|
+
}
|
|
285
|
+
|
|
279
286
|
const decompressHandler = new DecompressHandler(handler, options)
|
|
280
287
|
return dispatch(opts, decompressHandler)
|
|
281
288
|
}
|
|
@@ -12,6 +12,11 @@ const {
|
|
|
12
12
|
} = require('./mock-symbols')
|
|
13
13
|
const { InvalidArgumentError } = require('../core/errors')
|
|
14
14
|
const { serializePathWithQuery } = require('../core/util')
|
|
15
|
+
const {
|
|
16
|
+
types: {
|
|
17
|
+
isPromise
|
|
18
|
+
}
|
|
19
|
+
} = require('node:util')
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
22
|
* Defines the scope API for an interceptor reply
|
|
@@ -117,13 +122,9 @@ class MockInterceptor {
|
|
|
117
122
|
// Values of reply aren't available right now as they
|
|
118
123
|
// can only be available when the reply callback is invoked.
|
|
119
124
|
if (typeof replyOptionsCallbackOrStatusCode === 'function') {
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
const wrappedDefaultsCallback = (opts) => {
|
|
124
|
-
// Our reply options callback contains the parameter for statusCode, data and options.
|
|
125
|
-
const resolvedData = replyOptionsCallbackOrStatusCode(opts)
|
|
126
|
-
|
|
125
|
+
// Resolves the data returned by a reply options callback into
|
|
126
|
+
// dispatch data, validating its format along the way.
|
|
127
|
+
const resolveReplyCallbackData = (resolvedData) => {
|
|
127
128
|
// Check if it is in the right format
|
|
128
129
|
if (typeof resolvedData !== 'object' || resolvedData === null) {
|
|
129
130
|
throw new InvalidArgumentError('reply options callback must return an object')
|
|
@@ -138,6 +139,23 @@ class MockInterceptor {
|
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
// We'll first wrap the provided callback in another function,
|
|
143
|
+
// this function will properly resolve the data from the callback
|
|
144
|
+
// when invoked.
|
|
145
|
+
const wrappedDefaultsCallback = (opts) => {
|
|
146
|
+
// Our reply options callback contains the parameter for statusCode, data and options.
|
|
147
|
+
const resolvedData = replyOptionsCallbackOrStatusCode(opts)
|
|
148
|
+
|
|
149
|
+
// An asynchronous reply options callback resolves to the reply
|
|
150
|
+
// parameters, so the dispatch data can only be resolved once the
|
|
151
|
+
// returned promise settles.
|
|
152
|
+
if (isPromise(resolvedData)) {
|
|
153
|
+
return resolvedData.then(resolveReplyCallbackData)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return resolveReplyCallbackData(resolvedData)
|
|
157
|
+
}
|
|
158
|
+
|
|
141
159
|
// Add usual dispatch data, but this time set the data parameter to function that will eventually provide data.
|
|
142
160
|
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback, { ignoreTrailingSlash: this[kIgnoreTrailingSlash] })
|
|
143
161
|
return new MockScope(newMockDispatch)
|