msw
Version:
121 lines (103 loc) • 3.9 kB
text/typescript
import { until } from '@open-draft/until'
import { Emitter } from 'strict-event-emitter'
import { LifeCycleEventsMap, SharedOptions } from '../sharedOptions'
import { RequiredDeep } from '../typeUtils'
import type { RequestHandler } from '../handlers/RequestHandler'
import { HandlersExecutionResult, executeHandlers } from './executeHandlers'
import { onUnhandledRequest } from './request/onUnhandledRequest'
import { storeResponseCookies } from './request/storeResponseCookies'
export interface HandleRequestOptions {
/**
* `resolutionContext` is not part of the general public api
* but is exposed to aid in creating extensions like
* `@mswjs/http-middleware`.
*/
resolutionContext?: {
/**
* A base url to use when resolving relative urls.
* @note This is primarily used by the `@mswjs/http-middleware`
* to resolve relative urls in the context of the running server
*/
baseUrl?: string
}
/**
* Invoked whenever a request is performed as-is.
*/
onPassthroughResponse?(request: Request): void
/**
* Invoked when the mocked response is ready to be sent.
*/
onMockedResponse?(
response: Response,
handler: RequiredDeep<HandlersExecutionResult>,
): void
}
export async function handleRequest(
request: Request,
requestId: string,
handlers: Array<RequestHandler>,
options: RequiredDeep<SharedOptions>,
emitter: Emitter<LifeCycleEventsMap>,
handleRequestOptions?: HandleRequestOptions,
): Promise<Response | undefined> {
emitter.emit('request:start', { request, requestId })
// Perform requests wrapped in "bypass()" as-is.
if (request.headers.get('accept')?.includes('msw/passthrough')) {
emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
// Resolve a mocked response from the list of request handlers.
const lookupResult = await until(() => {
return executeHandlers({
request,
requestId,
handlers,
resolutionContext: handleRequestOptions?.resolutionContext,
})
})
if (lookupResult.error) {
// Allow developers to react to unhandled exceptions in request handlers.
emitter.emit('unhandledException', {
error: lookupResult.error,
request,
requestId,
})
throw lookupResult.error
}
// If the handler lookup returned nothing, no request handler was found
// matching this request. Report the request as unhandled.
if (!lookupResult.data) {
await onUnhandledRequest(request, options.onUnhandledRequest)
emitter.emit('request:unhandled', { request, requestId })
emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
const { response } = lookupResult.data
// When the handled request returned no mocked response, warn the developer,
// as it may be an oversight on their part. Perform the request as-is.
if (!response) {
emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
// Perform the request as-is when the developer explicitly returned "req.passthrough()".
// This produces no warning as the request was handled.
if (
response.status === 302 &&
response.headers.get('x-msw-intention') === 'passthrough'
) {
emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
// Store all the received response cookies in the cookie jar.
storeResponseCookies(request, response)
emitter.emit('request:match', { request, requestId })
const requiredLookupResult =
lookupResult.data as RequiredDeep<HandlersExecutionResult>
handleRequestOptions?.onMockedResponse?.(response, requiredLookupResult)
emitter.emit('request:end', { request, requestId })
return response
}