@mswjs/interceptors 0.25.4 → 0.25.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  var _chunkUF7QIAQ5js = require('./chunk-UF7QIAQ5.js');
4
4
 
5
5
 
6
- var _chunkBAS5F5A4js = require('./chunk-BAS5F5A4.js');
6
+ var _chunk44QGFZITjs = require('./chunk-44QGFZIT.js');
7
7
 
8
8
 
9
9
  var _chunkJCWVLTP7js = require('./chunk-JCWVLTP7.js');
@@ -24,7 +24,7 @@ var RemoteHttpInterceptor = class extends _chunkUF7QIAQ5js.BatchInterceptor {
24
24
  super({
25
25
  name: "remote-interceptor",
26
26
  interceptors: [
27
- new (0, _chunkBAS5F5A4js.ClientRequestInterceptor)(),
27
+ new (0, _chunk44QGFZITjs.ClientRequestInterceptor)(),
28
28
  new (0, _chunkJCWVLTP7js.XMLHttpRequestInterceptor)()
29
29
  ]
30
30
  });
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-UBEFEZXT.mjs";
4
4
  import {
5
5
  ClientRequestInterceptor
6
- } from "./chunk-6HZYLI7O.mjs";
6
+ } from "./chunk-Z7O2DO3X.mjs";
7
7
  import {
8
8
  XMLHttpRequestInterceptor
9
9
  } from "./chunk-FB53TMYN.mjs";
