From 28e533c9b9ab1a796254ebdcf6493617739dcd89 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Tue, 26 Mar 2024 10:24:07 +0100 Subject: [PATCH] fix: use internal request id header vs "X-Request-Id" (#529) --- src/Interceptor.ts | 12 +++++++++++- .../ClientRequest/NodeClientRequest.ts | 5 +++-- .../XMLHttpRequest/XMLHttpRequestController.ts | 17 ++++++++++++----- .../intercept/XMLHttpRequest.browser.test.ts | 8 +++++--- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/Interceptor.ts b/src/Interceptor.ts index 2d87a321..b33e74fd 100644 --- a/src/Interceptor.ts +++ b/src/Interceptor.ts @@ -1,9 +1,19 @@ import { Logger } from '@open-draft/logger' -import { Emitter, EventMap, Listener } from 'strict-event-emitter' +import { Emitter, Listener } from 'strict-event-emitter' export type InterceptorEventMap = Record export type InterceptorSubscription = () => void +/** + * Request header name to detect when a single request + * is being handled by nested interceptors (XHR -> ClientRequest). + * Obscure by design to prevent collisions with user-defined headers. + * Ideally, come up with the Interceptor-level mechanism for this. + * @see https://github.com/mswjs/interceptors/issues/378 + */ +export const INTERNAL_REQUEST_ID_HEADER_NAME = + 'x-interceptors-internal-request-id' + export function getGlobalSymbol(symbol: Symbol): V | undefined { return ( // @ts-ignore https://github.com/Microsoft/TypeScript/issues/24587 diff --git a/src/interceptors/ClientRequest/NodeClientRequest.ts b/src/interceptors/ClientRequest/NodeClientRequest.ts index 0bdec77f..81ad12d1 100644 --- a/src/interceptors/ClientRequest/NodeClientRequest.ts +++ b/src/interceptors/ClientRequest/NodeClientRequest.ts @@ -21,6 +21,7 @@ import { uuidv4 } from '../../utils/uuid' import { emitAsync } from '../../utils/emitAsync' import { getRawFetchHeaders } from '../../utils/getRawFetchHeaders' import { isPropertyAccessible } from '../../utils/isPropertyAccessible' +import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor' export type Protocol = 'http' | 'https' @@ -189,8 +190,8 @@ export class NodeClientRequest extends ClientRequest { // in another (parent) interceptor (like XMLHttpRequest -> ClientRequest). // That means some interceptor up the chain has concluded that // this request must be performed as-is. - if (this.getHeader('X-Request-Id') != null) { - this.removeHeader('X-Request-Id') + if (this.hasHeader(INTERNAL_REQUEST_ID_HEADER_NAME)) { + this.removeHeader(INTERNAL_REQUEST_ID_HEADER_NAME) return this.passthrough(chunk, encoding, callback) } diff --git a/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts b/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts index 9ebd204f..5c3474e3 100644 --- a/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +++ b/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts @@ -13,6 +13,7 @@ import { isDomParserSupportedType } from './utils/isDomParserSupportedType' import { parseJson } from '../../utils/parseJson' import { uuidv4 } from '../../utils/uuid' import { createResponse } from './utils/createResponse' +import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const IS_NODE = isNodeProcess() @@ -48,7 +49,10 @@ export class XMLHttpRequestController { private responseBuffer: Uint8Array private events: Map> - constructor(readonly initialRequest: XMLHttpRequest, public logger: Logger) { + constructor( + readonly initialRequest: XMLHttpRequest, + public logger: Logger + ) { this.events = new Map() this.requestId = uuidv4() this.requestHeaders = new Headers() @@ -99,7 +103,7 @@ export class XMLHttpRequestController { case 'addEventListener': { const [eventName, listener] = args as [ keyof XMLHttpRequestEventTargetEventMap, - Function + Function, ] this.registerEvent(eventName, listener) @@ -119,7 +123,7 @@ export class XMLHttpRequestController { case 'send': { const [body] = args as [ - body?: XMLHttpRequestBodyInit | Document | null + body?: XMLHttpRequestBodyInit | Document | null, ] if (body != null) { @@ -180,7 +184,10 @@ export class XMLHttpRequestController { * handle the same request at the same time (e.g. emit the "response" event twice). */ if (IS_NODE) { - this.request.setRequestHeader('X-Request-Id', this.requestId!) + this.request.setRequestHeader( + INTERNAL_REQUEST_ID_HEADER_NAME, + this.requestId! + ) } return invoke() @@ -517,7 +524,7 @@ export class XMLHttpRequestController { private trigger< EventName extends keyof (XMLHttpRequestEventTargetEventMap & { readystatechange: ProgressEvent - }) + }), >(eventName: EventName, options?: ProgressEventInit): void { const callback = this.request[`on${eventName}`] const event = createEvent(this.request, eventName, options) diff --git a/test/modules/XMLHttpRequest/intercept/XMLHttpRequest.browser.test.ts b/test/modules/XMLHttpRequest/intercept/XMLHttpRequest.browser.test.ts index 3e081c93..4f41e275 100644 --- a/test/modules/XMLHttpRequest/intercept/XMLHttpRequest.browser.test.ts +++ b/test/modules/XMLHttpRequest/intercept/XMLHttpRequest.browser.test.ts @@ -1,13 +1,15 @@ +import { invariant } from 'outvariant' import { HttpServer } from '@open-draft/test-server/http' import { RequestHandler } from 'express-serve-static-core' import { test, expect } from '../../../playwright.extend' -import { invariant } from 'outvariant' +import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../../../src/Interceptor' const httpServer = new HttpServer((app) => { const strictCorsMiddleware: RequestHandler = (req, res, next) => { invariant( - !req.header('x-request-id'), - 'Found unexpected "X-Request-Id" request header in the browser for "%s %s"', + !req.header(INTERNAL_REQUEST_ID_HEADER_NAME), + 'Found unexpected internal "%s" request header in the browser for "%s %s"', + INTERNAL_REQUEST_ID_HEADER_NAME, req.method, req.url )