diff --git a/package.json b/package.json index 66106e82..e78cb947 100644 --- a/package.json +++ b/package.json @@ -140,6 +140,7 @@ "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "headers-polyfill": "^3.1.0", + "is-node-process": "^1.2.0", "outvariant": "^1.2.1", "strict-event-emitter": "^0.5.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 865e776c..ea70a999 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,6 +31,7 @@ specifiers: follow-redirects: ^1.15.1 got: ^11.8.3 headers-polyfill: ^3.1.0 + is-node-process: ^1.2.0 jest: ^27.4.3 node-fetch: 2.6.7 outvariant: ^1.2.1 @@ -52,6 +53,7 @@ dependencies: '@open-draft/logger': 0.3.0 '@open-draft/until': 2.1.0 headers-polyfill: 3.2.0 + is-node-process: 1.2.0 outvariant: 1.4.0 strict-event-emitter: 0.5.0 diff --git a/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts b/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts index 3320f548..278332a7 100644 --- a/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +++ b/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts @@ -1,5 +1,6 @@ import { invariant } from 'outvariant' import { headersToString } from 'headers-polyfill' +import { isNodeProcess } from 'is-node-process' import type { Logger } from '@open-draft/logger' import { concatArrayBuffer } from './utils/concatArrayBuffer' import { createEvent } from './utils/createEvent' @@ -15,6 +16,7 @@ import { uuidv4 } from '../../utils/uuid' import { createResponse } from './utils/createResponse' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const IS_NODE = isNodeProcess() /** * An `XMLHttpRequest` instance controller that allows us @@ -22,7 +24,7 @@ const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') */ export class XMLHttpRequestController { public request: XMLHttpRequest - public requestId?: string + public requestId: string public onRequest?: ( this: XMLHttpRequestController, args: { @@ -49,6 +51,7 @@ export class XMLHttpRequestController { constructor(readonly initialRequest: XMLHttpRequest, public logger: Logger) { this.events = new Map() + this.requestId = uuidv4() this.requestHeaders = new Headers() this.responseBuffer = new Uint8Array() @@ -80,8 +83,6 @@ export class XMLHttpRequestController { case 'open': { const [method, url] = args as [string, string | undefined] - this.requestId = uuidv4() - if (typeof url === 'undefined') { this.method = 'GET' this.url = toAbsoluteUrl(method) @@ -171,7 +172,7 @@ export class XMLHttpRequestController { ) /** - * @note Set the intercepted request ID on the original request + * @note Set the intercepted request ID on the original request in Node.js * so that if it triggers any other interceptors, they don't attempt * to process it once again. * @@ -179,7 +180,9 @@ export class XMLHttpRequestController { * and we don't want for both XHR and ClientRequest interceptors to * handle the same request at the same time (e.g. emit the "response" event twice). */ - this.request.setRequestHeader('X-Request-Id', this.requestId!) + if (IS_NODE) { + this.request.setRequestHeader('X-Request-Id', this.requestId!) + } return invoke() } diff --git a/test/modules/XMLHttpRequest/intercept/XMLHttpRequest.browser.test.ts b/test/modules/XMLHttpRequest/intercept/XMLHttpRequest.browser.test.ts index 2ec2f639..3e081c93 100644 --- a/test/modules/XMLHttpRequest/intercept/XMLHttpRequest.browser.test.ts +++ b/test/modules/XMLHttpRequest/intercept/XMLHttpRequest.browser.test.ts @@ -1,30 +1,34 @@ 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' 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.method, + req.url + ) + res .set('Access-Control-Allow-Origin', req.headers.origin) .set('Access-Control-Allow-Methods', 'GET, POST') - .set('Access-Control-Allow-Headers', [ - 'content-type', - 'x-request-id', - 'x-request-header', - ]) + .set('Access-Control-Allow-Headers', ['content-type', 'x-request-header']) .set('Access-Control-Allow-Credentials', 'true') return next() } + app.use(strictCorsMiddleware) + const requestHandler: RequestHandler = (req, res) => { res.status(200).send('user-body') } - app.options('/user', strictCorsMiddleware, (req, res) => - res.status(200).end() - ) - app.get('/user', strictCorsMiddleware, requestHandler) - app.post('/user', strictCorsMiddleware, requestHandler) + app.options('/user', (req, res) => res.status(200).end()) + app.get('/user', requestHandler) + app.post('/user', requestHandler) }) test.beforeAll(async () => { @@ -135,15 +139,18 @@ test('sets "credentials" to "same-origin" on isomorphic request when "withCreden expect(request.credentials).toBe('same-origin') }) -test('ignores the body for HEAD requests', async ({ loadExample, callXMLHttpRequest}) => { +test('ignores the body for HEAD requests', async ({ + loadExample, + callXMLHttpRequest, +}) => { await loadExample(require.resolve('./XMLHttpRequest.browser.runtime.js')) const url = httpServer.http.url('/user') const call = callXMLHttpRequest({ method: 'HEAD', url, - body: "test" - }); + body: 'test', + }) await expect(call).resolves.not.toThrowError() @@ -151,15 +158,18 @@ test('ignores the body for HEAD requests', async ({ loadExample, callXMLHttpRequ expect(request.body).toBe(null) }) -test('ignores the body for GET requests', async ({ loadExample, callXMLHttpRequest}) => { +test('ignores the body for GET requests', async ({ + loadExample, + callXMLHttpRequest, +}) => { await loadExample(require.resolve('./XMLHttpRequest.browser.runtime.js')) const url = httpServer.http.url('/user') const call = callXMLHttpRequest({ method: 'GET', url, - body: "test" - }); + body: 'test', + }) await expect(call).resolves.not.toThrowError()