@@ -144,7 +144,7 @@ function createRequest(clientRequest) {
144
144
  const outgoingHeaders = clientRequest.getHeaders();
145
145
  for (const headerName in outgoingHeaders) {
146
146
  const headerValue = outgoingHeaders[headerName];
147
- if (!headerValue) {
147
+ if (typeof headerValue === "undefined") {
148
148
  continue;
149
149
  }
150
150
  const valuesList = Array.prototype.concat([], headerValue);
@@ -152,8 +152,8 @@ function createRequest(clientRequest) {
152
152
  headers.append(headerName, value.toString());
153
153
  }
154
154
  }
155
- if (clientRequest.url.username && clientRequest.url.password) {
156
- const auth = `${clientRequest.url.username}:${clientRequest.url.password}`;
155
+ if (clientRequest.url.username || clientRequest.url.password) {
156
+ const auth = `${clientRequest.url.username || ""}:${clientRequest.url.password || ""}`;
157
157
  headers.set("Authorization", `Basic ${btoa(auth)}`);
158
158
  clientRequest.url.username = "";
159
159
  clientRequest.url.password = "";
@@ -468,6 +468,7 @@ NodeClientRequest.suppressErrorCodes = [
468
468
 
469
469
 
470
470
 
471
+ var _url = require('url');
471
472
 
472
473
 
473
474
  // src/utils/getRequestOptionsByUrl.ts
@@ -642,6 +643,17 @@ function resolveRequestOptions(args, url) {
642
643
  logger5.info("using an empty object as request options");
643
644
  return {};
644
645
  }
646
+ function overrideUrlByRequestOptions(url, options) {
647
+ url.host = options.host || url.host;
648
+ url.hostname = options.hostname || url.hostname;
649
+ url.port = options.port ? options.port.toString() : url.port;
650
+ if (options.path) {
651
+ const parsedOptionsPath = _url.parse.call(void 0, options.path, false);
652
+ url.pathname = parsedOptionsPath.pathname || "";
653
+ url.search = parsedOptionsPath.search || "";
654
+ }
655
+ return url;
656
+ }
645
657
  function resolveCallback(args) {
646
658
  return typeof args[1] === "function" ? args[1] : args[2];
647
659
  }
@@ -663,6 +675,9 @@ function normalizeClientRequestArgs(defaultProtocol, ...args) {
663
675
  } else if (args[0] instanceof URL) {
664
676
  url = args[0];
665
677
  logger5.info("first argument is a URL:", url);
678
+ if (typeof args[1] !== "undefined" && isObject(args[1])) {
679
+ url = overrideUrlByRequestOptions(url, args[1]);
680
+ }
666
681
  options = resolveRequestOptions(args, url);
667
682
  logger5.info("derived request options:", options);
668
683
  callback = resolveCallback(args);
@@ -144,7 +144,7 @@ function createRequest(clientRequest) {
144
144
  const outgoingHeaders = clientRequest.getHeaders();
145
145
  for (const headerName in outgoingHeaders) {
146
146
  const headerValue = outgoingHeaders[headerName];
147
- if (!headerValue) {
147
+ if (typeof headerValue === "undefined") {
148
148
  continue;
149
149
  }
150
150
  const valuesList = Array.prototype.concat([], headerValue);
@@ -152,8 +152,8 @@ function createRequest(clientRequest) {
152
152
  headers.append(headerName, value.toString());
153
153
  }
154
154
  }
155
- if (clientRequest.url.username && clientRequest.url.password) {
156
- const auth = `${clientRequest.url.username}:${clientRequest.url.password}`;
155
+ if (clientRequest.url.username || clientRequest.url.password) {
156
+ const auth = `${clientRequest.url.username || ""}:${clientRequest.url.password || ""}`;
157
157
  headers.set("Authorization", `Basic ${btoa(auth)}`);
158
158
  clientRequest.url.username = "";
159
159
  clientRequest.url.password = "";
@@ -468,6 +468,7 @@ import {
468
468
  Agent as HttpsAgent,
469
469
  globalAgent as httpsGlobalAgent
470
470
  } from "https";
471
+ import { parse as parseUrl } from "url";
471
472
  import { Logger as Logger5 } from "@open-draft/logger";
472
473
 
473
474
  // src/utils/getRequestOptionsByUrl.ts
@@ -642,6 +643,17 @@ function resolveRequestOptions(args, url) {
642
643
  logger5.info("using an empty object as request options");
643
644
  return {};
644
645
  }
646
+ function overrideUrlByRequestOptions(url, options) {
647
+ url.host = options.host || url.host;
648
+ url.hostname = options.hostname || url.hostname;
649
+ url.port = options.port ? options.port.toString() : url.port;
650
+ if (options.path) {
651
+ const parsedOptionsPath = parseUrl(options.path, false);
652
+ url.pathname = parsedOptionsPath.pathname || "";
653
+ url.search = parsedOptionsPath.search || "";
654
+ }
655
+ return url;
656
+ }
645
657
  function resolveCallback(args) {
646
658
  return typeof args[1] === "function" ? args[1] : args[2];
647
659
  }
@@ -663,6 +675,9 @@ function normalizeClientRequestArgs(defaultProtocol, ...args) {
663
675
  } else if (args[0] instanceof URL) {
664
676
  url = args[0];
665
677
  logger5.info("first argument is a URL:", url);
678
+ if (typeof args[1] !== "undefined" && isObject(args[1])) {
679
+ url = overrideUrlByRequestOptions(url, args[1]);
680
+ }
666
681
  options = resolveRequestOptions(args, url);
667
682
  logger5.info("derived request options:", options);
668
683
  callback = resolveCallback(args);
@@ -1,9 +1,9 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkBAS5F5A4js = require('../../chunk-BAS5F5A4.js');
3
+ var _chunk44QGFZITjs = require('../../chunk-44QGFZIT.js');
4
4
  require('../../chunk-OGN3ZR35.js');
5
5
  require('../../chunk-5PTPJLB7.js');
6
6
  require('../../chunk-3XFLRXRY.js');
7
7
 
8
8
 
9
- exports.ClientRequestInterceptor = _chunkBAS5F5A4js.ClientRequestInterceptor;
9
+ exports.ClientRequestInterceptor = _chunk44QGFZITjs.ClientRequestInterceptor;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ClientRequestInterceptor
3
- } from "../../chunk-6HZYLI7O.mjs";
3
+ } from "../../chunk-Z7O2DO3X.mjs";
4
4
  import "../../chunk-3IYIKC3X.mjs";
5
5
  import "../../chunk-YQGTMMOZ.mjs";
6
6
  import "../../chunk-GM3YBSM3.mjs";
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkBAS5F5A4js = require('../chunk-BAS5F5A4.js');
3
+ var _chunk44QGFZITjs = require('../chunk-44QGFZIT.js');
4
4
 
5
5
 
6
6
  var _chunkJCWVLTP7js = require('../chunk-JCWVLTP7.js');
@@ -12,7 +12,7 @@ require('../chunk-3XFLRXRY.js');
12
12
 
13
13
  // src/presets/node.ts
14
14
  var node_default = [
15
- new (0, _chunkBAS5F5A4js.ClientRequestInterceptor)(),
15
+ new (0, _chunk44QGFZITjs.ClientRequestInterceptor)(),
16
16
  new (0, _chunkJCWVLTP7js.XMLHttpRequestInterceptor)()
17
17
  ];
18
18
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ClientRequestInterceptor
3
- } from "../chunk-6HZYLI7O.mjs";
3
+ } from "../chunk-Z7O2DO3X.mjs";
4
4
  import {
5
5
  XMLHttpRequestInterceptor
6
6
  } from "../chunk-FB53TMYN.mjs";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mswjs/interceptors",
3
3
  "description": "Low-level HTTP/HTTPS/XHR/fetch request interception library.",
4
- "version": "0.25.4",
4
+ "version": "0.25.6",
5
5
  "main": "./lib/node/index.js",
6
6
  "module": "./lib/node/index.mjs",
7
7
  "types": "./lib/node/index.d.ts",
@@ -83,3 +83,62 @@ it('creates a fetch Request with an empty string body', async () => {
83
83
  expect(request.url).toBe('https://api.github.com/')
84
84
  expect(request.body).toBe(null)
85
85
  })
86
+
87
+ it('creates a fetch Request with an empty password', async () => {
88
+ const clientRequest = new NodeClientRequest(
89
+ [
90
+ new URL('https://api.github.com'),
91
+ { auth: 'username:' },
92
+ () => {},
93
+ ],
94
+ {
95
+ emitter,
96
+ logger,
97
+ }
98
+ )
99
+ clientRequest.write('')
100
+
101
+ const request = createRequest(clientRequest)
102
+
103
+ expect(request.headers.get('Authorization')).toBe(`Basic ${btoa('username:')}`)
104
+ expect(request.url).toBe('https://api.github.com/')
105
+ })
106
+
107
+ it('creates a fetch Request with an empty username', async () => {
108
+ const clientRequest = new NodeClientRequest(
109
+ [
110
+ new URL('https://api.github.com'),
111
+ { auth: ':password' },
112
+ () => {},
113
+ ],
114
+ {
115
+ emitter,
116
+ logger,
117
+ }
118
+ )
119
+ clientRequest.write('')
120
+
121
+ const request = createRequest(clientRequest)
122
+
123
+ expect(request.headers.get('Authorization')).toBe(`Basic ${btoa(':password')}`)
124
+ expect(request.url).toBe('https://api.github.com/')
125
+ })
126
+
127
+ it('creates a fetch Request with falsy headers', async () => {
128
+ const clientRequest = new NodeClientRequest(
129
+ [
130
+ new URL('https://api.github.com'),
131
+ { headers: { 'foo': 0, 'empty': '' }}
132
+ ],
133
+ {
134
+ emitter,
135
+ logger,
136
+ }
137
+ )
138
+ clientRequest.write('')
139
+
140
+ const request = createRequest(clientRequest)
141
+
142
+ expect(request.headers.get('foo')).toBe('0')
143
+ expect(request.headers.get('empty')).toBe('')
144
+ })
@@ -10,7 +10,7 @@ export function createRequest(clientRequest: NodeClientRequest): Request {
10
10
  for (const headerName in outgoingHeaders) {
11
11
  const headerValue = outgoingHeaders[headerName]
12
12
 
13
- if (!headerValue) {
13
+ if (typeof headerValue === 'undefined') {
14
14
  continue
15
15
  }
16
16
 
@@ -25,8 +25,8 @@ export function createRequest(clientRequest: NodeClientRequest): Request {
25
25
  * the request "Authorization" header.
26
26
  * @see https://github.com/mswjs/interceptors/issues/438
27
27
  */
28
- if (clientRequest.url.username && clientRequest.url.password) {
29
- const auth = `${clientRequest.url.username}:${clientRequest.url.password}`
28
+ if (clientRequest.url.username || clientRequest.url.password) {
29
+ const auth = `${clientRequest.url.username || ''}:${clientRequest.url.password || ''}`
30
30
  headers.set('Authorization', `Basic ${btoa(auth)}`)
31
31
 
32
32
  // Remove the credentials from the URL since you cannot
@@ -212,6 +212,55 @@ it('handles [URL, RequestOptions, callback] input', () => {
212
212
  expect(callback?.name).toEqual('cb')
213
213
  })
214
214
 
215
+ it('handles [URL, RequestOptions] where options have custom "hostname"', () => {
216
+ const [url, options] = normalizeClientRequestArgs(
217
+ 'http:',
218
+ new URL('http://example.com/path-from-url'),
219
+ {
220
+ hostname: 'host-from-options.com',
221
+ }
222
+ )
223
+ expect(url.href).toBe('http://host-from-options.com/path-from-url')
224
+ expect(options).toMatchObject({
225
+ host: 'host-from-options.com',
226
+ path: '/path-from-url',
227
+ })
228
+ })
229
+
230
+ it('handles [URL, RequestOptions] where options contain "host" and "path" and "port"', () => {
231
+ const [url, options] = normalizeClientRequestArgs(
232
+ 'http:',
233
+ new URL('http://example.com/path-from-url?a=b&c=d'),
234
+ {
235
+ hostname: 'host-from-options.com',
236
+ path: '/path-from-options',
237
+ port: 1234,
238
+ }
239
+ )
240
+ // Must remove the query string since it's not specified in "options.path"
241
+ expect(url.href).toBe('http://host-from-options.com:1234/path-from-options')
242
+ expect(options).toMatchObject({
243
+ host: 'host-from-options.com:1234',
244
+ path: '/path-from-options',
245
+ port: 1234,
246
+ })
247
+ })
248
+
249
+ it('handles [URL, RequestOptions] where options contain "path" with query string', () => {
250
+ const [url, options] = normalizeClientRequestArgs(
251
+ 'http:',
252
+ new URL('http://example.com/path-from-url?a=b&c=d'),
253
+ {
254
+ path: '/path-from-options?foo=bar&baz=xyz',
255
+ }
256
+ )
257
+ expect(url.href).toBe('http://example.com/path-from-options?foo=bar&baz=xyz')
258
+ expect(options).toMatchObject({
259
+ host: 'example.com',
260
+ path: '/path-from-options?foo=bar&baz=xyz',
261
+ })
262
+ })
263
+
215
264
  it('handles [RequestOptions, callback] input', () => {
216
265
  const initialOptions = {
217
266
  method: 'POST',
@@ -371,3 +420,50 @@ it('merges URL-based RequestOptions with the custom RequestOptions', () => {
371
420
  expect(options.hostname).toEqual(url.hostname)
372
421
  expect(options.path).toEqual(url.pathname)
373
422
  })
423
+
424
+ it('respects custom "options.path" over URL path', () => {
425
+ const [url, options] = normalizeClientRequestArgs(
426
+ 'http:',
427
+ new URL('http://example.com/path-from-url'),
428
+ {
429
+ path: '/path-from-options',
430
+ }
431
+ )
432
+
433
+ expect(url.href).toBe('http://example.com/path-from-options')
434
+ expect(options.protocol).toBe('http:')
435
+ expect(options.host).toBe('example.com')
436
+ expect(options.hostname).toBe('example.com')
437
+ expect(options.path).toBe('/path-from-options')
438
+ })
439
+
440
+ it('respects custom "options.path" over URL path with query string', () => {
441
+ const [url, options] = normalizeClientRequestArgs(
442
+ 'http:',
443
+ new URL('http://example.com/path-from-url?a=b&c=d'),
444
+ {
445
+ path: '/path-from-options',
446
+ }
447
+ )
448
+
449
+ // Must replace both the path and the query string.
450
+ expect(url.href).toBe('http://example.com/path-from-options')
451
+ expect(options.protocol).toBe('http:')
452
+ expect(options.host).toBe('example.com')
453
+ expect(options.hostname).toBe('example.com')
454
+ expect(options.path).toBe('/path-from-options')
455
+ })
456
+
457
+ it('preserves URL query string', () => {
458
+ const [url, options] = normalizeClientRequestArgs(
459
+ 'http:',
460
+ new URL('http://example.com/resource?a=b&c=d')
461
+ )
462
+
463
+ expect(url.href).toBe('http://example.com/resource?a=b&c=d')
464
+ expect(options.protocol).toBe('http:')
465
+ expect(options.host).toBe('example.com')
466
+ expect(options.hostname).toBe('example.com')
467
+ // Query string is a part of the options path.
468
+ expect(options.path).toBe('/resource?a=b&c=d')
469
+ })
@@ -8,7 +8,7 @@ import {
8
8
  Agent as HttpsAgent,
9
9
  globalAgent as httpsGlobalAgent,
10
10
  } from 'https'
11
- import { Url as LegacyURL } from 'url'
11
+ import { Url as LegacyURL, parse as parseUrl } from 'url'
12
12
  import { Logger } from '@open-draft/logger'
13
13
  import { getRequestOptionsByUrl } from '../../../utils/getRequestOptionsByUrl'
14
14
  import {
@@ -63,6 +63,25 @@ function resolveRequestOptions(
63
63
  return {} as RequestOptions
64
64
  }
65
65
 
66
+ /**
67
+ * Overrides the given `URL` instance with the explicit properties provided
68
+ * on the `RequestOptions` object. The options object takes precedence,
69
+ * and will replace URL properties like "host", "path", and "port", if specified.
70
+ */
71
+ function overrideUrlByRequestOptions(url: URL, options: RequestOptions): URL {
72
+ url.host = options.host || url.host
73
+ url.hostname = options.hostname || url.hostname
74
+ url.port = options.port ? options.port.toString() : url.port
75
+
76
+ if (options.path) {
77
+ const parsedOptionsPath = parseUrl(options.path, false)
78
+ url.pathname = parsedOptionsPath.pathname || ''
79
+ url.search = parsedOptionsPath.search || ''
80
+ }
81
+
82
+ return url
83
+ }
84
+
66
85
  function resolveCallback(
67
86
  args: ClientRequestArgs
68
87
  ): HttpRequestCallback | undefined {
@@ -112,6 +131,15 @@ export function normalizeClientRequestArgs(
112
131
  url = args[0]
113
132
  logger.info('first argument is a URL:', url)
114
133
 
134
+ // Check if the second provided argument is RequestOptions.
135
+ // If it is, check if "options.path" was set and rewrite it
136
+ // on the input URL.
137
+ // Do this before resolving options from the URL below
138
+ // to prevent query string from being duplicated in the path.
139
+ if (typeof args[1] !== 'undefined' && isObject<RequestOptions>(args[1])) {
140
+ url = overrideUrlByRequestOptions(url, args[1])
141
+ }
142
+
115
143
  options = resolveRequestOptions(args, url)
116
144
  logger.info('derived request options:', options)
117
145
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Determines if a given value is an instance of object.
3
3
  */
4
- export function isObject(value: any): boolean {
4
+ export function isObject<T>(value: any): value is T {
5
5
  return Object.prototype.toString.call(value) === '[object Object]'
6
6
  }