UNPKG

msw

Version:

Seamless REST/GraphQL API mocking library for browser and Node.js.

185 lines (157 loc) 6.42 kB
import { invariant } from 'outvariant' import { isNodeProcess } from 'is-node-process' import { DeferredPromise } from '@open-draft/deferred-promise' import type { SetupWorkerInternalContext, StartReturnType, StartOptions, SetupWorker, } from './glossary' import { RequestHandler } from '~/core/handlers/RequestHandler' import { DEFAULT_START_OPTIONS } from './start/utils/prepareStartHandler' import { createStartHandler } from './start/createStartHandler' import { devUtils } from '~/core/utils/internal/devUtils' import { SetupApi } from '~/core/SetupApi' import { mergeRight } from '~/core/utils/internal/mergeRight' import type { LifeCycleEventsMap } from '~/core/sharedOptions' import type { WebSocketHandler } from '~/core/handlers/WebSocketHandler' import { webSocketInterceptor } from '~/core/ws/webSocketInterceptor' import { handleWebSocketEvent } from '~/core/ws/handleWebSocketEvent' import { attachWebSocketLogger } from '~/core/ws/utils/attachWebSocketLogger' import { WorkerChannel } from '../utils/workerChannel' import { createFallbackRequestListener } from './start/createFallbackRequestListener' import { printStartMessage } from './start/utils/printStartMessage' import { printStopMessage } from './stop/utils/printStopMessage' import { supportsServiceWorker } from '../utils/supports' export class SetupWorkerApi extends SetupApi<LifeCycleEventsMap> implements SetupWorker { private context: SetupWorkerInternalContext constructor(...handlers: Array<RequestHandler | WebSocketHandler>) { super(...handlers) invariant( !isNodeProcess(), devUtils.formatMessage( 'Failed to execute `setupWorker` in a non-browser environment. Consider using `setupServer` for Node.js environment instead.', ), ) this.context = this.createWorkerContext() } private createWorkerContext(): SetupWorkerInternalContext { const workerPromise = new DeferredPromise<ServiceWorker>() return { // Mocking is not considered enabled until the worker // signals back the successful activation event. isMockingEnabled: false, startOptions: null as any, workerPromise, registration: undefined, getRequestHandlers: () => { return this.handlersController.currentHandlers() }, emitter: this.emitter, workerChannel: new WorkerChannel({ worker: workerPromise, }), } } public async start(options: StartOptions = {}): StartReturnType { if ('waitUntilReady' in options) { devUtils.warn( 'The "waitUntilReady" option has been deprecated. Please remove it from this "worker.start()" call. Follow the recommended Browser integration (https://mswjs.io/docs/integrations/browser) to eliminate any race conditions between the Service Worker registration and any requests made by your application on initial render.', ) } // Warn the developer on multiple "worker.start()" calls. // While this will not affect the worker in any way, // it likely indicates an issue with the developer's code. if (this.context.isMockingEnabled) { devUtils.warn( `Found a redundant "worker.start()" call. Note that starting the worker while mocking is already enabled will have no effect. Consider removing this "worker.start()" call.`, ) return this.context.registration } this.context.workerStoppedAt = undefined this.context.startOptions = mergeRight( DEFAULT_START_OPTIONS, options, ) as SetupWorkerInternalContext['startOptions'] // Enable the WebSocket interception. handleWebSocketEvent({ getUnhandledRequestStrategy: () => { return this.context.startOptions.onUnhandledRequest }, getHandlers: () => { return this.handlersController.currentHandlers() }, onMockedConnection: (connection) => { if (!this.context.startOptions.quiet) { // Attach the logger for mocked connections since // those won't be visible in the browser's devtools. attachWebSocketLogger(connection) } }, onPassthroughConnection() {}, }) webSocketInterceptor.apply() this.subscriptions.push(() => { webSocketInterceptor.dispose() }) // Use a fallback interception algorithm in the environments // where the Service Worker API isn't supported. if (!supportsServiceWorker()) { const fallbackInterceptor = createFallbackRequestListener( this.context, this.context.startOptions, ) this.subscriptions.push(() => { fallbackInterceptor.dispose() }) this.context.isMockingEnabled = true printStartMessage({ message: 'Mocking enabled (fallback mode).', quiet: this.context.startOptions.quiet, }) return undefined } const startHandler = createStartHandler(this.context) const registration = await startHandler(this.context.startOptions, options) this.context.isMockingEnabled = true return registration } public stop(): void { super.dispose() if (!this.context.isMockingEnabled) { devUtils.warn( 'Found a redundant "worker.stop()" call. Notice that stopping the worker after it has already been stopped has no effect. Consider removing this "worker.stop()" call.', ) return } this.context.isMockingEnabled = false this.context.workerStoppedAt = Date.now() this.context.emitter.removeAllListeners() if (supportsServiceWorker()) { this.context.workerChannel.removeAllListeners('RESPONSE') window.clearInterval(this.context.keepAliveInterval) } // Post the internal stop message on the window // to let any logic know when the worker has stopped. // E.g. the WebSocket client manager needs this to know // when to clear its in-memory clients list. window.postMessage({ type: 'msw/worker:stop' }) printStopMessage({ quiet: this.context.startOptions?.quiet, }) } } /** * Sets up a requests interception in the browser with the given request handlers. * @param {RequestHandler[]} handlers List of request handlers. * * @see {@link https://mswjs.io/docs/api/setup-worker `setupWorker()` API reference} */ export function setupWorker( ...handlers: Array<RequestHandler | WebSocketHandler> ): SetupWorker { return new SetupWorkerApi(...handlers) }