@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.
Files changed (35) hide show
  1. package/lib/browser/create-request-id-Bk5YX1AM.js +720 -0
  2. package/lib/browser/create-request-id-Bk5YX1AM.js.map +1 -0
  3. package/lib/browser/{fetch-utils-zxA_SD66.js → fetch-utils-CUOrwEQf.js} +2 -2
  4. package/lib/browser/{fetch-utils-zxA_SD66.js.map → fetch-utils-CUOrwEQf.js.map} +1 -1
  5. package/lib/browser/{handle-request-CIOa9O-N.js → handle-request-CqVvBhcw.js} +3 -3
  6. package/lib/browser/{handle-request-CIOa9O-N.js.map → handle-request-CqVvBhcw.js.map} +1 -1
  7. package/lib/browser/index.js +2 -2
  8. package/lib/browser/interceptors/WebSocket/index.js +1 -1
  9. package/lib/browser/interceptors/XMLHttpRequest/web.js +1 -1
  10. package/lib/browser/interceptors/fetch/web.js +1 -1
  11. package/lib/browser/presets/browser.js +2 -2
  12. package/lib/browser/{web-C2oKsT3Q.js → web-BcKSQghf.js} +4 -4
  13. package/lib/browser/{web-C2oKsT3Q.js.map → web-BcKSQghf.js.map} +1 -1
  14. package/lib/browser/{web-BjVDkiX0.js → web-CdcYFjgm.js} +4 -4
  15. package/lib/browser/{web-BjVDkiX0.js.map → web-CdcYFjgm.js.map} +1 -1
  16. package/lib/node/{has-configurable-global-CJCh_g1I.js → has-configurable-global-IskkJJOL.js} +2 -2
  17. package/lib/node/{has-configurable-global-CJCh_g1I.js.map → has-configurable-global-IskkJJOL.js.map} +1 -1
  18. package/lib/node/interceptors/ClientRequest/index.js +2 -2
  19. package/lib/node/interceptors/XMLHttpRequest/node.js +3 -3
  20. package/lib/node/interceptors/fetch/node.js +3 -3
  21. package/lib/node/interceptors/http/index.js +1 -1
  22. package/lib/node/interceptors/net/index.d.ts +27 -1
  23. package/lib/node/interceptors/net/index.js +1 -1
  24. package/lib/node/{net-DtMnyEeg.js → net-9sRKnjIG.js} +115 -9
  25. package/lib/node/net-9sRKnjIG.js.map +1 -0
  26. package/lib/node/remote-http-interceptor.js +1 -1
  27. package/lib/node/{source-lP0yyEtA.js → source-BUVce4Q1.js} +17 -4
  28. package/lib/node/{source-lP0yyEtA.js.map → source-BUVce4Q1.js.map} +1 -1
  29. package/package.json +1 -1
  30. package/src/interceptors/http/source.ts +26 -3
  31. package/src/interceptors/net/index.ts +16 -15
  32. package/src/interceptors/net/socket-controller.ts +156 -2
  33. package/lib/browser/create-request-id-DlEd4GOA.js +0 -184
  34. package/lib/browser/create-request-id-DlEd4GOA.js.map +0 -1
  35. 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(): void {
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.createConnection()
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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"}