@mswjs/interceptors 0.25.3 → 0.25.5

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 _chunkNCHFM2TBjs = require('./chunk-NCHFM2TB.js');
6
+ var _chunkC3TKZUFLjs = require('./chunk-C3TKZUFL.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, _chunkNCHFM2TBjs.ClientRequestInterceptor)(),
27
+ new (0, _chunkC3TKZUFLjs.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-MCO3RLQC.mjs";
6
+ } from "./chunk-4B3HXVWT.mjs";
7
7
  import {
8
8
  XMLHttpRequestInterceptor
9
9
  } from "./chunk-FB53TMYN.mjs";
@@ -152,6 +152,12 @@ 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 || ""}`;
157
+ headers.set("Authorization", `Basic ${btoa(auth)}`);
158
+ clientRequest.url.username = "";
159
+ clientRequest.url.password = "";
160
+ }
155
161
  const method = clientRequest.method || "GET";
156
162
  return new Request(clientRequest.url, {
157
163
  method,
@@ -462,6 +468,7 @@ import {
462
468
  Agent as HttpsAgent,
463
469
  globalAgent as httpsGlobalAgent
464
470
  } from "https";
471
+ import { parse as parseUrl } from "url";
465
472
  import { Logger as Logger5 } from "@open-draft/logger";
466
473
 
467
474
  // src/utils/getRequestOptionsByUrl.ts
@@ -576,7 +583,9 @@ function getUrlByRequestOptions(options) {
576
583
  logger3.info("credentials", credentials);
577
584
  const authString = credentials ? `${credentials.username}:${credentials.password}@` : "";
578
585
  logger3.info("auth string:", authString);
579
- const url = new URL(`${protocol}//${authString}${hostname}${path}`);
586
+ const url = new URL(`${protocol}//${hostname}${path}`);
587
+ url.username = (credentials == null ? void 0 : credentials.username) || "";
588
+ url.password = (credentials == null ? void 0 : credentials.password) || "";
580
589
  logger3.info("created url:", url);
581
590
  return url;
582
591
  }
@@ -634,6 +643,17 @@ function resolveRequestOptions(args, url) {
634
643
  logger5.info("using an empty object as request options");
635
644
  return {};
636
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
+ }
637
657
  function resolveCallback(args) {
638
658
  return typeof args[1] === "function" ? args[1] : args[2];
639
659
  }
@@ -655,6 +675,9 @@ function normalizeClientRequestArgs(defaultProtocol, ...args) {
655
675
  } else if (args[0] instanceof URL) {
656
676
  url = args[0];
657
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
+ }
658
681
  options = resolveRequestOptions(args, url);
659
682
  logger5.info("derived request options:", options);
660
683
  callback = resolveCallback(args);
@@ -152,6 +152,12 @@ 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 || ""}`;
157
+ headers.set("Authorization", `Basic ${btoa(auth)}`);
158
+ clientRequest.url.username = "";
159
+ clientRequest.url.password = "";
160
+ }
155
161
  const method = clientRequest.method || "GET";
156
162
  return new Request(clientRequest.url, {
157
163
  method,
@@ -462,6 +468,7 @@ NodeClientRequest.suppressErrorCodes = [
462
468
 
463
469
 
464
470
 
471
+ var _url = require('url');
465
472
 
466
473
 
467
474
  // src/utils/getRequestOptionsByUrl.ts
@@ -576,7 +583,9 @@ function getUrlByRequestOptions(options) {
576
583
  logger3.info("credentials", credentials);
577
584
  const authString = credentials ? `${credentials.username}:${credentials.password}@` : "";
578
585
  logger3.info("auth string:", authString);
579
- const url = new URL(`${protocol}//${authString}${hostname}${path}`);
586
+ const url = new URL(`${protocol}//${hostname}${path}`);
587
+ url.username = (credentials == null ? void 0 : credentials.username) || "";
588
+ url.password = (credentials == null ? void 0 : credentials.password) || "";
580
589
  logger3.info("created url:", url);
581
590
  return url;
582
591
  }
