@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.
Files changed (179) hide show
  1. package/dist/action-runtime-api.js +1 -1
  2. package/dist/{api-CjnGBhM3.js → api-1GXlSrYP.js} +42 -21
  3. package/dist/api.js +14 -14
  4. package/dist/{approval-handler.runtime-BWPQrzpP.js → approval-handler.runtime-Dv4p7REU.js} +3 -3
  5. package/dist/{audit-BwJ_2OjL.js → audit-DEVi9nIs.js} +3 -3
  6. package/dist/{channel-Ddnonoy4.js → channel-CQIRiosn.js} +15 -15
  7. package/dist/{channel-actions-CwmIqnYN.js → channel-actions-JziPEcBU.js} +2 -2
  8. package/dist/{channel-actions.runtime-DvpTfaJ4.js → channel-actions.runtime-D7L47LY3.js} +5 -5
  9. package/dist/channel-plugin-api.js +1 -1
  10. package/dist/{channel.setup-glZ5Y98a.js → channel.setup-oMeR19Cc.js} +2 -2
  11. package/dist/{components-j6lTZBsT.js → components-_s5UPJqU.js} +1 -1
  12. package/dist/{conversation-identity-36FFBwm5.js → conversation-identity-B0k94-bz.js} +2 -2
  13. package/dist/{directory-config-BYMFzL29.js → directory-config-CD1svkyN.js} +1 -1
  14. package/dist/directory-contract-api.js +1 -1
  15. package/dist/{directory-live-9JtJCiu8.js → directory-live-DBTsSRxl.js} +5 -5
  16. package/dist/{handle-action.guild-admin-xxGYrp-I.js → handle-action.guild-admin-CYIFMxh-.js} +2 -2
  17. package/dist/index.js +1 -1
  18. package/dist/{manager.runtime-BK6cf2Zu.js → manager.runtime-BZVdGX2d.js} +17 -9
  19. package/dist/{message-handler-BqpJIavO.js → message-handler-DZd9Bpq8.js} +7 -6
  20. package/dist/{message-handler.preflight-C90MxCcv.js → message-handler.preflight-Axv9J9EO.js} +12 -11
  21. package/dist/{message-handler.process-CeQ7Bd_3.js → message-handler.process-CG0zFS-Z.js} +10 -10
  22. package/dist/{message-utils-CJeAoISH.js → message-utils-Dbuw27gd.js} +1 -1
  23. package/dist/{outbound-adapter-Dxs6iKEq.js → outbound-adapter-CKC_kmUN.js} +6 -6
  24. package/dist/pluralkit-CfTReLl3.js +40 -0
  25. package/dist/{probe-esE_Oqdm.js → probe-DcgZIpyF.js} +3 -2
  26. package/dist/{probe.runtime-C3Atgpvr.js → probe.runtime-CVwM9L5E.js} +1 -1
  27. package/dist/{provider-C7376XFH.js → provider-CwwSXIIj.js} +102 -37
  28. package/dist/{provider-session.runtime-CCdqogGf.js → provider-session.runtime-reBWkqHZ.js} +3 -3
  29. package/dist/provider.runtime-BsmTLYS_.js +2 -0
  30. package/dist/{resolve-allowlist-common-3gB2Gpmm.js → resolve-allowlist-common-D7bZqAw3.js} +3 -3
  31. package/dist/{resolve-channels-B3gQf8QL.js → resolve-channels--oE-RnO6.js} +6 -6
  32. package/dist/{resolve-users-BO10y5fJ.js → resolve-users-QXo8CXBU.js} +5 -5
  33. package/dist/{runtime-CFlR3L9X.js → runtime-CF7ramAs.js} +7 -7
  34. package/dist/runtime-api.actions.js +2 -2
  35. package/dist/runtime-api.js +19 -19
  36. package/dist/runtime-api.lookup.js +5 -5
  37. package/dist/runtime-api.monitor-CzF8MUl_.js +6 -0
  38. package/dist/runtime-api.monitor.js +5 -5
  39. package/dist/runtime-api.send.js +5 -5
  40. package/dist/runtime-api.threads.js +3 -3
  41. package/dist/{send-JHxfT4Pk.js → send-Bb295iHT.js} +5 -5
  42. package/dist/{send.components-CN35E2Ck.js → send.components-COkDUO3B.js} +4 -4
  43. package/dist/{send.outbound-B-cA1Esv.js → send.outbound-B_sk0hNe.js} +3 -3
  44. package/dist/{send.receipt-CGecJDin.js → send.receipt-Cli6_9Sn.js} +19 -7
  45. package/dist/{send.shared-vcbfThdy.js → send.shared-BCG7MWnP.js} +2 -2
  46. package/dist/session-binding-contract-api.js +1 -1
  47. package/dist/setup-plugin-api.js +1 -1
  48. package/dist/{subagent-hooks-20wXhVzZ.js → subagent-hooks-CtqoWK4x.js} +2 -2
  49. package/dist/subagent-hooks-api.js +1 -1
  50. package/dist/{system-events-DR27TE7p.js → system-events-BxGNk_wE.js} +1 -1
  51. package/dist/{target-resolver-CDi1gI90.js → target-resolver-BcoK6b0g.js} +2 -2
  52. package/dist/targets-Q-ZoP7K4.js +3 -0
  53. package/dist/{thread-bindings-DU4T6nDo.js → thread-bindings-DJ_HT6GJ.js} +4 -4
  54. package/dist/{thread-bindings.discord-api-CYkr-3Gj.js → thread-bindings.discord-api-OPyjvgyz.js} +4 -4
  55. package/dist/{thread-bindings.manager-BqWs1UE2.js → thread-bindings.manager-Cd2HAVw_.js} +3 -3
  56. package/dist/{transcripts-source-D-lBI5ZD.js → transcripts-source-CPf5gURy.js} +1 -1
  57. package/dist/transcripts-source-api.js +1 -1
  58. package/dist/{typing-xr05bL-r.js → typing-DY0cwLdg.js} +1 -1
  59. package/node_modules/undici/README.md +8 -2
  60. package/node_modules/undici/docs/docs/api/Agent.md +248 -44
  61. package/node_modules/undici/docs/docs/api/BalancedPool.md +246 -48
  62. package/node_modules/undici/docs/docs/api/CacheStorage.md +171 -13
  63. package/node_modules/undici/docs/docs/api/CacheStore.md +294 -98
  64. package/node_modules/undici/docs/docs/api/Client.md +386 -179
  65. package/node_modules/undici/docs/docs/api/ClientStats.md +80 -14
  66. package/node_modules/undici/docs/docs/api/Connector.md +118 -36
  67. package/node_modules/undici/docs/docs/api/ContentType.md +65 -25
  68. package/node_modules/undici/docs/docs/api/Cookies.md +122 -71
  69. package/node_modules/undici/docs/docs/api/Debug.md +34 -18
  70. package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +364 -164
  71. package/node_modules/undici/docs/docs/api/Dispatcher.md +485 -1102
  72. package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +102 -87
  73. package/node_modules/undici/docs/docs/api/Errors.md +503 -40
  74. package/node_modules/undici/docs/docs/api/EventSource.md +298 -32
  75. package/node_modules/undici/docs/docs/api/Fetch.md +680 -31
  76. package/node_modules/undici/docs/docs/api/GlobalInstallation.md +78 -98
  77. package/node_modules/undici/docs/docs/api/H2CClient.md +298 -176
  78. package/node_modules/undici/docs/docs/api/Interceptors.md +418 -0
  79. package/node_modules/undici/docs/docs/api/MockAgent.md +333 -314
  80. package/node_modules/undici/docs/docs/api/MockCallHistory.md +272 -98
  81. package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +189 -26
  82. package/node_modules/undici/docs/docs/api/MockClient.md +151 -33
  83. package/node_modules/undici/docs/docs/api/MockErrors.md +56 -5
  84. package/node_modules/undici/docs/docs/api/MockPool.md +321 -258
  85. package/node_modules/undici/docs/docs/api/Pool.md +235 -43
  86. package/node_modules/undici/docs/docs/api/PoolStats.md +119 -13
  87. package/node_modules/undici/docs/docs/api/ProxyAgent.md +179 -160
  88. package/node_modules/undici/docs/docs/api/RedirectHandler.md +238 -63
  89. package/node_modules/undici/docs/docs/api/RetryAgent.md +113 -26
  90. package/node_modules/undici/docs/docs/api/RetryHandler.md +161 -77
  91. package/node_modules/undici/docs/docs/api/RoundRobinPool.md +166 -72
  92. package/node_modules/undici/docs/docs/api/SnapshotAgent.md +264 -542
  93. package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +177 -196
  94. package/node_modules/undici/docs/docs/api/Util.md +68 -11
  95. package/node_modules/undici/docs/docs/api/WebSocket.md +475 -80
  96. package/node_modules/undici/docs/docs/api/api-lifecycle.md +100 -32
  97. package/node_modules/undici/docs/docs/best-practices/client-certificate.md +2 -0
  98. package/node_modules/undici/docs/docs/best-practices/crawling.md +3 -1
  99. package/node_modules/undici/docs/docs/best-practices/migrating-from-v7-to-v8.md +6 -4
  100. package/node_modules/undici/docs/docs/best-practices/mocking-request.md +9 -7
  101. package/node_modules/undici/docs/docs/best-practices/proxy.md +3 -1
  102. package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +10 -8
  103. package/node_modules/undici/docs/docs/best-practices/writing-tests.md +2 -0
  104. package/node_modules/undici/docs/docs/{GettingStarted.md → getting-started.md} +22 -19
  105. package/node_modules/undici/docs/docs/index.md +784 -0
  106. package/node_modules/undici/docs/docs/site.json +125 -0
  107. package/node_modules/undici/docs/docs/type-map.json +79 -0
  108. package/node_modules/undici/lib/api/api-request.js +7 -1
  109. package/node_modules/undici/lib/api/readable.js +42 -7
  110. package/node_modules/undici/lib/cache/memory-cache-store.js +56 -11
  111. package/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
  112. package/node_modules/undici/lib/core/connect.js +16 -2
  113. package/node_modules/undici/lib/core/errors.js +20 -0
  114. package/node_modules/undici/lib/core/request.js +14 -3
  115. package/node_modules/undici/lib/core/symbols.js +3 -0
  116. package/node_modules/undici/lib/core/util.js +30 -2
  117. package/node_modules/undici/lib/dispatcher/agent.js +10 -5
  118. package/node_modules/undici/lib/dispatcher/balanced-pool.js +7 -4
  119. package/node_modules/undici/lib/dispatcher/client-h1.js +74 -9
  120. package/node_modules/undici/lib/dispatcher/client-h2.js +483 -151
  121. package/node_modules/undici/lib/dispatcher/client.js +76 -30
  122. package/node_modules/undici/lib/dispatcher/dispatcher-base.js +13 -1
  123. package/node_modules/undici/lib/dispatcher/dispatcher.js +15 -1
  124. package/node_modules/undici/lib/dispatcher/dispatcher1-wrapper.js +3 -0
  125. package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +37 -8
  126. package/node_modules/undici/lib/dispatcher/proxy-agent.js +58 -7
  127. package/node_modules/undici/lib/dispatcher/retry-agent.js +3 -0
  128. package/node_modules/undici/lib/dispatcher/round-robin-pool.js +1 -1
  129. package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +106 -22
  130. package/node_modules/undici/lib/global.js +36 -16
  131. package/node_modules/undici/lib/handler/cache-handler.js +297 -65
  132. package/node_modules/undici/lib/handler/cache-revalidation-handler.js +13 -3
  133. package/node_modules/undici/lib/handler/decorator-handler.js +7 -1
  134. package/node_modules/undici/lib/handler/deduplication-handler.js +12 -6
  135. package/node_modules/undici/lib/handler/redirect-handler.js +26 -5
  136. package/node_modules/undici/lib/handler/retry-handler.js +238 -51
  137. package/node_modules/undici/lib/interceptor/cache.js +221 -58
  138. package/node_modules/undici/lib/interceptor/decompress.js +124 -15
  139. package/node_modules/undici/lib/interceptor/deduplicate.js +5 -4
  140. package/node_modules/undici/lib/interceptor/dns.js +7 -0
  141. package/node_modules/undici/lib/interceptor/dump.js +10 -23
  142. package/node_modules/undici/lib/mock/mock-agent.js +13 -1
  143. package/node_modules/undici/lib/mock/mock-interceptor.js +25 -7
  144. package/node_modules/undici/lib/mock/mock-utils.js +246 -27
  145. package/node_modules/undici/lib/util/cache.js +415 -71
  146. package/node_modules/undici/lib/util/date.js +23 -6
  147. package/node_modules/undici/lib/web/cookies/parse.js +150 -146
  148. package/node_modules/undici/lib/web/cookies/util.js +79 -9
  149. package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +28 -1
  150. package/node_modules/undici/lib/web/eventsource/eventsource.js +13 -3
  151. package/node_modules/undici/lib/web/eventsource/util.js +1 -1
  152. package/node_modules/undici/lib/web/fetch/body.js +2 -1
  153. package/node_modules/undici/lib/web/fetch/constants.js +1 -1
  154. package/node_modules/undici/lib/web/fetch/index.js +17 -2
  155. package/node_modules/undici/lib/web/fetch/request.js +2 -3
  156. package/node_modules/undici/lib/web/fetch/response.js +1 -3
  157. package/node_modules/undici/lib/web/fetch/util.js +10 -2
  158. package/node_modules/undici/lib/web/webidl/index.js +3 -3
  159. package/node_modules/undici/lib/web/websocket/connection.js +1 -1
  160. package/node_modules/undici/lib/web/websocket/permessage-deflate.js +5 -0
  161. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +8 -10
  162. package/node_modules/undici/lib/web/websocket/websocket.js +22 -0
  163. package/node_modules/undici/package.json +4 -4
  164. package/node_modules/undici/types/client.d.ts +53 -3
  165. package/node_modules/undici/types/cookies.d.ts +1 -1
  166. package/node_modules/undici/types/dispatcher.d.ts +3 -3
  167. package/node_modules/undici/types/errors.d.ts +10 -0
  168. package/node_modules/undici/types/handlers.d.ts +2 -0
  169. package/node_modules/undici/types/interceptors.d.ts +2 -0
  170. package/node_modules/undici/types/mock-agent.d.ts +11 -10
  171. package/node_modules/undici/types/mock-interceptor.d.ts +5 -1
  172. package/node_modules/undici/types/proxy-agent.d.ts +7 -0
  173. package/node_modules/undici/types/socks5-proxy-agent.d.ts +2 -0
  174. package/npm-shrinkwrap.json +7 -7
  175. package/package.json +6 -5
  176. package/dist/pluralkit-DI6RYaVj.js +0 -24
  177. package/dist/provider.runtime-D1m66kDS.js +0 -2
  178. package/dist/runtime-api.monitor-4oc58TSk.js +0 -6
  179. package/dist/targets-C6yd3y8R.js +0 -3
