@mswjs/interceptors 0.25.6 → 0.25.7
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.
- package/lib/node/RemoteHttpInterceptor.js +2 -2
- package/lib/node/RemoteHttpInterceptor.mjs +1 -1
- package/lib/node/{chunk-Z7O2DO3X.mjs → chunk-G5IEXC7T.mjs} +70 -16
- package/lib/node/{chunk-44QGFZIT.js → chunk-YVNH3GJ5.js} +70 -16
- package/lib/node/interceptors/ClientRequest/index.js +2 -2
- package/lib/node/interceptors/ClientRequest/index.mjs +1 -1
- package/lib/node/presets/node.js +2 -2
- package/lib/node/presets/node.mjs +1 -1
- package/package.json +1 -1
- package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +0 -106
- package/src/interceptors/ClientRequest/NodeClientRequest.ts +70 -21
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts +10 -0
- package/src/utils/getRawFetchHeaders.test.ts +50 -0
- package/src/utils/getRawFetchHeaders.ts +56 -0
- package/src/utils/getValueBySymbol.test.ts +14 -0
- package/src/utils/getValueBySymbol.ts +19 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var _chunkUF7QIAQ5js = require('./chunk-UF7QIAQ5.js');
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunkYVNH3GJ5js = require('./chunk-YVNH3GJ5.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,
|
|
27
|
+
new (0, _chunkYVNH3GJ5js.ClientRequestInterceptor)(),
|
|
28
28
|
new (0, _chunkJCWVLTP7js.XMLHttpRequestInterceptor)()
|
|
29
29
|
]
|
|
30
30
|
});
|
|
@@ -167,12 +167,52 @@ function createRequest(clientRequest) {
|
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// src/utils/getValueBySymbol.ts
|
|
171
|
+
function getValueBySymbol(symbolName, source) {
|
|
172
|
+
const ownSymbols = Object.getOwnPropertySymbols(source);
|
|
173
|
+
const symbol = ownSymbols.find((symbol2) => {
|
|
174
|
+
return symbol2.description === symbolName;
|
|
175
|
+
});
|
|
176
|
+
if (symbol) {
|
|
177
|
+
return Reflect.get(source, symbol);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/utils/isObject.ts
|
|
183
|
+
function isObject(value) {
|
|
184
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/utils/getRawFetchHeaders.ts
|
|
188
|
+
function getRawFetchHeaders(headers) {
|
|
189
|
+
const headersList = getValueBySymbol("headers list", headers);
|
|
190
|
+
if (!headersList) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const headersMap = getValueBySymbol("headers map", headersList);
|
|
194
|
+
if (!headersMap || !isHeadersMapWithRawHeaderNames(headersMap)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const rawHeaders = /* @__PURE__ */ new Map();
|
|
198
|
+
headersMap.forEach(({ name, value }) => {
|
|
199
|
+
rawHeaders.set(name, value);
|
|
200
|
+
});
|
|
201
|
+
return rawHeaders;
|
|
202
|
+
}
|
|
203
|
+
function isHeadersMapWithRawHeaderNames(headersMap) {
|
|
204
|
+
return Array.from(
|
|
205
|
+
headersMap.values()
|
|
206
|
+
).every((value) => {
|
|
207
|
+
return isObject(value) && "name" in value;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
170
211
|
// src/interceptors/ClientRequest/NodeClientRequest.ts
|
|
171
212
|
var _NodeClientRequest = class extends ClientRequest {
|
|
172
213
|
constructor([url, requestOptions, callback], options) {
|
|
173
214
|
super(requestOptions, callback);
|
|
174
215
|
this.chunks = [];
|
|
175
|
-
this.responseSource = "mock";
|
|
176
216
|
this.logger = options.logger.extend(
|
|
177
217
|
`request ${requestOptions.method} ${url.href}`
|
|
178
218
|
);
|
|
@@ -181,6 +221,7 @@ var _NodeClientRequest = class extends ClientRequest {
|
|
|
181
221
|
requestOptions,
|
|
182
222
|
callback
|
|
183
223
|
});
|
|
224
|
+
this.state = 0 /* Idle */;
|
|
184
225
|
this.url = url;
|
|
185
226
|
this.emitter = options.emitter;
|
|
186
227
|
this.requestBuffer = null;
|
|
@@ -219,6 +260,7 @@ var _NodeClientRequest = class extends ClientRequest {
|
|
|
219
260
|
const [chunk, encoding, callback] = normalizeClientRequestEndArgs(...args);
|
|
220
261
|
this.logger.info("normalized arguments:", { chunk, encoding, callback });
|
|
221
262
|
this.writeRequestBodyChunk(chunk, encoding || void 0);
|
|
263
|
+
this.state = 2 /* Sent */;
|
|
222
264
|
const capturedRequest = createRequest(this);
|
|
223
265
|
const { interactiveRequest, requestController } = toInteractiveRequest(capturedRequest);
|
|
224
266
|
Object.defineProperty(capturedRequest, "respondWith", {
|
|
@@ -244,6 +286,7 @@ var _NodeClientRequest = class extends ClientRequest {
|
|
|
244
286
|
'emitting the "request" event for %d listener(s)...',
|
|
245
287
|
this.emitter.listenerCount("request")
|
|
246
288
|
);
|
|
289
|
+
this.state = 3 /* MockLookupStart */;
|
|
247
290
|
await emitAsync(this.emitter, "request", {
|
|
248
291
|
request: interactiveRequest,
|
|
249
292
|
requestId
|
|
@@ -254,6 +297,7 @@ var _NodeClientRequest = class extends ClientRequest {
|
|
|
254
297
|
return mockedResponse;
|
|
255
298
|
}).then((resolverResult) => {
|
|
256
299
|
this.logger.info("the listeners promise awaited!");
|
|
300
|
+
this.state = 4 /* MockLookupEnd */;
|
|
257
301
|
if (!this.headersSent) {
|
|
258
302
|
for (const [headerName, headerValue] of capturedRequest.headers) {
|
|
259
303
|
this.setHeader(headerName, headerValue);
|
|
@@ -286,7 +330,6 @@ var _NodeClientRequest = class extends ClientRequest {
|
|
|
286
330
|
return this;
|
|
287
331
|
}
|
|
288
332
|
const responseClone = mockedResponse.clone();
|
|
289
|
-
this.responseSource = "mock";
|
|
290
333
|
this.respondWith(mockedResponse);
|
|
291
334
|
this.logger.info(
|
|
292
335
|
mockedResponse.status,
|
|
@@ -342,12 +385,17 @@ var _NodeClientRequest = class extends ClientRequest {
|
|
|
342
385
|
const error = data[0];
|
|
343
386
|
const errorCode = error.code || "";
|
|
344
387
|
this.logger.info("error:\n", error);
|
|
345
|
-
if (
|
|
346
|
-
if (
|
|
347
|
-
this.capturedError
|
|
348
|
-
|
|
388
|
+
if (_NodeClientRequest.suppressErrorCodes.includes(errorCode)) {
|
|
389
|
+
if (this.state < 4 /* MockLookupEnd */) {
|
|
390
|
+
if (!this.capturedError) {
|
|
391
|
+
this.capturedError = error;
|
|
392
|
+
this.logger.info("captured the first error:", this.capturedError);
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
if (this.state === 5 /* ResponseReceived */ && this.responseType === "mock") {
|
|
397
|
+
return false;
|
|
349
398
|
}
|
|
350
|
-
return false;
|
|
351
399
|
}
|
|
352
400
|
}
|
|
353
401
|
return super.emit(event, ...data);
|
|
@@ -359,7 +407,8 @@ var _NodeClientRequest = class extends ClientRequest {
|
|
|
359
407
|
* up the request with `super.end()`.
|
|
360
408
|
*/
|
|
361
409
|
passthrough(chunk, encoding, callback) {
|
|
362
|
-
this.
|
|
410
|
+
this.state = 5 /* ResponseReceived */;
|
|
411
|
+
this.responseType = "passthrough";
|
|
363
412
|
if (this.capturedError) {
|
|
364
413
|
this.emit("error", this.capturedError);
|
|
365
414
|
return this;
|
|
@@ -390,6 +439,8 @@ var _NodeClientRequest = class extends ClientRequest {
|
|
|
390
439
|
*/
|
|
391
440
|
respondWith(mockedResponse) {
|
|
392
441
|
this.logger.info("responding with a mocked response...", mockedResponse);
|
|
442
|
+
this.state = 5 /* ResponseReceived */;
|
|
443
|
+
this.responseType = "mock";
|
|
393
444
|
Object.defineProperties(this, {
|
|
394
445
|
writableFinished: { value: true },
|
|
395
446
|
writableEnded: { value: true }
|
|
@@ -398,9 +449,10 @@ var _NodeClientRequest = class extends ClientRequest {
|
|
|
398
449
|
const { status, statusText, headers, body } = mockedResponse;
|
|
399
450
|
this.response.statusCode = status;
|
|
400
451
|
this.response.statusMessage = statusText;
|
|
401
|
-
|
|
452
|
+
const rawHeaders = getRawFetchHeaders(headers) || headers;
|
|
453
|
+
if (rawHeaders) {
|
|
402
454
|
this.response.headers = {};
|
|
403
|
-
|
|
455
|
+
rawHeaders.forEach((headerValue, headerName) => {
|
|
404
456
|
this.response.rawHeaders.push(headerName, headerValue);
|
|
405
457
|
const insensitiveHeaderName = headerName.toLowerCase();
|
|
406
458
|
const prevHeaders = this.response.headers[insensitiveHeaderName];
|
|
@@ -456,7 +508,9 @@ NodeClientRequest.suppressErrorCodes = [
|
|
|
456
508
|
"ENOTFOUND",
|
|
457
509
|
"ECONNREFUSED",
|
|
458
510
|
"ECONNRESET",
|
|
459
|
-
"EAI_AGAIN"
|
|
511
|
+
"EAI_AGAIN",
|
|
512
|
+
"ENETUNREACH",
|
|
513
|
+
"EHOSTUNREACH"
|
|
460
514
|
];
|
|
461
515
|
|
|
462
516
|
// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
|
|
@@ -616,11 +670,6 @@ function cloneObject(obj) {
|
|
|
616
670
|
return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties);
|
|
617
671
|
}
|
|
618
672
|
|
|
619
|
-
// src/utils/isObject.ts
|
|
620
|
-
function isObject(value) {
|
|
621
|
-
return Object.prototype.toString.call(value) === "[object Object]";
|
|
622
|
-
}
|
|
623
|
-
|
|
624
673
|
// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
|
|
625
674
|
var logger5 = new Logger5("http normalizeClientRequestArgs");
|
|
626
675
|
function resolveRequestOptions(args, url) {
|
|
@@ -663,6 +712,11 @@ function normalizeClientRequestArgs(defaultProtocol, ...args) {
|
|
|
663
712
|
let callback;
|
|
664
713
|
logger5.info("arguments", args);
|
|
665
714
|
logger5.info("using default protocol:", defaultProtocol);
|
|
715
|
+
if (args.length === 0) {
|
|
716
|
+
const url2 = new URL("http://localhost");
|
|
717
|
+
const options2 = resolveRequestOptions(args, url2);
|
|
718
|
+
return [url2, options2];
|
|
719
|
+
}
|
|
666
720
|
if (typeof args[0] === "string") {
|
|
667
721
|
logger5.info("first argument is a location string:", args[0]);
|
|
668
722
|
url = new URL(args[0]);
|
|
@@ -167,12 +167,52 @@ function createRequest(clientRequest) {
|
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// src/utils/getValueBySymbol.ts
|
|
171
|
+
function getValueBySymbol(symbolName, source) {
|
|
172
|
+
const ownSymbols = Object.getOwnPropertySymbols(source);
|
|
173
|
+
const symbol = ownSymbols.find((symbol2) => {
|
|
174
|
+
return symbol2.description === symbolName;
|
|
175
|
+
});
|
|
176
|
+
if (symbol) {
|
|
177
|
+
return Reflect.get(source, symbol);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/utils/isObject.ts
|
|
183
|
+
function isObject(value) {
|
|
184
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/utils/getRawFetchHeaders.ts
|
|
188
|
+
function getRawFetchHeaders(headers) {
|
|
189
|
+
const headersList = getValueBySymbol("headers list", headers);
|
|
190
|
+
if (!headersList) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const headersMap = getValueBySymbol("headers map", headersList);
|
|
194
|
+
if (!headersMap || !isHeadersMapWithRawHeaderNames(headersMap)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const rawHeaders = /* @__PURE__ */ new Map();
|
|
198
|
+
headersMap.forEach(({ name, value }) => {
|
|
199
|
+
rawHeaders.set(name, value);
|
|
200
|
+
});
|
|
201
|
+
return rawHeaders;
|
|
202
|
+
}
|
|
203
|
+
function isHeadersMapWithRawHeaderNames(headersMap) {
|
|
204
|
+
return Array.from(
|
|
205
|
+
headersMap.values()
|
|
206
|
+
).every((value) => {
|
|
207
|
+
return isObject(value) && "name" in value;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
170
211
|
// src/interceptors/ClientRequest/NodeClientRequest.ts
|
|
171
212
|
var _NodeClientRequest = class extends _http.ClientRequest {
|
|
172
213
|
constructor([url, requestOptions, callback], options) {
|
|
173
214
|
super(requestOptions, callback);
|
|
174
215
|
this.chunks = [];
|
|
175
|
-
this.responseSource = "mock";
|
|
176
216
|
this.logger = options.logger.extend(
|
|
177
217
|
`request ${requestOptions.method} ${url.href}`
|
|
178
218
|
);
|
|
@@ -181,6 +221,7 @@ var _NodeClientRequest = class extends _http.ClientRequest {
|
|
|
181
221
|
requestOptions,
|
|
182
222
|
callback
|
|
183
223
|
});
|
|
224
|
+
this.state = 0 /* Idle */;
|
|
184
225
|
this.url = url;
|
|
185
226
|
this.emitter = options.emitter;
|
|
186
227
|
this.requestBuffer = null;
|
|
@@ -219,6 +260,7 @@ var _NodeClientRequest = class extends _http.ClientRequest {
|
|
|
219
260
|
const [chunk, encoding, callback] = normalizeClientRequestEndArgs(...args);
|
|
220
261
|
this.logger.info("normalized arguments:", { chunk, encoding, callback });
|
|
221
262
|
this.writeRequestBodyChunk(chunk, encoding || void 0);
|
|
263
|
+
this.state = 2 /* Sent */;
|
|
222
264
|
const capturedRequest = createRequest(this);
|
|
223
265
|
const { interactiveRequest, requestController } = _chunk5PTPJLB7js.toInteractiveRequest.call(void 0, capturedRequest);
|
|
224
266
|
Object.defineProperty(capturedRequest, "respondWith", {
|
|
@@ -244,6 +286,7 @@ var _NodeClientRequest = class extends _http.ClientRequest {
|
|
|
244
286
|
'emitting the "request" event for %d listener(s)...',
|
|
245
287
|
this.emitter.listenerCount("request")
|
|
246
288
|
);
|
|
289
|
+
this.state = 3 /* MockLookupStart */;
|
|
247
290
|
await _chunk5PTPJLB7js.emitAsync.call(void 0, this.emitter, "request", {
|
|
248
291
|
request: interactiveRequest,
|
|
249
292
|
requestId
|
|
@@ -254,6 +297,7 @@ var _NodeClientRequest = class extends _http.ClientRequest {
|
|
|
254
297
|
return mockedResponse;
|
|
255
298
|
}).then((resolverResult) => {
|
|
256
299
|
this.logger.info("the listeners promise awaited!");
|
|
300
|
+
this.state = 4 /* MockLookupEnd */;
|
|
257
301
|
if (!this.headersSent) {
|
|
258
302
|
for (const [headerName, headerValue] of capturedRequest.headers) {
|
|
259
303
|
this.setHeader(headerName, headerValue);
|
|
@@ -286,7 +330,6 @@ var _NodeClientRequest = class extends _http.ClientRequest {
|
|
|
286
330
|
return this;
|
|
287
331
|
}
|
|
288
332
|
const responseClone = mockedResponse.clone();
|
|
289
|
-
this.responseSource = "mock";
|
|
290
333
|
this.respondWith(mockedResponse);
|
|
291
334
|
this.logger.info(
|
|
292
335
|
mockedResponse.status,
|
|
@@ -342,12 +385,17 @@ var _NodeClientRequest = class extends _http.ClientRequest {
|
|
|
342
385
|
const error = data[0];
|
|
343
386
|
const errorCode = error.code || "";
|
|
344
387
|
this.logger.info("error:\n", error);
|
|
345
|
-
if (
|
|
346
|
-
if (
|
|
347
|
-
this.capturedError
|
|
348
|
-
|
|
388
|
+
if (_NodeClientRequest.suppressErrorCodes.includes(errorCode)) {
|
|
389
|
+
if (this.state < 4 /* MockLookupEnd */) {
|
|
390
|
+
if (!this.capturedError) {
|
|
391
|
+
this.capturedError = error;
|
|
392
|
+
this.logger.info("captured the first error:", this.capturedError);
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
if (this.state === 5 /* ResponseReceived */ && this.responseType === "mock") {
|
|
397
|
+
return false;
|
|
349
398
|
}
|
|
350
|
-
return false;
|
|
351
399
|
}
|
|
352
400
|
}
|
|
353
401
|
return super.emit(event, ...data);
|
|
@@ -359,7 +407,8 @@ var _NodeClientRequest = class extends _http.ClientRequest {
|
|
|
359
407
|
* up the request with `super.end()`.
|
|
360
408
|
*/
|
|
361
409
|
passthrough(chunk, encoding, callback) {
|
|
362
|
-
this.
|
|
410
|
+
this.state = 5 /* ResponseReceived */;
|
|
411
|
+
this.responseType = "passthrough";
|
|
363
412
|
if (this.capturedError) {
|
|
364
413
|
this.emit("error", this.capturedError);
|
|
365
414
|
return this;
|
|
@@ -390,6 +439,8 @@ var _NodeClientRequest = class extends _http.ClientRequest {
|
|
|
390
439
|
*/
|
|
391
440
|
respondWith(mockedResponse) {
|
|
392
441
|
this.logger.info("responding with a mocked response...", mockedResponse);
|
|
442
|
+
this.state = 5 /* ResponseReceived */;
|
|
443
|
+
this.responseType = "mock";
|
|
393
444
|
Object.defineProperties(this, {
|
|
394
445
|
writableFinished: { value: true },
|
|
395
446
|
writableEnded: { value: true }
|
|
@@ -398,9 +449,10 @@ var _NodeClientRequest = class extends _http.ClientRequest {
|
|
|
398
449
|
const { status, statusText, headers, body } = mockedResponse;
|
|
399
450
|
this.response.statusCode = status;
|
|
400
451
|
this.response.statusMessage = statusText;
|
|
401
|
-
|
|
452
|
+
const rawHeaders = getRawFetchHeaders(headers) || headers;
|
|
453
|
+
if (rawHeaders) {
|
|
402
454
|
this.response.headers = {};
|
|
403
|
-
|
|
455
|
+
rawHeaders.forEach((headerValue, headerName) => {
|
|
404
456
|
this.response.rawHeaders.push(headerName, headerValue);
|
|
405
457
|
const insensitiveHeaderName = headerName.toLowerCase();
|
|
406
458
|
const prevHeaders = this.response.headers[insensitiveHeaderName];
|
|
@@ -456,7 +508,9 @@ NodeClientRequest.suppressErrorCodes = [
|
|
|
456
508
|
"ENOTFOUND",
|
|
457
509
|
"ECONNREFUSED",
|
|
458
510
|
"ECONNRESET",
|
|
459
|
-
"EAI_AGAIN"
|
|
511
|
+
"EAI_AGAIN",
|
|
512
|
+
"ENETUNREACH",
|
|
513
|
+
"EHOSTUNREACH"
|
|
460
514
|
];
|
|
461
515
|
|
|
462
516
|
// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
|
|
@@ -616,11 +670,6 @@ function cloneObject(obj) {
|
|
|
616
670
|
return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties);
|
|
617
671
|
}
|
|
618
672
|
|
|
619
|
-
// src/utils/isObject.ts
|
|
620
|
-
function isObject(value) {
|
|
621
|
-
return Object.prototype.toString.call(value) === "[object Object]";
|
|
622
|
-
}
|
|
623
|
-
|
|
624
673
|
// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
|
|
625
674
|
var logger5 = new (0, _logger.Logger)("http normalizeClientRequestArgs");
|
|
626
675
|
function resolveRequestOptions(args, url) {
|
|
@@ -663,6 +712,11 @@ function normalizeClientRequestArgs(defaultProtocol, ...args) {
|
|
|
663
712
|
let callback;
|
|
664
713
|
logger5.info("arguments", args);
|
|
665
714
|
logger5.info("using default protocol:", defaultProtocol);
|
|
715
|
+
if (args.length === 0) {
|
|
716
|
+
const url2 = new URL("http://localhost");
|
|
717
|
+
const options2 = resolveRequestOptions(args, url2);
|
|
718
|
+
return [url2, options2];
|
|
719
|
+
}
|
|
666
720
|
if (typeof args[0] === "string") {
|
|
667
721
|
logger5.info("first argument is a location string:", args[0]);
|
|
668
722
|
url = new URL(args[0]);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkYVNH3GJ5js = require('../../chunk-YVNH3GJ5.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 =
|
|
9
|
+
exports.ClientRequestInterceptor = _chunkYVNH3GJ5js.ClientRequestInterceptor;
|
package/lib/node/presets/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkYVNH3GJ5js = require('../chunk-YVNH3GJ5.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,
|
|
15
|
+
new (0, _chunkYVNH3GJ5js.ClientRequestInterceptor)(),
|
|
16
16
|
new (0, _chunkJCWVLTP7js.XMLHttpRequestInterceptor)()
|
|
17
17
|
];
|
|
18
18
|
|
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
|
+
"version": "0.25.7",
|
|
5
5
|
"main": "./lib/node/index.js",
|
|
6
6
|
"module": "./lib/node/index.mjs",
|
|
7
7
|
"types": "./lib/node/index.d.ts",
|
|
@@ -146,112 +146,6 @@ it('performs the request as-is given resolver returned no mocked response', asyn
|
|
|
146
146
|
expect(text).toBe('original-response')
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
-
it('emits the ENOTFOUND error connecting to a non-existing hostname given no mocked response', async () => {
|
|
150
|
-
const emitter = new Emitter<HttpRequestEventMap>()
|
|
151
|
-
const request = new NodeClientRequest(
|
|
152
|
-
normalizeClientRequestArgs('http:', 'http://non-existing-url.com'),
|
|
153
|
-
{ emitter, logger }
|
|
154
|
-
)
|
|
155
|
-
request.end()
|
|
156
|
-
|
|
157
|
-
const errorReceived = new DeferredPromise<NodeJS.ErrnoException>()
|
|
158
|
-
request.on('error', async (error) => {
|
|
159
|
-
errorReceived.resolve(error)
|
|
160
|
-
})
|
|
161
|
-
const error = await errorReceived
|
|
162
|
-
|
|
163
|
-
expect(error.code).toBe('ENOTFOUND')
|
|
164
|
-
expect(error.syscall).toBe('getaddrinfo')
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
it('emits the ECONNREFUSED error connecting to an inactive server given no mocked response', async () => {
|
|
168
|
-
const emitter = new Emitter<HttpRequestEventMap>()
|
|
169
|
-
const request = new NodeClientRequest(
|
|
170
|
-
normalizeClientRequestArgs('http:', 'http://127.0.0.1:12345'),
|
|
171
|
-
{
|
|
172
|
-
emitter,
|
|
173
|
-
logger,
|
|
174
|
-
}
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
request.end()
|
|
178
|
-
|
|
179
|
-
const errorReceived = new DeferredPromise<ErrorConnectionRefused>()
|
|
180
|
-
request.on('error', async (error: ErrorConnectionRefused) => {
|
|
181
|
-
errorReceived.resolve(error)
|
|
182
|
-
})
|
|
183
|
-
request.end()
|
|
184
|
-
|
|
185
|
-
const error = await errorReceived
|
|
186
|
-
|
|
187
|
-
expect(error.code).toBe('ECONNREFUSED')
|
|
188
|
-
expect(error.syscall).toBe('connect')
|
|
189
|
-
expect(error.address).toBe('127.0.0.1')
|
|
190
|
-
expect(error.port).toBe(12345)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('does not emit ENOTFOUND error connecting to an inactive server given mocked response', async () => {
|
|
194
|
-
const emitter = new Emitter<HttpRequestEventMap>()
|
|
195
|
-
const handleError = vi.fn()
|
|
196
|
-
const request = new NodeClientRequest(
|
|
197
|
-
normalizeClientRequestArgs('http:', 'http://non-existing-url.com'),
|
|
198
|
-
{ emitter, logger }
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
emitter.on('request', async ({ request }) => {
|
|
202
|
-
await sleep(250)
|
|
203
|
-
request.respondWith(
|
|
204
|
-
new Response(null, { status: 200, statusText: 'Works' })
|
|
205
|
-
)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
request.end()
|
|
209
|
-
|
|
210
|
-
request.on('error', handleError)
|
|
211
|
-
|
|
212
|
-
const responseReceived = new DeferredPromise<IncomingMessage>()
|
|
213
|
-
request.on('response', (response) => {
|
|
214
|
-
responseReceived.resolve(response)
|
|
215
|
-
})
|
|
216
|
-
const response = await responseReceived
|
|
217
|
-
|
|
218
|
-
expect(handleError).not.toHaveBeenCalled()
|
|
219
|
-
expect(response.statusCode).toBe(200)
|
|
220
|
-
expect(response.statusMessage).toBe('Works')
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('does not emit ECONNREFUSED error connecting to an inactive server given mocked response', async () => {
|
|
224
|
-
const emitter = new Emitter<HttpRequestEventMap>()
|
|
225
|
-
const handleError = vi.fn()
|
|
226
|
-
const request = new NodeClientRequest(
|
|
227
|
-
normalizeClientRequestArgs('http:', 'http://localhost:9876'),
|
|
228
|
-
{
|
|
229
|
-
emitter,
|
|
230
|
-
logger,
|
|
231
|
-
}
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
emitter.on('request', async ({ request }) => {
|
|
235
|
-
await sleep(250)
|
|
236
|
-
request.respondWith(
|
|
237
|
-
new Response(null, { status: 200, statusText: 'Works' })
|
|
238
|
-
)
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
request.on('error', handleError)
|
|
242
|
-
request.end()
|
|
243
|
-
|
|
244
|
-
const responseReceived = new DeferredPromise<IncomingMessage>()
|
|
245
|
-
request.on('response', (response) => {
|
|
246
|
-
responseReceived.resolve(response)
|
|
247
|
-
})
|
|
248
|
-
const response = await responseReceived
|
|
249
|
-
|
|
250
|
-
expect(handleError).not.toHaveBeenCalled()
|
|
251
|
-
expect(response.statusCode).toBe(200)
|
|
252
|
-
expect(response.statusMessage).toBe('Works')
|
|
253
|
-
})
|
|
254
|
-
|
|
255
149
|
it('sends the request body to the server given no mocked response', async () => {
|
|
256
150
|
const emitter = new Emitter<HttpRequestEventMap>()
|
|
257
151
|
const request = new NodeClientRequest(
|
|
@@ -19,9 +19,22 @@ import { createRequest } from './utils/createRequest'
|
|
|
19
19
|
import { toInteractiveRequest } from '../../utils/toInteractiveRequest'
|
|
20
20
|
import { uuidv4 } from '../../utils/uuid'
|
|
21
21
|
import { emitAsync } from '../../utils/emitAsync'
|
|
22
|
+
import { getRawFetchHeaders } from '../../utils/getRawFetchHeaders'
|
|
22
23
|
|
|
23
24
|
export type Protocol = 'http' | 'https'
|
|
24
25
|
|
|
26
|
+
enum HttpClientInternalState {
|
|
27
|
+
// Have the concept of an idle request because different
|
|
28
|
+
// request methods can kick off request sending
|
|
29
|
+
// (e.g. ".end()" or ".flushHeaders()").
|
|
30
|
+
Idle,
|
|
31
|
+
Sending,
|
|
32
|
+
Sent,
|
|
33
|
+
MockLookupStart,
|
|
34
|
+
MockLookupEnd,
|
|
35
|
+
ResponseReceived,
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
export interface NodeClientOptions {
|
|
26
39
|
emitter: ClientRequestEmitter
|
|
27
40
|
logger: Logger
|
|
@@ -37,8 +50,15 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
37
50
|
'ECONNREFUSED',
|
|
38
51
|
'ECONNRESET',
|
|
39
52
|
'EAI_AGAIN',
|
|
53
|
+
'ENETUNREACH',
|
|
54
|
+
'EHOSTUNREACH',
|
|
40
55
|
]
|
|
41
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Internal state of the request.
|
|
59
|
+
*/
|
|
60
|
+
private state: HttpClientInternalState
|
|
61
|
+
private responseType?: 'mock' | 'passthrough'
|
|
42
62
|
private response: IncomingMessage
|
|
43
63
|
private emitter: ClientRequestEmitter
|
|
44
64
|
private logger: Logger
|
|
@@ -46,7 +66,6 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
46
66
|
chunk?: string | Buffer
|
|
47
67
|
encoding?: BufferEncoding
|
|
48
68
|
}> = []
|
|
49
|
-
private responseSource: 'mock' | 'bypass' = 'mock'
|
|
50
69
|
private capturedError?: NodeJS.ErrnoException
|
|
51
70
|
|
|
52
71
|
public url: URL
|
|
@@ -68,6 +87,7 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
68
87
|
callback,
|
|
69
88
|
})
|
|
70
89
|
|
|
90
|
+
this.state = HttpClientInternalState.Idle
|
|
71
91
|
this.url = url
|
|
72
92
|
this.emitter = options.emitter
|
|
73
93
|
|
|
@@ -138,6 +158,19 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
138
158
|
// Write the last request body chunk passed to the "end()" method.
|
|
139
159
|
this.writeRequestBodyChunk(chunk, encoding || undefined)
|
|
140
160
|
|
|
161
|
+
/**
|
|
162
|
+
* @note Mark the request as sent immediately when invoking ".end()".
|
|
163
|
+
* In Node.js, calling ".end()" will flush the remaining request body
|
|
164
|
+
* and mark the request as "finished" immediately ("end" is synchronous)
|
|
165
|
+
* but we delegate that property update to:
|
|
166
|
+
*
|
|
167
|
+
* - respondWith(), in the case of mocked responses;
|
|
168
|
+
* - super.end(), in the case of bypassed responses.
|
|
169
|
+
*
|
|
170
|
+
* For that reason, we have to keep an internal flag for a finished request.
|
|
171
|
+
*/
|
|
172
|
+
this.state = HttpClientInternalState.Sent
|
|
173
|
+
|
|
141
174
|
const capturedRequest = createRequest(this)
|
|
142
175
|
const { interactiveRequest, requestController } =
|
|
143
176
|
toInteractiveRequest(capturedRequest)
|
|
@@ -192,6 +225,8 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
192
225
|
this.emitter.listenerCount('request')
|
|
193
226
|
)
|
|
194
227
|
|
|
228
|
+
this.state = HttpClientInternalState.MockLookupStart
|
|
229
|
+
|
|
195
230
|
await emitAsync(this.emitter, 'request', {
|
|
196
231
|
request: interactiveRequest,
|
|
197
232
|
requestId,
|
|
@@ -206,6 +241,8 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
206
241
|
}).then((resolverResult) => {
|
|
207
242
|
this.logger.info('the listeners promise awaited!')
|
|
208
243
|
|
|
244
|
+
this.state = HttpClientInternalState.MockLookupEnd
|
|
245
|
+
|
|
209
246
|
/**
|
|
210
247
|
* @fixme We are in the "end()" method that still executes in parallel
|
|
211
248
|
* to our mocking logic here. This can be solved by migrating to the
|
|
@@ -267,8 +304,6 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
267
304
|
|
|
268
305
|
const responseClone = mockedResponse.clone()
|
|
269
306
|
|
|
270
|
-
this.responseSource = 'mock'
|
|
271
|
-
|
|
272
307
|
this.respondWith(mockedResponse)
|
|
273
308
|
this.logger.info(
|
|
274
309
|
mockedResponse.status,
|
|
@@ -349,20 +384,28 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
349
384
|
|
|
350
385
|
this.logger.info('error:\n', error)
|
|
351
386
|
|
|
352
|
-
// Suppress
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
387
|
+
// Suppress only specific Node.js connection errors.
|
|
388
|
+
if (NodeClientRequest.suppressErrorCodes.includes(errorCode)) {
|
|
389
|
+
// Until we aren't sure whether the request will be
|
|
390
|
+
// passthrough, capture the first emitted connection
|
|
391
|
+
// error in case we have to replay it for this request.
|
|
392
|
+
if (this.state < HttpClientInternalState.MockLookupEnd) {
|
|
393
|
+
if (!this.capturedError) {
|
|
394
|
+
this.capturedError = error
|
|
395
|
+
this.logger.info('captured the first error:', this.capturedError)
|
|
396
|
+
}
|
|
397
|
+
return false
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Ignore any connection errors once we know the request
|
|
401
|
+
// has been resolved with a mocked response. Don't capture
|
|
402
|
+
// them as they won't ever be replayed.
|
|
403
|
+
if (
|
|
404
|
+
this.state === HttpClientInternalState.ResponseReceived &&
|
|
405
|
+
this.responseType === 'mock'
|
|
406
|
+
) {
|
|
407
|
+
return false
|
|
364
408
|
}
|
|
365
|
-
return false
|
|
366
409
|
}
|
|
367
410
|
}
|
|
368
411
|
|
|
@@ -380,9 +423,8 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
380
423
|
encoding?: BufferEncoding | null,
|
|
381
424
|
callback?: ClientRequestEndCallback | null
|
|
382
425
|
): this {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this.responseSource = 'bypass'
|
|
426
|
+
this.state = HttpClientInternalState.ResponseReceived
|
|
427
|
+
this.responseType = 'passthrough'
|
|
386
428
|
|
|
387
429
|
// Propagate previously captured errors.
|
|
388
430
|
// For example, a ECONNREFUSED error when connecting to a non-existing host.
|
|
@@ -430,6 +472,9 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
430
472
|
private respondWith(mockedResponse: Response): void {
|
|
431
473
|
this.logger.info('responding with a mocked response...', mockedResponse)
|
|
432
474
|
|
|
475
|
+
this.state = HttpClientInternalState.ResponseReceived
|
|
476
|
+
this.responseType = 'mock'
|
|
477
|
+
|
|
433
478
|
/**
|
|
434
479
|
* Mark the request as finished right before streaming back the response.
|
|
435
480
|
* This is not entirely conventional but this will allow the consumer to
|
|
@@ -448,10 +493,14 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
448
493
|
this.response.statusCode = status
|
|
449
494
|
this.response.statusMessage = statusText
|
|
450
495
|
|
|
451
|
-
|
|
496
|
+
// Try extracting the raw headers from the headers instance.
|
|
497
|
+
// If not possible, fallback to the headers instance as-is.
|
|
498
|
+
const rawHeaders = getRawFetchHeaders(headers) || headers
|
|
499
|
+
|
|
500
|
+
if (rawHeaders) {
|
|
452
501
|
this.response.headers = {}
|
|
453
502
|
|
|
454
|
-
|
|
503
|
+
rawHeaders.forEach((headerValue, headerName) => {
|
|
455
504
|
/**
|
|
456
505
|
* @note Make sure that multi-value headers are appended correctly.
|
|
457
506
|
*/
|
|
@@ -23,6 +23,8 @@ const logger = new Logger('http normalizeClientRequestArgs')
|
|
|
23
23
|
export type HttpRequestCallback = (response: IncomingMessage) => void
|
|
24
24
|
|
|
25
25
|
export type ClientRequestArgs =
|
|
26
|
+
// Request without any arguments is also possible.
|
|
27
|
+
| []
|
|
26
28
|
| [string | URL | LegacyURL, HttpRequestCallback?]
|
|
27
29
|
| [string | URL | LegacyURL, RequestOptions, HttpRequestCallback?]
|
|
28
30
|
| [RequestOptions, HttpRequestCallback?]
|
|
@@ -109,6 +111,14 @@ export function normalizeClientRequestArgs(
|
|
|
109
111
|
logger.info('arguments', args)
|
|
110
112
|
logger.info('using default protocol:', defaultProtocol)
|
|
111
113
|
|
|
114
|
+
// Support "http.request()" calls without any arguments.
|
|
115
|
+
// That call results in a "GET http://localhost" request.
|
|
116
|
+
if (args.length === 0) {
|
|
117
|
+
const url = new URL('http://localhost')
|
|
118
|
+
const options = resolveRequestOptions(args, url)
|
|
119
|
+
return [url, options]
|
|
120
|
+
}
|
|
121
|
+
|
|
112
122
|
// Convert a url string into a URL instance
|
|
113
123
|
// and derive request options from it.
|
|
114
124
|
if (typeof args[0] === 'string') {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { it, expect } from 'vitest'
|
|
2
|
+
import { getRawFetchHeaders } from './getRawFetchHeaders'
|
|
3
|
+
|
|
4
|
+
it('returns undefined given a non-Headers object', () => {
|
|
5
|
+
expect(getRawFetchHeaders({} as Headers)).toBeUndefined()
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
it('returns an empty Map given an empty Headers instance', () => {
|
|
9
|
+
expect(getRawFetchHeaders(new Headers())).toEqual(new Map())
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('returns undefined for headers map on older Node.js versions', () => {
|
|
13
|
+
// Emulate the Headers symbol structure on older
|
|
14
|
+
// versions of Node.js (e.g. 18.8.0).
|
|
15
|
+
const headers = {
|
|
16
|
+
[Symbol('headers list')]: {
|
|
17
|
+
[Symbol('headers map')]: new Map([['header-name', 'header-value']]),
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
expect(getRawFetchHeaders(headers as unknown as Headers)).toBeUndefined()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns raw headers from the given Headers instance', () => {
|
|
24
|
+
expect(
|
|
25
|
+
getRawFetchHeaders(
|
|
26
|
+
new Headers([
|
|
27
|
+
['lowercase-header', 'one'],
|
|
28
|
+
['UPPERCASE-HEADER', 'TWO'],
|
|
29
|
+
['MiXeD-cAsE-hEaDeR', 'ThReE'],
|
|
30
|
+
])
|
|
31
|
+
)
|
|
32
|
+
).toEqual(
|
|
33
|
+
new Map([
|
|
34
|
+
['lowercase-header', 'one'],
|
|
35
|
+
['UPPERCASE-HEADER', 'TWO'],
|
|
36
|
+
['MiXeD-cAsE-hEaDeR', 'ThReE'],
|
|
37
|
+
])
|
|
38
|
+
)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('returns raw headers for a header with multiple values', () => {
|
|
42
|
+
expect(
|
|
43
|
+
getRawFetchHeaders(
|
|
44
|
+
new Headers([
|
|
45
|
+
['Set-CookiE', 'a=b'],
|
|
46
|
+
['Set-CookiE', 'c=d'],
|
|
47
|
+
])
|
|
48
|
+
)
|
|
49
|
+
).toEqual(new Map([['Set-CookiE', 'a=b, c=d']]))
|
|
50
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getValueBySymbol } from './getValueBySymbol'
|
|
2
|
+
import { isObject } from './isObject'
|
|
3
|
+
|
|
4
|
+
type RawHeadersMap = Map<string, string>
|
|
5
|
+
type HeadersMapHeader = { name: string; value: string }
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns raw headers from the given `Headers` instance.
|
|
9
|
+
* @example
|
|
10
|
+
* const headers = new Headers([
|
|
11
|
+
* ['X-HeadeR-NamE', 'Value']
|
|
12
|
+
* ])
|
|
13
|
+
* getRawFetchHeaders(headers)
|
|
14
|
+
* // { 'X-HeadeR-NamE': 'Value' }
|
|
15
|
+
*/
|
|
16
|
+
export function getRawFetchHeaders(
|
|
17
|
+
headers: Headers
|
|
18
|
+
): RawHeadersMap | undefined {
|
|
19
|
+
const headersList = getValueBySymbol<object>('headers list', headers)
|
|
20
|
+
|
|
21
|
+
if (!headersList) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const headersMap = getValueBySymbol<
|
|
26
|
+
Map<string, string> | Map<string, HeadersMapHeader>
|
|
27
|
+
>('headers map', headersList)
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @note Older versions of Node.js (e.g. 18.8.0) keep headers map
|
|
31
|
+
* as Map<normalizedHeaderName, value> without any means to tap
|
|
32
|
+
* into raw header values. Detect that and return undefined.
|
|
33
|
+
*/
|
|
34
|
+
if (!headersMap || !isHeadersMapWithRawHeaderNames(headersMap)) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Raw headers is a map of { rawHeaderName: rawHeaderValue }
|
|
39
|
+
const rawHeaders: RawHeadersMap = new Map<string, string>()
|
|
40
|
+
|
|
41
|
+
headersMap.forEach(({ name, value }) => {
|
|
42
|
+
rawHeaders.set(name, value)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return rawHeaders
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isHeadersMapWithRawHeaderNames(
|
|
49
|
+
headersMap: Map<string, string> | Map<string, HeadersMapHeader>
|
|
50
|
+
): headersMap is Map<string, HeadersMapHeader> {
|
|
51
|
+
return Array.from(
|
|
52
|
+
headersMap.values() as Iterable<string | HeadersMapHeader>
|
|
53
|
+
).every((value) => {
|
|
54
|
+
return isObject<HeadersMapHeader>(value) && 'name' in value
|
|
55
|
+
})
|
|
56
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { it, expect } from 'vitest'
|
|
2
|
+
import { getValueBySymbol } from './getValueBySymbol'
|
|
3
|
+
|
|
4
|
+
it('returns undefined given a non-existing symbol', () => {
|
|
5
|
+
expect(getValueBySymbol('non-existing', {})).toBeUndefined()
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
it('returns value behind the given symbol', () => {
|
|
9
|
+
const symbol = Symbol('kInternal')
|
|
10
|
+
|
|
11
|
+
expect(getValueBySymbol('kInternal', { [symbol]: null })).toBe(null)
|
|
12
|
+
expect(getValueBySymbol('kInternal', { [symbol]: true })).toBe(true)
|
|
13
|
+
expect(getValueBySymbol('kInternal', { [symbol]: 'value' })).toBe('value')
|
|
14
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the value behind the symbol with the given name.
|
|
3
|
+
*/
|
|
4
|
+
export function getValueBySymbol<T>(
|
|
5
|
+
symbolName: string,
|
|
6
|
+
source: object
|
|
7
|
+
): T | undefined {
|
|
8
|
+
const ownSymbols = Object.getOwnPropertySymbols(source)
|
|
9
|
+
|
|
10
|
+
const symbol = ownSymbols.find((symbol) => {
|
|
11
|
+
return symbol.description === symbolName
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
if (symbol) {
|
|
15
|
+
return Reflect.get(source, symbol)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return
|
|
19
|
+
}
|