@openclaw/discord 2026.6.8 → 2026.6.9

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 (114) hide show
  1. package/dist/action-runtime-api.js +1 -1
  2. package/dist/{api-BmXlcjYW.js → api-4GFMoWu0.js} +4 -1
  3. package/dist/api.js +15 -15
  4. package/dist/{approval-handler.runtime-B3gyUd-L.js → approval-handler.runtime-20qhf0uH.js} +3 -3
  5. package/dist/{audit-BlfewK04.js → audit-CCffO6o7.js} +3 -3
  6. package/dist/{channel-actions-BnPHwCZ_.js → channel-actions-DDT9kJXw.js} +12 -6
  7. package/dist/{channel-actions.runtime-DX5iW6ut.js → channel-actions.runtime-CfMUtfAv.js} +5 -5
  8. package/dist/{channel-b0hY1EJw.js → channel-fEBKaO2Q.js} +28 -20
  9. package/dist/channel-plugin-api.js +1 -1
  10. package/dist/{channel.setup-By5cfELZ.js → channel.setup-DRJiHe7I.js} +2 -3
  11. package/dist/{components-D-CYw0-b.js → components-CXEorOhu.js} +1 -1
  12. package/dist/{conversation-identity-CKzQAqFF.js → conversation-identity-BvlP-djn.js} +2 -2
  13. package/dist/{directory-config-Bu7FYOsl.js → directory-config-Bg8bulWB.js} +1 -1
  14. package/dist/directory-contract-api.js +1 -1
  15. package/dist/{directory-live-LjENjK6L.js → directory-live-BAJw6kS2.js} +2 -2
  16. package/dist/{handle-action.guild-admin-BS2qnhXF.js → handle-action.guild-admin-RxnJFLgU.js} +14 -9
  17. package/dist/index.js +1 -1
  18. package/dist/{manager.runtime-BU1vkOeO.js → manager.runtime-Bh5X0o8P.js} +12 -4
  19. package/dist/{message-handler-CQVkXHMN.js → message-handler-Dg3DRD0e.js} +6 -6
  20. package/dist/{message-handler.preflight-DAnLOeDA.js → message-handler.preflight-CcexCYuy.js} +10 -10
  21. package/dist/{message-handler.process-Dp5NQT05.js → message-handler.process-CN8bVDxj.js} +37 -45
  22. package/dist/{message-utils-Bx993JLN.js → message-utils-ConlUKCU.js} +1 -1
  23. package/dist/{outbound-adapter-CmN7ao1t.js → outbound-adapter-XuMiR77A.js} +6 -6
  24. package/dist/{pluralkit-SYmlmerw.js → pluralkit-o6s-EP9g.js} +4 -2
  25. package/dist/{probe-2MdGjUAf.js → probe-CxnDikWm.js} +5 -2
  26. package/dist/{probe.runtime-J927koKG.js → probe.runtime-DLFkSDVB.js} +1 -1
  27. package/dist/{provider-DrScDA1p.js → provider-4VG5CvC-.js} +98 -69
  28. package/dist/{provider-session.runtime-DXTzSYOJ.js → provider-session.runtime-ChdHgGCo.js} +3 -3
  29. package/dist/provider.runtime-B4QuZl_o.js +2 -0
  30. package/dist/{resolve-allowlist-common-B3p7cT8F.js → resolve-allowlist-common-BDpBb8dF.js} +1 -1
  31. package/dist/{resolve-channels-Rautpk8n.js → resolve-channels-DpM2v0Am.js} +3 -3
  32. package/dist/{resolve-users-Bw7vvtsi.js → resolve-users-Dl6np4pH.js} +3 -3
  33. package/dist/{runtime-C80YEJ7Z.js → runtime-CkdyyjTb.js} +24 -10
  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-9pShEBWs.js +6 -0
  38. package/dist/runtime-api.monitor.js +4 -4
  39. package/dist/runtime-api.send.js +5 -5
  40. package/dist/runtime-api.threads.js +3 -3
  41. package/dist/{send-zGsXF-up.js → send-CIhsDMqs.js} +8 -5
  42. package/dist/{send.components-ktzrUTkt.js → send.components-DxoGyTkn.js} +4 -4
  43. package/dist/{send.outbound-C8oC51um.js → send.outbound-DaUyYuZa.js} +53 -10
  44. package/dist/{send.receipt-DsQWEQ2O.js → send.receipt-De_2iEWU.js} +2 -24
  45. package/dist/{send.shared-Dvo2ZCVG.js → send.shared-gtgBGDuV.js} +13 -5
  46. package/dist/session-binding-contract-api.js +1 -1
  47. package/dist/setup-plugin-api.js +1 -1
  48. package/dist/{subagent-hooks-kjrWDeDg.js → subagent-hooks-DPCipg2M.js} +2 -2
  49. package/dist/subagent-hooks-api.js +1 -1
  50. package/dist/{system-events-CvU3Aduf.js → system-events-4vmuyaBF.js} +1 -1
  51. package/dist/{target-resolver-DMPTzuo7.js → target-resolver-CVrOhQyC.js} +2 -2
  52. package/dist/targets-CRGgRKs7.js +3 -0
  53. package/dist/{thread-bindings-DO32M2kW.js → thread-bindings-D2SBj6F8.js} +4 -4
  54. package/dist/{thread-bindings.discord-api-304M1PMr.js → thread-bindings.discord-api-C7STOVXm.js} +4 -4
  55. package/dist/{thread-bindings.manager-C9YT7wF2.js → thread-bindings.manager-CN4MIGoJ.js} +3 -3
  56. package/dist/{transcripts-source-Chy2OrO_.js → transcripts-source-39NBdNeE.js} +1 -1
  57. package/dist/transcripts-source-api.js +1 -1
  58. package/dist/{typing-BaivbXIG.js → typing-CxIYS3vQ.js} +1 -1
  59. package/node_modules/undici/README.md +59 -18
  60. package/node_modules/undici/docs/docs/GettingStarted.md +278 -0
  61. package/node_modules/undici/docs/docs/api/Agent.md +3 -0
  62. package/node_modules/undici/docs/docs/api/BalancedPool.md +1 -1
  63. package/node_modules/undici/docs/docs/api/Client.md +44 -5
  64. package/node_modules/undici/docs/docs/api/Connector.md +1 -0
  65. package/node_modules/undici/docs/docs/api/Cookies.md +28 -1
  66. package/node_modules/undici/docs/docs/api/Dispatcher.md +22 -5
  67. package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +6 -9
  68. package/node_modules/undici/docs/docs/api/Errors.md +12 -0
  69. package/node_modules/undici/docs/docs/api/EventSource.md +50 -3
  70. package/node_modules/undici/docs/docs/api/Fetch.md +5 -3
  71. package/node_modules/undici/docs/docs/api/H2CClient.md +3 -3
  72. package/node_modules/undici/docs/docs/api/MockAgent.md +1 -1
  73. package/node_modules/undici/docs/docs/api/MockCallHistory.md +1 -1
  74. package/node_modules/undici/docs/docs/api/Pool.md +4 -1
  75. package/node_modules/undici/docs/docs/api/RedirectHandler.md +4 -1
  76. package/node_modules/undici/docs/docs/api/RetryAgent.md +3 -3
  77. package/node_modules/undici/docs/docs/api/RetryHandler.md +6 -6
  78. package/node_modules/undici/docs/docs/api/RoundRobinPool.md +1 -1
  79. package/node_modules/undici/docs/docs/api/SnapshotAgent.md +3 -3
  80. package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +1 -0
  81. package/node_modules/undici/docs/docs/api/api-lifecycle.md +4 -4
  82. package/node_modules/undici/lib/core/connect.js +29 -4
  83. package/node_modules/undici/lib/core/util.js +8 -6
  84. package/node_modules/undici/lib/dispatcher/client-h1.js +69 -2
  85. package/node_modules/undici/lib/dispatcher/client-h2.js +160 -37
  86. package/node_modules/undici/lib/dispatcher/client.js +36 -7
  87. package/node_modules/undici/lib/dispatcher/dispatcher-base.js +1 -0
  88. package/node_modules/undici/lib/dispatcher/proxy-agent.js +2 -1
  89. package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +4 -2
  90. package/node_modules/undici/lib/handler/redirect-handler.js +36 -11
  91. package/node_modules/undici/lib/interceptor/dns.js +4 -0
  92. package/node_modules/undici/lib/interceptor/redirect.js +3 -3
  93. package/node_modules/undici/lib/mock/mock-call-history.js +1 -1
  94. package/node_modules/undici/lib/mock/snapshot-agent.js +9 -1
  95. package/node_modules/undici/lib/util/cache.js +8 -2
  96. package/node_modules/undici/lib/web/cookies/parse.js +17 -25
  97. package/node_modules/undici/lib/web/eventsource/eventsource.js +7 -18
  98. package/node_modules/undici/lib/web/eventsource/util.js +32 -1
  99. package/node_modules/undici/lib/web/fetch/body.js +43 -0
  100. package/node_modules/undici/lib/web/fetch/index.js +17 -3
  101. package/node_modules/undici/lib/web/fetch/request.js +33 -3
  102. package/node_modules/undici/lib/web/websocket/receiver.js +20 -3
  103. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +8 -1
  104. package/node_modules/undici/lib/web/websocket/websocket.js +3 -1
  105. package/node_modules/undici/package.json +1 -1
  106. package/node_modules/undici/types/client.d.ts +5 -0
  107. package/node_modules/undici/types/connector.d.ts +1 -0
  108. package/node_modules/undici/types/fetch.d.ts +5 -1
  109. package/node_modules/undici/types/interceptors.d.ts +1 -1
  110. package/npm-shrinkwrap.json +7 -7
  111. package/package.json +5 -5
  112. package/dist/provider.runtime-nb-6cRoy.js +0 -2
  113. package/dist/runtime-api.monitor-D8KNDAd5.js +0 -6
  114. package/dist/targets-CKaNidbk.js +0 -3
