import { StartOptions, SetupWorkerInternalContext, ServiceWorkerIncomingEventsMap, } from '../glossary' import { ServiceWorkerMessage, WorkerChannel, } from './utils/createMessageChannel' import { parseWorkerRequest } from '../../utils/parseWorkerRequest' import { RequestHandler } from '~/core/handlers/RequestHandler' import { handleRequest } from '~/core/utils/handleRequest' import { RequiredDeep } from '~/core/typeUtils' import { devUtils } from '~/core/utils/internal/devUtils' import { toResponseInit } from '~/core/utils/toResponseInit' import { isHandlerKind } from '~/core/utils/internal/isHandlerKind' export const createRequestListener = ( context: SetupWorkerInternalContext, options: RequiredDeep, ) => { return async ( event: MessageEvent, message: ServiceWorkerMessage< 'REQUEST', ServiceWorkerIncomingEventsMap['REQUEST'] >, ) => { const messageChannel = new WorkerChannel(event.ports[0]) const requestId = message.payload.id const request = parseWorkerRequest(message.payload) const requestCloneForLogs = request.clone() // Make this the first requets clone before the // request resolution pipeline even starts. // Store the clone in cache so the first matching // request handler would skip the cloning phase. const requestClone = request.clone() RequestHandler.cache.set(request, requestClone) context.requests.set(requestId, requestClone) try { await handleRequest( request, requestId, context.getRequestHandlers().filter(isHandlerKind('RequestHandler')), options, context.emitter, { onPassthroughResponse() { messageChannel.postMessage('PASSTHROUGH') }, async onMockedResponse(response, { handler, parsedResult }) { // Clone the mocked response so its body could be read // to buffer to be sent to the worker and also in the // ".log()" method of the request handler. const responseClone = response.clone() const responseCloneForLogs = response.clone() const responseInit = toResponseInit(response) /** * @note Safari doesn't support transferring a "ReadableStream". * Check that the browser supports that before sending it to the worker. */ if (context.supports.readableStreamTransfer) { const responseStreamOrNull = response.body messageChannel.postMessage( 'MOCK_RESPONSE', { ...responseInit, body: responseStreamOrNull, }, responseStreamOrNull ? [responseStreamOrNull] : undefined, ) } else { /** * @note If we are here, this means the current environment doesn't * support "ReadableStream" as transferable. In that case, * attempt to read the non-empty response body as ArrayBuffer, if it's not empty. * @see https://github.com/mswjs/msw/issues/1827 */ const responseBufferOrNull = response.body === null ? null : await responseClone.arrayBuffer() messageChannel.postMessage('MOCK_RESPONSE', { ...responseInit, body: responseBufferOrNull, }) } if (!options.quiet) { context.emitter.once('response:mocked', () => { handler.log({ request: requestCloneForLogs, response: responseCloneForLogs, parsedResult, }) }) } }, }, ) } catch (error) { if (error instanceof Error) { devUtils.error( `Uncaught exception in the request handler for "%s %s": %s This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses`, request.method, request.url, error.stack ?? error, ) // Treat all other exceptions in a request handler as unintended, // alerting that there is a problem that needs fixing. messageChannel.postMessage('MOCK_RESPONSE', { status: 500, statusText: 'Request Handler Error', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: error.name, message: error.message, stack: error.stack, }), }) } } } }