Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions src/core/experimental/define-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
type AnyHandler,
} from './handlers-controller'
import { toReadonlyArray } from '../utils/internal/toReadonlyArray'
import { Disposable } from '../utils/internal/Disposable'

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
Expand Down Expand Up @@ -112,6 +113,7 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
): NetworkApi<Sources> {
let readyState: NetworkReadyState = NetworkReadyState.DISABLED
const events = new Emitter<MergeEventMaps<Sources>>()
const disposable = new Disposable()

const deriveHandlersController = (
handlers: DefineNetworkOptions<Sources>['handlers'],
Expand All @@ -130,15 +132,17 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
* certain setup APIs, like `setupServer`, don't await `.enable` (`.listen`).
*/
let handlersController = deriveHandlersController(resolvedOptions.handlers)
let listenersController: AbortController

return {
get readyState() {
return readyState
},
events,
configure(options) {
invariant(readyState === NetworkReadyState.DISABLED, '')
invariant(
readyState === NetworkReadyState.DISABLED,
'Failed to call "configure()" on the network: cannot configure an already enabled network.',
)

if (
options.handlers &&
Expand All @@ -158,9 +162,18 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
'Failed to call "enable" on the network: already enabled',
)

listenersController = new AbortController()
readyState = NetworkReadyState.ENABLED

/**
* @note Use a session object scoped to the current "enable()"
* to prevent "frame.events" listeners from surviving across enable/disable cycles.
* @see The note about `AbortController` below.
*/
const session = { active: true }
disposable['subscriptions'].push(() => {
session.active = false
})

const result = resolvedOptions.sources.map((source) => {
/**
* @note Preemptively disable the network source before enabling.
Expand All @@ -171,8 +184,19 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
NetworkSource.prototype.disable.call(source)

source.on('frame', async ({ frame }) => {
frame.events.on('*', (event) => events.emit(event), {
signal: listenersController.signal,
frame.events.on('*', (event) => {
/**
* @note Prevent event forwarding manually and not via an AbortController
* because certain runtimes, like Cloudflare, throw when referencing an
* AbortController created in a different context. Bear in mind that the frame
* events run in the patched request client context while the AbortController
* is created outside, in the "defineNetwork" closure, which is a test context.
*/
if (!session.active) {
return
}

events.emit(event)
})
Comment thread
kettanaito marked this conversation as resolved.

const handlers = frame.getHandlers(handlersController)
Expand All @@ -197,8 +221,8 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
'Failed to call "disable" on the network: already disabled',
)

listenersController.abort()
readyState = NetworkReadyState.DISABLED
disposable.dispose()

return colorlessPromiseAll(
resolvedOptions.sources.map((source) => source.disable()),
Expand Down
23 changes: 22 additions & 1 deletion src/core/utils/internal/Disposable.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
import { devUtils } from './devUtils'

export type DisposableSubscription = () => void

export class Disposable {
protected subscriptions: Array<DisposableSubscription> = []

public dispose() {
let subscription: DisposableSubscription | undefined
const errors: Array<Error> = []

while ((subscription = this.subscriptions.shift())) {
subscription()
try {
subscription()
} catch (error) {
if (error instanceof Error) {
errors.push(error)
}
}
Comment thread
kettanaito marked this conversation as resolved.
}

if (errors.length > 0) {
console.error(
new AggregateError(
errors,
devUtils.formatMessage(
'Failed to dispose of some side effects. This is likely an issue with MSW, please report it on GitHub: https://github.com/mswjs/msw/issues',
),
),
)
}
}
}
Loading