@@ -29,9 +29,11 @@ class RedirectHandler {
29
29
 
30
30
  this.dispatch = dispatch
31
31
  this.location = null
32
- const { maxRedirections: _, ...cleanOpts } = opts
32
+ const { maxRedirections: _, stripHeadersOnRedirect, stripHeadersOnCrossOriginRedirect, ...cleanOpts } = opts
33
33
  this.opts = cleanOpts // opts must be a copy, exclude maxRedirections
34
34
  this.opts.body = util.wrapRequestBody(this.opts.body)
35
+ this.stripHeadersOnRedirect = normalizeStripHeaders(stripHeadersOnRedirect, 'stripHeadersOnRedirect')
36
+ this.stripHeadersOnCrossOriginRedirect = normalizeStripHeaders(stripHeadersOnCrossOriginRedirect, 'stripHeadersOnCrossOriginRedirect')
35
37
  this.maxRedirections = maxRedirections
36
38
  this.handler = handler
37
39
  this.history = []
@@ -100,7 +102,7 @@ class RedirectHandler {
100
102
  // Remove headers referring to the original URL.
101
103
  // By default it is Host only, unless it's a 303 (see below), which removes also all Content-* headers.
102
104
  // https://tools.ietf.org/html/rfc7231#section-6.4
103
- this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin)
105
+ this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin, this.stripHeadersOnRedirect, this.stripHeadersOnCrossOriginRedirect)
104
106
  this.opts.path = path