@@ -6,8 +6,18 @@ 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 {
10
+ assertCacheStore,
11
+ assertCacheMethods,
12
+ getInterceptorOrigin,
13
+ makeCacheKey,
14
+ normalizeHeaders,
15
+ parseCacheControlHeader,
16
+ isInvalidOrWildcardVaryHeader,
17
+ parseVaryHeader
18
+ } = require('../util/cache.js')
10
19
  const { AbortError } = require('../core/errors.js')
20
+ const { parseHttpDate } = require('../util/date.js')
11
21
 
12
22
  /**
13
23
  * @param {(string | RegExp)[] | undefined} origins
@@ -28,6 +38,44 @@ function assertCacheOrigins (origins, name) {
28
38
 
29
39
  const nop = () => {}
30
40
 
41
+ function trimOWS (value) {
42
+ return value.replace(/^[\t ]+|[\t ]+$/g, '')
43
+ }
44
+
45
+ function arrayIncludes (array, value) {
46
+ for (let i = 0; i < array.length; i++) {
47
+ if (array[i] === value) {
48
+ return true
49
+ }
50
+ }
51
+
52
+ return false
53
+ }
54
+
55
+ function hasPragmaNoCache (headers) {
56
+ const pragma = headers?.pragma
57
+ if (!pragma) {
58
+ return false
59
+ }
60
+
61
+ const values = Array.isArray(pragma) ? pragma : [pragma]
62
+ for (let i = 0; i < values.length; i++) {
63
+ const value = values[i]
64
+ if (typeof value !== 'string') {
65
+ continue
66
+ }
67
+
68
+ const directives = value.split(',')
69
+ for (let j = 0; j < directives.length; j++) {
70
+ if (trimOWS(directives[j]).toLowerCase() === 'no-cache') {
71
+ return true
72
+ }
73
+ }
74
+ }
75
+
76
+ return false
77
+ }
78
+
31
79
  /**
32
80
  * @typedef {(options: import('../../types/dispatcher.d.ts').default.DispatchOptions, handler: import('../../types/dispatcher.d.ts').default.DispatchHandler) => void} DispatchFn
33
81
  */
