@mswjs/interceptors 0.33.2 → 0.34.0

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.
@@ -1,141 +1,10 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkIDEEMJ3Fjs = require('../../chunk-IDEEMJ3F.js');
3
+ var _chunkUN335ZTDjs = require('../../chunk-UN335ZTD.js');
4
+ require('../../chunk-IDEEMJ3F.js');
5
+ require('../../chunk-5WWNCLB3.js');
6
+ require('../../chunk-YGM3BCJU.js');
4
7
 
5
8
 
6
-
7
-
8
- var _chunk5WWNCLB3js = require('../../chunk-5WWNCLB3.js');
9
-
10
-
11
-
12
- var _chunkYGM3BCJUjs = require('../../chunk-YGM3BCJU.js');
13
-
14
- // src/interceptors/fetch/index.ts
15
- var _outvariant = require('outvariant');
16
- var _deferredpromise = require('@open-draft/deferred-promise');
17
-
18
- // src/utils/canParseUrl.ts
19
- function canParseUrl(url) {
20
- try {
21
- new URL(url);
22
- return true;
23
- } catch (_error) {
24
- return false;
25
- }
26
- }
27
-
28
- // src/interceptors/fetch/index.ts
29
- var _FetchInterceptor = class extends _chunkYGM3BCJUjs.Interceptor {
30
- constructor() {
31
- super(_FetchInterceptor.symbol);
32
- }
33
- checkEnvironment() {
34
- return typeof globalThis !== "undefined" && typeof globalThis.fetch !== "undefined";
35
- }
36
- async setup() {
37
- const pureFetch = globalThis.fetch;
38
- _outvariant.invariant.call(void 0,
39
- !pureFetch[_chunkIDEEMJ3Fjs.IS_PATCHED_MODULE],
40
- 'Failed to patch the "fetch" module: already patched.'
41
- );
42
- globalThis.fetch = async (input, init) => {
43
- const requestId = _chunkYGM3BCJUjs.createRequestId.call(void 0, );
44
- const resolvedInput = typeof input === "string" && typeof location !== "undefined" && !canParseUrl(input) ? new URL(input, location.origin) : input;
45
- const request = new Request(resolvedInput, init);
46
- const responsePromise = new (0, _deferredpromise.DeferredPromise)();
47
- const controller = new (0, _chunk5WWNCLB3js.RequestController)(request);
48
- this.logger.info("[%s] %s", request.method, request.url);
49
- this.logger.info("awaiting for the mocked response...");
50
- this.logger.info(
51
- 'emitting the "request" event for %s listener(s)...',
52
- this.emitter.listenerCount("request")
53
- );
54
- const isRequestHandled = await _chunk5WWNCLB3js.handleRequest.call(void 0, {
55
- request,
56
- requestId,
57
- emitter: this.emitter,
58
- controller,
59
- onResponse: async (response) => {
60
- this.logger.info("received mocked response!", {
61
- response
62
- });
63
- if (this.emitter.listenerCount("response") > 0) {
64
- this.logger.info('emitting the "response" event...');
65
- await _chunk5WWNCLB3js.emitAsync.call(void 0, this.emitter, "response", {
66
- // Clone the mocked response for the "response" event listener.
67
- // This way, the listener can read the response and not lock its body
68
- // for the actual fetch consumer.
69
- response: response.clone(),
70
- isMockedResponse: true,
71
- request,
72
- requestId
73
- });
74
- }
75
- Object.defineProperty(response, "url", {
76
- writable: false,
77
- enumerable: true,
78
- configurable: false,
79
- value: request.url
80
- });
81
- responsePromise.resolve(response);
82
- },
83
- onRequestError: (response) => {
84
- this.logger.info("request has errored!", { response });
85
- responsePromise.reject(createNetworkError(response));
86
- },
87
- onError: (error) => {
88
- this.logger.info("request has been aborted!", { error });
89
- responsePromise.reject(error);
90
- }
91
- });
92
- if (isRequestHandled) {
93
- this.logger.info("request has been handled, returning mock promise...");
94
- return responsePromise;
95
- }
96
- this.logger.info(
97
- "no mocked response received, performing request as-is..."
98
- );
99
- return pureFetch(request).then((response) => {
100
- this.logger.info("original fetch performed", response);
101
- if (this.emitter.listenerCount("response") > 0) {
102
- this.logger.info('emitting the "response" event...');
103
- const responseClone = response.clone();
104
- this.emitter.emit("response", {
105
- response: responseClone,
106
- isMockedResponse: false,
107
- request,
108
- requestId
109
- });
110
- }
111
- return response;
112
- });
113
- };
114
- Object.defineProperty(globalThis.fetch, _chunkIDEEMJ3Fjs.IS_PATCHED_MODULE, {
115
- enumerable: true,
116
- configurable: true,
117
- value: true
118
- });
119
- this.subscriptions.push(() => {
120
- Object.defineProperty(globalThis.fetch, _chunkIDEEMJ3Fjs.IS_PATCHED_MODULE, {
121
- value: void 0
122
- });
123
- globalThis.fetch = pureFetch;
124
- this.logger.info(
125
- 'restored native "globalThis.fetch"!',
126
- globalThis.fetch.name
127
- );
128
- });
129
- }
130
- };
131
- var FetchInterceptor = _FetchInterceptor;
132
- FetchInterceptor.symbol = Symbol("fetch");
133
- function createNetworkError(cause) {
134
- return Object.assign(new TypeError("Failed to fetch"), {
135
- cause
136
- });
137
- }
138
-
139
-
140
- exports.FetchInterceptor = FetchInterceptor;
9
+ exports.FetchInterceptor = _chunkUN335ZTDjs.FetchInterceptor;
141
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/interceptors/fetch/index.ts","../../../../src/utils/canParseUrl.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;;;ACIzB,SAAS,YAAY,KAAsB;AAChD,MAAI;AACF,QAAI,IAAI,GAAG;AACX,WAAO;AAAA,EACT,SAAS,QAAP;AACA,WAAO;AAAA,EACT;AACF;;;ADFO,IAAM,oBAAN,cAA+B,YAAiC;AAAA,EAGrE,cAAc;AACZ,UAAM,kBAAiB,MAAM;AAAA,EAC/B;AAAA,EAEU,mBAAmB;AAC3B,WACE,OAAO,eAAe,eACtB,OAAO,WAAW,UAAU;AAAA,EAEhC;AAAA,EAEA,MAAgB,QAAQ;AACtB,UAAM,YAAY,WAAW;AAE7B;AAAA,MACE,CAAE,UAAkB,iBAAiB;AAAA,MACrC;AAAA,IACF;AAEA,eAAW,QAAQ,OAAO,OAAO,SAAS;AACxC,YAAM,YAAY,gBAAgB;AAQlC,YAAM,gBACJ,OAAO,UAAU,YACjB,OAAO,aAAa,eACpB,CAAC,YAAY,KAAK,IACd,IAAI,IAAI,OAAO,SAAS,MAAM,IAC9B;AAEN,YAAM,UAAU,IAAI,QAAQ,eAAe,IAAI;AAC/C,YAAM,kBAAkB,IAAI,gBAA0B;AACtD,YAAM,aAAa,IAAI,kBAAkB,OAAO;AAEhD,WAAK,OAAO,KAAK,WAAW,QAAQ,QAAQ,QAAQ,GAAG;AACvD,WAAK,OAAO,KAAK,qCAAqC;AAEtD,WAAK,OAAO;AAAA,QACV;AAAA,QACA,KAAK,QAAQ,cAAc,SAAS;AAAA,MACtC;AAEA,YAAM,mBAAmB,MAAM,cAAc;AAAA,QAC3C;AAAA,QACA;AAAA,QACA,SAAS,KAAK;AAAA,QACd;AAAA,QACA,YAAY,OAAO,aAAa;AAC9B,eAAK,OAAO,KAAK,6BAA6B;AAAA,YAC5C;AAAA,UACF,CAAC;AAED,cAAI,KAAK,QAAQ,cAAc,UAAU,IAAI,GAAG;AAC9C,iBAAK,OAAO,KAAK,kCAAkC;AAKnD,kBAAM,UAAU,KAAK,SAAS,YAAY;AAAA;AAAA;AAAA;AAAA,cAIxC,UAAU,SAAS,MAAM;AAAA,cACzB,kBAAkB;AAAA,cAClB;AAAA,cACA;AAAA,YACF,CAAC;AAAA,UACH;AAGA,iBAAO,eAAe,UAAU,OAAO;AAAA,YACrC,UAAU;AAAA,YACV,YAAY;AAAA,YACZ,cAAc;AAAA,YACd,OAAO,QAAQ;AAAA,UACjB,CAAC;AAED,0BAAgB,QAAQ,QAAQ;AAAA,QAClC;AAAA,QACA,gBAAgB,CAAC,aAAa;AAC5B,eAAK,OAAO,KAAK,wBAAwB,EAAE,SAAS,CAAC;AACrD,0BAAgB,OAAO,mBAAmB,QAAQ,CAAC;AAAA,QACrD;AAAA,QACA,SAAS,CAAC,UAAU;AAClB,eAAK,OAAO,KAAK,6BAA6B,EAAE,MAAM,CAAC;AACvD,0BAAgB,OAAO,KAAK;AAAA,QAC9B;AAAA,MACF,CAAC;AAED,UAAI,kBAAkB;AACpB,aAAK,OAAO,KAAK,qDAAqD;AACtE,eAAO;AAAA,MACT;AAEA,WAAK,OAAO;AAAA,QACV;AAAA,MACF;AAEA,aAAO,UAAU,OAAO,EAAE,KAAK,CAAC,aAAa;AAC3C,aAAK,OAAO,KAAK,4BAA4B,QAAQ;AAErD,YAAI,KAAK,QAAQ,cAAc,UAAU,IAAI,GAAG;AAC9C,eAAK,OAAO,KAAK,kCAAkC;AAEnD,gBAAM,gBAAgB,SAAS,MAAM;AAErC,eAAK,QAAQ,KAAK,YAAY;AAAA,YAC5B,UAAU;AAAA,YACV,kBAAkB;AAAA,YAClB;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH;AAEA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,WAAO,eAAe,WAAW,OAAO,mBAAmB;AAAA,MACzD,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,OAAO;AAAA,IACT,CAAC;AAED,SAAK,cAAc,KAAK,MAAM;AAC5B,aAAO,eAAe,WAAW,OAAO,mBAAmB;AAAA,QACzD,OAAO;AAAA,MACT,CAAC;AAED,iBAAW,QAAQ;AAEnB,WAAK,OAAO;AAAA,QACV;AAAA,QACA,WAAW,MAAM;AAAA,MACnB;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAjJO,IAAM,mBAAN;AAAM,iBACJ,SAAS,OAAO,OAAO;AAkJhC,SAAS,mBAAmB,OAAgB;AAC1C,SAAO,OAAO,OAAO,IAAI,UAAU,iBAAiB,GAAG;AAAA,IACrD;AAAA,EACF,CAAC;AACH","sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.fetch !== 'undefined'\n )\n }\n\n protected async setup() {\n const pureFetch = globalThis.fetch\n\n invariant(\n !(pureFetch as any)[IS_PATCHED_MODULE],\n 'Failed to patch the \"fetch\" module: already patched.'\n )\n\n globalThis.fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.origin)\n : input\n\n const request = new Request(resolvedInput, init)\n const responsePromise = new DeferredPromise<Response>()\n const controller = new RequestController(request)\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n const isRequestHandled = await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n onResponse: async (response) => {\n this.logger.info('received mocked response!', {\n response,\n })\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: response.clone(),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n // Set the \"response.url\" property to equal the intercepted request URL.\n Object.defineProperty(response, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n responsePromise.resolve(response)\n },\n onRequestError: (response) => {\n this.logger.info('request has errored!', { response })\n responsePromise.reject(createNetworkError(response))\n },\n onError: (error) => {\n this.logger.info('request has been aborted!', { error })\n responsePromise.reject(error)\n },\n })\n\n if (isRequestHandled) {\n this.logger.info('request has been handled, returning mock promise...')\n return responsePromise\n }\n\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n\n return pureFetch(request).then((response) => {\n this.logger.info('original fetch performed', response)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = response.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: false,\n request,\n requestId,\n })\n }\n\n return response\n })\n }\n\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n enumerable: true,\n configurable: true,\n value: true,\n })\n\n this.subscriptions.push(() => {\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n value: undefined,\n })\n\n globalThis.fetch = pureFetch\n\n this.logger.info(\n 'restored native \"globalThis.fetch\"!',\n globalThis.fetch.name\n )\n })\n }\n}\n\nfunction createNetworkError(cause: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n"]}
1
+ {"version":3,"sources":[],"names":[],"mappings":""}
@@ -1,140 +1,9 @@
1
1
  import {
2
- IS_PATCHED_MODULE
3
- } from "../../chunk-BZ3Y7YV5.mjs";
4
- import {
5
- RequestController,
6
- emitAsync,
7
- handleRequest
8
- } from "../../chunk-KY3RJ2M3.mjs";
9
- import {
10
- Interceptor,
11
- createRequestId
12
- } from "../../chunk-BUCULLYM.mjs";
13
-
14
- // src/interceptors/fetch/index.ts
15
- import { invariant } from "outvariant";
16
- import { DeferredPromise } from "@open-draft/deferred-promise";
17
-
18
- // src/utils/canParseUrl.ts
19
- function canParseUrl(url) {
20
- try {
21
- new URL(url);
22
- return true;
23
- } catch (_error) {
24
- return false;
25
- }
26
- }
27
-
28
- // src/interceptors/fetch/index.ts
29
- var _FetchInterceptor = class extends Interceptor {
30
- constructor() {
31
- super(_FetchInterceptor.symbol);
32
- }
33
- checkEnvironment() {
34
- return typeof globalThis !== "undefined" && typeof globalThis.fetch !== "undefined";
35
- }
36
- async setup() {
37
- const pureFetch = globalThis.fetch;
38
- invariant(
39
- !pureFetch[IS_PATCHED_MODULE],
40
- 'Failed to patch the "fetch" module: already patched.'
41
- );
42
- globalThis.fetch = async (input, init) => {
43
- const requestId = createRequestId();
44
- const resolvedInput = typeof input === "string" && typeof location !== "undefined" && !canParseUrl(input) ? new URL(input, location.origin) : input;
45
- const request = new Request(resolvedInput, init);
46
- const responsePromise = new DeferredPromise();
47
- const controller = new RequestController(request);
48
- this.logger.info("[%s] %s", request.method, request.url);
49
- this.logger.info("awaiting for the mocked response...");
50
- this.logger.info(
51
- 'emitting the "request" event for %s listener(s)...',
52
- this.emitter.listenerCount("request")
53
- );
54
- const isRequestHandled = await handleRequest({
55
- request,
56
- requestId,
57
- emitter: this.emitter,
58
- controller,
59
- onResponse: async (response) => {
60
- this.logger.info("received mocked response!", {
61
- response
62
- });
63
- if (this.emitter.listenerCount("response") > 0) {
64
- this.logger.info('emitting the "response" event...');
65
- await emitAsync(this.emitter, "response", {
66
- // Clone the mocked response for the "response" event listener.
67
- // This way, the listener can read the response and not lock its body
68
- // for the actual fetch consumer.
69
- response: response.clone(),
70
- isMockedResponse: true,
71
- request,
72
- requestId
73
- });
74
- }
75
- Object.defineProperty(response, "url", {
76
- writable: false,
77
- enumerable: true,
78
- configurable: false,
79
- value: request.url
80
- });
81
- responsePromise.resolve(response);
82
- },
83
- onRequestError: (response) => {
84
- this.logger.info("request has errored!", { response });
85
- responsePromise.reject(createNetworkError(response));
86
- },
87
- onError: (error) => {
88
- this.logger.info("request has been aborted!", { error });
89
- responsePromise.reject(error);
90
- }
91
- });
92
- if (isRequestHandled) {
93
- this.logger.info("request has been handled, returning mock promise...");
94
- return responsePromise;
95
- }
96
- this.logger.info(
97
- "no mocked response received, performing request as-is..."
98
- );
99
- return pureFetch(request).then((response) => {
100
- this.logger.info("original fetch performed", response);
101
- if (this.emitter.listenerCount("response") > 0) {
102
- this.logger.info('emitting the "response" event...');
103
- const responseClone = response.clone();
104
- this.emitter.emit("response", {
105
- response: responseClone,
106
- isMockedResponse: false,
107
- request,
108
- requestId
109
- });
110
- }
111
- return response;
112
- });
113
- };
114
- Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {
115
- enumerable: true,
116
- configurable: true,
117
- value: true
118
- });
119
- this.subscriptions.push(() => {
120
- Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {
121
- value: void 0
122
- });
123
- globalThis.fetch = pureFetch;
124
- this.logger.info(
125
- 'restored native "globalThis.fetch"!',
126
- globalThis.fetch.name
127
- );
128
- });
129
- }
130
- };
131
- var FetchInterceptor = _FetchInterceptor;
132
- FetchInterceptor.symbol = Symbol("fetch");
133
- function createNetworkError(cause) {
134
- return Object.assign(new TypeError("Failed to fetch"), {
135
- cause
136
- });
137
- }
2
+ FetchInterceptor
3
+ } from "../../chunk-RTGLFNO3.mjs";
4
+ import "../../chunk-BZ3Y7YV5.mjs";
5
+ import "../../chunk-KY3RJ2M3.mjs";
6
+ import "../../chunk-BUCULLYM.mjs";
138
7
  export {
139
8
  FetchInterceptor
140
9
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/interceptors/fetch/index.ts","../../../../src/utils/canParseUrl.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.fetch !== 'undefined'\n )\n }\n\n protected async setup() {\n const pureFetch = globalThis.fetch\n\n invariant(\n !(pureFetch as any)[IS_PATCHED_MODULE],\n 'Failed to patch the \"fetch\" module: already patched.'\n )\n\n globalThis.fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.origin)\n : input\n\n const request = new Request(resolvedInput, init)\n const responsePromise = new DeferredPromise<Response>()\n const controller = new RequestController(request)\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n const isRequestHandled = await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n onResponse: async (response) => {\n this.logger.info('received mocked response!', {\n response,\n })\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: response.clone(),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n // Set the \"response.url\" property to equal the intercepted request URL.\n Object.defineProperty(response, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n responsePromise.resolve(response)\n },\n onRequestError: (response) => {\n this.logger.info('request has errored!', { response })\n responsePromise.reject(createNetworkError(response))\n },\n onError: (error) => {\n this.logger.info('request has been aborted!', { error })\n responsePromise.reject(error)\n },\n })\n\n if (isRequestHandled) {\n this.logger.info('request has been handled, returning mock promise...')\n return responsePromise\n }\n\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n\n return pureFetch(request).then((response) => {\n this.logger.info('original fetch performed', response)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = response.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: false,\n request,\n requestId,\n })\n }\n\n return response\n })\n }\n\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n enumerable: true,\n configurable: true,\n value: true,\n })\n\n this.subscriptions.push(() => {\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n value: undefined,\n })\n\n globalThis.fetch = pureFetch\n\n this.logger.info(\n 'restored native \"globalThis.fetch\"!',\n globalThis.fetch.name\n )\n })\n }\n}\n\nfunction createNetworkError(cause: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,5 +1,6 @@
1
1
  import { ClientRequestInterceptor } from '../interceptors/ClientRequest/index.js';
2
2
  import { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest/index.js';
3
+ import { FetchInterceptor } from '../interceptors/fetch/index.js';
3
4
  import '../Interceptor-a31b1217.js';
4
5
  import '@open-draft/deferred-promise';
5
6
  import '@open-draft/logger';
@@ -10,6 +11,6 @@ import 'node:net';
10
11
  * The default preset provisions the interception of requests
11
12
  * regardless of their type (http/https/XMLHttpRequest).
12
13
  */
13
- declare const _default: readonly [ClientRequestInterceptor, XMLHttpRequestInterceptor];
14
+ declare const _default: readonly [ClientRequestInterceptor, XMLHttpRequestInterceptor, FetchInterceptor];
14
15
 
15
16
  export { _default as default };
@@ -5,6 +5,9 @@ var _chunkI57YSVAVjs = require('../chunk-I57YSVAV.js');
5
5
 
6
6
  var _chunkT34TGCMRjs = require('../chunk-T34TGCMR.js');
7
7
  require('../chunk-LK6DILFK.js');
8
+
9
+
10
+ var _chunkUN335ZTDjs = require('../chunk-UN335ZTD.js');
8
11
  require('../chunk-IDEEMJ3F.js');
9
12
  require('../chunk-5WWNCLB3.js');
10
13
  require('../chunk-YGM3BCJU.js');
@@ -12,7 +15,8 @@ require('../chunk-YGM3BCJU.js');
12
15
  // src/presets/node.ts
13
16
  var node_default = [
14
17
  new (0, _chunkI57YSVAVjs.ClientRequestInterceptor)(),
15
- new (0, _chunkT34TGCMRjs.XMLHttpRequestInterceptor)()
18
+ new (0, _chunkT34TGCMRjs.XMLHttpRequestInterceptor)(),
19
+ new (0, _chunkUN335ZTDjs.FetchInterceptor)()
16
20
  ];
17
21
 
18
22
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presets/node.ts"],"names":[],"mappings":";;;;;;;;;;;;AAOA,IAAO,eAAQ;AAAA,EACb,IAAI,yBAAyB;AAAA,EAC7B,IAAI,0BAA0B;AAChC","sourcesContent":["import { ClientRequestInterceptor } from '../interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'\n\n/**\n * The default preset provisions the interception of requests\n * regardless of their type (http/https/XMLHttpRequest).\n */\nexport default [\n new ClientRequestInterceptor(),\n new XMLHttpRequestInterceptor(),\n] as const\n"]}
1
+ {"version":3,"sources":["../../../src/presets/node.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAQA,IAAO,eAAQ;AAAA,EACb,IAAI,yBAAyB;AAAA,EAC7B,IAAI,0BAA0B;AAAA,EAC9B,IAAI,iBAAiB;AACvB","sourcesContent":["import { ClientRequestInterceptor } from '../interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'\nimport { FetchInterceptor } from '../interceptors/fetch'\n\n/**\n * The default preset provisions the interception of requests\n * regardless of their type (http/https/XMLHttpRequest).\n */\nexport default [\n new ClientRequestInterceptor(),\n new XMLHttpRequestInterceptor(),\n new FetchInterceptor(),\n] as const\n"]}
@@ -5,6 +5,9 @@ import {
5
5
  XMLHttpRequestInterceptor
6
6
  } from "../chunk-RJGP7FQP.mjs";
7
7
  import "../chunk-6HYIRFX2.mjs";
8
+ import {
9
+ FetchInterceptor
10
+ } from "../chunk-RTGLFNO3.mjs";
8
11
  import "../chunk-BZ3Y7YV5.mjs";
9
12
  import "../chunk-KY3RJ2M3.mjs";
10
13
  import "../chunk-BUCULLYM.mjs";
@@ -12,7 +15,8 @@ import "../chunk-BUCULLYM.mjs";
12
15
  // src/presets/node.ts
13
16
  var node_default = [
14
17
  new ClientRequestInterceptor(),
15
- new XMLHttpRequestInterceptor()
18
+ new XMLHttpRequestInterceptor(),
19
+ new FetchInterceptor()
16
20
  ];
17
21
  export {
18
22
  node_default as default
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presets/node.ts"],"sourcesContent":["import { ClientRequestInterceptor } from '../interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'\n\n/**\n * The default preset provisions the interception of requests\n * regardless of their type (http/https/XMLHttpRequest).\n */\nexport default [\n new ClientRequestInterceptor(),\n new XMLHttpRequestInterceptor(),\n] as const\n"],"mappings":";;;;;;;;;;;;AAOA,IAAO,eAAQ;AAAA,EACb,IAAI,yBAAyB;AAAA,EAC7B,IAAI,0BAA0B;AAChC;","names":[]}
1
+ {"version":3,"sources":["../../../src/presets/node.ts"],"sourcesContent":["import { ClientRequestInterceptor } from '../interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'\nimport { FetchInterceptor } from '../interceptors/fetch'\n\n/**\n * The default preset provisions the interception of requests\n * regardless of their type (http/https/XMLHttpRequest).\n */\nexport default [\n new ClientRequestInterceptor(),\n new XMLHttpRequestInterceptor(),\n new FetchInterceptor(),\n] as const\n"],"mappings":";;;;;;;;;;;;;;;AAQA,IAAO,eAAQ;AAAA,EACb,IAAI,yBAAyB;AAAA,EAC7B,IAAI,0BAA0B;AAAA,EAC9B,IAAI,iBAAiB;AACvB;","names":[]}
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.33.2",
4
+ "version": "0.34.0",
5
5
  "main": "./lib/node/index.js",
6
6
  "module": "./lib/node/index.mjs",
7
7
  "types": "./lib/node/index.d.ts",
@@ -2,6 +2,7 @@ import { invariant } from 'outvariant'
2
2
  import type { WebSocketData } from './WebSocketTransport'
3
3
  import { bindEvent } from './utils/bindEvent'
4
4
  import { CloseEvent } from './utils/events'
5
+ import { DeferredPromise } from '@open-draft/deferred-promise'
5
6
 
6
7
  export type WebSocketEventListener<
7
8
  EventType extends WebSocketEventMap[keyof WebSocketEventMap] = Event
@@ -10,6 +11,7 @@ export type WebSocketEventListener<
10
11
  const WEBSOCKET_CLOSE_CODE_RANGE_ERROR =
11
12
  'InvalidAccessError: close code out of user configurable range'
12
13
 
14
+ export const kPassthroughPromise = Symbol('kPassthroughPromise')
13
15
  export const kOnSend = Symbol('kOnSend')
14
16
  export const kClose = Symbol('kClose')
15
17
 
@@ -37,6 +39,7 @@ export class WebSocketOverride extends EventTarget implements WebSocket {
37
39
  private _onerror: WebSocketEventListener | null = null
38
40
  private _onclose: WebSocketEventListener<CloseEvent> | null = null
39
41
 
42
+ private [kPassthroughPromise]: DeferredPromise<boolean>
40
43
  private [kOnSend]?: (data: WebSocketData) => void
41
44
 
42
45
  constructor(url: string | URL, protocols?: string | Array<string>) {
@@ -48,9 +51,15 @@ export class WebSocketOverride extends EventTarget implements WebSocket {
48
51
  this.readyState = this.CONNECTING
49
52
  this.bufferedAmount = 0
50
53
 
51
- Reflect.set(this, 'readyState', this.CONNECTING)
52
- queueMicrotask(() => {
53
- Reflect.set(this, 'readyState', this.OPEN)
54
+ this[kPassthroughPromise] = new DeferredPromise<boolean>()
55
+
56
+ queueMicrotask(async () => {
57
+ if (await this[kPassthroughPromise]) {
58
+ return
59
+ }
60
+
61
+ this.readyState = this.OPEN
62
+
54
63
  this.protocol =
55
64
  typeof protocols === 'string'
56
65
  ? protocols
@@ -151,26 +160,24 @@ export class WebSocketOverride extends EventTarget implements WebSocket {
151
160
  WEBSOCKET_CLOSE_CODE_RANGE_ERROR
152
161
  )
153
162
 
154
- if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {
155
- return
156
- }
157
-
158
163
  this[kClose](code, reason)
159
164
  }
160
165
 
161
166
  private [kClose](code: number = 1000, reason?: string): void {
167
+ /**
168
+ * @note Move this check here so that even internall closures,
169
+ * like those triggered by the `server` connection, are not
170
+ * performed twice.
171
+ */
172
+ if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {
173
+ return
174
+ }
175
+
162
176
  this.readyState = this.CLOSING
163
177
 
164
178
  queueMicrotask(() => {
165
179
  this.readyState = this.CLOSED
166
180
 
167
- // Non-user-configurable close status codes
168
- // represent connection termination and must
169
- // emit the "error" event before closing.
170
- if (code > 1000 && code <= 1015) {
171
- this.dispatchEvent(bindEvent(this, new Event('error')))
172
- }
173
-
174
181
  this.dispatchEvent(
175
182
  bindEvent(
176
183
  this,
@@ -30,6 +30,7 @@ export class WebSocketServerConnection {
30
30
  */
31
31
  private realWebSocket?: WebSocket
32
32
  private mockCloseController: AbortController
33
+ private realCloseController: AbortController
33
34
  private [kEmitter]: EventTarget
34
35
 
35
36
  constructor(
@@ -39,6 +40,7 @@ export class WebSocketServerConnection {
39
40
  ) {
40
41
  this[kEmitter] = new EventTarget()
41
42
  this.mockCloseController = new AbortController()
43
+ this.realCloseController = new AbortController()
42
44
 
43
45
  // Automatically forward outgoing client events
44
46
  // to the actual server unless the outgoing message event
@@ -126,18 +128,24 @@ export class WebSocketServerConnection {
126
128
  })
127
129
 
128
130
  // Close the original connection when the mock client closes.
129
- // E.g. "client.close()" was called.
131
+ // E.g. "client.close()" was called. This is never forwarded anywhere.
130
132
  this.socket.addEventListener('close', this.handleMockClose.bind(this), {
131
133
  signal: this.mockCloseController.signal,
132
134
  })
133
135
 
134
136
  // Forward the "close" event to let the interceptor handle
135
137
  // closures initiated by the original server.
136
- realWebSocket.addEventListener('close', this.handleRealClose.bind(this))
138
+ realWebSocket.addEventListener('close', this.handleRealClose.bind(this), {
139
+ signal: this.realCloseController.signal,
140
+ })
137
141
 
138
- // Forward server errors to the WebSocket client as-is.
139
- // We may consider exposing them to the interceptor in the future.
140
142
  realWebSocket.addEventListener('error', () => {
143
+ // Emit the "error" event on the `server` connection
144
+ // to let the interceptor react to original server errors.
145
+ this[kEmitter].dispatchEvent(bindEvent(realWebSocket, new Event('error')))
146
+
147
+ // Forward original server errors to the WebSocket client.
148
+ // This ensures the client is closed if the original server errors.
141
149
  this.socket.dispatchEvent(bindEvent(this.socket, new Event('error')))
142
150
  })
143
151
 
@@ -239,8 +247,9 @@ export class WebSocketServerConnection {
239
247
 
240
248
  // Remove the "close" event listener from the server
241
249
  // so it doesn't close the underlying WebSocket client
242
- // when you call "server.close()".
243
- realWebSocket.removeEventListener('close', this.handleRealClose)
250
+ // when you call "server.close()". This also prevents the
251
+ // `close` event on the `server` connection from being dispatched twice.
252
+ this.realCloseController.abort()
244
253
 
245
254
  if (
246
255
  realWebSocket.readyState === WebSocket.CLOSING ||
@@ -251,10 +260,19 @@ export class WebSocketServerConnection {
251
260
 
252
261
  realWebSocket.close()
253
262
 
254
- // Dispatch the "close" event on the server connection.
263
+ // Dispatch the "close" event on the `server` connection.
255
264
  queueMicrotask(() => {
256
265
  this[kEmitter].dispatchEvent(
257
- bindEvent(this.realWebSocket, new CloseEvent('close'))
266
+ bindEvent(
267
+ this.realWebSocket,
268
+ new CloseEvent('close', {
269
+ /**
270
+ * @note `server.close()` in the interceptor
271
+ * always results in clean closures.
272
+ */
273
+ code: 1000,
274
+ })
275
+ )
258
276
  )
259
277
  })
260
278
  }
@@ -316,7 +334,7 @@ export class WebSocketServerConnection {
316
334
  private handleRealClose(event: CloseEvent): void {
317
335
  // For closures originating from the original server,
318
336
  // remove the "close" listener from the mock client.
319
- // original close -> (?) client[kClose]() --X-> "close" (again).
337
+ // original close -> (?) client[kClose]() --X--> "close" (again).
320
338
  this.mockCloseController.abort()
321
339
 
322
340
  const closeEvent = bindEvent(
@@ -5,7 +5,8 @@ import {
5
5
  } from './WebSocketClientConnection'
6
6
  import { WebSocketServerConnection } from './WebSocketServerConnection'
7
7
  import { WebSocketClassTransport } from './WebSocketClassTransport'
8
- import { WebSocketOverride } from './WebSocketOverride'
8
+ import { kPassthroughPromise, WebSocketOverride } from './WebSocketOverride'
9
+ import { bindEvent } from './utils/bindEvent'
9
10
 
10
11
  export { type WebSocketData, WebSocketTransport } from './WebSocketTransport'
11
12
  export {
@@ -82,20 +83,42 @@ export class WebSocketInterceptor extends Interceptor<WebSocketEventMap> {
82
83
  // so the client can modify WebSocket options, like "binaryType"
83
84
  // while the connection is already pending.
84
85
  queueMicrotask(() => {
86
+ const server = new WebSocketServerConnection(
87
+ socket,
88
+ transport,
89
+ createConnection
90
+ )
91
+
85
92
  // The "globalThis.WebSocket" class stands for
86
93
  // the client-side connection. Assume it's established
87
94
  // as soon as the WebSocket instance is constructed.
88
- this.emitter.emit('connection', {
95
+ const hasConnectionListeners = this.emitter.emit('connection', {
89
96
  client: new WebSocketClientConnection(socket, transport),
90
- server: new WebSocketServerConnection(
91
- socket,
92
- transport,
93
- createConnection
94
- ),
97
+ server,
95
98
  info: {
96
99
  protocols,
97
100
  },
98
101
  })
102
+
103
+ if (hasConnectionListeners) {
104
+ socket[kPassthroughPromise].resolve(false)
105
+ } else {
106
+ socket[kPassthroughPromise].resolve(true)
107
+
108
+ server.connect()
109
+
110
+ // Forward the "open" event from the original server
111
+ // to the mock WebSocket client in the case of a passthrough connection.
112
+ server.addEventListener('open', () => {
113
+ socket.dispatchEvent(bindEvent(socket, new Event('open')))
114
+
115
+ // Forward the original connection protocol to the
116
+ // mock WebSocket client.
117
+ if (server['realWebSocket']) {
118
+ socket.protocol = server['realWebSocket'].protocol
119
+ }
120
+ })
121
+ }
99
122
  })
100
123
 
101
124
  return socket
@@ -1,5 +1,6 @@
1
1
  import { ClientRequestInterceptor } from '../interceptors/ClientRequest'
2
2
  import { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'
3
+ import { FetchInterceptor } from '../interceptors/fetch'
3
4
 
4
5
  /**
5
6
  * The default preset provisions the interception of requests
@@ -8,4 +9,5 @@ import { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'
8
9
  export default [
9
10
  new ClientRequestInterceptor(),
10
11
  new XMLHttpRequestInterceptor(),
12
+ new FetchInterceptor(),
11
13
  ] as const