@@ -12,6 +12,7 @@ import {
1212 type AnyHandler ,
1313} from './handlers-controller'
1414import { toReadonlyArray } from '../utils/internal/toReadonlyArray'
15+ import { Disposable } from '../utils/internal/Disposable'
1516
1617type UnionToIntersection < U > = ( U extends any ? ( k : U ) => void : never ) extends (
1718 k : infer I ,
@@ -112,6 +113,7 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
112113) : NetworkApi < Sources > {
113114 let readyState : NetworkReadyState = NetworkReadyState . DISABLED
114115 const events = new Emitter < MergeEventMaps < Sources > > ( )
116+ const disposable = new Disposable ( )
115117
116118 const deriveHandlersController = (
117119 handlers : DefineNetworkOptions < Sources > [ 'handlers' ] ,
@@ -130,15 +132,17 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
130132 * certain setup APIs, like `setupServer`, don't await `.enable` (`.listen`).
131133 */
132134 let handlersController = deriveHandlersController ( resolvedOptions . handlers )
133- let listenersController : AbortController
134135
135136 return {
136137 get readyState ( ) {
137138 return readyState
138139 } ,
139140 events,
140141 configure ( options ) {
141- invariant ( readyState === NetworkReadyState . DISABLED , '' )
142+ invariant (
143+ readyState === NetworkReadyState . DISABLED ,
144+ 'Failed to call "configure()" on the network: cannot configure an already enabled network.' ,
145+ )
142146
143147 if (
144148 options . handlers &&
@@ -158,9 +162,18 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
158162 'Failed to call "enable" on the network: already enabled' ,
159163 )
160164
161- listenersController = new AbortController ( )
162165 readyState = NetworkReadyState . ENABLED
163166
167+ /**
168+ * @note Use a session object scoped to the current "enable()"
169+ * to prevent "frame.events" listeners from surviving across enable/disable cycles.
170+ * @see The note about `AbortController` below.
171+ */
172+ const session = { active : true }
173+ disposable [ 'subscriptions' ] . push ( ( ) => {
174+ session . active = false
175+ } )
176+
164177 const result = resolvedOptions . sources . map ( ( source ) => {
165178 /**
166179 * @note Preemptively disable the network source before enabling.
@@ -171,8 +184,19 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
171184 NetworkSource . prototype . disable . call ( source )
172185
173186 source . on ( 'frame' , async ( { frame } ) => {
174- frame . events . on ( '*' , ( event ) => events . emit ( event ) , {
175- signal : listenersController . signal ,
187+ frame . events . on ( '*' , ( event ) => {
188+ /**
189+ * @note Prevent event forwarding manually and not via an AbortController
190+ * because certain runtimes, like Cloudflare, throw when referencing an
191+ * AbortController created in a different context. Bear in mind that the frame
192+ * events run in the patched request client context while the AbortController
193+ * is created outside, in the "defineNetwork" closure, which is a test context.
194+ */
195+ if ( ! session . active ) {
196+ return
197+ }
198+
199+ events . emit ( event )
176200 } )
177201
178202 const handlers = frame . getHandlers ( handlersController )
@@ -197,8 +221,8 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
197221 'Failed to call "disable" on the network: already disabled' ,
198222 )
199223
200- listenersController . abort ( )
201224 readyState = NetworkReadyState . DISABLED
225+ disposable . dispose ( )
202226
203227 return colorlessPromiseAll (
204228 resolvedOptions . sources . map ( ( source ) => source . disable ( ) ) ,
0 commit comments