@@ -57,16 +105,114 @@ function needsRevalidation (result, cacheControlDirectives, { headers = {} }) {
57
105
  return false
58
106
  }
59
107
 
108
+ /**
109
+ * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
110
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
111
+ * @returns {boolean}
112
+ */
113
+ function staleResponseRequiresRevalidation (result, cacheType) {
114
+ return result.cacheControlDirectives?.['must-revalidate'] === true ||
115
+ (cacheType === 'shared' && (
116
+ result.cacheControlDirectives?.['proxy-revalidate'] === true ||
117
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10
118
+ // s-maxage implies proxy-revalidate for shared caches.
119
+ result.cacheControlDirectives?.['s-maxage'] !== undefined
120
+ ))
121
+ }
122
+
123
+ /**
124
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
125
+ * @param {import('../../types/header.d.ts').IncomingHttpHeaders} headers
126
+ * @returns {boolean}
127
+ */
128
+ function revalidationResponseDisallowsCachedReuse (cacheType, headers) {
129
+ if (
130
+ (headers.vary && isInvalidOrWildcardVaryHeader(headers.vary)) ||
131
+ (cacheType === 'shared' && Object.hasOwn(headers, 'set-cookie'))
132
+ ) {
133
+ return true
134
+ }
135
+
136
+ const cacheControl = headers['cache-control']
137
+ if (!cacheControl) {
138
+ return false
139
+ }
140
+
141
+ const cacheControlDirectives = parseCacheControlHeader(cacheControl)
142
+ return cacheControlDirectives['no-store'] === true ||
143
+ (cacheType === 'shared' && cacheControlDirectives.private === true)
144
+ }
145
+
146
+ function revalidationResponseUpdatesCacheControl (headers) {
147
+ return headers['cache-control'] !== undefined
148
+ }
149
+
150
+ /**
151
+ * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
152
+ * @param {Record<string, string | string[] | null> | undefined} varyDirectives
153
+ * @returns {boolean}
154
+ */
155
+ function revalidationResponseAddsVary (result, varyDirectives) {
156
+ if (!varyDirectives) {
157
+ return false
158
+ }
159
+
160
+ for (const key in varyDirectives) {
161
+ if (result.vary == null || !Object.hasOwn(result.vary, key)) {
162
+ return true
163
+ }
164
+ }
165
+
166
+ return false
167
+ }
168
+
169
+ function deleteCachedValue (store, cacheKey) {
170
+ try {
171
+ store.delete(cacheKey)?.catch?.(nop)
172
+ } catch {
173
+ // Fail silently
174
+ }
175
+ }
176
+
177
+ function getUsableLastModified (headers) {
178
+ const lastModified = headers?.['last-modified']
179
+ if (typeof lastModified === 'string' && parseHttpDate(lastModified)) {
180
+ return lastModified
181
+ }
182
+ }
183
+
184
+ function makeRevalidationHeaders (opts, result) {
185
+ const headers = {
186
+ ...opts.headers,
187
+ 'if-modified-since': getUsableLastModified(result.headers) ?? new Date(result.cachedAt).toUTCString()
188
+ }
189
+
190
+ if (result.etag) {
191
+ headers['if-none-match'] = result.etag
192
+ }
193
+
194
+ if (result.vary) {
195
+ for (const key in result.vary) {
196
+ if (result.vary[key] != null) {
197
+ headers[key] = result.vary[key]
198
+ }
199
+ }
200
+ }
201
+
202
+ return headers
203
+ }
204
+
60
205
  /**
61
206
  * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
62
207
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} cacheControlDirectives
208
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
63
209
  * @returns {boolean}
64
210
  */
