Skip to content
Closed
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
49 changes: 46 additions & 3 deletions src/browser/sources/service-worker-source.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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<ServiceWorkerHttpNetworkFrame> {
#bypassInterceptor?: BatchInterceptor<
[FetchInterceptor, XMLHttpRequestInterceptor],
HttpRequestEventMap
>
#frames: Map<string, ServiceWorkerHttpNetworkFrame>
#channel: WorkerChannel
#clientPromise?: Promise<WorkerChannelClient>
Expand Down Expand Up @@ -73,6 +85,7 @@ export class ServiceWorkerSource extends NetworkSource<ServiceWorkerHttpNetworkF

public async enable(): Promise<ServiceWorkerRegistration> {
this.#stoppedAt = undefined
this.#disposeBypassInterceptor()

if (this.workerPromise.state !== 'pending') {
devUtils.warn(
Expand Down Expand Up @@ -137,9 +150,14 @@ export class ServiceWorkerSource extends NetworkSource<ServiceWorkerHttpNetworkF
}

this.#stoppedAt = Date.now()
this.#applyBypassInterceptor()
this.#frames.clear()
this.workerPromise = new DeferredPromise()

if (this.#keepAliveInterval) {
clearInterval(this.#keepAliveInterval)
}

if (!this.options.quiet) {
this.#printStopMessage()
}
Expand Down Expand Up @@ -214,12 +232,37 @@ Please consider using a custom "serviceWorker.url" option to point to the actual
return [worker, registration] as const
}

#applyBypassInterceptor(): void {
if (this.#bypassInterceptor) {
return
}

const interceptor = new BatchInterceptor({
name: 'service-worker-bypass',
interceptors: [new FetchInterceptor(), new XMLHttpRequestInterceptor()],
})

interceptor.on('request', ({ request }) => {
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<void> {
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({
Expand Down
6 changes: 3 additions & 3 deletions src/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(', '))
Expand Down
15 changes: 15 additions & 0 deletions test/browser/msw-api/setup-worker/stop/in-flight-request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
Loading