@openclaw/discord 2026.6.33 → 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/api.js +1 -1
- package/dist/{channel-Ddnonoy4.js → channel-DU4jnQgl.js} +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{manager.runtime-BK6cf2Zu.js → manager.runtime-NxCd-PVt.js} +1 -1
- package/dist/{message-handler-BqpJIavO.js → message-handler-cizxbaDc.js} +3 -2
- package/dist/{message-handler.preflight-C90MxCcv.js → message-handler.preflight-CbD0u7U7.js} +2 -2
- package/dist/{message-handler.process-CeQ7Bd_3.js → message-handler.process-Do4EM591.js} +2 -2
- package/dist/{provider-C7376XFH.js → provider-B-s93q9s.js} +31 -6
- package/dist/{provider-session.runtime-CCdqogGf.js → provider-session.runtime-BKMOqHpd.js} +1 -1
- package/dist/provider.runtime-CaTJrpnl.js +2 -0
- package/dist/runtime-api.js +3 -3
- package/dist/{runtime-api.monitor-4oc58TSk.js → runtime-api.monitor-CRiBIQEI.js} +2 -2
- package/dist/runtime-api.monitor.js +3 -3
- 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/provider.runtime-D1m66kDS.js +0 -2
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
const util = require('../core/util')
|
|
4
4
|
const {
|
|
5
5
|
parseCacheControlHeader,
|
|
6
|
+
hasInvalidCacheControlDirective,
|
|
6
7
|
parseVaryHeader,
|
|
8
|
+
hasVaryStar,
|
|
9
|
+
isInvalidOrWildcardVaryHeader,
|
|
7
10
|
isEtagUsable
|
|
8
11
|
} = require('../util/cache')
|
|
9
12
|
const { parseHttpDate } = require('../util/date.js')
|
|
@@ -26,6 +29,95 @@ const NOT_UNDERSTOOD_STATUS_CODES = [
|
|
|
26
29
|
|
|
27
30
|
const MAX_RESPONSE_AGE = 2147483647000
|
|
28
31
|
|
|
32
|
+
// Retention for revalidation-only entries (zero freshness lifetime but a
|
|
33
|
+
// validator present); each successful revalidation re-stores the entry.
|
|
34
|
+
const REVALIDATION_ONLY_RETENTION = 86400000 // 24 hours
|
|
35
|
+
|
|
36
|
+
function trimOWS (value) {
|
|
37
|
+
return value.replace(/^[\t ]+|[\t ]+$/g, '')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function arrayIncludes (array, value) {
|
|
41
|
+
for (let i = 0; i < array.length; i++) {
|
|
42
|
+
if (array[i] === value) {
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function appendConnectionHeaderTokens (headersToRemove, connectionHeader) {
|
|
51
|
+
const values = Array.isArray(connectionHeader) ? connectionHeader : [connectionHeader]
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < values.length; i++) {
|
|
54
|
+
const tokens = values[i].split(',')
|
|
55
|
+
for (let j = 0; j < tokens.length; j++) {
|
|
56
|
+
headersToRemove.push(trimOWS(tokens[j]).toLowerCase())
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getSameOriginPath (cacheKey, location) {
|
|
62
|
+
if (typeof location !== 'string') {
|
|
63
|
+
return undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let originUrl
|
|
67
|
+
let requestUrl
|
|
68
|
+
let locationUrl
|
|
69
|
+
try {
|
|
70
|
+
originUrl = new URL(cacheKey.origin)
|
|
71
|
+
requestUrl = new URL(cacheKey.path, originUrl)
|
|
72
|
+
locationUrl = new URL(location, requestUrl)
|
|
73
|
+
} catch {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (locationUrl.origin !== originUrl.origin) {
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return locationUrl.pathname + locationUrl.search
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function deleteCachedUri (store, cacheKey, path) {
|
|
85
|
+
deleteCachedValue(store, {
|
|
86
|
+
...cacheKey,
|
|
87
|
+
path
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < util.safeHTTPMethods.length; i++) {
|
|
91
|
+
const method = util.safeHTTPMethods[i]
|
|
92
|
+
if (method !== cacheKey.method) {
|
|
93
|
+
deleteCachedValue(store, {
|
|
94
|
+
...cacheKey,
|
|
95
|
+
method,
|
|
96
|
+
path
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function deleteLocationTargets (store, cacheKey, headerValue) {
|
|
103
|
+
if (headerValue === undefined) {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const values = Array.isArray(headerValue) ? headerValue : [headerValue]
|
|
108
|
+
for (let i = 0; i < values.length; i++) {
|
|
109
|
+
const path = getSameOriginPath(cacheKey, values[i])
|
|
110
|
+
if (path !== undefined) {
|
|
111
|
+
deleteCachedUri(store, cacheKey, path)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function invalidateUnsafeRequest (store, cacheKey, resHeaders) {
|
|
117
|
+
deleteCachedUri(store, cacheKey, cacheKey.path)
|
|
118
|
+
deleteLocationTargets(store, cacheKey, resHeaders.location)
|
|
119
|
+
deleteLocationTargets(store, cacheKey, resHeaders['content-location'])
|
|
120
|
+
}
|
|
29
121
|
/**
|
|
30
122
|
* @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
|
|
31
123
|
*
|
|
@@ -107,28 +199,28 @@ class CacheHandler {
|
|
|
107
199
|
const handler = this
|
|
108
200
|
|
|
109
201
|
if (
|
|
110
|
-
!util.safeHTTPMethods
|
|
202
|
+
!arrayIncludes(util.safeHTTPMethods, this.#cacheKey.method) &&
|
|
111
203
|
statusCode >= 200 &&
|
|
112
204
|
statusCode <= 399
|
|
113
205
|
) {
|
|
114
206
|
// Successful response to an unsafe method, delete it from cache
|
|
115
207
|
// https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-response
|
|
116
|
-
|
|
117
|
-
this.#store.delete(this.#cacheKey)?.catch?.(noop)
|
|
118
|
-
} catch {
|
|
119
|
-
// Fail silently
|
|
120
|
-
}
|
|
208
|
+
invalidateUnsafeRequest(this.#store, this.#cacheKey, resHeaders)
|
|
121
209
|
return downstreamOnHeaders()
|
|
122
210
|
}
|
|
123
211
|
|
|
124
212
|
const cacheControlHeader = resHeaders['cache-control']
|
|
125
|
-
const heuristicallyCacheable = resHeaders['last-modified'] && HEURISTICALLY_CACHEABLE_STATUS_CODES
|
|
213
|
+
const heuristicallyCacheable = resHeaders['last-modified'] && arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode)
|
|
126
214
|
if (
|
|
127
215
|
!cacheControlHeader &&
|
|
128
216
|
!resHeaders['expires'] &&
|
|
129
217
|
!heuristicallyCacheable &&
|
|
130
218
|
!this.#cacheByDefault
|
|
131
219
|
) {
|
|
220
|
+
if (statusCode === 304 && resHeaders.vary && isInvalidOrWildcardVaryHeader(resHeaders.vary)) {
|
|
221
|
+
deleteCachedValue(this.#store, this.#cacheKey)
|
|
222
|
+
}
|
|
223
|
+
|
|
132
224
|
// Don't have anything to tell us this response is cachable and we're not
|
|
133
225
|
// caching by default
|
|
134
226
|
return downstreamOnHeaders()
|
|
@@ -136,31 +228,54 @@ class CacheHandler {
|
|
|
136
228
|
|
|
137
229
|
const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
|
|
138
230
|
if (!canCacheResponse(this.#cacheType, statusCode, resHeaders, cacheControlDirectives, this.#cacheKey.headers)) {
|
|
231
|
+
if (statusCode === 304 && (cacheControlHeader || revalidationResponseDisallowsCachedReuse(this.#cacheType, resHeaders, cacheControlDirectives))) {
|
|
232
|
+
deleteCachedValue(this.#store, this.#cacheKey)
|
|
233
|
+
}
|
|
234
|
+
|
|
139
235
|
return downstreamOnHeaders()
|
|
140
236
|
}
|
|
141
237
|
|
|
142
238
|
const now = Date.now()
|
|
143
|
-
const resAge = resHeaders
|
|
144
|
-
if (resAge && resAge >= MAX_RESPONSE_AGE) {
|
|
239
|
+
const resAge = Object.hasOwn(resHeaders, 'age') ? getAge(resHeaders.age) : undefined
|
|
240
|
+
if (resAge !== undefined && resAge >= MAX_RESPONSE_AGE) {
|
|
145
241
|
// Response considered stale
|
|
242
|
+
deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
|
|
243
|
+
return downstreamOnHeaders()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const resDate = Object.hasOwn(resHeaders, 'date') ? getDate(resHeaders.date) : undefined
|
|
247
|
+
if (resDate === null) {
|
|
248
|
+
deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
|
|
146
249
|
return downstreamOnHeaders()
|
|
147
250
|
}
|
|
148
251
|
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
252
|
+
const apparentAge = resDate ? Math.max(0, now - resDate.getTime()) : 0
|
|
253
|
+
const currentAge = Math.max(apparentAge, resAge ?? 0)
|
|
254
|
+
|
|
255
|
+
const hasValidator =
|
|
256
|
+
(typeof resHeaders.etag === 'string' && isEtagUsable(resHeaders.etag)) ||
|
|
257
|
+
typeof resHeaders['last-modified'] === 'string'
|
|
152
258
|
|
|
153
259
|
const staleAt =
|
|
154
|
-
determineStaleAt(this.#cacheType, now, resAge, resHeaders, resDate, cacheControlDirectives) ??
|
|
260
|
+
determineStaleAt(this.#cacheType, now, resAge, resHeaders, resDate, cacheControlDirectives, hasValidator) ??
|
|
155
261
|
this.#cacheByDefault
|
|
156
|
-
|
|
262
|
+
// Zero freshness lifetime but a validator: stale from the start, yet still
|
|
263
|
+
// storable since each reuse is preceded by a revalidation request.
|
|
264
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.4
|
|
265
|
+
const revalidationOnly = staleAt === 0 && hasValidator
|
|
266
|
+
if (staleAt === undefined || (currentAge >= staleAt && !revalidationOnly)) {
|
|
267
|
+
if (cacheControlHeader || staleAt !== undefined) {
|
|
268
|
+
deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
|
|
269
|
+
}
|
|
270
|
+
|
|
157
271
|
return downstreamOnHeaders()
|
|
158
272
|
}
|
|
159
273
|
|
|
160
|
-
const baseTime =
|
|
274
|
+
const baseTime = now - currentAge
|
|
161
275
|
const absoluteStaleAt = staleAt + baseTime
|
|
162
|
-
if (now >= absoluteStaleAt) {
|
|
276
|
+
if (now >= absoluteStaleAt && !revalidationOnly) {
|
|
163
277
|
// Response is already stale
|
|
278
|
+
deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
|
|
164
279
|
return downstreamOnHeaders()
|
|
165
280
|
}
|
|
166
281
|
|
|
@@ -173,8 +288,8 @@ class CacheHandler {
|
|
|
173
288
|
}
|
|
174
289
|
}
|
|
175
290
|
|
|
176
|
-
const cachedAt =
|
|
177
|
-
const deleteAt = determineDeleteAt(baseTime,
|
|
291
|
+
const cachedAt = baseTime
|
|
292
|
+
const deleteAt = determineDeleteAt(baseTime, now, cacheControlDirectives, absoluteStaleAt)
|
|
178
293
|
const strippedHeaders = stripNecessaryHeaders(resHeaders, cacheControlDirectives)
|
|
179
294
|
|
|
180
295
|
/**
|
|
@@ -204,6 +319,7 @@ class CacheHandler {
|
|
|
204
319
|
value.statusCode = cachedValue.statusCode
|
|
205
320
|
value.statusMessage = cachedValue.statusMessage
|
|
206
321
|
value.etag = cachedValue.etag
|
|
322
|
+
value.vary = varyDirectives ?? cachedValue.vary
|
|
207
323
|
value.headers = { ...cachedValue.headers, ...strippedHeaders }
|
|
208
324
|
|
|
209
325
|
downstreamOnHeaders()
|
|
@@ -334,6 +450,36 @@ class CacheHandler {
|
|
|
334
450
|
}
|
|
335
451
|
}
|
|
336
452
|
|
|
453
|
+
/**
|
|
454
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheStore} store
|
|
455
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
|
|
456
|
+
*/
|
|
457
|
+
function deleteCachedValue (store, cacheKey) {
|
|
458
|
+
try {
|
|
459
|
+
store.delete(cacheKey)?.catch?.(noop)
|
|
460
|
+
} catch {
|
|
461
|
+
// Fail silently
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function deleteCachedValueIfNotModified (statusCode, store, cacheKey) {
|
|
466
|
+
if (statusCode === 304) {
|
|
467
|
+
deleteCachedValue(store, cacheKey)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
473
|
+
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
|
|
474
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
475
|
+
* @returns {boolean}
|
|
476
|
+
*/
|
|
477
|
+
function revalidationResponseDisallowsCachedReuse (cacheType, resHeaders, cacheControlDirectives) {
|
|
478
|
+
return cacheControlDirectives['no-store'] === true ||
|
|
479
|
+
(cacheType === 'shared' && cacheControlDirectives.private === true) ||
|
|
480
|
+
(resHeaders.vary ? isInvalidOrWildcardVaryHeader(resHeaders.vary) : false)
|
|
481
|
+
}
|
|
482
|
+
|
|
337
483
|
/**
|
|
338
484
|
* @see https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
|
|
339
485
|
*
|
|
@@ -345,12 +491,12 @@ class CacheHandler {
|
|
|
345
491
|
*/
|
|
346
492
|
function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives, reqHeaders) {
|
|
347
493
|
// Status code must be final and understood.
|
|
348
|
-
if (statusCode < 200 || NOT_UNDERSTOOD_STATUS_CODES
|
|
494
|
+
if (statusCode < 200 || arrayIncludes(NOT_UNDERSTOOD_STATUS_CODES, statusCode)) {
|
|
349
495
|
return false
|
|
350
496
|
}
|
|
351
497
|
// Responses with neither status codes that are heuristically cacheable, nor "explicit enough" caching
|
|
352
498
|
// directives, are not cacheable. "Explicit enough": see https://www.rfc-editor.org/rfc/rfc9111.html#section-3
|
|
353
|
-
if (!HEURISTICALLY_CACHEABLE_STATUS_CODES
|
|
499
|
+
if (!arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode) && !resHeaders['expires'] &&
|
|
354
500
|
!cacheControlDirectives.public &&
|
|
355
501
|
cacheControlDirectives['max-age'] === undefined &&
|
|
356
502
|
// RFC 9111: a private response directive, if the cache is not shared
|
|
@@ -369,12 +515,12 @@ function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirect
|
|
|
369
515
|
}
|
|
370
516
|
|
|
371
517
|
// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1-5
|
|
372
|
-
if (resHeaders.vary
|
|
518
|
+
if (resHeaders.vary && hasVaryStar(resHeaders.vary)) {
|
|
373
519
|
return false
|
|
374
520
|
}
|
|
375
521
|
|
|
376
522
|
// https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
|
|
377
|
-
if (reqHeaders
|
|
523
|
+
if (reqHeaders != null && Object.hasOwn(reqHeaders, 'authorization')) {
|
|
378
524
|
if (
|
|
379
525
|
!cacheControlDirectives.public &&
|
|
380
526
|
!cacheControlDirectives['s-maxage'] &&
|
|
@@ -389,14 +535,14 @@ function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirect
|
|
|
389
535
|
|
|
390
536
|
if (
|
|
391
537
|
Array.isArray(cacheControlDirectives['no-cache']) &&
|
|
392
|
-
cacheControlDirectives['no-cache']
|
|
538
|
+
arrayIncludes(cacheControlDirectives['no-cache'], 'authorization')
|
|
393
539
|
) {
|
|
394
540
|
return false
|
|
395
541
|
}
|
|
396
542
|
|
|
397
543
|
if (
|
|
398
544
|
Array.isArray(cacheControlDirectives['private']) &&
|
|
399
|
-
cacheControlDirectives['private']
|
|
545
|
+
arrayIncludes(cacheControlDirectives['private'], 'authorization')
|
|
400
546
|
) {
|
|
401
547
|
return false
|
|
402
548
|
}
|
|
@@ -405,14 +551,51 @@ function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirect
|
|
|
405
551
|
return true
|
|
406
552
|
}
|
|
407
553
|
|
|
554
|
+
/**
|
|
555
|
+
* @param {string | string[]} dateHeader
|
|
556
|
+
* @returns {Date | null | undefined}
|
|
557
|
+
*/
|
|
558
|
+
function getDate (dateHeader) {
|
|
559
|
+
let dateValue = dateHeader
|
|
560
|
+
if (Array.isArray(dateValue)) {
|
|
561
|
+
if (dateValue.length !== 1) {
|
|
562
|
+
return null
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
dateValue = dateValue[0]
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (typeof dateValue !== 'string') {
|
|
569
|
+
return null
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return parseHttpDate(dateValue)
|
|
573
|
+
}
|
|
574
|
+
|
|
408
575
|
/**
|
|
409
576
|
* @param {string | string[]} ageHeader
|
|
410
577
|
* @returns {number | undefined}
|
|
411
578
|
*/
|
|
412
579
|
function getAge (ageHeader) {
|
|
413
|
-
|
|
580
|
+
let ageValue = ageHeader
|
|
581
|
+
if (Array.isArray(ageValue)) {
|
|
582
|
+
if (ageValue.length !== 1) {
|
|
583
|
+
return MAX_RESPONSE_AGE
|
|
584
|
+
}
|
|
414
585
|
|
|
415
|
-
|
|
586
|
+
ageValue = ageValue[0]
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (typeof ageValue !== 'string' || !/^[\t ]*[0-9]+[\t ]*$/.test(ageValue)) {
|
|
590
|
+
return MAX_RESPONSE_AGE
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const age = BigInt(ageValue.replace(/^[\t ]+|[\t ]+$/g, ''))
|
|
594
|
+
if (age >= BigInt(MAX_RESPONSE_AGE / 1000)) {
|
|
595
|
+
return MAX_RESPONSE_AGE
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return Number(age) * 1000
|
|
416
599
|
}
|
|
417
600
|
|
|
418
601
|
/**
|
|
@@ -422,51 +605,80 @@ function getAge (ageHeader) {
|
|
|
422
605
|
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
|
|
423
606
|
* @param {Date | undefined} responseDate
|
|
424
607
|
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
608
|
+
* @param {boolean} hasValidator whether the response has a validator (etag or
|
|
609
|
+
* last-modified) that revalidation requests can be made with
|
|
425
610
|
*
|
|
426
611
|
* @returns {number | undefined} time that the value is stale at in seconds or undefined if it shouldn't be cached
|
|
427
612
|
*/
|
|
428
|
-
function determineStaleAt (cacheType, now, age, resHeaders, responseDate, cacheControlDirectives) {
|
|
613
|
+
function determineStaleAt (cacheType, now, age, resHeaders, responseDate, cacheControlDirectives, hasValidator) {
|
|
429
614
|
if (cacheType === 'shared') {
|
|
430
615
|
// Prioritize s-maxage since we're a shared cache
|
|
431
616
|
// s-maxage > max-age > Expire
|
|
432
617
|
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-3
|
|
618
|
+
if (hasInvalidCacheControlDirective(cacheControlDirectives, 's-maxage')) {
|
|
619
|
+
return 0
|
|
620
|
+
}
|
|
621
|
+
|
|
433
622
|
const sMaxAge = cacheControlDirectives['s-maxage']
|
|
434
623
|
if (sMaxAge !== undefined) {
|
|
435
|
-
|
|
624
|
+
if (sMaxAge > 0) {
|
|
625
|
+
return sMaxAge * 1000
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Immediately stale, but storable if we can revalidate it before reuse.
|
|
629
|
+
return 0
|
|
436
630
|
}
|
|
437
631
|
}
|
|
438
632
|
|
|
633
|
+
if (hasInvalidCacheControlDirective(cacheControlDirectives, 'max-age')) {
|
|
634
|
+
return 0
|
|
635
|
+
}
|
|
636
|
+
|
|
439
637
|
const maxAge = cacheControlDirectives['max-age']
|
|
440
638
|
if (maxAge !== undefined) {
|
|
441
|
-
|
|
639
|
+
if (maxAge > 0) {
|
|
640
|
+
return maxAge * 1000
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Immediately stale, but storable if we can revalidate it before reuse.
|
|
644
|
+
return 0
|
|
442
645
|
}
|
|
443
646
|
|
|
444
|
-
if (
|
|
647
|
+
if (Object.hasOwn(resHeaders, 'expires')) {
|
|
445
648
|
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
|
|
649
|
+
if (typeof resHeaders.expires !== 'string') {
|
|
650
|
+
return 0
|
|
651
|
+
}
|
|
652
|
+
|
|
446
653
|
const expiresDate = parseHttpDate(resHeaders.expires)
|
|
447
|
-
if (expiresDate) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
654
|
+
if (!expiresDate) {
|
|
655
|
+
return 0
|
|
656
|
+
}
|
|
451
657
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
658
|
+
if (now >= expiresDate.getTime()) {
|
|
659
|
+
return 0
|
|
660
|
+
}
|
|
456
661
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
662
|
+
if (responseDate) {
|
|
663
|
+
if (responseDate >= expiresDate) {
|
|
664
|
+
return 0
|
|
460
665
|
}
|
|
461
666
|
|
|
462
|
-
|
|
667
|
+
const freshnessLifetime = expiresDate.getTime() - responseDate.getTime()
|
|
668
|
+
if (age !== undefined && age >= freshnessLifetime) {
|
|
669
|
+
return 0
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return freshnessLifetime
|
|
463
673
|
}
|
|
674
|
+
|
|
675
|
+
return expiresDate.getTime() - now
|
|
464
676
|
}
|
|
465
677
|
|
|
466
678
|
if (typeof resHeaders['last-modified'] === 'string') {
|
|
467
679
|
// https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-heuristic-fresh
|
|
468
|
-
const lastModified =
|
|
469
|
-
if (
|
|
680
|
+
const lastModified = parseHttpDate(resHeaders['last-modified'])
|
|
681
|
+
if (lastModified) {
|
|
470
682
|
if (lastModified.getTime() >= now) {
|
|
471
683
|
return undefined
|
|
472
684
|
}
|
|
@@ -482,6 +694,12 @@ function determineStaleAt (cacheType, now, age, resHeaders, responseDate, cacheC
|
|
|
482
694
|
return 31536000000
|
|
483
695
|
}
|
|
484
696
|
|
|
697
|
+
if (cacheControlDirectives['no-cache'] === true && hasValidator) {
|
|
698
|
+
// No freshness source, but a validator lets us revalidate before reuse.
|
|
699
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.4
|
|
700
|
+
return 0
|
|
701
|
+
}
|
|
702
|
+
|
|
485
703
|
return undefined
|
|
486
704
|
}
|
|
487
705
|
|
|
@@ -518,6 +736,11 @@ function determineDeleteAt (baseTime, cachedAt, cacheControlDirectives, staleAt)
|
|
|
518
736
|
// revalidated.
|
|
519
737
|
if (staleWhileRevalidate === -Infinity && staleIfError === -Infinity && immutable === -Infinity) {
|
|
520
738
|
const freshnessLifetime = staleAt - baseTime
|
|
739
|
+
if (freshnessLifetime <= 0) {
|
|
740
|
+
// Revalidation-only entry: no freshness lifetime to size the buffer on,
|
|
741
|
+
// so retain it for a bounded window instead.
|
|
742
|
+
return cachedAt + REVALIDATION_ONLY_RETENTION
|
|
743
|
+
}
|
|
521
744
|
const datePrecisionPadding = Math.min(Math.max(cachedAt - baseTime, 0), 1000)
|
|
522
745
|
return staleAt + freshnessLifetime + datePrecisionPadding
|
|
523
746
|
}
|
|
@@ -546,14 +769,7 @@ function stripNecessaryHeaders (resHeaders, cacheControlDirectives) {
|
|
|
546
769
|
]
|
|
547
770
|
|
|
548
771
|
if (resHeaders['connection']) {
|
|
549
|
-
|
|
550
|
-
// connection: a
|
|
551
|
-
// connection: b
|
|
552
|
-
headersToRemove.push(...resHeaders['connection'].map(header => header.trim()))
|
|
553
|
-
} else {
|
|
554
|
-
// connection: a, b
|
|
555
|
-
headersToRemove.push(...resHeaders['connection'].split(',').map(header => header.trim()))
|
|
556
|
-
}
|
|
772
|
+
appendConnectionHeaderTokens(headersToRemove, resHeaders['connection'])
|
|
557
773
|
}
|
|
558
774
|
|
|
559
775
|
if (Array.isArray(cacheControlDirectives['no-cache'])) {
|
|
@@ -566,7 +782,7 @@ function stripNecessaryHeaders (resHeaders, cacheControlDirectives) {
|
|
|
566
782
|
|
|
567
783
|
let strippedHeaders
|
|
568
784
|
for (const headerName of headersToRemove) {
|
|
569
|
-
if (resHeaders
|
|
785
|
+
if (Object.hasOwn(resHeaders, headerName)) {
|
|
570
786
|
strippedHeaders ??= { ...resHeaders }
|
|
571
787
|
delete strippedHeaders[headerName]
|
|
572
788
|
}
|
|
@@ -575,12 +791,4 @@ function stripNecessaryHeaders (resHeaders, cacheControlDirectives) {
|
|
|
575
791
|
return strippedHeaders ?? resHeaders
|
|
576
792
|
}
|
|
577
793
|
|
|
578
|
-
/**
|
|
579
|
-
* @param {Date} date
|
|
580
|
-
* @returns {boolean}
|
|
581
|
-
*/
|
|
582
|
-
function isValidDate (date) {
|
|
583
|
-
return date instanceof Date && Number.isFinite(date.valueOf())
|
|
584
|
-
}
|
|
585
|
-
|
|
586
794
|
module.exports = CacheHandler
|
|
@@ -19,7 +19,7 @@ class CacheRevalidationHandler {
|
|
|
19
19
|
#successful = false
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* @type {((boolean, any) => void) | null}
|
|
22
|
+
* @type {((success: boolean, context?: any, statusCode?: number, headers?: import('../../types/header.d.ts').IncomingHttpHeaders) => void) | null}
|
|
23
23
|
*/
|
|
24
24
|
#callback
|
|
25
25
|
|
|
@@ -36,7 +36,7 @@ class CacheRevalidationHandler {
|
|
|
36
36
|
#allowErrorStatusCodes
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* @param {(boolean) => void} callback Function to call if the cached value is valid
|
|
39
|
+
* @param {(success: boolean, context?: any, statusCode?: number, headers?: import('../../types/header.d.ts').IncomingHttpHeaders) => void} callback Function to call if the cached value is valid
|
|
40
40
|
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
|
|
41
41
|
* @param {boolean} allowErrorStatusCodes
|
|
42
42
|
*/
|
|
@@ -71,7 +71,7 @@ class CacheRevalidationHandler {
|
|
|
71
71
|
// https://datatracker.ietf.org/doc/html/rfc5861#section-4
|
|
72
72
|
this.#successful = statusCode === 304 ||
|
|
73
73
|
(this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
|
|
74
|
-
this.#callback(this.#successful, this.#context)
|
|
74
|
+
this.#callback(this.#successful, this.#context, statusCode, headers)
|
|
75
75
|
this.#callback = null
|
|
76
76
|
|
|
77
77
|
if (this.#successful) {
|
|
@@ -109,6 +109,16 @@ class CacheRevalidationHandler {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
if (this.#callback) {
|
|
112
|
+
// Serve the stale cached response on a connection error, per stale-if-error:
|
|
113
|
+
// RFC 5861 counts an unreachable origin (a would-be 5xx) as an error.
|
|
114
|
+
// https://datatracker.ietf.org/doc/html/rfc5861#section-4
|
|
115
|
+
if (this.#allowErrorStatusCodes) {
|
|
116
|
+
this.#successful = true
|
|
117
|
+
this.#callback(true, this.#context)
|
|
118
|
+
this.#callback = null
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
112
122
|
this.#callback(false)
|
|
113
123
|
this.#callback = null
|
|
114
124
|
}
|
|
@@ -52,15 +52,19 @@ class RedirectHandler {
|
|
|
52
52
|
throw new Error('max redirects')
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
let removeContentHeaders = statusCode === 303
|
|
56
|
+
|
|
55
57
|
// https://tools.ietf.org/html/rfc7231#section-6.4.2
|
|
56
58
|
// https://fetch.spec.whatwg.org/#http-redirect-fetch
|
|
57
59
|
// In case of HTTP 301 or 302 with POST, change the method to GET
|
|
60
|
+
// QUERY is safe (RFC 10008) and should not change method like GET.
|
|
58
61
|
if ((statusCode === 301 || statusCode === 302) && this.opts.method === 'POST') {
|
|
59
62
|
this.opts.method = 'GET'
|
|
60
63
|
if (util.isStream(this.opts.body)) {
|
|
61
64
|
util.destroy(this.opts.body.on('error', noop))
|
|
62
65
|
}
|
|
63
66
|
this.opts.body = null
|
|
67
|
+
removeContentHeaders = true
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
|
@@ -100,9 +104,9 @@ class RedirectHandler {
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
// Remove headers referring to the original URL.
|
|
103
|
-
// By default it is Host only
|
|
107
|
+
// By default it is Host only. A 303 or a 301/302 POST-to-GET redirect also removes all Content-* headers.
|
|
104
108
|
// https://tools.ietf.org/html/rfc7231#section-6.4
|
|
105
|
-
this.opts.headers = cleanRequestHeaders(this.opts.headers,
|
|
109
|
+
this.opts.headers = cleanRequestHeaders(this.opts.headers, removeContentHeaders, this.opts.origin !== origin, this.stripHeadersOnRedirect, this.stripHeadersOnCrossOriginRedirect)
|
|
106
110
|
this.opts.path = path
|
|
107
111
|
this.opts.origin = origin
|
|
108
112
|
this.opts.query = null
|