Spaces:
Paused
Paused
| /** | |
| * @note This API is extended by both "msw/node" and "msw/native" | |
| * so be minding about the things you import! | |
| */ | |
| import type { RequiredDeep } from 'type-fest' | |
| import { invariant } from 'outvariant' | |
| import { | |
| BatchInterceptor, | |
| InterceptorReadyState, | |
| type HttpRequestEventMap, | |
| type Interceptor, | |
| } from '@mswjs/interceptors' | |
| import type { LifeCycleEventsMap, SharedOptions } from '~/core/sharedOptions' | |
| import { SetupApi } from '~/core/SetupApi' | |
| import { handleRequest } from '~/core/utils/handleRequest' | |
| import type { RequestHandler } from '~/core/handlers/RequestHandler' | |
| import type { WebSocketHandler } from '~/core/handlers/WebSocketHandler' | |
| import { mergeRight } from '~/core/utils/internal/mergeRight' | |
| import { InternalError, devUtils } from '~/core/utils/internal/devUtils' | |
| import type { SetupServerCommon } from './glossary' | |
| import { handleWebSocketEvent } from '~/core/ws/handleWebSocketEvent' | |
| import { webSocketInterceptor } from '~/core/ws/webSocketInterceptor' | |
| import { isHandlerKind } from '~/core/utils/internal/isHandlerKind' | |
| export const DEFAULT_LISTEN_OPTIONS: RequiredDeep<SharedOptions> = { | |
| onUnhandledRequest: 'warn', | |
| } | |
| export class SetupServerCommonApi | |
| extends SetupApi<LifeCycleEventsMap> | |
| implements SetupServerCommon | |
| { | |
| protected readonly interceptor: BatchInterceptor< | |
| Array<Interceptor<HttpRequestEventMap>>, | |
| HttpRequestEventMap | |
| > | |
| private resolvedOptions: RequiredDeep<SharedOptions> | |
| constructor( | |
| interceptors: Array<{ new (): Interceptor<HttpRequestEventMap> }>, | |
| handlers: Array<RequestHandler | WebSocketHandler>, | |
| ) { | |
| super(...handlers) | |
| this.interceptor = new BatchInterceptor({ | |
| name: 'setup-server', | |
| interceptors: interceptors.map((Interceptor) => new Interceptor()), | |
| }) | |
| this.resolvedOptions = {} as RequiredDeep<SharedOptions> | |
| } | |
| /** | |
| * Subscribe to all requests that are using the interceptor object | |
| */ | |
| private init(): void { | |
| this.interceptor.on( | |
| 'request', | |
| async ({ request, requestId, controller }) => { | |
| const response = await handleRequest( | |
| request, | |
| requestId, | |
| this.handlersController | |
| .currentHandlers() | |
| .filter(isHandlerKind('RequestHandler')), | |
| this.resolvedOptions, | |
| this.emitter, | |
| { | |
| onPassthroughResponse(request) { | |
| const acceptHeader = request.headers.get('accept') | |
| /** | |
| * @note Remove the internal bypass request header. | |
| * In the browser, this is done by the worker script. | |
| * In Node.js, it has to be done here. | |
| */ | |
| if (acceptHeader) { | |
| const nextAcceptHeader = acceptHeader.replace( | |
| /(,\s+)?msw\/passthrough/, | |
| '', | |
| ) | |
| if (nextAcceptHeader) { | |
| request.headers.set('accept', nextAcceptHeader) | |
| } else { | |
| request.headers.delete('accept') | |
| } | |
| } | |
| }, | |
| }, | |
| ) | |
| if (response) { | |
| controller.respondWith(response) | |
| } | |
| return | |
| }, | |
| ) | |
| this.interceptor.on('unhandledException', ({ error }) => { | |
| if (error instanceof InternalError) { | |
| throw error | |
| } | |
| }) | |
| this.interceptor.on( | |
| 'response', | |
| ({ response, isMockedResponse, request, requestId }) => { | |
| this.emitter.emit( | |
| isMockedResponse ? 'response:mocked' : 'response:bypass', | |
| { | |
| response, | |
| request, | |
| requestId, | |
| }, | |
| ) | |
| }, | |
| ) | |
| // Preconfigure the WebSocket interception but don't enable it just yet. | |
| // It will be enabled when the server starts. | |
| handleWebSocketEvent({ | |
| getUnhandledRequestStrategy: () => { | |
| return this.resolvedOptions.onUnhandledRequest | |
| }, | |
| getHandlers: () => { | |
| return this.handlersController.currentHandlers() | |
| }, | |
| onMockedConnection: () => {}, | |
| onPassthroughConnection: () => {}, | |
| }) | |
| } | |
| public listen(options: Partial<SharedOptions> = {}): void { | |
| this.resolvedOptions = mergeRight( | |
| DEFAULT_LISTEN_OPTIONS, | |
| options, | |
| ) as RequiredDeep<SharedOptions> | |
| // Apply the interceptor when starting the server. | |
| // Attach the event listeners to the interceptor here | |
| // so they get re-attached whenever `.listen()` is called. | |
| this.interceptor.apply() | |
| this.init() | |
| this.subscriptions.push(() => this.interceptor.dispose()) | |
| // Apply the WebSocket interception. | |
| webSocketInterceptor.apply() | |
| this.subscriptions.push(() => webSocketInterceptor.dispose()) | |
| // Assert that the interceptor has been applied successfully. | |
| // Also guards us from forgetting to call "interceptor.apply()" | |
| // as a part of the "listen" method. | |
| invariant( | |
| [InterceptorReadyState.APPLYING, InterceptorReadyState.APPLIED].includes( | |
| this.interceptor.readyState, | |
| ), | |
| devUtils.formatMessage( | |
| 'Failed to start "setupServer": the interceptor failed to apply. This is likely an issue with the library and you should report it at "%s".', | |
| ), | |
| 'https://github.com/mswjs/msw/issues/new/choose', | |
| ) | |
| } | |
| public close(): void { | |
| this.dispose() | |
| } | |
| } | |