diff --git a/src/browser/sources/service-worker-source.ts b/src/browser/sources/service-worker-source.ts index 789fc5cd7..29c1874e8 100644 --- a/src/browser/sources/service-worker-source.ts +++ b/src/browser/sources/service-worker-source.ts @@ -1,7 +1,13 @@ import { invariant } from 'outvariant' import { Emitter } from 'rettime' import { DeferredPromise } from '@open-draft/deferred-promise' -import { FetchResponse } from '@mswjs/interceptors' +import { + BatchInterceptor, + FetchResponse, + type HttpRequestEventMap, +} from '@mswjs/interceptors' +import { FetchInterceptor } from '@mswjs/interceptors/fetch' +import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest' import { NetworkSource } from '#core/experimental/sources/network-source' import { RequestHandler } from '#core/handlers/RequestHandler' import { @@ -45,7 +51,13 @@ type WorkerChannelResponseEvent = Emitter.EventType< type WorkerChannelClient = WorkerChannelEventMap['MOCKING_ENABLED']['data']['client'] +const STOP_BYPASS_TOKEN = 'msw/stop-passthrough' + export class ServiceWorkerSource extends NetworkSource { + #bypassInterceptor?: BatchInterceptor< + [FetchInterceptor, XMLHttpRequestInterceptor], + HttpRequestEventMap + > #frames: Map #channel: WorkerChannel #clientPromise?: Promise @@ -73,6 +85,7 @@ export class ServiceWorkerSource extends NetworkSource { this.#stoppedAt = undefined + this.#disposeBypassInterceptor() if (this.workerPromise.state !== 'pending') { devUtils.warn( @@ -137,9 +150,14 @@ export class ServiceWorkerSource extends NetworkSource { + request.headers.append('accept', 'msw/passthrough') + request.headers.append('accept', STOP_BYPASS_TOKEN) + }) + + interceptor.apply() + this.#bypassInterceptor = interceptor + } + + #disposeBypassInterceptor(): void { + this.#bypassInterceptor?.dispose() + this.#bypassInterceptor = undefined + } + async #handleRequest(event: WorkerChannelRequestEvent): Promise { - if (this.#stoppedAt && event.data.interceptedAt > this.#stoppedAt) { + const request = deserializeRequest(event.data) + + if (request.headers.get('accept')?.includes(STOP_BYPASS_TOKEN)) { return event.postMessage('PASSTHROUGH') } - const request = deserializeRequest(event.data) RequestHandler.cache.set(request, request.clone()) const frame = new ServiceWorkerHttpNetworkFrame({ diff --git a/src/mockServiceWorker.js b/src/mockServiceWorker.js index d02b80689..1b353b3d1 100644 --- a/src/mockServiceWorker.js +++ b/src/mockServiceWorker.js @@ -224,9 +224,9 @@ async function getResponse(event, client, requestId, requestInterceptedAt) { const acceptHeader = headers.get('accept') if (acceptHeader) { const values = acceptHeader.split(',').map((value) => value.trim()) - const filteredValues = values.filter( - (value) => value !== 'msw/passthrough', - ) + const filteredValues = values.filter((value) => { + return value !== 'msw/passthrough' && value !== 'msw/stop-passthrough' + }) if (filteredValues.length > 0) { headers.set('accept', filteredValues.join(', ')) diff --git a/test/browser/msw-api/setup-worker/stop/in-flight-request.test.ts b/test/browser/msw-api/setup-worker/stop/in-flight-request.test.ts index f48b0a3f2..4d82bfd85 100644 --- a/test/browser/msw-api/setup-worker/stop/in-flight-request.test.ts +++ b/test/browser/msw-api/setup-worker/stop/in-flight-request.test.ts @@ -60,3 +60,18 @@ test('bypasses requests made after the worker was stopped', async ({ await expect(dataPromise).resolves.toBe('original response') }) + +test('bypasses requests immediately made after the worker was stopped', async ({ + loadExample, + page, +}) => { + await loadExample(new URL('./in-flight-request.mocks.ts', import.meta.url)) + + const data = await page.evaluate(async () => { + window.msw.worker.stop() + + return fetch('/resource').then((response) => response.text()) + }) + + expect(data).toContain('Cannot GET /resource') +})