105
107
  this.opts.origin = origin
106
108
  this.opts.query = null
@@ -152,26 +154,49 @@ class RedirectHandler {
152
154
  }
153
155
 
154
156
  // https://tools.ietf.org/html/rfc7231#section-6.4.4
155
- function shouldRemoveHeader (header, removeContent, unknownOrigin) {
156
- if (header.length === 4) {
157
- return util.headerNameToString(header) === 'host'
157
+ function shouldRemoveHeader (header, removeContent, unknownOrigin, stripHeaders, stripHeadersOnCrossOrigin) {
158
+ const name = util.headerNameToString(header)
159
+ if (name === 'host') {
160
+ return true
161
+ }
162
+ if (stripHeaders?.has(name) || (unknownOrigin && stripHeadersOnCrossOrigin?.has(name))) {
163
+ return true
158
164
  }
159
- if (removeContent && util.headerNameToString(header).startsWith('content-')) {
165
+ if (removeContent && name.startsWith('content-')) {
160
166
  return true
161
167
  }
162
- if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) {
163
- const name = util.headerNameToString(header)
168
+ if (unknownOrigin) {
164
169
  return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization'
165
170
  }
166
171
  return false
167
172
  }
168
173
 
169
174
  // https://tools.ietf.org/html/rfc7231#section-6.4
170
- function cleanRequestHeaders (headers, removeContent, unknownOrigin) {
175
+ function normalizeStripHeaders (headers, optionName) {
176
+ if (headers == null) {
177
+ return null
178
+ }
179
+
180
+ if (!Array.isArray(headers)) {
181
+ throw new InvalidArgumentError(`${optionName} must be an array`)
182
+ }
183
+
184
+ const normalized = new Set()
185
+ for (const header of headers) {
186
+ if (typeof header !== 'string') {
187
+ throw new InvalidArgumentError(`${optionName} must contain header names`)
188
+ }
189
+
190
+ normalized.add(util.headerNameToString(header))
191
+ }
192
+ return normalized
193
+ }
194
+
195
+ function cleanRequestHeaders (headers, removeContent, unknownOrigin, stripHeaders, stripHeadersOnCrossOrigin) {
171
196
  const ret = []
172
197
  if (Array.isArray(headers)) {
173
198
  for (let i = 0; i < headers.length; i += 2) {
174
- if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin)) {
199
+ if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin, stripHeaders, stripHeadersOnCrossOrigin)) {
175
200
  ret.push(headers[i], headers[i + 1])
176
201
  }
177
202
  }
@@ -179,7 +204,7 @@ function cleanRequestHeaders (headers, removeContent, unknownOrigin) {
179
204
  const entries = util.hasSafeIterator(headers) ? headers : Object.entries(headers)
180
205
 
181
206
  for (const [key, value] of entries) {
182
- if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) {
207
+ if (!shouldRemoveHeader(key, removeContent, unknownOrigin, stripHeaders, stripHeadersOnCrossOrigin)) {
183
208
  ret.push(key, value)
184
209
  }
185
210
  }
@@ -535,6 +535,10 @@ module.exports = interceptorOpts => {
535
535
 
536
536
  return dispatch => {
537
537
  return function dnsInterceptor (origDispatchOpts, handler) {
538
+ if (origDispatchOpts.origin == null) {
539
+ return dispatch(origDispatchOpts, handler)
540
+ }
541
+
538
542
  const origin =
539
543
  origDispatchOpts.origin.constructor === URL
540
544
  ? origDispatchOpts.origin
@@ -2,16 +2,16 @@
2
2
 
3
3
  const RedirectHandler = require('../handler/redirect-handler')
4
4
 
5
- function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections, throwOnMaxRedirect: defaultThrowOnMaxRedirect } = {}) {
5
+ function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections, throwOnMaxRedirect: defaultThrowOnMaxRedirect, stripHeadersOnRedirect: defaultStripHeadersOnRedirect, stripHeadersOnCrossOriginRedirect: defaultStripHeadersOnCrossOriginRedirect } = {}) {
6
6
  return (dispatch) => {
7
7
  return function Intercept (opts, handler) {
8
- const { maxRedirections = defaultMaxRedirections, throwOnMaxRedirect = defaultThrowOnMaxRedirect, ...rest } = opts
8
+ const { maxRedirections = defaultMaxRedirections, throwOnMaxRedirect = defaultThrowOnMaxRedirect, stripHeadersOnRedirect = defaultStripHeadersOnRedirect, stripHeadersOnCrossOriginRedirect = defaultStripHeadersOnCrossOriginRedirect, ...rest } = opts
9
9
 
10
10
  if (maxRedirections == null || maxRedirections === 0) {
11
11
  return dispatch(opts, handler)
12
12
  }
13
13
 
14
- const dispatchOpts = { ...rest, throwOnMaxRedirect } // Stop sub dispatcher from also redirecting.
14
+ const dispatchOpts = { ...rest, throwOnMaxRedirect, stripHeadersOnRedirect, stripHeadersOnCrossOriginRedirect } // Stop sub dispatcher from also redirecting.
15
15
  const redirectHandler = new RedirectHandler(dispatch, maxRedirections, dispatchOpts, handler)
16
16
  return dispatch(dispatchOpts, redirectHandler)
17
17
  }
@@ -35,7 +35,7 @@ function buildAndValidateFilterCallsOptions (options = {}) {
35
35
  }
36
36
 
37
37
  function makeFilterCalls (parameterName) {
38
- return (parameterValue, logs) => {
38
+ return (parameterValue, logs = this.logs) => {
39
39
  if (typeof parameterValue === 'string' || parameterValue == null) {
40
40
  return logs.filter((log) => {
41
41
  return log[parameterName] === parameterValue
@@ -354,7 +354,15 @@ class SnapshotAgent extends MockAgent {
354
354
  * @returns {Promise<void>}
355
355
  */
356
356
  async close () {
357
- await this[kSnapshotRecorder].close()
357
+ // In playback mode the recorder must not persist to disk. findSnapshot()
358
+ // mutates each matched snapshot's callCount, so saving on close would
359
+ // rewrite the snapshot file even though nothing new was recorded. Only
360
+ // record/update modes should write snapshots; playback just cleans up.
361
+ if (this[kSnapshotMode] === 'playback') {
362
+ this[kSnapshotRecorder].destroy()
363
+ } else {
364
+ await this[kSnapshotRecorder].close()
365
+ }
358
366
  await this[kRealAgent]?.close()
359
367
  await super.close()
360
368
  }
@@ -228,6 +228,10 @@ function parseCacheControlHeader (header) {
228
228
  headers[headers.length - 1] = lastHeader
229
229
  }
230
230
 
231
+ for (let j = 0; j < headers.length; j++) {
232
+ headers[j] = headers[j].trim()
233
+ }
234
+
231
235
  if (key in output) {
232
236
  output[key] = output[key].concat(headers)
233
237
  } else {
@@ -236,10 +240,12 @@ function parseCacheControlHeader (header) {
236
240
  }
237
241
  } else {
238
242
  // Something like `no-cache="some-header"`
243
+ const fieldName = value.trim()
244
+
239
245
  if (key in output) {
240
- output[key] = output[key].concat(value)
246
+ output[key] = output[key].concat(fieldName)
241
247
  } else {
242
- output[key] = [value]
248
+ output[key] = [fieldName]
243
249
  }
244
250
  }
245
251
 
@@ -4,7 +4,6 @@ const { collectASequenceOfCodePointsFast } = require('../infra')
4
4
  const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants')
5
5
  const { isCTLExcludingHtab } = require('./util')
6
6
  const assert = require('node:assert')
7
- const { unescape: qsUnescape } = require('node:querystring')
8
7
 
9
8
  /**
10
9
  * @description Parses the field-value attributes of a set-cookie header string.
@@ -82,7 +81,7 @@ function parseSetCookie (header) {
82
81
  // store arbitrary data in a cookie-value SHOULD encode that data, for
83
82
  // example, using Base64 [RFC4648].
84
83
  return {
85
- name, value: qsUnescape(value), ...parseUnparsedAttributes(unparsedAttributes)
84
+ name, value, ...parseUnparsedAttributes(unparsedAttributes)
86
85
  }
87
86
  }
88
87
 
@@ -280,32 +279,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
280
279
  // If the attribute-name case-insensitively matches the string
281
280
  // "SameSite", the user agent MUST process the cookie-av as follows:
282
281
 
283
- // 1. Let enforcement be "Default".
284
- let enforcement = 'Default'
285
-
286
282
  const attributeValueLowercase = attributeValue.toLowerCase()
287
- // 2. If cookie-av's attribute-value is a case-insensitive match for
288
- // "None", set enforcement to "None".
289
- if (attributeValueLowercase.includes('none')) {
290
- enforcement = 'None'
291
- }
292
283
 
293
- // 3. If cookie-av's attribute-value is a case-insensitive match for
294
- // "Strict", set enforcement to "Strict".
295
- if (attributeValueLowercase.includes('strict')) {
296
- enforcement = 'Strict'
284
+ // 1. If cookie-av's attribute-value is a case-insensitive match for
285
+ // "None", append an attribute to the cookie-attribute-list with an
286
+ // attribute-name of "SameSite" and an attribute-value of "None".
287
+ if (attributeValueLowercase === 'none') {
288
+ cookieAttributeList.sameSite = 'None'
289
+ } else if (attributeValueLowercase === 'strict') {
290
+ // 2. If cookie-av's attribute-value is a case-insensitive match for
291
+ // "Strict", append an attribute to the cookie-attribute-list with
292
+ // an attribute-name of "SameSite" and an attribute-value of
293
+ // "Strict".
294
+ cookieAttributeList.sameSite = 'Strict'
295
+ } else if (attributeValueLowercase === 'lax') {
296
+ // 3. If cookie-av's attribute-value is a case-insensitive match for
297
+ // "Lax", append an attribute to the cookie-attribute-list with an
298
+ // attribute-name of "SameSite" and an attribute-value of "Lax".
299
+ cookieAttributeList.sameSite = 'Lax'
297
300
  }
298
-
299
- // 4. If cookie-av's attribute-value is a case-insensitive match for
300
- // "Lax", set enforcement to "Lax".
301
- if (attributeValueLowercase.includes('lax')) {
302
- enforcement = 'Lax'
303
- }
304
-
305
- // 5. Append an attribute to the cookie-attribute-list with an
306
- // attribute-name of "SameSite" and an attribute-value of
307
- // enforcement.
308
- cookieAttributeList.sameSite = enforcement
309
301
  } else {
310
302
  cookieAttributeList.unparsed ??= []
311
303
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { pipeline } = require('node:stream')
4
4
  const { fetching } = require('../fetch')
5
- const { makeRequest } = require('../fetch/request')
6
5
  const { webidl } = require('../webidl')
7
6
  const { EventSourceStream } = require('./eventsource-stream')
8
7
  const { parseMIMEType } = require('../fetch/data-url')
@@ -10,6 +9,7 @@ const { createFastMessageEvent } = require('../websocket/events')
10
9
  const { isNetworkError } = require('../fetch/response')
11
10
  const { kEnumerableProperty } = require('../../core/util')
12
11
  const { environmentSettingsObject } = require('../fetch/util')
12
+ const { createPotentialCORSRequest } = require('./util')
13
13
 
14
14
  let experimentalWarned = false
15
15
 
@@ -160,33 +160,22 @@ class EventSource extends EventTarget {
160
160
 
161
161
  // 8. Let request be the result of creating a potential-CORS request given
162
162
  // urlRecord, the empty string, and corsAttributeState.
163
- const initRequest = {
164
- redirect: 'follow',
165
- keepalive: true,
166
- // @see https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes
167
- mode: 'cors',
168
- credentials: corsAttributeState === 'anonymous'
169
- ? 'same-origin'
170
- : 'omit',
171
- referrer: 'no-referrer'
172
- }
163
+ const request = createPotentialCORSRequest(urlRecord, '', corsAttributeState)
173
164
 
174
165
  // 9. Set request's client to settings.
175
- initRequest.client = environmentSettingsObject.settingsObject
166
+ request.client = environmentSettingsObject.settingsObject
176
167
 
177
168
  // 10. User agents may set (`Accept`, `text/event-stream`) in request's header list.
178
- initRequest.headersList = [['accept', { name: 'accept', value: 'text/event-stream' }]]
169
+ request.headersList.set('Accept', 'text/event-stream')
179
170
 
180
171
  // 11. Set request's cache mode to "no-store".
181
- initRequest.cache = 'no-store'
172
+ request.cache = 'no-store'
182
173
 
183
174
  // 12. Set request's initiator type to "other".
184
- initRequest.initiator = 'other'
185
-
186
- initRequest.urlList = [new URL(this.#url)]
175
+ request.initiator = 'other'
187
176
 
188
177
  // 13. Set ev's request to request.
189
- this.#request = makeRequest(initRequest)
178
+ this.#request = request
190
179
 
191
180
  this.#connect()
192
181
  }
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { makeRequest } = require('../fetch/request')
4
+
3
5
  /**
4
6
  * Checks if the given value is a valid LastEventId.
5
7
  * @param {string} value
@@ -23,7 +25,36 @@ function isASCIINumber (value) {
23
25
  return true
24
26
  }
25
27
 
28
+ function createPotentialCORSRequest (url, destination, corsAttributeState, sameOriginFallback) {
29
+ // 1. Let mode be "no-cors" if corsAttributeState is No CORS, and "cors" otherwise.
30
+ let mode = corsAttributeState === 'no cors' ? 'no-cors' : 'cors'
31
+
32
+ // 2. If same-origin fallback flag is set and mode is "no-cors", set mode to "same-origin".
33
+ if (sameOriginFallback && mode === 'no-cors') {
34
+ mode = 'same-origin'
35
+ }
36
+
37
+ // 3. Let credentialsMode be "include".
38
+ let credentialsMode = 'include'
39
+
40
+ // 4. If corsAttributeState is Anonymous, set credentialsMode to "same-origin".
41
+ if (corsAttributeState === 'anonymous') {
42
+ credentialsMode = 'same-origin'
43
+ }
44
+
45
+ // 5. Return a new request whose URL is url, destination is destination, mode is mode,
46
+ // credentials mode is credentialsMode, and whose use-URL-credentials flag is set.
47
+ return makeRequest({
48
+ urlList: [url],
49
+ destination,
50
+ mode,
51
+ credentials: credentialsMode,
52
+ useCredentials: true
53
+ })
54
+ }
55
+
26
56
  module.exports = {
27
57
  isValidLastEventId,
28
- isASCIINumber
58
+ isASCIINumber,
59
+ createPotentialCORSRequest
29
60
  }
@@ -392,6 +392,49 @@ function bodyMixinMethods (instance, getInternalState) {
392
392
  return consumeBody(this, (bytes) => {
393
393
  return new Uint8Array(bytes)
394
394
  }, instance, getInternalState)
395
+ },
396
+
397
+ textStream () {
398
+ const this_ = getInternalState(this)
399
+
400
+ // 1. If this is unusable, then throw a TypeError.
401
+ if (bodyUnusable(this_)) {
402
+ throw new TypeError('Body is unusable: Body has already been read')
403
+ }
404
+
405
+ // 2. If this’s body is null:
406
+ if (this_.body == null) {
407
+ // 2.1. Let emptyStream be a new ReadableStream in this’s relevant realm.
408
+ // 2.2. Set up emptyStream.
409
+ /** @type {ReadableStreamDefaultController<any>} */
410
+ let controller
411
+ const emptyStream = new ReadableStream({
412
+ start: (c) => {
413
+ controller = c
414
+ },
415
+ pull: () => Promise.resolve(),
416
+ cancel: () => Promise.resolve()
417
+ }, {
418
+ size: () => 1
419
+ })
420
+
421
+ // 2.3. Close emptyStream.
422
+ controller.close()
423
+
424
+ // 2.4. Return emptyStream.
425
+ return emptyStream
426
+ }
427
+
428
+ // 3. Let stream be this’s body’s stream.
429
+ /** @type {ReadableStream} */
430
+ const stream = this_.body.stream
431
+
432
+ // 4. Let decoder be a new TextDecoderStream object in this’s relevant realm.
433
+ // 5. Set up decoder with UTF-8.
434
+ const decoder = new TextDecoderStream('UTF-8')
435
+
436
+ // 6. Return the result of stream, piped through decoder.
437
+ return stream.pipeThrough(decoder)
395
438
  }
396
439
  }
397
440
 
@@ -11,7 +11,7 @@ const {
11
11
  getResponseState
12
12
  } = require('./response')
13
13
  const { HeadersList } = require('./headers')
14
- const { Request, cloneRequest, getRequestDispatcher, getRequestState } = require('./request')
14
+ const { Request, cloneRequest, getRequestDispatcher, getRequestState, removeRequestAbortListener } = require('./request')
15
15
  const zlib = require('node:zlib')
16
16
  const {
17
17
  makePolicyContainer,
@@ -208,7 +208,7 @@ function fetch (input, init = undefined) {
208
208
  let controller = null
209
209
 
210
210
  // 11. Add the following abort steps to requestObject’s signal:
211
- addAbortListener(
211
+ const removeAbortListener = addAbortListener(
212
212
  requestObject.signal,
213
213
  () => {
214
214
  // 1. Set locallyAborted to true.
@@ -228,6 +228,15 @@ function fetch (input, init = undefined) {
228
228
  }
229
229
  )
230
230
 
231
+ // Remove the `abort` listeners registered above and in the Request
232
+ // constructor once the fetch has settled. Without this, reusing a single
233
+ // signal across many requests leaks listeners and Node.js emits a
234
+ // MaxListenersExceededWarning. See https://github.com/nodejs/undici/issues/5285
235
+ const cleanupAbortListeners = () => {
236
+ removeAbortListener()
237
+ removeRequestAbortListener(requestObject)
238
+ }
239
+
231
240
  // 12. Let handleFetchDone given response response be to finalize and
232
241
  // report timing with response, globalObject, and "fetch".
233
242
  // see function handleFetchDone
@@ -252,6 +261,7 @@ function fetch (input, init = undefined) {
252
261
  // deserializedError.
253
262
 
254
263
  abortFetch(p, request, responseObject, controller.serializedAbortReason, controller.controller)
264
+ cleanupAbortListeners()
255
265
  return
256
266
  }
257
267
 
@@ -259,6 +269,7 @@ function fetch (input, init = undefined) {
259
269
  // and terminate these substeps.
260
270
  if (response.type === 'error') {
261
271
  p.reject(new TypeError('fetch failed', { cause: response.error }))
272
+ cleanupAbortListeners()
262
273
  return
263
274
  }
264
275
 
@@ -273,7 +284,10 @@ function fetch (input, init = undefined) {
273
284
 
274
285
  controller = fetching({
275
286
  request,
276
- processResponseEndOfBody: handleFetchDone,
287
+ processResponseEndOfBody: (response) => {
288
+ handleFetchDone(response)
289
+ cleanupAbortListeners()
290
+ },
277
291
  processResponse,
278
292
  dispatcher: getRequestDispatcher(requestObject), // undici
279
293
  // Keep requestObject alive to prevent its AbortController from being GC'd
@@ -97,6 +97,13 @@ class Request {
97
97
 
98
98
  #state
99
99
 
100
+ /**
101
+ * Removes the `abort` listener that makes this request's signal follow the
102
+ * passed signal. `null` when no such listener was registered.
103
+ * @type {(() => void) | null}
104
+ */
105
+ #abortCleanup = null
106
+
100
107
  // https://fetch.spec.whatwg.org/#dom-request
101
108
  constructor (input, init = undefined) {
102
109
  webidl.util.markAsUncloneable(this)
@@ -436,12 +443,23 @@ class Request {
436
443
  setMaxListeners(1500, signal)
437
444
  }
438
445
 
439
- util.addAbortListener(signal, abort)
446
+ const removeAbortListener = util.addAbortListener(signal, abort)
440
447
  // The third argument must be a registry key to be unregistered.
441
448
  // Without it, you cannot unregister.
442
449
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
443
450
  // abort is used as the unregister key. (because it is unique)
444
451
  requestFinalizer.register(ac, { signal, abort }, abort)
452
+
453
+ // Allow the listener to be removed deterministically once the fetch
454
+ // that owns this request has settled, instead of relying solely on the
455
+ // FinalizationRegistry (i.e. garbage collection). Reusing a single
456
+ // signal across many requests would otherwise leak listeners.
457
+ // See https://github.com/nodejs/undici/issues/5285
458
+ this.#abortCleanup = () => {
459
+ requestFinalizer.unregister(abort)
460
+ removeAbortListener()
461
+ this.#abortCleanup = null
462
+ }
445
463
  }
446
464
  }
447
465
 
@@ -868,15 +886,25 @@ class Request {
868
886
  static setRequestState (request, newState) {
869
887
  request.#state = newState
870
888
  }
889
+
890
+ /**
891
+ * Removes the `abort` listener that makes this request's signal follow the
892
+ * signal passed to its constructor, if any. Idempotent.
893
+ * @param {Request} request
894
+ */
895
+ static removeRequestAbortListener (request) {
896
+ request.#abortCleanup?.()
897
+ }
871
898
  }
872
899
 
873
- const { setRequestSignal, getRequestDispatcher, setRequestDispatcher, setRequestHeaders, getRequestState, setRequestState } = Request
900
+ const { setRequestSignal, getRequestDispatcher, setRequestDispatcher, setRequestHeaders, getRequestState, setRequestState, removeRequestAbortListener } = Request
874
901
  Reflect.deleteProperty(Request, 'setRequestSignal')
875
902
  Reflect.deleteProperty(Request, 'getRequestDispatcher')
876
903
  Reflect.deleteProperty(Request, 'setRequestDispatcher')
877
904
  Reflect.deleteProperty(Request, 'setRequestHeaders')
878
905
  Reflect.deleteProperty(Request, 'getRequestState')
879
906
  Reflect.deleteProperty(Request, 'setRequestState')
907
+ Reflect.deleteProperty(Request, 'removeRequestAbortListener')
880
908
 
881
909
  mixinBody(Request, getRequestState)
882
910
 
@@ -902,6 +930,7 @@ function makeRequest (init) {
902
930
  referrerPolicy: init.referrerPolicy ?? '',
903
931
  mode: init.mode ?? 'no-cors',
904
932
  useCORSPreflightFlag: init.useCORSPreflightFlag ?? false,
933
+ // TODO: is this credentials mode? https://fetch.spec.whatwg.org/#concept-request-credentials-mode
905
934
  credentials: init.credentials ?? 'same-origin',
906
935
  useCredentials: init.useCredentials ?? false,
907
936
  cache: init.cache ?? 'default',
@@ -1111,5 +1140,6 @@ module.exports = {
1111
1140
  fromInnerRequest,
1112
1141
  cloneRequest,
1113
1142
  getRequestDispatcher,
1114
- getRequestState
1143
+ getRequestState,
1144
+ removeRequestAbortListener
1115
1145
  }
@@ -39,6 +39,9 @@ class ByteParser extends Writable {
39
39
  /** @type {import('./websocket').Handler} */
40
40
  #handler
41
41
 
42
+ /** @type {number} */
43
+ #maxFragments
44
+
42
45
  /** @type {number} */
43
46
  #maxPayloadSize
44
47
 
@@ -52,6 +55,7 @@ class ByteParser extends Writable {
52
55
 
53
56
  this.#handler = handler
54
57
  this.#extensions = extensions == null ? new Map() : extensions
58
+ this.#maxFragments = options.maxFragments ?? 0
55
59
  this.#maxPayloadSize = options.maxPayloadSize ?? 0
56
60
 
57
61
  if (this.#extensions.has('permessage-deflate')) {
@@ -75,7 +79,7 @@ class ByteParser extends Writable {
75
79
  if (
76
80
  this.#maxPayloadSize > 0 &&
77
81
  !isControlFrame(this.#info.opcode) &&
78
- this.#info.payloadLength > this.#maxPayloadSize
82
+ this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
79
83
  ) {
80
84
  failWebsocketConnection(this.#handler, 1009, 'Payload size exceeds maximum allowed size')
81
85
  return false
@@ -242,7 +246,9 @@ class ByteParser extends Writable {
242
246
  this.#state = parserStates.INFO
243
247
  } else {
244
248
  if (!this.#info.compressed) {
245
- this.writeFragments(body)
249
+ if (!this.writeFragments(body)) {
250
+ return
251
+ }
246
252
 
247
253
  // If the frame is not fragmented, a message has been received.
248
254
  // If the frame is fragmented, it will terminate with a fin bit set
@@ -264,7 +270,9 @@ class ByteParser extends Writable {
264
270
  return
265
271
  }
266
272
 
267
- this.writeFragments(data)
273
+ if (!this.writeFragments(data)) {
274
+ return
275
+ }
268
276
 
269
277
  // Check cumulative fragment size
270
278
  if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
@@ -345,8 +353,17 @@ class ByteParser extends Writable {
345
353
  }
346
354
 
347
355
  writeFragments (fragment) {
356
+ if (
357
+ this.#maxFragments > 0 &&
358
+ this.#fragments.length === this.#maxFragments
359
+ ) {
360
+ failWebsocketConnection(this.#handler, 1008, 'Too many message fragments')
361
+ return false
362
+ }
363
+
348
364
  this.#fragmentsBytes += fragment.length
349
365
  this.#fragments.push(fragment)
366
+ return true
350
367
  }
351
368
 
352
369
  consumeFragments () {
@@ -258,7 +258,14 @@ class WebSocketStream {
258
258
  #onConnectionEstablished (response, parsedExtensions) {
259
259
  this.#handler.socket = response.socket
260
260
 
261
- const parser = new ByteParser(this.#handler, parsedExtensions)
261
+ // Get options from dispatcher options
262
+ const maxFragments = this.#handler.controller.dispatcher?.webSocketOptions?.maxFragments
263
+ const maxPayloadSize = this.#handler.controller.dispatcher?.webSocketOptions?.maxPayloadSize
264
+
265
+ const parser = new ByteParser(this.#handler, parsedExtensions, {
266
+ maxFragments,
267
+ maxPayloadSize
268
+ })
262
269
  parser.on('drain', () => this.#handler.onParserDrain())
263
270
  parser.on('error', (err) => this.#handler.onParserError(err))
264
271
 
@@ -468,10 +468,12 @@ class WebSocket extends EventTarget {
468
468
  // once this happens, the connection is open
469
469
  this.#handler.socket = response.socket
470
470
 
471
- // Get maxPayloadSize from dispatcher options
471
+ // Get options from dispatcher options
472
+ const maxFragments = this.#handler.controller.dispatcher?.webSocketOptions?.maxFragments
472
473
  const maxPayloadSize = this.#handler.controller.dispatcher?.webSocketOptions?.maxPayloadSize
473
474
 
474
475
  const parser = new ByteParser(this.#handler, parsedExtensions, {
476
+ maxFragments,
475
477
  maxPayloadSize
476
478
  })
477
479
  parser.on('drain', () => this.#handler.onParserDrain())
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "8.3.0",
3
+ "version": "8.5.0",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -116,6 +116,11 @@ export declare namespace Client {
116
116
  bytesRead?: number
117
117
  }
118
118
  export interface WebSocketOptions {
119
+ /**
120
+ * Maximum number of fragments in a message. Set to 0 to disable the limit.
121
+ * @default 131072
122
+ */
123
+ maxFragments?: number;
119
124
  /**
120
125
  * Maximum allowed payload size in bytes for WebSocket messages.
121
126
  * Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages.
@@ -7,6 +7,7 @@ declare function buildConnector (options?: buildConnector.BuildOptions): buildCo
7
7
  declare namespace buildConnector {
8
8
  export type BuildOptions = (ConnectionOptions | TcpNetConnectOpts | IpcNetConnectOpts) & {
9
9
  allowH2?: boolean;
10
+ preferH2?: boolean;
10
11
  maxCachedSessions?: number | null;
11
12
  socketPath?: string | null;
12
13
  timeout?: number | null;