@@ -634,6 +643,17 @@ function resolveRequestOptions(args, url) {
634
643
  logger5.info("using an empty object as request options");
635
644
  return {};
636
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
+ }
637
657
  function resolveCallback(args) {
638
658
  return typeof args[1] === "function" ? args[1] : args[2];
639
659
  }
@@ -655,6 +675,9 @@ function normalizeClientRequestArgs(defaultProtocol, ...args) {
655
675
  } else if (args[0] instanceof URL) {
656
676
  url = args[0];
657
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
+ }
658
681
  options = resolveRequestOptions(args, url);
659
682
  logger5.info("derived request options:", options);
660
683
  callback = resolveCallback(args);
@@ -1,9 +1,9 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkNCHFM2TBjs = require('../../chunk-NCHFM2TB.js');
3
+ var _chunkC3TKZUFLjs = require('../../chunk-C3TKZUFL.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 = _chunkNCHFM2TBjs.ClientRequestInterceptor;
9
+ exports.ClientRequestInterceptor = _chunkC3TKZUFLjs.ClientRequestInterceptor;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ClientRequestInterceptor
3
- } from "../../chunk-MCO3RLQC.mjs";
3
+ } from "../../chunk-4B3HXVWT.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 _chunkNCHFM2TBjs = require('../chunk-NCHFM2TB.js');
3
+ var _chunkC3TKZUFLjs = require('../chunk-C3TKZUFL.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, _chunkNCHFM2TBjs.ClientRequestInterceptor)(),
15
+ new (0, _chunkC3TKZUFLjs.ClientRequestInterceptor)(),
16
16
  new (0, _chunkJCWVLTP7js.XMLHttpRequestInterceptor)()
17
17
  ];
18
18
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ClientRequestInterceptor
3
- } from "../chunk-MCO3RLQC.mjs";
3
+ } from "../chunk-4B3HXVWT.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.3",
4
+ "version": "0.25.5",
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,43 @@ 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
+ })
@@ -20,6 +20,21 @@ export function createRequest(clientRequest: NodeClientRequest): Request {
20
20
  }
21
21
  }
22
22
 
23
+ /**
24
+ * Translate the authentication from the request URL to
25
+ * the request "Authorization" header.
26
+ * @see https://github.com/mswjs/interceptors/issues/438
27
+ */
28
+ if (clientRequest.url.username || clientRequest.url.password) {
29
+ const auth = `${clientRequest.url.username || ''}:${clientRequest.url.password || ''}`
30
+ headers.set('Authorization', `Basic ${btoa(auth)}`)
31
+
32
+ // Remove the credentials from the URL since you cannot
33
+ // construct a Request instance with such a URL.
34
+ clientRequest.url.username = ''
35
+ clientRequest.url.password = ''
36
+ }
37
+
23
38
  const method = clientRequest.method || 'GET'
24
39
 
25
40
  return new Request(clientRequest.url, {
@@ -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
 
@@ -86,7 +86,14 @@ function getHostByRequestOptions(options: ResolvedRequestOptions): string {
86
86
  return host || DEFAULT_HOST
87
87
  }
88
88
 
89
- function getAuthByRequestOptions(options: ResolvedRequestOptions) {
89
+ interface RequestAuth {
90
+ username: string
91
+ password: string
92
+ }
93
+
94
+ function getAuthByRequestOptions(
95
+ options: ResolvedRequestOptions
96
+ ): RequestAuth | undefined {
90
97
  if (options.auth) {
91
98
  const [username, password] = options.auth.split(':')
92
99
  return { username, password }
@@ -159,7 +166,10 @@ export function getUrlByRequestOptions(options: ResolvedRequestOptions): URL {
159
166
  : ''
160
167
  logger.info('auth string:', authString)
161
168
 
162
- const url = new URL(`${protocol}//${authString}${hostname}${path}`)
169
+ const url = new URL(`${protocol}//${hostname}${path}`)
170
+ url.username = credentials?.username || ''
171
+ url.password = credentials?.password || ''
172
+
163
173
  logger.info('created url:', url)
164
174
 
165
175
  return url
@@ -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
  }