@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
@@ -3,6 +3,7 @@ const { isIP } = require('node:net')
3
3
  const { lookup } = require('node:dns')
4
4
  const DecoratorHandler = require('../handler/decorator-handler')
5
5
  const { InvalidArgumentError, InformationalError } = require('../core/errors')
6
+ const { kRequestOrigin } = require('../core/symbols')
6
7
  const maxInt = Math.pow(2, 31) - 1
7
8
 
8
9
  function hasSafeIterator (headers) {
@@ -434,6 +435,9 @@ class DNSDispatchHandler extends DecoratorHandler {
434
435
  origin: `${this.#origin.protocol}//${
435
436
  ip.family === 6 ? `[${ip.address}]` : ip.address
436
437
  }${port}`,
438
+ [kRequestOrigin]: this.#opts[kRequestOrigin] === undefined
439
+ ? this.#origin
440
+ : this.#opts[kRequestOrigin],
437
441
  headers: withHostHeader(this.#origin.host, this.#opts.headers)
438
442
  }
439
443
  this.#dispatch(dispatchOpts, this)
@@ -557,6 +561,9 @@ module.exports = interceptorOpts => {
557
561
  ...origDispatchOpts,
558
562
  servername: origin.hostname, // For SNI on TLS
559
563
  origin: newOrigin.origin,
564
+ [kRequestOrigin]: origDispatchOpts[kRequestOrigin] === undefined
565
+ ? origin
566
+ : origDispatchOpts[kRequestOrigin],
560
567
  headers: withHostHeader(origin.host, origDispatchOpts.headers)
561
568
  }
562
569
 
@@ -7,7 +7,6 @@ class DumpHandler extends DecoratorHandler {
7
7
  #maxSize = 1024 * 1024
8
8
  #dumped = false
9
9
  #size = 0
10
- #controller = null
11
10
  aborted = false
12
11
  reason = false
13
12
 
@@ -29,7 +28,6 @@ class DumpHandler extends DecoratorHandler {
29
28
 
30
29
  onRequestStart (controller, context) {
31
30
  controller.abort = this.#abort.bind(this)
32
- this.#controller = controller
33
31
 
34
32
  return super.onRequestStart(controller, context)
35
33
  }
@@ -53,43 +51,32 @@ class DumpHandler extends DecoratorHandler {
53
51
  }
54
52
 
55
53
  onResponseError (controller, err) {
56
- if (this.#dumped) {
57
- return
58
- }
59
-
60
- // On network errors before connect, controller will be null
61
- err = this.#controller?.reason ?? err
62
-
63
- super.onResponseError(controller, err)
54
+ super.onResponseError(controller, this.aborted === true ? this.reason : err)
64
55
  }
65
56
 
66
57
  onResponseData (controller, chunk) {
67
58
  this.#size = this.#size + chunk.length
68
59
 
69
- if (this.#size >= this.#maxSize) {
70
- this.#dumped = true
60
+ if (this.#size > this.#maxSize) {
61
+ throw new RequestAbortedError(
62
+ `Response size (${this.#size}) larger than maxSize (${this.#maxSize})`
63
+ )
64
+ }
71
65
 
72
- if (this.aborted === true) {
73
- super.onResponseError(controller, this.reason)
74
- } else {
75
- super.onResponseEnd(controller, {})
76
- }
66
+ if (this.#size === this.#maxSize) {
67
+ this.#dumped = true
77
68
  }
78
69
 
79
70
  return true
80
71
  }
81
72
 
82
73
  onResponseEnd (controller, trailers) {
83
- if (this.#dumped) {
84
- return
85
- }
86
-
87
- if (this.#controller.aborted === true) {
74
+ if (this.aborted === true) {
88
75
  super.onResponseError(controller, this.reason)
89
76
  return
90
77
  }
91
78
 
92
- super.onResponseEnd(controller, trailers)
79
+ super.onResponseEnd(controller, this.#dumped ? {} : trailers)
93
80
  }
94
81
  }
95
82
 
@@ -73,7 +73,7 @@ class MockAgent extends Dispatcher {
73
73
  opts.origin = normalizeOrigin(opts.origin)
74
74
 
75
75
  // Call MockAgent.get to perform additional setup before dispatching as normal
76
- this.get(opts.origin)
76
+ const mockDispatcher = this.get(opts.origin)
77
77
 
78
78
  this[kMockAgentAddCallHistoryLog](opts)
79
79
 
@@ -81,6 +81,18 @@ class MockAgent extends Dispatcher {
81
81
 
82
82
  const dispatchOpts = { ...opts }
83
83
 
84
+ // Agent keeps HTTP/1.1-only dispatchers under a separate key. Legacy
85
+ // global dispatcher consumers use that path, so mirror the mock dispatches
86
+ // before delegating to the internal Agent.
87
+ if (dispatchOpts.allowH2 === false) {
88
+ const http1OnlyKey = `${dispatchOpts.origin}#http1-only`
89
+ if (!this[kClients].has(http1OnlyKey)) {
90
+ const http1OnlyDispatcher = this[kFactory](dispatchOpts.origin)
91
+ http1OnlyDispatcher[kDispatches] = mockDispatcher[kDispatches]
92
+ this[kMockAgentSet](http1OnlyKey, http1OnlyDispatcher)
93
+ }
94
+ }
95
+
84
96
  if (acceptNonStandardSearchParameters && dispatchOpts.path) {
85
97
  const [path, searchParams] = dispatchOpts.path.split('?')
86
98
  const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters)
@@ -12,6 +12,11 @@ const {
12
12
  } = require('./mock-symbols')
13
13
  const { InvalidArgumentError } = require('../core/errors')
14
14
  const { serializePathWithQuery } = require('../core/util')
15
+ const {
16
+ types: {
17
+ isPromise
18
+ }
19
+ } = require('node:util')
15
20
 
16
21
  /**
17
22
  * Defines the scope API for an interceptor reply
@@ -117,13 +122,9 @@ class MockInterceptor {
117
122
  // Values of reply aren't available right now as they
118
123
  // can only be available when the reply callback is invoked.
119
124
  if (typeof replyOptionsCallbackOrStatusCode === 'function') {
120
- // We'll first wrap the provided callback in another function,
121
- // this function will properly resolve the data from the callback
122
- // when invoked.
123
- const wrappedDefaultsCallback = (opts) => {
124
- // Our reply options callback contains the parameter for statusCode, data and options.
125
- const resolvedData = replyOptionsCallbackOrStatusCode(opts)
126
-
125
+ // Resolves the data returned by a reply options callback into
126
+ // dispatch data, validating its format along the way.
127
+ const resolveReplyCallbackData = (resolvedData) => {
127
128
  // Check if it is in the right format
128
129
  if (typeof resolvedData !== 'object' || resolvedData === null) {
129
130
  throw new InvalidArgumentError('reply options callback must return an object')
@@ -138,6 +139,23 @@ class MockInterceptor {
138
139
  }
139
140
  }
140
141
 
142
+ // We'll first wrap the provided callback in another function,
143
+ // this function will properly resolve the data from the callback
144
+ // when invoked.
145
+ const wrappedDefaultsCallback = (opts) => {
146
+ // Our reply options callback contains the parameter for statusCode, data and options.
147
+ const resolvedData = replyOptionsCallbackOrStatusCode(opts)
148
+
149
+ // An asynchronous reply options callback resolves to the reply
150
+ // parameters, so the dispatch data can only be resolved once the
151
+ // returned promise settles.
152
+ if (isPromise(resolvedData)) {
153
+ return resolvedData.then(resolveReplyCallbackData)
154
+ }
155
+
156
+ return resolveReplyCallbackData(resolvedData)
157
+ }
158
+
141
159
  // Add usual dispatch data, but this time set the data parameter to function that will eventually provide data.
142
160
  const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback, { ignoreTrailingSlash: this[kIgnoreTrailingSlash] })
143
161
  return new MockScope(newMockDispatch)
@@ -17,6 +17,7 @@ const {
17
17
  }
18
18
  } = require('node:util')
19
19
  const { InvalidArgumentError } = require('../core/errors')
20
+ const requestAborted = Symbol('request aborted')
20
21
 
21
22
  function matchValue (match, value) {
22
23
  if (typeof match === 'string') {
@@ -153,6 +154,11 @@ function getResponseData (data) {
153
154
  return data
154
155
  } else if (data instanceof ArrayBuffer) {
155
156
  return data
157
+ } else if (ArrayBuffer.isView(data)) {
158
+ // A DataView, or any non-Uint8Array typed array, is a byte container
159
+ // rather than a plain object. Buffer.from() cannot read one directly, so
160
+ // expose the bytes it covers instead of letting it reach JSON.stringify.
161
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
156
162
  } else if (typeof data === 'object') {
157
163
  return JSON.stringify(data)
158
164
  } else if (data) {
@@ -225,9 +231,15 @@ function deleteMockDispatch (mockDispatches, key) {
225
231
  }
226
232
 
227
233
  /**
228
- * @param {string} path Path to remove trailing slash from
234
+ * @param {string|RegExp|Function} path Path, or path matcher, to remove trailing slash from
229
235
  */
230
236
  function removeTrailingSlash (path) {
237
+ // Registered path matchers may be a RegExp or a function, which have no
238
+ // trailing slash to strip; hand those back for matchValue to apply.
239
+ if (typeof path !== 'string') {
240
+ return path
241
+ }
242
+
231
243
  while (path.endsWith('/')) {
232
244
  path = path.slice(0, -1)
233
245
  }
@@ -292,26 +304,69 @@ function mockDispatch (opts, handler) {
292
304
  // Get mock dispatch from built key
293
305
  const key = buildKey(opts)
294
306
  const mockDispatch = getMockDispatch(this[kDispatches], key)
307
+ const mockDispatches = this[kDispatches]
295
308
 
296
309
  mockDispatch.timesInvoked++
297
310
 
298
- // Here's where we resolve a callback if a callback is present for the dispatch data.
299
- if (mockDispatch.data.callback) {
300
- mockDispatch.data = { ...mockDispatch.data, ...mockDispatch.data.callback(opts) }
301
- }
302
-
303
- // Parse mockDispatch data
304
- const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch
305
311
  const { timesInvoked, times } = mockDispatch
306
312
 
307
313
  // If it's used up and not persistent, mark as consumed
308
- mockDispatch.consumed = !persist && timesInvoked >= times
314
+ mockDispatch.consumed = !mockDispatch.persist && timesInvoked >= times
309
315
  mockDispatch.pending = timesInvoked < times
310
316
 
317
+ const hasBodyHooks = typeof handler.onBodySent === 'function' ||
318
+ typeof handler.onRequestSent === 'function'
319
+
320
+ // Here's where we resolve a callback if a callback is present for the dispatch data.
321
+ if (mockDispatch.data.callback && (!hasBodyHooks || opts.body == null)) {
322
+ const { callback, ...responseDefaults } = mockDispatch.data
323
+ const callbackResult = callback(opts)
324
+
325
+ // An asynchronous reply options callback resolves to the reply data, so
326
+ // the dispatch can only continue once the returned promise settles.
327
+ // A rejection cannot be thrown synchronously from the dispatch at that
328
+ // point, so it is surfaced as a response error instead.
329
+ if (isPromise(callbackResult)) {
330
+ callbackResult.then(
331
+ (resolvedData) => {
332
+ if (resolvedData == null || typeof resolvedData !== 'object') {
333
+ handler.onResponseError(null, new InvalidArgumentError('reply options callback must return an object'))
334
+ return
335
+ }
336
+ dispatchMockReply(mockDispatches, mockDispatch, key, opts, handler, { ...responseDefaults, ...resolvedData })
337
+ },
338
+ (error) => {
339
+ handler.onResponseError(null, error)
340
+ }
341
+ )
342
+ return true
343
+ }
344
+
345
+ if (callbackResult == null || typeof callbackResult !== 'object') {
346
+ throw new InvalidArgumentError('reply options callback must return an object')
347
+ }
348
+
349
+ return dispatchMockReply(mockDispatches, mockDispatch, key, opts, handler, { ...responseDefaults, ...callbackResult })
350
+ }
351
+
352
+ return dispatchMockReply(mockDispatches, mockDispatch, key, opts, handler)
353
+ }
354
+
355
+ /**
356
+ * Replies to a request once the mock dispatch data is fully resolved
357
+ */
358
+ function dispatchMockReply (mockDispatches, mockDispatch, key, opts, handler, resolvedResponse) {
359
+ // Parse mockDispatch data. When a reply callback has already been resolved
360
+ // in mockDispatch() (i.e. no body lifecycle hooks are involved), the resolved
361
+ // response is passed in here, leaving mockDispatch.data untouched so the
362
+ // callback can be re-invoked for persistent / times() replies.
363
+ const { data: responseData, delay } = mockDispatch
364
+ const response = resolvedResponse ?? responseData
365
+
311
366
  // If specified, trigger dispatch error
312
- if (error !== null) {
313
- deleteMockDispatch(this[kDispatches], key)
314
- handler.onResponseError(null, error)
367
+ if (response.error !== null) {
368
+ deleteMockDispatch(mockDispatches, key)
369
+ handler.onResponseError(null, response.error)
315
370
  return true
316
371
  }
317
372
 
@@ -346,32 +401,105 @@ function mockDispatch (opts, handler) {
346
401
  }
347
402
  }
348
403
 
404
+ let replyOpts = opts
405
+ const dispatches = mockDispatches
406
+
349
407
  // Call onRequestStart to allow the handler to receive the controller
350
408
  handler.onRequestStart?.(controller, null)
351
409
 
352
- // Handle the request with a delay if necessary
353
- if (typeof delay === 'number' && delay > 0) {
354
- timer = setTimeout(() => {
355
- timer = null
356
- handleReply(this[kDispatches])
357
- }, delay)
358
- } else {
359
- handleReply(this[kDispatches])
410
+ if (aborted) {
411
+ return true
412
+ }
413
+
414
+ const requestBody = dispatchRequestBody(opts.body, handler, controller, () => aborted)
415
+
416
+ if (isPromise(requestBody)) {
417
+ requestBody.then((body) => {
418
+ if (body === requestAborted) {
419
+ return
420
+ }
421
+
422
+ if (body !== opts.body) {
423
+ replyOpts = { ...opts, body }
424
+ }
425
+
426
+ sendReply()
427
+ }, (error) => controller.abort(error))
428
+ return true
429
+ }
430
+
431
+ if (requestBody === requestAborted) {
432
+ return true
433
+ }
434
+
435
+ if (requestBody !== opts.body) {
436
+ replyOpts = { ...opts, body: requestBody }
437
+ }
438
+
439
+ sendReply()
440
+
441
+ function sendReply () {
442
+ if (response.callback) {
443
+ const { callback, ...responseDefaults } = response
444
+ let callbackResult
445
+ try {
446
+ callbackResult = callback(replyOpts)
447
+ } catch (err) {
448
+ deleteMockDispatch(mockDispatches, key)
449
+ handler.onResponseError(null, err)
450
+ return
451
+ }
452
+
453
+ if (isPromise(callbackResult)) {
454
+ callbackResult.then(
455
+ (resolvedData) => {
456
+ if (resolvedData == null || typeof resolvedData !== 'object') {
457
+ handler.onResponseError(null, new InvalidArgumentError('reply options callback must return an object'))
458
+ return
459
+ }
460
+ handleReply(dispatches, { ...responseDefaults, ...resolvedData })
461
+ },
462
+ (err) => {
463
+ handler.onResponseError(null, err)
464
+ }
465
+ )
466
+ return
467
+ }
468
+
469
+ if (callbackResult == null || typeof callbackResult !== 'object') {
470
+ throw new InvalidArgumentError('reply options callback must return an object')
471
+ }
472
+
473
+ handleReply(dispatches, { ...responseDefaults, ...callbackResult })
474
+ return
475
+ }
476
+
477
+ // Handle the request with a delay if necessary
478
+ if (typeof delay === 'number' && delay > 0) {
479
+ timer = setTimeout(() => {
480
+ timer = null
481
+ handleReply(dispatches)
482
+ }, delay)
483
+ } else {
484
+ handleReply(dispatches)
485
+ }
360
486
  }
361
487
 
362
- function handleReply (mockDispatches, _data = data) {
488
+ function handleReply (mockDispatches, _response = response) {
363
489
  // Don't send response if the request was aborted
364
490
  if (aborted) {
365
491
  return
366
492
  }
367
493
 
494
+ const { statusCode, data, headers, trailers } = _response
495
+
368
496
  // fetch's HeadersList is a 1D string array
369
497
  const optsHeaders = Array.isArray(opts.headers)
370
498
  ? buildHeadersFromArray(opts.headers)
371
499
  : opts.headers
372
- const body = typeof _data === 'function'
373
- ? _data({ ...opts, headers: optsHeaders })
374
- : _data
500
+ const body = typeof data === 'function'
501
+ ? data({ ...replyOpts, headers: optsHeaders })
502
+ : data
375
503
 
376
504
  // util.types.isPromise is likely needed for jest.
377
505
  if (isPromise(body)) {
@@ -380,7 +508,7 @@ function mockDispatch (opts, handler) {
380
508
  // synchronously throw the error, which breaks some tests.
381
509
  // Rather, we wait for the callback to resolve if it is a
382
510
  // promise, and then re-run handleReply with the new body.
383
- return body.then((newData) => handleReply(mockDispatches, newData))
511
+ return body.then((newData) => handleReply(mockDispatches, { ..._response, data: newData }))
384
512
  }
385
513
 
386
514
  // Check again if aborted after async body resolution
@@ -389,8 +517,8 @@ function mockDispatch (opts, handler) {
389
517
  }
390
518
 
391
519
  const responseData = getResponseData(body)
392
- const responseHeaders = generateKeyValues(headers)
393
- const responseTrailers = generateKeyValues(trailers)
520
+ const responseHeaders = generateKeyValues(headers ?? {})
521
+ const responseTrailers = generateKeyValues(trailers ?? {})
394
522
 
395
523
  // Update the controller with response data
396
524
  controller.rawHeaders = responseHeaders
@@ -405,6 +533,97 @@ function mockDispatch (opts, handler) {
405
533
  return true
406
534
  }
407
535
 
536
+ function dispatchRequestBody (body, handler, controller, isAborted) {
537
+ if (typeof handler.onBodySent !== 'function' && typeof handler.onRequestSent !== 'function') {
538
+ return body
539
+ }
540
+
541
+ if (body == null) {
542
+ return callOnRequestSent(handler, controller, isAborted) ? body : requestAborted
543
+ }
544
+
545
+ if (body && typeof body[Symbol.asyncIterator] === 'function') {
546
+ return dispatchAsyncIterableBody(body, handler, controller, isAborted)
547
+ }
548
+
549
+ if (isIterableBody(body)) {
550
+ const chunks = []
551
+
552
+ for (const chunk of body) {
553
+ if (isAborted()) {
554
+ return requestAborted
555
+ }
556
+ chunks.push(chunk)
557
+ if (!callOnBodySent(handler, controller, chunk) || isAborted()) {
558
+ return requestAborted
559
+ }
560
+ }
561
+
562
+ return callOnRequestSent(handler, controller, isAborted) ? chunks : requestAborted
563
+ }
564
+
565
+ if (isAborted()) {
566
+ return requestAborted
567
+ }
568
+
569
+ if (!callOnBodySent(handler, controller, body)) {
570
+ return requestAborted
571
+ }
572
+
573
+ return callOnRequestSent(handler, controller, isAborted) ? body : requestAborted
574
+ }
575
+
576
+ async function dispatchAsyncIterableBody (body, handler, controller, isAborted) {
577
+ const chunks = []
578
+
579
+ for await (const chunk of body) {
580
+ if (isAborted()) {
581
+ return requestAborted
582
+ }
583
+ chunks.push(chunk)
584
+ if (!callOnBodySent(handler, controller, chunk) || isAborted()) {
585
+ return requestAborted
586
+ }
587
+ }
588
+
589
+ if (!callOnRequestSent(handler, controller, isAborted)) {
590
+ return requestAborted
591
+ }
592
+
593
+ return {
594
+ async * [Symbol.asyncIterator] () {
595
+ yield * chunks
596
+ }
597
+ }
598
+ }
599
+
600
+ function callOnBodySent (handler, controller, chunk) {
601
+ try {
602
+ handler.onBodySent?.(chunk)
603
+ return true
604
+ } catch (error) {
605
+ controller.abort(error)
606
+ return false
607
+ }
608
+ }
609
+
610
+ function callOnRequestSent (handler, controller, isAborted) {
611
+ try {
612
+ handler.onRequestSent?.()
613
+ return !isAborted()
614
+ } catch (error) {
615
+ controller.abort(error)
616
+ return false
617
+ }
618
+ }
619
+
620
+ function isIterableBody (body) {
621
+ return typeof body !== 'string' &&
622
+ !Buffer.isBuffer(body) &&
623
+ !ArrayBuffer.isView(body) &&
624
+ typeof body[Symbol.iterator] === 'function'
625
+ }
626
+
408
627
  function buildMockDispatch () {
409
628
  const agent = this[kMockAgent]
410
629
  const origin = this[kOrigin]