65
- function isStale (result, cacheControlDirectives) {
211
+ function isStale (result, cacheControlDirectives, cacheType) {
66
212
  const now = Date.now()
67
213
  if (now > result.staleAt) {
68
214
  // Response is stale
69
- if (cacheControlDirectives?.['max-stale']) {
215
+ if (!staleResponseRequiresRevalidation(result, cacheType) && cacheControlDirectives?.['max-stale']) {
70
216
  // There's a threshold where we can serve stale responses, let's see if
71
217
  // we're in it
72
218
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-max-stale
@@ -93,11 +239,12 @@ function isStale (result, cacheControlDirectives) {
93
239
  /**
94
240
  * Check if we're within the stale-while-revalidate window for a stale response
95
241
  * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
242
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
96
243
  * @returns {boolean}
97
244
  */
98
- function withinStaleWhileRevalidateWindow (result) {
245
+ function withinStaleWhileRevalidateWindow (result, cacheType) {
99
246
  const staleWhileRevalidate = result.cacheControlDirectives?.['stale-while-revalidate']
100
- if (!staleWhileRevalidate) {
247
+ if (!staleWhileRevalidate || staleResponseRequiresRevalidation(result, cacheType)) {
101
248
  return false
102
249
  }
103
250
 
@@ -273,6 +420,17 @@ function handleResult (
273
420
  return handleUncachedResponse(dispatch, globalOpts, cacheKey, handler, opts, reqCacheControl)
274
421
  }
275
422
 
423
+ // Shared stores may outlive the Undici version that wrote them. Do not
424
+ // re-serve a Set-Cookie header from an existing shared-cache entry.
425
+ if (globalOpts.type === 'shared' && Object.hasOwn(result.headers, 'set-cookie')) {
426
+ if (util.isStream(result.body)) {
427
+ result.body.on('error', nop).destroy()
428
+ }
429
+
430
+ deleteCachedValue(globalOpts.store, cacheKey)
431
+ return handleUncachedResponse(dispatch, globalOpts, cacheKey, handler, opts, reqCacheControl)
432
+ }
433
+
276
434
  const now = Date.now()
277
435
  if (now > result.deleteAt) {
278
436
  // Response is expired, cache store shouldn't have given this to us
@@ -280,14 +438,10 @@ function handleResult (
280
438
  }
281
439
 
282
440
  const age = Math.round((now - result.cachedAt) / 1000)
283
- if (reqCacheControl?.['max-age'] && age >= reqCacheControl['max-age']) {
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
- }
441
+ const requestMaxAgeExpired = reqCacheControl?.['max-age'] !== undefined && age >= reqCacheControl['max-age']
288
442
 
289
- const stale = isStale(result, reqCacheControl)
290
- const revalidate = needsRevalidation(result, reqCacheControl, opts)
443
+ const stale = requestMaxAgeExpired || isStale(result, reqCacheControl, globalOpts.type)
444
+ const revalidate = requestMaxAgeExpired || needsRevalidation(result, reqCacheControl, opts)
291
445
 
292
446
  // Check if the response is stale
293
447
  if (stale || revalidate) {
@@ -299,28 +453,13 @@ function handleResult (
299
453
 
300
454
  // RFC 5861: If we're within stale-while-revalidate window, serve stale immediately
301
455
  // and revalidate in background, unless immediate revalidation is necessary
302
- if (!revalidate && withinStaleWhileRevalidateWindow(result)) {
456
+ if (!revalidate && withinStaleWhileRevalidateWindow(result, globalOpts.type)) {
303
457
  // Serve stale response immediately
304
458
  sendCachedValue(handler, opts, result, age, null, true)
305
459
 
306
460
  // Start background revalidation (fire-and-forget)
307
461
  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
- }
462
+ const headers = makeRevalidationHeaders(opts, result)
324
463
 
325
464
  // Background revalidation - update cache if we get new data
326
465
  dispatch(
@@ -344,28 +483,15 @@ function handleResult (
344
483
  }
345
484
 
346
485
  let withinStaleIfErrorThreshold = false
347
- const staleIfErrorExpiry = result.cacheControlDirectives['stale-if-error'] ?? reqCacheControl?.['stale-if-error']
348
- if (staleIfErrorExpiry) {
349
- withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000))
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
- }
486
+ if (!staleResponseRequiresRevalidation(result, globalOpts.type)) {
487
+ const staleIfErrorExpiry = result.cacheControlDirectives['stale-if-error'] ?? reqCacheControl?.['stale-if-error']
488
+ if (staleIfErrorExpiry) {
489
+ withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000))
366
490
  }
367
491
  }
368
492
 
493
+ const headers = makeRevalidationHeaders(opts, result)
494
+
369
495
  // We need to revalidate the response
370
496
  return dispatch(
371
497
  {
@@ -373,8 +499,30 @@ function handleResult (
373
499
  headers
374
500
  },
375
501
  new CacheRevalidationHandler(
376
- (success, context) => {
502
+ (success, context, statusCode, headers) => {
377
503
  if (success) {
504
+ if (statusCode === 304) {
505
+ if (revalidationResponseDisallowsCachedReuse(globalOpts.type, headers)) {
506
+ if (util.isStream(result.body)) {
507
+ result.body.on('error', nop).destroy()
508
+ }
509
+
510
+ deleteCachedValue(globalOpts.store, cacheKey)
511
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
512
+ }
513
+
514
+ if (revalidationResponseUpdatesCacheControl(headers)) {
515
+ deleteCachedValue(globalOpts.store, cacheKey)
516
+ } else if (revalidationResponseAddsVary(result, headers.vary ? parseVaryHeader(headers.vary, opts.headers) : undefined)) {
517
+ if (util.isStream(result.body)) {
518
+ result.body.on('error', nop).destroy()
519
+ }
520
+
521
+ deleteCachedValue(globalOpts.store, cacheKey)
522
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
523
+ }
524
+ }
525
+
378
526
  // TODO: successful revalidation should be considered fresh (not give stale warning).
379
527
  sendCachedValue(handler, opts, result, age, context, stale)
380
528
  } else if (util.isStream(result.body)) {
@@ -431,28 +579,36 @@ module.exports = (opts = {}) => {
431
579
  type
432
580
  }
433
581
 
434
- const safeMethodsToNotCache = util.safeHTTPMethods.filter(method => methods.includes(method) === false)
582
+ const safeMethodsToNotCache = []
583
+ for (let i = 0; i < util.safeHTTPMethods.length; i++) {
584
+ const method = util.safeHTTPMethods[i]
585
+ if (!arrayIncludes(methods, method)) {
586
+ safeMethodsToNotCache.push(method)
587
+ }
588
+ }
435
589
 
436
- return dispatch => {
590
+ return (dispatch, interceptorOrigin) => {
437
591
  return (opts, handler) => {
438
- if (!opts.origin || safeMethodsToNotCache.includes(opts.method)) {
439
- // Not a method we want to cache or we don't have the origin, skip
592
+ const requestOrigin = getInterceptorOrigin(opts, interceptorOrigin)
593
+ if (!requestOrigin || arrayIncludes(safeMethodsToNotCache, opts.method)) {
594
+ // We cannot safely cache without an authoritative origin, or this is
595
+ // not a method we want to cache.
440
596
  return dispatch(opts, handler)
441
597
  }
442
598
 
443
599
  // Check if origin is in whitelist
444
600
  if (origins !== undefined) {
445
- const requestOrigin = opts.origin.toString().toLowerCase()
601
+ const normalizedRequestOrigin = requestOrigin.toString().toLowerCase()
446
602
  let isAllowed = false
447
603
 
448
604
  for (let i = 0; i < origins.length; i++) {
449
605
  const allowed = origins[i]
450
606
  if (typeof allowed === 'string') {
451
- if (allowed.toLowerCase() === requestOrigin) {
607
+ if (allowed.toLowerCase() === normalizedRequestOrigin) {
452
608
  isAllowed = true
453
609
  break
454
610
  }
455
- } else if (allowed.test(requestOrigin)) {
611
+ } else if (allowed.test(normalizedRequestOrigin)) {
456
612
  isAllowed = true
457
613
  break
458
614
  }
@@ -470,7 +626,9 @@ module.exports = (opts = {}) => {
470
626
 
471
627
  const reqCacheControl = opts.headers?.['cache-control']
472
628
  ? parseCacheControlHeader(opts.headers['cache-control'])
473
- : undefined
629
+ : hasPragmaNoCache(opts.headers)
630
+ ? { 'no-cache': true }
631
+ : undefined
474
632
 
475
633
  if (reqCacheControl?.['no-store']) {
476
634
  return dispatch(opts, handler)
@@ -479,7 +637,12 @@ module.exports = (opts = {}) => {
479
637
  /**
480
638
  * @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
481
639
  */
482
- const cacheKey = makeCacheKey(opts)
640
+ const cacheKey = makeCacheKey(opts, requestOrigin)
641
+
642
+ if (!arrayIncludes(util.safeHTTPMethods, opts.method)) {
643
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
644
+ }
645
+
483
646
  const result = store.get(cacheKey)
484
647
 
485
648
  if (result && typeof result.then === 'function') {
@@ -1,7 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const { createInflate, createGunzip, createBrotliDecompress, createZstdDecompress } = require('node:zlib')
4
- const { pipeline } = require('node:stream')
4
+ const { pipeline, Transform: TransformStream } = require('node:stream')
5
+ const { InvalidArgumentError, ResponseExceededMaxSizeError } = require('../core/errors')
5
6
  const DecoratorHandler = require('../handler/decorator-handler')
6
7
 
7
8
  /** @typedef {import('node:stream').Transform} Transform */
@@ -20,6 +21,31 @@ const supportedEncodings = {
20
21
  }
21
22
 
22
23
  const defaultSkipStatusCodes = /** @type {const} */ ([204, 304])
24
+ const defaultMaxSize = 64 * 1024 * 1024
25
+
26
+ /**
27
+ * Limits the output of one stage in a decompression chain.
28
+ * @param {number} maxSize - Maximum output size in bytes
29
+ * @returns {Transform}
30
+ */
31
+ function createMaxSizeLimiter (maxSize) {
32
+ let size = 0
33
+
34
+ return new TransformStream({
35
+ transform (chunk, _encoding, callback) {
36
+ const decompressedSize = size + chunk.length
37
+ if (decompressedSize > maxSize) {
38
+ callback(new ResponseExceededMaxSizeError(
39
+ `Decompressed response size (${decompressedSize}) exceeded maxSize (${maxSize})`
40
+ ))
41
+ return
42
+ }
43
+
44
+ size = decompressedSize
45
+ callback(null, chunk)
46
+ }
47
+ })
48
+ }
23
49
 
24
50
  let warningEmitted = /** @type {boolean} */ (false)
25
51
 
@@ -27,20 +53,36 @@ let warningEmitted = /** @type {boolean} */ (false)
27
53
  * @typedef {Object} DecompressHandlerOptions
28
54
  * @property {number[]|Readonly<number[]>} [skipStatusCodes=[204, 304]] - List of status codes to skip decompression for
29
55
  * @property {boolean} [skipErrorResponses] - Whether to skip decompression for error responses (status codes >= 400)
56
+ * @property {number} [maxSize=67108864] - Maximum decompressed response size in bytes
30
57
  */
31
58
 
32
59
  class DecompressHandler extends DecoratorHandler {
33
60
  /** @type {Transform[]} */
34
61
  #decompressors = []
62
+ /** @type {Record<string, string | string[]> | undefined} */
63
+ #trailers
35
64
  /** @type {Readonly<number[]>} */
36
65
  #skipStatusCodes
37
66
  /** @type {boolean} */
38
67
  #skipErrorResponses
68
+ /** @type {number} */
69
+ #maxSize
70
+ /** @type {number} */
71
+ #decompressedSize = 0
72
+ /** @type {boolean} */
73
+ #terminated = false
74
+ /** @type {boolean} */
75
+ #inputEnded = false
76
+
77
+ constructor (handler, { skipStatusCodes = defaultSkipStatusCodes, skipErrorResponses = true, maxSize = defaultMaxSize } = {}) {
78
+ if (!Number.isSafeInteger(maxSize) || maxSize < 1) {
79
+ throw new InvalidArgumentError('maxSize must be a positive integer')
80
+ }
39
81
 
40
- constructor (handler, { skipStatusCodes = defaultSkipStatusCodes, skipErrorResponses = true } = {}) {
41
82
  super(handler)
42
83
  this.#skipStatusCodes = skipStatusCodes
43
84
  this.#skipErrorResponses = skipErrorResponses
85
+ this.#maxSize = maxSize
44
86
  }
45
87
 
46
88
  /**
@@ -60,7 +102,7 @@ class DecompressHandler extends DecoratorHandler {
60
102
  * Creates a chain of decompressors for multiple content encodings
61
103
  *
62
104
  * @param {string} encodings - Comma-separated list of content encodings
63
- * @returns {Array<DecompressorStream>} - Array of decompressor streams
105
+ * @returns {Array<Transform>} - Array of decompressor and limiting streams
64
106
  * @throws {Error} - If the number of content-encodings exceeds the maximum allowed
65
107
  */
66
108
  #createDecompressionChain (encodings) {
@@ -88,7 +130,40 @@ class DecompressHandler extends DecoratorHandler {
88
130
  decompressors.push(supportedEncodings[encoding]())
89
131
  }
90
132
 
91
- return decompressors
133
+ if (decompressors.length < 2) {
134
+ return decompressors
135
+ }
136
+
137
+ /** @type {Transform[]} */
138
+ const streams = []
139
+ for (let i = 0; i < decompressors.length; i++) {
140
+ streams.push(decompressors[i])
141
+ if (i < decompressors.length - 1) {
142
+ streams.push(createMaxSizeLimiter(this.#maxSize))
143
+ }
144
+ }
145
+
146
+ return streams
147
+ }
148
+
149
+ /**
150
+ * Stops decompression and reports an error.
151
+ * @param {Controller} controller - The controller to coordinate with
152
+ * @param {Error} error - The decompression error
153
+ * @returns {void}
154
+ */
155
+ #fail (controller, error) {
156
+ if (this.#terminated) {
157
+ return
158
+ }
159
+
160
+ if (this.#inputEnded) {
161
+ // The request is already marked complete once the compressed input ends,
162
+ // so controller.abort() can no longer propagate decoder flush errors.
163
+ this.onResponseError(controller, error)
164
+ } else {
165
+ controller.abort(error)
166
+ }
92
167
  }
93
168
 
94
169
  /**
@@ -99,8 +174,21 @@ class DecompressHandler extends DecoratorHandler {
99
174
  */
100
175
  #setupDecompressorEvents (decompressor, controller) {
101
176
  decompressor.on('readable', () => {
177
+ if (this.#terminated) {
178
+ return
179
+ }
180
+
102
181
  let chunk
103
182
  while ((chunk = decompressor.read()) !== null) {
183
+ const decompressedSize = this.#decompressedSize + chunk.length
184
+ if (decompressedSize > this.#maxSize) {
185
+ this.#fail(controller, new ResponseExceededMaxSizeError(
186
+ `Decompressed response size (${decompressedSize}) exceeded maxSize (${this.#maxSize})`
187
+ ))
188
+ return
189
+ }
190
+
191
+ this.#decompressedSize = decompressedSize
104
192
  const result = super.onResponseData(controller, chunk)
105
193
  if (result === false) {
106
194
  break
@@ -109,7 +197,7 @@ class DecompressHandler extends DecoratorHandler {
109
197
  })
110
198
 
111
199
  decompressor.on('error', (error) => {
112
- super.onResponseError(controller, error)
200
+ this.#fail(controller, error)
113
201
  })
114
202
  }
115
203
 
@@ -123,7 +211,13 @@ class DecompressHandler extends DecoratorHandler {
123
211
  this.#setupDecompressorEvents(decompressor, controller)
124
212
 
125
213
  decompressor.on('end', () => {
126
- super.onResponseEnd(controller, {})
214
+ if (this.#terminated) {
215
+ return
216
+ }
217
+
218
+ this.#terminated = true
219
+ this.#cleanupDecompressors()
220
+ super.onResponseEnd(controller, this.#trailers)
127
221
  })
128
222
  }
129
223
 
@@ -137,11 +231,18 @@ class DecompressHandler extends DecoratorHandler {
137
231
  this.#setupDecompressorEvents(lastDecompressor, controller)
138
232
 
139
233
  pipeline(this.#decompressors, (err) => {
234
+ if (this.#terminated) {
235
+ return
236
+ }
237
+
140
238
  if (err) {
141
- super.onResponseError(controller, err)
239
+ this.#fail(controller, err)
142
240
  return
143
241
  }
144
- super.onResponseEnd(controller, {})
242
+
243
+ this.#terminated = true
244
+ this.#cleanupDecompressors()
245
+ super.onResponseEnd(controller, this.#trailers)
145
246
  })
146
247
  }
147
248
 
@@ -196,7 +297,7 @@ class DecompressHandler extends DecoratorHandler {
196
297
 
197
298
  filteredHeaders.push(rawHeaders[i], rawHeaders[i + 1])
198
299
  }
199
- controller.rawHeaders = filteredHeaders
300
+ rawHeaders.splice(0, rawHeaders.length, ...filteredHeaders)
200
301
  } else if (typeof rawHeaders === 'object') {
201
302
  for (const name of Object.keys(rawHeaders)) {
202
303
  const lowerName = name.toLowerCase()
@@ -236,8 +337,9 @@ class DecompressHandler extends DecoratorHandler {
236
337
  */
237
338
  onResponseEnd (controller, trailers) {
238
339
  if (this.#decompressors.length > 0) {
340
+ this.#inputEnded = true
341
+ this.#trailers = trailers
239
342
  this.#decompressors[0].end()
240
- this.#cleanupDecompressors()
241
343
  return
242
344
  }
243
345
  super.onResponseEnd(controller, trailers)
@@ -249,12 +351,15 @@ class DecompressHandler extends DecoratorHandler {
249
351
  * @returns {void}
250
352
  */
251
353
  onResponseError (controller, err) {
252
- if (this.#decompressors.length > 0) {
253
- for (const decompressor of this.#decompressors) {
254
- decompressor.destroy(err)
255
- }
256
- this.#cleanupDecompressors()
354
+ if (this.#terminated) {
355
+ return
356
+ }
357
+
358
+ this.#terminated = true
359
+ for (const decompressor of this.#decompressors) {
360
+ decompressor.destroy()
257
361
  }
362
+ this.#cleanupDecompressors()
258
363
  super.onResponseError(controller, err)
259
364
  }
260
365
  }
@@ -276,6 +381,10 @@ function createDecompressInterceptor (options = {}) {
276
381
 
277
382
  return (dispatch) => {
278
383
  return (opts, handler) => {
384
+ if (opts.method === 'HEAD') {
385
+ return dispatch(opts, handler)
386
+ }
387
+
279
388
  const decompressHandler = new DecompressHandler(handler, options)
280
389
  return dispatch(opts, decompressHandler)
281
390
  }
@@ -3,7 +3,7 @@
3
3
  const diagnosticsChannel = require('node:diagnostics_channel')
4
4
  const util = require('../core/util')
5
5
  const DeduplicationHandler = require('../handler/deduplication-handler')
6
- const { normalizeHeaders, makeCacheKey, makeDeduplicationKey } = require('../util/cache.js')
6
+ const { getInterceptorOrigin, normalizeHeaders, makeCacheKey, makeDeduplicationKey } = require('../util/cache.js')
7
7
 
8
8
  const pendingRequestsChannel = diagnosticsChannel.channel('undici:request:pending-requests')
9
9
 
@@ -57,9 +57,10 @@ module.exports = (opts = {}) => {
57
57
  */
58
58
  const pendingRequests = new Map()
59
59
 
60
- return dispatch => {
60
+ return (dispatch, interceptorOrigin) => {
61
61
  return (opts, handler) => {
62
- if (!opts.origin || methods.includes(opts.method) === false) {
62
+ const requestOrigin = getInterceptorOrigin(opts, interceptorOrigin)
63
+ if (!requestOrigin || opts.upgrade || methods.includes(opts.method) === false) {
63
64
  return dispatch(opts, handler)
64
65
  }
65
66
 
@@ -77,7 +78,7 @@ module.exports = (opts = {}) => {
77
78
  }
78
79
  }
79
80
 
80
- const cacheKey = makeCacheKey(opts)
81
+ const cacheKey = makeCacheKey(opts, requestOrigin)
81
82
  const dedupeKey = makeDeduplicationKey(cacheKey, excludeHeaderNamesSet)
82
83
 
83
84
  // Check if there's already a pending request for this key