@mswjs/interceptors 0.42.0 → 0.42.2
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/browser/create-request-id-Bk5YX1AM.js +720 -0
- package/lib/browser/create-request-id-Bk5YX1AM.js.map +1 -0
- package/lib/browser/{fetch-utils-zxA_SD66.js → fetch-utils-CUOrwEQf.js} +2 -2
- package/lib/browser/{fetch-utils-zxA_SD66.js.map → fetch-utils-CUOrwEQf.js.map} +1 -1
- package/lib/browser/{handle-request-CIOa9O-N.js → handle-request-CqVvBhcw.js} +3 -3
- package/lib/browser/{handle-request-CIOa9O-N.js.map → handle-request-CqVvBhcw.js.map} +1 -1
- package/lib/browser/index.js +2 -2
- package/lib/browser/interceptors/WebSocket/index.js +1 -1
- package/lib/browser/interceptors/XMLHttpRequest/web.js +1 -1
- package/lib/browser/interceptors/fetch/web.js +1 -1
- package/lib/browser/presets/browser.js +2 -2
- package/lib/browser/{web-C2oKsT3Q.js → web-BcKSQghf.js} +4 -4
- package/lib/browser/{web-C2oKsT3Q.js.map → web-BcKSQghf.js.map} +1 -1
- package/lib/browser/{web-BjVDkiX0.js → web-CdcYFjgm.js} +4 -4
- package/lib/browser/{web-BjVDkiX0.js.map → web-CdcYFjgm.js.map} +1 -1
- package/lib/node/{has-configurable-global-CJCh_g1I.js → has-configurable-global-IskkJJOL.js} +2 -2
- package/lib/node/{has-configurable-global-CJCh_g1I.js.map → has-configurable-global-IskkJJOL.js.map} +1 -1
- package/lib/node/interceptors/ClientRequest/index.js +2 -2
- package/lib/node/interceptors/XMLHttpRequest/node.js +3 -3
- package/lib/node/interceptors/fetch/node.js +3 -3
- package/lib/node/interceptors/http/index.js +1 -1
- package/lib/node/interceptors/net/index.d.ts +27 -1
- package/lib/node/interceptors/net/index.js +1 -1
- package/lib/node/{net-DtMnyEeg.js → net-9sRKnjIG.js} +115 -9
- package/lib/node/net-9sRKnjIG.js.map +1 -0
- package/lib/node/remote-http-interceptor.js +1 -1
- package/lib/node/{source-lP0yyEtA.js → source-BUVce4Q1.js} +17 -4
- package/lib/node/{source-lP0yyEtA.js.map → source-BUVce4Q1.js.map} +1 -1
- package/package.json +1 -1
- package/src/interceptors/http/source.ts +26 -3
- package/src/interceptors/net/index.ts +16 -15
- package/src/interceptors/net/socket-controller.ts +156 -2
- package/lib/browser/create-request-id-DlEd4GOA.js +0 -184
- package/lib/browser/create-request-id-DlEd4GOA.js.map +0 -1
- package/lib/node/net-DtMnyEeg.js.map +0 -1
|
@@ -362,6 +362,8 @@ export abstract class SocketController {
|
|
|
362
362
|
| typeof SocketController.CLAIMED
|
|
363
363
|
| typeof SocketController.PASSTHROUGH
|
|
364
364
|
|
|
365
|
+
#awaitedVerdicts = 0
|
|
366
|
+
|
|
365
367
|
private [kRawSocket]: net.Socket
|
|
366
368
|
|
|
367
369
|
constructor(socket: net.Socket) {
|
|
@@ -400,6 +402,55 @@ export abstract class SocketController {
|
|
|
400
402
|
|
|
401
403
|
this.readyState = SocketController.PASSTHROUGH
|
|
402
404
|
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Await a verdict on this connection from the given number of
|
|
408
|
+
* subscribers. A connection nobody awaits to inspect (or one that
|
|
409
|
+
* every awaited subscriber has declined) is passed through as-is.
|
|
410
|
+
* This makes "unclaimed after everyone declined" a state owned by
|
|
411
|
+
* the controller instead of the individual subscribers.
|
|
412
|
+
*/
|
|
413
|
+
public awaitVerdicts(count: number): void {
|
|
414
|
+
this.#awaitedVerdicts = count
|
|
415
|
+
|
|
416
|
+
if (this.#awaitedVerdicts === 0) {
|
|
417
|
+
this.passthrough()
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Decline this socket connection. Declining means the subscriber
|
|
423
|
+
* has inspected the connection and will not handle it (e.g. the
|
|
424
|
+
* traffic is not of the protocol that subscriber implements).
|
|
425
|
+
* Once every awaited subscriber declines, the connection is
|
|
426
|
+
* passed through as-is.
|
|
427
|
+
*/
|
|
428
|
+
public decline(): void {
|
|
429
|
+
if (this.readyState !== SocketController.PENDING) {
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
this.#awaitedVerdicts -= 1
|
|
434
|
+
|
|
435
|
+
if (this.#awaitedVerdicts <= 0) {
|
|
436
|
+
/**
|
|
437
|
+
* @note Defer the passthrough so it never transitions this
|
|
438
|
+
* controller in the middle of a client write. Declines are
|
|
439
|
+
* issued while the written data is being pushed to the server
|
|
440
|
+
* socket, and a synchronous transition would race the write's
|
|
441
|
+
* own bookkeeping (e.g. re-buffering the write after a reset
|
|
442
|
+
* at an exchange boundary).
|
|
443
|
+
*/
|
|
444
|
+
process.nextTick(() => {
|
|
445
|
+
if (
|
|
446
|
+
this.readyState === SocketController.PENDING &&
|
|
447
|
+
!this[kRawSocket].destroyed
|
|
448
|
+
) {
|
|
449
|
+
this.passthrough()
|
|
450
|
+
}
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
}
|
|
403
454
|
}
|
|
404
455
|
|
|
405
456
|
export type FlushPendingDataFunction = (
|
|
@@ -419,6 +470,8 @@ export class TcpSocketController extends SocketController {
|
|
|
419
470
|
protected pendingConnection: PromiseWithResolvers<[TcpWrap, TcpHandle]>
|
|
420
471
|
|
|
421
472
|
#connectionOptions?: NetworkConnectionOptions
|
|
473
|
+
#retargetedConnectionOptions?: NetworkConnectionOptions &
|
|
474
|
+
net.SocketConnectOpts
|
|
422
475
|
#realWriteGeneric: net.Socket['_writeGeneric']
|
|
423
476
|
#passthroughSocket: net.Socket | null = null
|
|
424
477
|
#bufferedWrites: Array<Parameters<net.Socket['_writeGeneric']>> = []
|
|
@@ -532,8 +585,59 @@ export class TcpSocketController extends SocketController {
|
|
|
532
585
|
* on this socket can be handled anew. This is meant for kept-alive
|
|
533
586
|
* sockets that are reused for multiple exchanges by clients that
|
|
534
587
|
* don't emit the "free" event on the socket (e.g. Undici).
|
|
588
|
+
*
|
|
589
|
+
* Providing connection options retargets this connection: the
|
|
590
|
+
* exchanges that follow belong to the given target (e.g. the
|
|
591
|
+
* authority of an established "CONNECT" tunnel). An unclaimed
|
|
592
|
+
* exchange then passes through to that target instead of the
|
|
593
|
+
* originally dialed one, and a claimed exchange reports it as the
|
|
594
|
+
* peer. The verdict count is deliberately not re-armed: subscribers
|
|
595
|
+
* that declined this connection's protocol stay declined across
|
|
596
|
+
* the exchanges, retargeted or not.
|
|
535
597
|
*/
|
|
536
|
-
public reset(
|
|
598
|
+
public reset(
|
|
599
|
+
connectionOptions?: NetworkConnectionOptions & net.SocketConnectOpts
|
|
600
|
+
): void {
|
|
601
|
+
if (connectionOptions != null) {
|
|
602
|
+
this.#retargetedConnectionOptions = connectionOptions
|
|
603
|
+
this.#connectionOptions = connectionOptions
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* @note The passthrough connection to the original target, if
|
|
607
|
+
* any, cannot serve the retargeted exchanges. Detach its
|
|
608
|
+
* forwarding listeners before destroying it so its teardown is
|
|
609
|
+
* not mistaken for the client connection's own (e.g. its "close"
|
|
610
|
+
* must not close the client socket).
|
|
611
|
+
*/
|
|
612
|
+
if (this.#passthroughSocket) {
|
|
613
|
+
this.#passthroughSocket
|
|
614
|
+
.removeListener('connect', this.#onRealSocketConnect)
|
|
615
|
+
.removeListener(
|
|
616
|
+
'connectionAttemptFailed',
|
|
617
|
+
this.#onRealSocketConnectionAttemptFailed
|
|
618
|
+
)
|
|
619
|
+
.removeListener(
|
|
620
|
+
'connectionAttemptTimeout',
|
|
621
|
+
this.#onRealSocketConnectionAttemptTimeout
|
|
622
|
+
)
|
|
623
|
+
.removeListener('data', this.#onRealSocketData)
|
|
624
|
+
.removeListener('error', this.#onRealSocketError)
|
|
625
|
+
.removeListener('end', this.#onRealSocketEnd)
|
|
626
|
+
.removeListener('close', this.#onRealSocketClose)
|
|
627
|
+
.destroy()
|
|
628
|
+
|
|
629
|
+
this.#passthroughSocket = null
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* @note The handle swapped in from the destroyed connection,
|
|
633
|
+
* if any, no longer carries this socket's traffic. Treat the
|
|
634
|
+
* socket as not swapped so the retargeted passthrough writes
|
|
635
|
+
* directly to the new connection until its own handle swap.
|
|
636
|
+
*/
|
|
637
|
+
this.#realHandleSwapped = false
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
537
641
|
/**
|
|
538
642
|
* @note Only settled (claimed or passed-through) sockets need a reset.
|
|
539
643
|
* Resetting a pending socket again would discard the writes already
|
|
@@ -1021,6 +1125,19 @@ export class TcpSocketController extends SocketController {
|
|
|
1021
1125
|
this.#passthroughSocket?.resume()
|
|
1022
1126
|
}
|
|
1023
1127
|
|
|
1128
|
+
/**
|
|
1129
|
+
* Forward the client's half-close to the passthrough socket.
|
|
1130
|
+
* The client's writable side may finish before the real handle is
|
|
1131
|
+
* swapped in ("_final" of a connected client runs against the mock
|
|
1132
|
+
* handle), leaving the FIN unsent. Once the handle is swapped, the
|
|
1133
|
+
* client's own shutdown reaches the real connection natively.
|
|
1134
|
+
*/
|
|
1135
|
+
#forwardClientFinish = () => {
|
|
1136
|
+
if (!this.#realHandleSwapped) {
|
|
1137
|
+
this.#passthroughSocket?.end()
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1024
1141
|
/**
|
|
1025
1142
|
* Suspend forwarding of the passthrough socket events ("data", "end", "close")
|
|
1026
1143
|
* to the client socket. The events are buffered in order until `uncorkReads()`
|
|
@@ -1159,7 +1276,9 @@ export class TcpSocketController extends SocketController {
|
|
|
1159
1276
|
logger.verbose('-> passthrough!')
|
|
1160
1277
|
|
|
1161
1278
|
const createRealSocket = () => {
|
|
1162
|
-
const realSocket = this
|
|
1279
|
+
const realSocket = this.#retargetedConnectionOptions
|
|
1280
|
+
? this.#createRetargetedConnection(this.#retargetedConnectionOptions)
|
|
1281
|
+
: this.createConnection()
|
|
1163
1282
|
|
|
1164
1283
|
// Mark the passthrough socket as patched so it's exempt from
|
|
1165
1284
|
// the unpatched socket detection (it never enters agent pools,
|
|
@@ -1246,8 +1365,43 @@ export class TcpSocketController extends SocketController {
|
|
|
1246
1365
|
.on('end', this.#onRealSocketEnd)
|
|
1247
1366
|
.on('close', this.#onRealSocketClose)
|
|
1248
1367
|
|
|
1368
|
+
/**
|
|
1369
|
+
* @note Forward the client's half-close, unless the real handle
|
|
1370
|
+
* is already swapped in — the client's own shutdown then reaches
|
|
1371
|
+
* the real connection natively (see "#forwardClientFinish").
|
|
1372
|
+
*/
|
|
1373
|
+
if (!this.#realHandleSwapped) {
|
|
1374
|
+
if (this.socket.writableFinished) {
|
|
1375
|
+
this.#forwardClientFinish()
|
|
1376
|
+
} else {
|
|
1377
|
+
this.socket.removeListener('finish', this.#forwardClientFinish)
|
|
1378
|
+
this.socket.once('finish', this.#forwardClientFinish)
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1249
1382
|
return realSocket
|
|
1250
1383
|
}
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Create the passthrough connection to the target this controller
|
|
1387
|
+
* was retargeted to (see `reset()`). The original `createConnection`
|
|
1388
|
+
* dials the originally requested target and cannot serve retargeted
|
|
1389
|
+
* exchanges.
|
|
1390
|
+
*/
|
|
1391
|
+
#createRetargetedConnection(
|
|
1392
|
+
connectionOptions: net.SocketConnectOpts
|
|
1393
|
+
): net.Socket {
|
|
1394
|
+
const realSocket = new net.Socket()
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* @note Mark the socket as patched before connecting: the patched
|
|
1398
|
+
* "Socket.prototype.connect" exempts such sockets, establishing
|
|
1399
|
+
* the connection for real.
|
|
1400
|
+
*/
|
|
1401
|
+
realSocket[kPatched] = true
|
|
1402
|
+
|
|
1403
|
+
return realSocket.connect(connectionOptions)
|
|
1404
|
+
}
|
|
1251
1405
|
}
|
|
1252
1406
|
|
|
1253
1407
|
export class TlsSocketController extends TcpSocketController {
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { Emitter } from "rettime";
|
|
2
|
-
import debug from "debug";
|
|
3
|
-
//#region src/disposable.ts
|
|
4
|
-
var Disposable = class {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.subscriptions = [];
|
|
7
|
-
}
|
|
8
|
-
dispose() {
|
|
9
|
-
let subscription;
|
|
10
|
-
while (subscription = this.subscriptions.pop()) subscription();
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region src/utils/logger.ts
|
|
15
|
-
const LOG_TIMESTAMP_REGEXP = /\d{2}:\d{2}:\d{2}\.\d{3}/;
|
|
16
|
-
function normalizeNamespace(namespace) {
|
|
17
|
-
return namespace.split(":").map((segment) => {
|
|
18
|
-
return segment.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
19
|
-
}).filter(Boolean).join(":");
|
|
20
|
-
}
|
|
21
|
-
function getTimestamp() {
|
|
22
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
23
|
-
}
|
|
24
|
-
async function readBody(message) {
|
|
25
|
-
if (message.body == null) return null;
|
|
26
|
-
try {
|
|
27
|
-
return await message.clone().text();
|
|
28
|
-
} catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function formatHeaders(headers) {
|
|
33
|
-
return Array.from(headers.entries()).map(([name, value]) => {
|
|
34
|
-
return `${name}: ${value}`;
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
async function formatHttpMessage(startLine, message) {
|
|
38
|
-
const lines = [startLine, ...formatHeaders(message.headers)];
|
|
39
|
-
const body = await readBody(message);
|
|
40
|
-
lines.push("", body ?? "");
|
|
41
|
-
return lines.join("\n");
|
|
42
|
-
}
|
|
43
|
-
async function formatRequest(request) {
|
|
44
|
-
return formatHttpMessage(`${request.method} ${request.url}`, request);
|
|
45
|
-
}
|
|
46
|
-
async function formatResponse(response) {
|
|
47
|
-
const statusText = response.statusText ? ` ${response.statusText}` : "";
|
|
48
|
-
return formatHttpMessage(`HTTP ${response.status}${statusText}`, response);
|
|
49
|
-
}
|
|
50
|
-
function formatLogArguments(arguments_) {
|
|
51
|
-
const message = arguments_[0];
|
|
52
|
-
if (typeof message === "string") {
|
|
53
|
-
const messageWithoutDebugTimestamp = message.replace(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z /, "");
|
|
54
|
-
const timestampMatch = messageWithoutDebugTimestamp.match(LOG_TIMESTAMP_REGEXP);
|
|
55
|
-
if (!timestampMatch || timestampMatch.index === void 0) {
|
|
56
|
-
arguments_[0] = messageWithoutDebugTimestamp;
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const messagePrefix = messageWithoutDebugTimestamp.slice(0, timestampMatch.index).trim();
|
|
60
|
-
const messageBody = messageWithoutDebugTimestamp.slice(timestampMatch.index + timestampMatch[0].length).trimStart();
|
|
61
|
-
arguments_[0] = `${timestampMatch[0]} ${messagePrefix} ${messageBody}`;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function useConciseTimestamp(logger) {
|
|
65
|
-
logger.log = (...arguments_) => {
|
|
66
|
-
formatLogArguments(arguments_);
|
|
67
|
-
debug.log(...arguments_);
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function isVerboseLoggingEnabled() {
|
|
71
|
-
if (typeof process !== "undefined" && process.env.DEBUG_LEVEL === "verbose") return true;
|
|
72
|
-
/**
|
|
73
|
-
* @note Consult the localStorage only in browser-like environments.
|
|
74
|
-
* In Node.js 26+, reading "globalThis.localStorage" without the
|
|
75
|
-
* "--localstorage-file" flag set emits an experimental warning
|
|
76
|
-
* (a try/catch cannot suppress it). Node.js consumers control the
|
|
77
|
-
* log level via the "DEBUG_LEVEL" environment variable above.
|
|
78
|
-
*/
|
|
79
|
-
if (typeof document === "undefined") return false;
|
|
80
|
-
try {
|
|
81
|
-
return globalThis.localStorage?.getItem("debugLevel") === "verbose";
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function createLogger(namespace) {
|
|
87
|
-
const logger = debug(`interceptors:${normalizeNamespace(namespace)}`);
|
|
88
|
-
Reflect.set(logger, "useColors", true);
|
|
89
|
-
useConciseTimestamp(logger);
|
|
90
|
-
return {
|
|
91
|
-
info(message, ...positionals) {
|
|
92
|
-
logger(`${getTimestamp()} ${message}`, ...positionals);
|
|
93
|
-
},
|
|
94
|
-
verbose(message, ...positionals) {
|
|
95
|
-
if (!isVerboseLoggingEnabled()) return;
|
|
96
|
-
logger(`${getTimestamp()} ${message}`, ...positionals);
|
|
97
|
-
},
|
|
98
|
-
isEnabled(level) {
|
|
99
|
-
return logger.enabled && (level === "default" || isVerboseLoggingEnabled());
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/interceptor.ts
|
|
105
|
-
const interceptorsRegistry = globalThis.__MSW_INTERCEPTORS_REGISTRY ??= /* @__PURE__ */ new Map();
|
|
106
|
-
var Interceptor = class extends Disposable {
|
|
107
|
-
#owners;
|
|
108
|
-
static singleton(InterceptorClass) {
|
|
109
|
-
const symbol = InterceptorClass.symbol;
|
|
110
|
-
const existing = interceptorsRegistry.get(symbol);
|
|
111
|
-
if (existing instanceof InterceptorClass) return existing;
|
|
112
|
-
const newInstance = new InterceptorClass();
|
|
113
|
-
interceptorsRegistry.set(symbol, newInstance);
|
|
114
|
-
return newInstance;
|
|
115
|
-
}
|
|
116
|
-
constructor() {
|
|
117
|
-
super();
|
|
118
|
-
this.on = (type, listener, options) => {
|
|
119
|
-
return this.emitter.on(type, listener, options);
|
|
120
|
-
};
|
|
121
|
-
this.once = (type, listener, options) => {
|
|
122
|
-
return this.emitter.once(type, listener, options);
|
|
123
|
-
};
|
|
124
|
-
this.listeners = (type) => {
|
|
125
|
-
return this.emitter.listeners(type);
|
|
126
|
-
};
|
|
127
|
-
this.listenerCount = (type) => {
|
|
128
|
-
return this.emitter.listenerCount(type);
|
|
129
|
-
};
|
|
130
|
-
this.removeListener = (type, listener) => {
|
|
131
|
-
return this.emitter.removeListener(type, listener);
|
|
132
|
-
};
|
|
133
|
-
this.removeAllListeners = (type) => {
|
|
134
|
-
this.logger.info("removeAllListeners %o", { eventType: type ?? "*" });
|
|
135
|
-
return this.emitter.removeAllListeners(type);
|
|
136
|
-
};
|
|
137
|
-
this.#owners = /* @__PURE__ */ new Set();
|
|
138
|
-
this.readyState = "INACTIVE";
|
|
139
|
-
this.emitter = new Emitter();
|
|
140
|
-
this.logger = createLogger(this.#getLoggerNamespace());
|
|
141
|
-
}
|
|
142
|
-
apply(owner = this) {
|
|
143
|
-
if (this.#owners.has(owner)) return;
|
|
144
|
-
if (this.readyState !== "ACTIVE" && !this.predicate()) return;
|
|
145
|
-
this.#owners.add(owner);
|
|
146
|
-
if (this.readyState === "ACTIVE") return;
|
|
147
|
-
try {
|
|
148
|
-
this.setup();
|
|
149
|
-
this.readyState = "ACTIVE";
|
|
150
|
-
this.logger.info("apply");
|
|
151
|
-
} catch (error) {
|
|
152
|
-
this.dispose(owner);
|
|
153
|
-
throw error;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
dispose(owner = this) {
|
|
157
|
-
if (!this.#owners.delete(owner)) return;
|
|
158
|
-
if (this.#owners.size > 0) return;
|
|
159
|
-
super.dispose();
|
|
160
|
-
this.emitter.removeAllListeners();
|
|
161
|
-
this.readyState = "DISPOSED";
|
|
162
|
-
this.logger.info("disable");
|
|
163
|
-
}
|
|
164
|
-
#getLoggerNamespace() {
|
|
165
|
-
const symbolDescription = this.constructor.symbol?.description;
|
|
166
|
-
if (symbolDescription) return symbolDescription.replace(/-interceptor$/, "");
|
|
167
|
-
return this.constructor.name.replace(/Interceptor$/, "");
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
//#endregion
|
|
171
|
-
//#region src/create-request-id.ts
|
|
172
|
-
/**
|
|
173
|
-
* Generate a random ID string to represent a request.
|
|
174
|
-
* @example
|
|
175
|
-
* createRequestId()
|
|
176
|
-
* // "f774b6c9c600f"
|
|
177
|
-
*/
|
|
178
|
-
function createRequestId() {
|
|
179
|
-
return Math.random().toString(16).slice(2);
|
|
180
|
-
}
|
|
181
|
-
//#endregion
|
|
182
|
-
export { formatResponse as a, formatRequest as i, Interceptor as n, createLogger as r, createRequestId as t };
|
|
183
|
-
|
|
184
|
-
//# sourceMappingURL=create-request-id-DlEd4GOA.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"create-request-id-DlEd4GOA.js","names":["#owners","#getLoggerNamespace"],"sources":["../../src/disposable.ts","../../src/utils/logger.ts","../../src/interceptor.ts","../../src/create-request-id.ts"],"sourcesContent":["export type DisposableSubscription = () => void\n\nexport class Disposable {\n protected subscriptions: Array<DisposableSubscription> = []\n\n public dispose() {\n let subscription: DisposableSubscription | undefined\n\n while ((subscription = this.subscriptions.pop())) {\n subscription()\n }\n }\n}\n","import debug from 'debug'\n\nexport type LogLevel = 'default' | 'verbose'\n\nexport interface Logger {\n info(message: string, ...positionals: Array<unknown>): void\n verbose(message: string, ...positionals: Array<unknown>): void\n isEnabled(level: LogLevel): boolean\n}\n\nconst LOG_TIMESTAMP_REGEXP = /\\d{2}:\\d{2}:\\d{2}\\.\\d{3}/\n\nfunction normalizeNamespace(namespace: string): string {\n return namespace\n .split(':')\n .map((segment) => {\n return segment\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-|-$/g, '')\n .toLowerCase()\n })\n .filter(Boolean)\n .join(':')\n}\n\nfunction getTimestamp(): string {\n return new Date().toISOString().slice(11, 23)\n}\n\nasync function readBody(message: Request | Response): Promise<string | null> {\n if (message.body == null) {\n return null\n }\n\n try {\n return await message.clone().text()\n } catch {\n return null\n }\n}\n\nfunction formatHeaders(headers: Headers): Array<string> {\n return Array.from(headers.entries()).map(([name, value]) => {\n return `${name}: ${value}`\n })\n}\n\nasync function formatHttpMessage(\n startLine: string,\n message: Request | Response\n): Promise<string> {\n const lines = [startLine, ...formatHeaders(message.headers)]\n const body = await readBody(message)\n\n lines.push('', body ?? '')\n\n return lines.join('\\n')\n}\n\nexport async function formatRequest(request: Request): Promise<string> {\n return formatHttpMessage(`${request.method} ${request.url}`, request)\n}\n\nexport async function formatResponse(response: Response): Promise<string> {\n const statusText = response.statusText ? ` ${response.statusText}` : ''\n return formatHttpMessage(\n `HTTP ${response.status}${statusText}`,\n response\n )\n}\n\nfunction formatLogArguments(arguments_: Array<unknown>): void {\n const message = arguments_[0]\n\n if (typeof message === 'string') {\n const messageWithoutDebugTimestamp = message.replace(\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z /,\n ''\n )\n const timestampMatch = messageWithoutDebugTimestamp.match(\n LOG_TIMESTAMP_REGEXP\n )\n\n if (!timestampMatch || timestampMatch.index === undefined) {\n arguments_[0] = messageWithoutDebugTimestamp\n return\n }\n\n const messagePrefix = messageWithoutDebugTimestamp\n .slice(0, timestampMatch.index)\n .trim()\n const messageBody = messageWithoutDebugTimestamp\n .slice(timestampMatch.index + timestampMatch[0].length)\n .trimStart()\n\n arguments_[0] = `${timestampMatch[0]} ${messagePrefix} ${messageBody}`\n }\n}\n\nfunction useConciseTimestamp(logger: debug.Debugger): void {\n logger.log = (...arguments_) => {\n formatLogArguments(arguments_)\n debug.log(...arguments_)\n }\n}\n\nfunction isVerboseLoggingEnabled(): boolean {\n if (typeof process !== 'undefined' && process.env.DEBUG_LEVEL === 'verbose') {\n return true\n }\n\n /**\n * @note Consult the localStorage only in browser-like environments.\n * In Node.js 26+, reading \"globalThis.localStorage\" without the\n * \"--localstorage-file\" flag set emits an experimental warning\n * (a try/catch cannot suppress it). Node.js consumers control the\n * log level via the \"DEBUG_LEVEL\" environment variable above.\n */\n if (typeof document === 'undefined') {\n return false\n }\n\n try {\n return globalThis.localStorage?.getItem('debugLevel') === 'verbose'\n } catch {\n return false\n }\n}\n\nexport function createLogger(namespace: string): Logger {\n const normalizedNamespace = normalizeNamespace(namespace)\n const logger = debug(`interceptors:${normalizedNamespace}`)\n Reflect.set(logger, 'useColors', true)\n useConciseTimestamp(logger)\n\n return {\n info(message, ...positionals) {\n logger(`${getTimestamp()} ${message}`, ...positionals)\n },\n verbose(message, ...positionals) {\n if (!isVerboseLoggingEnabled()) {\n return\n }\n\n logger(`${getTimestamp()} ${message}`, ...positionals)\n },\n isEnabled(level) {\n return (\n logger.enabled && (level === 'default' || isVerboseLoggingEnabled())\n )\n },\n }\n}\n","import { Emitter, type DefaultEventMap } from 'rettime'\nimport { Disposable } from './disposable'\nimport { createLogger, type Logger } from './utils/logger'\n\nexport enum InterceptorReadyState {\n INACTIVE = 'INACTIVE',\n ACTIVE = 'ACTIVE',\n DISPOSED = 'DISPOSED',\n}\n\ndeclare global {\n var __MSW_INTERCEPTORS_REGISTRY: Map<symbol, Interceptor<any>> | undefined\n}\n\nconst interceptorsRegistry = (globalThis.__MSW_INTERCEPTORS_REGISTRY ??=\n new Map<symbol, Interceptor<any>>())\n\nexport abstract class Interceptor<\n Events extends DefaultEventMap,\n> extends Disposable {\n declare ['constructor']: typeof Interceptor\n\n protected emitter: Emitter<Events>\n protected readonly logger: Logger\n\n public readyState: InterceptorReadyState\n\n static readonly symbol: symbol\n\n #owners: Set<object>\n\n static singleton<T extends Interceptor<any>>(\n InterceptorClass: (new () => T) & { symbol: symbol }\n ): T {\n const symbol = InterceptorClass.symbol\n const existing = interceptorsRegistry.get(symbol)\n\n if (existing instanceof InterceptorClass) {\n return existing\n }\n\n const newInstance = new InterceptorClass()\n interceptorsRegistry.set(symbol, newInstance)\n return newInstance\n }\n\n constructor() {\n super()\n\n this.#owners = new Set()\n this.readyState = InterceptorReadyState.INACTIVE\n this.emitter = new Emitter()\n this.logger = createLogger(this.#getLoggerNamespace())\n }\n\n protected abstract predicate(): boolean\n protected abstract setup(): void\n\n public apply(owner: object = this): void {\n if (this.#owners.has(owner)) {\n return\n }\n\n if (\n this.readyState !== InterceptorReadyState.ACTIVE &&\n !this.predicate()\n ) {\n return\n }\n\n this.#owners.add(owner)\n\n if (this.readyState === InterceptorReadyState.ACTIVE) {\n return\n }\n\n try {\n this.setup()\n this.readyState = InterceptorReadyState.ACTIVE\n this.logger.info('apply')\n } catch (error) {\n this.dispose(owner)\n throw error\n }\n }\n\n public dispose(owner: object = this): void {\n if (!this.#owners.delete(owner)) {\n return\n }\n\n if (this.#owners.size > 0) {\n return\n }\n\n super.dispose()\n this.emitter.removeAllListeners()\n this.readyState = InterceptorReadyState.DISPOSED\n this.logger.info('disable')\n }\n\n public on: Emitter<Events>['on'] = (type, listener, options) => {\n return this.emitter.on(type, listener, options)\n }\n\n public once: Emitter<Events>['once'] = (type, listener, options) => {\n return this.emitter.once(type, listener, options)\n }\n\n public listeners: Emitter<Events>['listeners'] = (type) => {\n return this.emitter.listeners(type)\n }\n\n public listenerCount: Emitter<Events>['listenerCount'] = (type) => {\n return this.emitter.listenerCount(type)\n }\n\n public removeListener: Emitter<Events>['removeListener'] = (\n type,\n listener\n ) => {\n return this.emitter.removeListener(type, listener)\n }\n\n public removeAllListeners: Emitter<Events>['removeAllListeners'] = (type) => {\n this.logger.info('removeAllListeners %o', { eventType: type ?? '*' })\n return this.emitter.removeAllListeners(type)\n }\n\n #getLoggerNamespace(): string {\n const symbolDescription = this.constructor.symbol?.description\n\n if (symbolDescription) {\n return symbolDescription.replace(/-interceptor$/, '')\n }\n\n return this.constructor.name.replace(/Interceptor$/, '')\n }\n}\n","/**\n * Generate a random ID string to represent a request.\n * @example\n * createRequestId()\n * // \"f774b6c9c600f\"\n */\nexport function createRequestId(): string {\n return Math.random().toString(16).slice(2)\n}\n"],"mappings":"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"}
|