msw 2.9.0 → 2.10.1
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/index.js +8 -8
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/index.mjs +8 -8
- package/lib/browser/index.mjs.map +1 -1
- package/lib/core/handlers/WebSocketHandler.d.mts +10 -7
- package/lib/core/handlers/WebSocketHandler.d.ts +10 -7
- package/lib/core/handlers/WebSocketHandler.js +12 -8
- package/lib/core/handlers/WebSocketHandler.js.map +1 -1
- package/lib/core/handlers/WebSocketHandler.mjs +12 -8
- package/lib/core/handlers/WebSocketHandler.mjs.map +1 -1
- package/lib/core/ws/WebSocketClientManager.d.mts +4 -2
- package/lib/core/ws/WebSocketClientManager.d.ts +4 -2
- package/lib/core/ws/WebSocketClientManager.js +10 -0
- package/lib/core/ws/WebSocketClientManager.js.map +1 -1
- package/lib/core/ws/WebSocketClientManager.mjs +10 -0
- package/lib/core/ws/WebSocketClientManager.mjs.map +1 -1
- package/lib/core/ws/handleWebSocketEvent.js +26 -39
- package/lib/core/ws/handleWebSocketEvent.js.map +1 -1
- package/lib/core/ws/handleWebSocketEvent.mjs +26 -39
- package/lib/core/ws/handleWebSocketEvent.mjs.map +1 -1
- package/lib/core/ws/utils/getMessageLength.js.map +1 -1
- package/lib/core/ws/utils/getMessageLength.mjs.map +1 -1
- package/lib/core/ws/utils/getPublicData.js.map +1 -1
- package/lib/core/ws/utils/getPublicData.mjs.map +1 -1
- package/lib/iife/index.js +57 -54
- package/lib/iife/index.js.map +1 -1
- package/lib/mockServiceWorker.js +1 -1
- package/package.json +3 -3
- package/src/core/handlers/WebSocketHandler.test.ts +12 -61
- package/src/core/handlers/WebSocketHandler.ts +27 -15
- package/src/core/ws/WebSocketClientManager.ts +33 -3
- package/src/core/ws/handleWebSocketEvent.ts +36 -55
- package/src/core/ws/utils/getMessageLength.ts +1 -1
- package/src/core/ws/utils/getPublicData.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Emitter } from 'strict-event-emitter';
|
|
2
|
-
import { WebSocketConnectionData } from '@mswjs/interceptors/WebSocket';
|
|
2
|
+
import { WebSocketClientConnectionProtocol, WebSocketServerConnectionProtocol, WebSocketConnectionData } from '@mswjs/interceptors/WebSocket';
|
|
3
3
|
import { PathParams, Path, Match } from '../utils/matching/matchRequestUrl.mjs';
|
|
4
4
|
|
|
5
5
|
type WebSocketHandlerParsedResult = {
|
|
@@ -8,11 +8,13 @@ type WebSocketHandlerParsedResult = {
|
|
|
8
8
|
type WebSocketHandlerEventMap = {
|
|
9
9
|
connection: [args: WebSocketHandlerConnection];
|
|
10
10
|
};
|
|
11
|
-
interface WebSocketHandlerConnection
|
|
11
|
+
interface WebSocketHandlerConnection {
|
|
12
|
+
client: WebSocketClientConnectionProtocol;
|
|
13
|
+
server: WebSocketServerConnectionProtocol;
|
|
14
|
+
info: WebSocketConnectionData['info'];
|
|
12
15
|
params: PathParams;
|
|
13
16
|
}
|
|
14
17
|
declare const kEmitter: unique symbol;
|
|
15
|
-
declare const kDispatchEvent: unique symbol;
|
|
16
18
|
declare const kSender: unique symbol;
|
|
17
19
|
declare class WebSocketHandler {
|
|
18
20
|
private readonly url;
|
|
@@ -22,13 +24,14 @@ declare class WebSocketHandler {
|
|
|
22
24
|
protected [kEmitter]: Emitter<WebSocketHandlerEventMap>;
|
|
23
25
|
constructor(url: Path);
|
|
24
26
|
parse(args: {
|
|
25
|
-
|
|
27
|
+
url: URL;
|
|
26
28
|
}): WebSocketHandlerParsedResult;
|
|
27
29
|
predicate(args: {
|
|
28
|
-
|
|
30
|
+
url: URL;
|
|
29
31
|
parsedResult: WebSocketHandlerParsedResult;
|
|
30
32
|
}): boolean;
|
|
31
|
-
|
|
33
|
+
run(connection: Omit<WebSocketHandlerConnection, 'params'>): Promise<boolean>;
|
|
34
|
+
protected connect(connection: WebSocketHandlerConnection): boolean;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
export { WebSocketHandler, type WebSocketHandlerConnection, type WebSocketHandlerEventMap,
|
|
37
|
+
export { WebSocketHandler, type WebSocketHandlerConnection, type WebSocketHandlerEventMap, kEmitter, kSender };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Emitter } from 'strict-event-emitter';
|
|
2
|
-
import { WebSocketConnectionData } from '@mswjs/interceptors/WebSocket';
|
|
2
|
+
import { WebSocketClientConnectionProtocol, WebSocketServerConnectionProtocol, WebSocketConnectionData } from '@mswjs/interceptors/WebSocket';
|
|
3
3
|
import { PathParams, Path, Match } from '../utils/matching/matchRequestUrl.js';
|
|
4
4
|
|
|
5
5
|
type WebSocketHandlerParsedResult = {
|
|
@@ -8,11 +8,13 @@ type WebSocketHandlerParsedResult = {
|
|
|
8
8
|
type WebSocketHandlerEventMap = {
|
|
9
9
|
connection: [args: WebSocketHandlerConnection];
|
|
10
10
|
};
|
|
11
|
-
interface WebSocketHandlerConnection
|
|
11
|
+
interface WebSocketHandlerConnection {
|
|
12
|
+
client: WebSocketClientConnectionProtocol;
|
|
13
|
+
server: WebSocketServerConnectionProtocol;
|
|
14
|
+
info: WebSocketConnectionData['info'];
|
|
12
15
|
params: PathParams;
|
|
13
16
|
}
|
|
14
17
|
declare const kEmitter: unique symbol;
|
|
15
|
-
declare const kDispatchEvent: unique symbol;
|
|
16
18
|
declare const kSender: unique symbol;
|
|
17
19
|
declare class WebSocketHandler {
|
|
18
20
|
private readonly url;
|
|
@@ -22,13 +24,14 @@ declare class WebSocketHandler {
|
|
|
22
24
|
protected [kEmitter]: Emitter<WebSocketHandlerEventMap>;
|
|
23
25
|
constructor(url: Path);
|
|
24
26
|
parse(args: {
|
|
25
|
-
|
|
27
|
+
url: URL;
|
|
26
28
|
}): WebSocketHandlerParsedResult;
|
|
27
29
|
predicate(args: {
|
|
28
|
-
|
|
30
|
+
url: URL;
|
|
29
31
|
parsedResult: WebSocketHandlerParsedResult;
|
|
30
32
|
}): boolean;
|
|
31
|
-
|
|
33
|
+
run(connection: Omit<WebSocketHandlerConnection, 'params'>): Promise<boolean>;
|
|
34
|
+
protected connect(connection: WebSocketHandlerConnection): boolean;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
export { WebSocketHandler, type WebSocketHandlerConnection, type WebSocketHandlerEventMap,
|
|
37
|
+
export { WebSocketHandler, type WebSocketHandlerConnection, type WebSocketHandlerEventMap, kEmitter, kSender };
|
|
@@ -19,7 +19,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var WebSocketHandler_exports = {};
|
|
20
20
|
__export(WebSocketHandler_exports, {
|
|
21
21
|
WebSocketHandler: () => WebSocketHandler,
|
|
22
|
-
kDispatchEvent: () => kDispatchEvent,
|
|
23
22
|
kEmitter: () => kEmitter,
|
|
24
23
|
kSender: () => kSender
|
|
25
24
|
});
|
|
@@ -29,7 +28,6 @@ var import_interceptors = require("@mswjs/interceptors");
|
|
|
29
28
|
var import_matchRequestUrl = require("../utils/matching/matchRequestUrl");
|
|
30
29
|
var import_getCallFrame = require("../utils/internal/getCallFrame");
|
|
31
30
|
const kEmitter = Symbol("kEmitter");
|
|
32
|
-
const kDispatchEvent = Symbol("kDispatchEvent");
|
|
33
31
|
const kSender = Symbol("kSender");
|
|
34
32
|
const kStopPropagationPatched = Symbol("kStopPropagationPatched");
|
|
35
33
|
const KOnStopPropagation = Symbol("KOnStopPropagation");
|
|
@@ -46,8 +44,7 @@ class WebSocketHandler {
|
|
|
46
44
|
callFrame;
|
|
47
45
|
[kEmitter];
|
|
48
46
|
parse(args) {
|
|
49
|
-
const
|
|
50
|
-
const { url: clientUrl } = connection.client;
|
|
47
|
+
const clientUrl = new URL(args.url);
|
|
51
48
|
clientUrl.pathname = clientUrl.pathname.replace(/^\/socket.io\//, "/");
|
|
52
49
|
const match = (0, import_matchRequestUrl.matchRequestUrl)(clientUrl, this.url);
|
|
53
50
|
return {
|
|
@@ -57,13 +54,20 @@ class WebSocketHandler {
|
|
|
57
54
|
predicate(args) {
|
|
58
55
|
return args.parsedResult.match.matches;
|
|
59
56
|
}
|
|
60
|
-
async
|
|
61
|
-
const parsedResult = this.parse({
|
|
62
|
-
|
|
57
|
+
async run(connection) {
|
|
58
|
+
const parsedResult = this.parse({
|
|
59
|
+
url: connection.client.url
|
|
60
|
+
});
|
|
61
|
+
if (!this.predicate({ url: connection.client.url, parsedResult })) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
63
64
|
const resolvedConnection = {
|
|
64
65
|
...connection,
|
|
65
66
|
params: parsedResult.match.params || {}
|
|
66
67
|
};
|
|
68
|
+
return this.connect(resolvedConnection);
|
|
69
|
+
}
|
|
70
|
+
connect(connection) {
|
|
67
71
|
connection.client.addEventListener(
|
|
68
72
|
"message",
|
|
69
73
|
createStopPropagationListener(this)
|
|
@@ -88,7 +92,7 @@ class WebSocketHandler {
|
|
|
88
92
|
"close",
|
|
89
93
|
createStopPropagationListener(this)
|
|
90
94
|
);
|
|
91
|
-
this[kEmitter].emit("connection",
|
|
95
|
+
return this[kEmitter].emit("connection", connection);
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
function createStopPropagationListener(handler) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/handlers/WebSocketHandler.ts"],"sourcesContent":["import { Emitter } from 'strict-event-emitter'\nimport { createRequestId } from '@mswjs/interceptors'\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../../src/core/handlers/WebSocketHandler.ts"],"sourcesContent":["import { Emitter } from 'strict-event-emitter'\nimport { createRequestId } from '@mswjs/interceptors'\nimport type {\n WebSocketClientConnectionProtocol,\n WebSocketConnectionData,\n WebSocketServerConnectionProtocol,\n} from '@mswjs/interceptors/WebSocket'\nimport {\n type Match,\n type Path,\n type PathParams,\n matchRequestUrl,\n} from '../utils/matching/matchRequestUrl'\nimport { getCallFrame } from '../utils/internal/getCallFrame'\nimport type { HandlerKind } from './common'\n\ntype WebSocketHandlerParsedResult = {\n match: Match\n}\n\nexport type WebSocketHandlerEventMap = {\n connection: [args: WebSocketHandlerConnection]\n}\n\nexport interface WebSocketHandlerConnection {\n client: WebSocketClientConnectionProtocol\n server: WebSocketServerConnectionProtocol\n info: WebSocketConnectionData['info']\n params: PathParams\n}\n\nexport const kEmitter = Symbol('kEmitter')\nexport const kSender = Symbol('kSender')\nconst kStopPropagationPatched = Symbol('kStopPropagationPatched')\nconst KOnStopPropagation = Symbol('KOnStopPropagation')\n\nexport class WebSocketHandler {\n private readonly __kind: HandlerKind\n\n public id: string\n public callFrame?: string\n\n protected [kEmitter]: Emitter<WebSocketHandlerEventMap>\n\n constructor(private readonly url: Path) {\n this.id = createRequestId()\n\n this[kEmitter] = new Emitter()\n this.callFrame = getCallFrame(new Error())\n this.__kind = 'EventHandler'\n }\n\n public parse(args: { url: URL }): WebSocketHandlerParsedResult {\n const clientUrl = new URL(args.url)\n\n /**\n * @note Remove the Socket.IO path prefix from the WebSocket\n * client URL. This is an exception to keep the users from\n * including the implementation details in their handlers.\n */\n clientUrl.pathname = clientUrl.pathname.replace(/^\\/socket.io\\//, '/')\n\n const match = matchRequestUrl(clientUrl, this.url)\n\n return {\n match,\n }\n }\n\n public predicate(args: {\n url: URL\n parsedResult: WebSocketHandlerParsedResult\n }): boolean {\n return args.parsedResult.match.matches\n }\n\n public async run(\n connection: Omit<WebSocketHandlerConnection, 'params'>,\n ): Promise<boolean> {\n const parsedResult = this.parse({\n url: connection.client.url,\n })\n\n if (!this.predicate({ url: connection.client.url, parsedResult })) {\n return false\n }\n\n const resolvedConnection: WebSocketHandlerConnection = {\n ...connection,\n params: parsedResult.match.params || {},\n }\n\n return this.connect(resolvedConnection)\n }\n\n protected connect(connection: WebSocketHandlerConnection): boolean {\n // Support `event.stopPropagation()` for various client/server events.\n connection.client.addEventListener(\n 'message',\n createStopPropagationListener(this),\n )\n connection.client.addEventListener(\n 'close',\n createStopPropagationListener(this),\n )\n\n connection.server.addEventListener(\n 'open',\n createStopPropagationListener(this),\n )\n connection.server.addEventListener(\n 'message',\n createStopPropagationListener(this),\n )\n connection.server.addEventListener(\n 'error',\n createStopPropagationListener(this),\n )\n connection.server.addEventListener(\n 'close',\n createStopPropagationListener(this),\n )\n\n // Emit the connection event on the handler.\n // This is what the developer adds listeners for.\n return this[kEmitter].emit('connection', connection)\n }\n}\n\nfunction createStopPropagationListener(handler: WebSocketHandler) {\n return function stopPropagationListener(event: Event) {\n const propagationStoppedAt = Reflect.get(event, 'kPropagationStoppedAt') as\n | string\n | undefined\n\n if (propagationStoppedAt && handler.id !== propagationStoppedAt) {\n event.stopImmediatePropagation()\n return\n }\n\n Object.defineProperty(event, KOnStopPropagation, {\n value(this: WebSocketHandler) {\n Object.defineProperty(event, 'kPropagationStoppedAt', {\n value: handler.id,\n })\n },\n configurable: true,\n })\n\n // Since the same event instance is shared between all client/server objects,\n // make sure to patch its `stopPropagation` method only once.\n if (!Reflect.get(event, kStopPropagationPatched)) {\n event.stopPropagation = new Proxy(event.stopPropagation, {\n apply: (target, thisArg, args) => {\n Reflect.get(event, KOnStopPropagation)?.call(handler)\n return Reflect.apply(target, thisArg, args)\n },\n })\n\n Object.defineProperty(event, kStopPropagationPatched, {\n value: true,\n // If something else attempts to redefine this, throw.\n configurable: false,\n })\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
} from '../utils/matching/matchRequestUrl.mjs';
|
|
6
6
|
import { getCallFrame } from '../utils/internal/getCallFrame.mjs';
|
|
7
7
|
const kEmitter = Symbol("kEmitter");
|
|
8
|
-
const kDispatchEvent = Symbol("kDispatchEvent");
|
|
9
8
|
const kSender = Symbol("kSender");
|
|
10
9
|
const kStopPropagationPatched = Symbol("kStopPropagationPatched");
|
|
11
10
|
const KOnStopPropagation = Symbol("KOnStopPropagation");
|
|
@@ -22,8 +21,7 @@ class WebSocketHandler {
|
|
|
22
21
|
callFrame;
|
|
23
22
|
[kEmitter];
|
|
24
23
|
parse(args) {
|
|
25
|
-
const
|
|
26
|
-
const { url: clientUrl } = connection.client;
|
|
24
|
+
const clientUrl = new URL(args.url);
|
|
27
25
|
clientUrl.pathname = clientUrl.pathname.replace(/^\/socket.io\//, "/");
|
|
28
26
|
const match = matchRequestUrl(clientUrl, this.url);
|
|
29
27
|
return {
|
|
@@ -33,13 +31,20 @@ class WebSocketHandler {
|
|
|
33
31
|
predicate(args) {
|
|
34
32
|
return args.parsedResult.match.matches;
|
|
35
33
|
}
|
|
36
|
-
async
|
|
37
|
-
const parsedResult = this.parse({
|
|
38
|
-
|
|
34
|
+
async run(connection) {
|
|
35
|
+
const parsedResult = this.parse({
|
|
36
|
+
url: connection.client.url
|
|
37
|
+
});
|
|
38
|
+
if (!this.predicate({ url: connection.client.url, parsedResult })) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
39
41
|
const resolvedConnection = {
|
|
40
42
|
...connection,
|
|
41
43
|
params: parsedResult.match.params || {}
|
|
42
44
|
};
|
|
45
|
+
return this.connect(resolvedConnection);
|
|
46
|
+
}
|
|
47
|
+
connect(connection) {
|
|
43
48
|
connection.client.addEventListener(
|
|
44
49
|
"message",
|
|
45
50
|
createStopPropagationListener(this)
|
|
@@ -64,7 +69,7 @@ class WebSocketHandler {
|
|
|
64
69
|
"close",
|
|
65
70
|
createStopPropagationListener(this)
|
|
66
71
|
);
|
|
67
|
-
this[kEmitter].emit("connection",
|
|
72
|
+
return this[kEmitter].emit("connection", connection);
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
function createStopPropagationListener(handler) {
|
|
@@ -99,7 +104,6 @@ function createStopPropagationListener(handler) {
|
|
|
99
104
|
}
|
|
100
105
|
export {
|
|
101
106
|
WebSocketHandler,
|
|
102
|
-
kDispatchEvent,
|
|
103
107
|
kEmitter,
|
|
104
108
|
kSender
|
|
105
109
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/handlers/WebSocketHandler.ts"],"sourcesContent":["import { Emitter } from 'strict-event-emitter'\nimport { createRequestId } from '@mswjs/interceptors'\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../../src/core/handlers/WebSocketHandler.ts"],"sourcesContent":["import { Emitter } from 'strict-event-emitter'\nimport { createRequestId } from '@mswjs/interceptors'\nimport type {\n WebSocketClientConnectionProtocol,\n WebSocketConnectionData,\n WebSocketServerConnectionProtocol,\n} from '@mswjs/interceptors/WebSocket'\nimport {\n type Match,\n type Path,\n type PathParams,\n matchRequestUrl,\n} from '../utils/matching/matchRequestUrl'\nimport { getCallFrame } from '../utils/internal/getCallFrame'\nimport type { HandlerKind } from './common'\n\ntype WebSocketHandlerParsedResult = {\n match: Match\n}\n\nexport type WebSocketHandlerEventMap = {\n connection: [args: WebSocketHandlerConnection]\n}\n\nexport interface WebSocketHandlerConnection {\n client: WebSocketClientConnectionProtocol\n server: WebSocketServerConnectionProtocol\n info: WebSocketConnectionData['info']\n params: PathParams\n}\n\nexport const kEmitter = Symbol('kEmitter')\nexport const kSender = Symbol('kSender')\nconst kStopPropagationPatched = Symbol('kStopPropagationPatched')\nconst KOnStopPropagation = Symbol('KOnStopPropagation')\n\nexport class WebSocketHandler {\n private readonly __kind: HandlerKind\n\n public id: string\n public callFrame?: string\n\n protected [kEmitter]: Emitter<WebSocketHandlerEventMap>\n\n constructor(private readonly url: Path) {\n this.id = createRequestId()\n\n this[kEmitter] = new Emitter()\n this.callFrame = getCallFrame(new Error())\n this.__kind = 'EventHandler'\n }\n\n public parse(args: { url: URL }): WebSocketHandlerParsedResult {\n const clientUrl = new URL(args.url)\n\n /**\n * @note Remove the Socket.IO path prefix from the WebSocket\n * client URL. This is an exception to keep the users from\n * including the implementation details in their handlers.\n */\n clientUrl.pathname = clientUrl.pathname.replace(/^\\/socket.io\\//, '/')\n\n const match = matchRequestUrl(clientUrl, this.url)\n\n return {\n match,\n }\n }\n\n public predicate(args: {\n url: URL\n parsedResult: WebSocketHandlerParsedResult\n }): boolean {\n return args.parsedResult.match.matches\n }\n\n public async run(\n connection: Omit<WebSocketHandlerConnection, 'params'>,\n ): Promise<boolean> {\n const parsedResult = this.parse({\n url: connection.client.url,\n })\n\n if (!this.predicate({ url: connection.client.url, parsedResult })) {\n return false\n }\n\n const resolvedConnection: WebSocketHandlerConnection = {\n ...connection,\n params: parsedResult.match.params || {},\n }\n\n return this.connect(resolvedConnection)\n }\n\n protected connect(connection: WebSocketHandlerConnection): boolean {\n // Support `event.stopPropagation()` for various client/server events.\n connection.client.addEventListener(\n 'message',\n createStopPropagationListener(this),\n )\n connection.client.addEventListener(\n 'close',\n createStopPropagationListener(this),\n )\n\n connection.server.addEventListener(\n 'open',\n createStopPropagationListener(this),\n )\n connection.server.addEventListener(\n 'message',\n createStopPropagationListener(this),\n )\n connection.server.addEventListener(\n 'error',\n createStopPropagationListener(this),\n )\n connection.server.addEventListener(\n 'close',\n createStopPropagationListener(this),\n )\n\n // Emit the connection event on the handler.\n // This is what the developer adds listeners for.\n return this[kEmitter].emit('connection', connection)\n }\n}\n\nfunction createStopPropagationListener(handler: WebSocketHandler) {\n return function stopPropagationListener(event: Event) {\n const propagationStoppedAt = Reflect.get(event, 'kPropagationStoppedAt') as\n | string\n | undefined\n\n if (propagationStoppedAt && handler.id !== propagationStoppedAt) {\n event.stopImmediatePropagation()\n return\n }\n\n Object.defineProperty(event, KOnStopPropagation, {\n value(this: WebSocketHandler) {\n Object.defineProperty(event, 'kPropagationStoppedAt', {\n value: handler.id,\n })\n },\n configurable: true,\n })\n\n // Since the same event instance is shared between all client/server objects,\n // make sure to patch its `stopPropagation` method only once.\n if (!Reflect.get(event, kStopPropagationPatched)) {\n event.stopPropagation = new Proxy(event.stopPropagation, {\n apply: (target, thisArg, args) => {\n Reflect.get(event, KOnStopPropagation)?.call(handler)\n return Reflect.apply(target, thisArg, args)\n },\n })\n\n Object.defineProperty(event, kStopPropagationPatched, {\n value: true,\n // If something else attempts to redefine this, throw.\n configurable: false,\n })\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebSocketData, WebSocketClientConnectionProtocol,
|
|
1
|
+
import { WebSocketData, WebSocketClientConnectionProtocol, WebSocketClientEventMap } from '@mswjs/interceptors/WebSocket';
|
|
2
2
|
|
|
3
3
|
type WebSocketBroadcastChannelMessage = {
|
|
4
4
|
type: 'extraneous:send';
|
|
@@ -42,7 +42,7 @@ declare class WebSocketClientManager {
|
|
|
42
42
|
* connection object because `addConnection()` is called only
|
|
43
43
|
* for the opened connections in the same runtime.
|
|
44
44
|
*/
|
|
45
|
-
addConnection(client:
|
|
45
|
+
addConnection(client: WebSocketClientConnectionProtocol): Promise<void>;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* A wrapper class to operate with WebSocket client connections
|
|
@@ -58,6 +58,8 @@ declare class WebSocketRemoteClientConnection implements WebSocketClientConnecti
|
|
|
58
58
|
constructor(id: string, url: URL, channel: BroadcastChannel);
|
|
59
59
|
send(data: WebSocketData): void;
|
|
60
60
|
close(code?: number | undefined, reason?: string | undefined): void;
|
|
61
|
+
addEventListener<EventType extends keyof WebSocketClientEventMap>(_type: EventType, _listener: (this: WebSocket, event: WebSocketClientEventMap[EventType]) => void, _options?: AddEventListenerOptions | boolean): void;
|
|
62
|
+
removeEventListener<EventType extends keyof WebSocketClientEventMap>(_event: EventType, _listener: (this: WebSocket, event: WebSocketClientEventMap[EventType]) => void, _options?: EventListenerOptions | boolean): void;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
export { type WebSocketBroadcastChannelMessage, WebSocketClientManager, WebSocketRemoteClientConnection };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebSocketData, WebSocketClientConnectionProtocol,
|
|
1
|
+
import { WebSocketData, WebSocketClientConnectionProtocol, WebSocketClientEventMap } from '@mswjs/interceptors/WebSocket';
|
|
2
2
|
|
|
3
3
|
type WebSocketBroadcastChannelMessage = {
|
|
4
4
|
type: 'extraneous:send';
|
|
@@ -42,7 +42,7 @@ declare class WebSocketClientManager {
|
|
|
42
42
|
* connection object because `addConnection()` is called only
|
|
43
43
|
* for the opened connections in the same runtime.
|
|
44
44
|
*/
|
|
45
|
-
addConnection(client:
|
|
45
|
+
addConnection(client: WebSocketClientConnectionProtocol): Promise<void>;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* A wrapper class to operate with WebSocket client connections
|
|
@@ -58,6 +58,8 @@ declare class WebSocketRemoteClientConnection implements WebSocketClientConnecti
|
|
|
58
58
|
constructor(id: string, url: URL, channel: BroadcastChannel);
|
|
59
59
|
send(data: WebSocketData): void;
|
|
60
60
|
close(code?: number | undefined, reason?: string | undefined): void;
|
|
61
|
+
addEventListener<EventType extends keyof WebSocketClientEventMap>(_type: EventType, _listener: (this: WebSocket, event: WebSocketClientEventMap[EventType]) => void, _options?: AddEventListenerOptions | boolean): void;
|
|
62
|
+
removeEventListener<EventType extends keyof WebSocketClientEventMap>(_event: EventType, _listener: (this: WebSocket, event: WebSocketClientEventMap[EventType]) => void, _options?: EventListenerOptions | boolean): void;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
export { type WebSocketBroadcastChannelMessage, WebSocketClientManager, WebSocketRemoteClientConnection };
|
|
@@ -145,5 +145,15 @@ class WebSocketRemoteClientConnection {
|
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
|
+
addEventListener(_type, _listener, _options) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
"WebSocketRemoteClientConnection.addEventListener is not supported"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
removeEventListener(_event, _listener, _options) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
"WebSocketRemoteClientConnection.removeEventListener is not supported"
|
|
156
|
+
);
|
|
157
|
+
}
|
|
148
158
|
}
|
|
149
159
|
//# sourceMappingURL=WebSocketClientManager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/ws/WebSocketClientManager.ts"],"sourcesContent":["import type {\n WebSocketData,\n
|
|
1
|
+
{"version":3,"sources":["../../../src/core/ws/WebSocketClientManager.ts"],"sourcesContent":["import type {\n WebSocketData,\n WebSocketClientConnectionProtocol,\n WebSocketClientEventMap,\n} from '@mswjs/interceptors/WebSocket'\nimport { WebSocketClientStore } from './WebSocketClientStore'\nimport { WebSocketMemoryClientStore } from './WebSocketMemoryClientStore'\nimport { WebSocketIndexedDBClientStore } from './WebSocketIndexedDBClientStore'\n\nexport type WebSocketBroadcastChannelMessage =\n | {\n type: 'extraneous:send'\n payload: {\n clientId: string\n data: WebSocketData\n }\n }\n | {\n type: 'extraneous:close'\n payload: {\n clientId: string\n code?: number\n reason?: string\n }\n }\n\n/**\n * A manager responsible for accumulating WebSocket client\n * connections across different browser runtimes.\n */\nexport class WebSocketClientManager {\n private store: WebSocketClientStore\n private runtimeClients: Map<string, WebSocketClientConnectionProtocol>\n private allClients: Set<WebSocketClientConnectionProtocol>\n\n constructor(private channel: BroadcastChannel) {\n // Store the clients in the IndexedDB in the browser,\n // otherwise, store the clients in memory.\n this.store =\n typeof indexedDB !== 'undefined'\n ? new WebSocketIndexedDBClientStore()\n : new WebSocketMemoryClientStore()\n\n this.runtimeClients = new Map()\n this.allClients = new Set()\n\n this.channel.addEventListener('message', (message) => {\n if (message.data?.type === 'db:update') {\n this.flushDatabaseToMemory()\n }\n })\n\n if (typeof window !== 'undefined') {\n window.addEventListener('message', async (message) => {\n if (message.data?.type === 'msw/worker:stop') {\n await this.removeRuntimeClients()\n }\n })\n }\n }\n\n private async flushDatabaseToMemory() {\n const storedClients = await this.store.getAll()\n\n this.allClients = new Set(\n storedClients.map((client) => {\n const runtimeClient = this.runtimeClients.get(client.id)\n\n /**\n * @note For clients originating in this runtime, use their\n * direct references. No need to wrap them in a remote connection.\n */\n if (runtimeClient) {\n return runtimeClient\n }\n\n return new WebSocketRemoteClientConnection(\n client.id,\n new URL(client.url),\n this.channel,\n )\n }),\n )\n }\n\n private async removeRuntimeClients(): Promise<void> {\n await this.store.deleteMany(Array.from(this.runtimeClients.keys()))\n this.runtimeClients.clear()\n await this.flushDatabaseToMemory()\n this.notifyOthersAboutDatabaseUpdate()\n }\n\n /**\n * All active WebSocket client connections.\n */\n get clients(): Set<WebSocketClientConnectionProtocol> {\n return this.allClients\n }\n\n /**\n * Notify other runtimes about the database update\n * using the shared `BroadcastChannel` instance.\n */\n private notifyOthersAboutDatabaseUpdate(): void {\n this.channel.postMessage({ type: 'db:update' })\n }\n\n private async addClient(\n client: WebSocketClientConnectionProtocol,\n ): Promise<void> {\n await this.store.add(client)\n // Sync the in-memory clients in this runtime with the\n // updated database. This pulls in all the stored clients.\n await this.flushDatabaseToMemory()\n this.notifyOthersAboutDatabaseUpdate()\n }\n\n /**\n * Adds the given `WebSocket` client connection to the set\n * of all connections. The given connection is always the complete\n * connection object because `addConnection()` is called only\n * for the opened connections in the same runtime.\n */\n public async addConnection(\n client: WebSocketClientConnectionProtocol,\n ): Promise<void> {\n // Store this client in the map of clients created in this runtime.\n // This way, the manager can distinguish between this runtime clients\n // and extraneous runtime clients when synchronizing clients storage.\n this.runtimeClients.set(client.id, client)\n\n // Add the new client to the storage.\n await this.addClient(client)\n\n // Handle the incoming BroadcastChannel messages from other runtimes\n // that attempt to control this runtime (via a remote connection wrapper).\n // E.g. another runtime calling `client.send()` for the client in this runtime.\n const handleExtraneousMessage = (\n message: MessageEvent<WebSocketBroadcastChannelMessage>,\n ) => {\n const { type, payload } = message.data\n\n // Ignore broadcasted messages for other clients.\n if (\n typeof payload === 'object' &&\n 'clientId' in payload &&\n payload.clientId !== client.id\n ) {\n return\n }\n\n switch (type) {\n case 'extraneous:send': {\n client.send(payload.data)\n break\n }\n\n case 'extraneous:close': {\n client.close(payload.code, payload.reason)\n break\n }\n }\n }\n\n const abortController = new AbortController()\n\n this.channel.addEventListener('message', handleExtraneousMessage, {\n signal: abortController.signal,\n })\n\n // Once closed, this connection cannot be operated on.\n // This must include the extraneous runtimes as well.\n client.addEventListener('close', () => abortController.abort(), {\n once: true,\n })\n }\n}\n\n/**\n * A wrapper class to operate with WebSocket client connections\n * from other runtimes. This class maintains 1-1 public API\n * compatibility to the `WebSocketClientConnection` but relies\n * on the given `BroadcastChannel` to communicate instructions\n * with the client connections from other runtimes.\n */\nexport class WebSocketRemoteClientConnection\n implements WebSocketClientConnectionProtocol\n{\n constructor(\n public readonly id: string,\n public readonly url: URL,\n private channel: BroadcastChannel,\n ) {}\n\n send(data: WebSocketData): void {\n this.channel.postMessage({\n type: 'extraneous:send',\n payload: {\n clientId: this.id,\n data,\n },\n } as WebSocketBroadcastChannelMessage)\n }\n\n close(code?: number | undefined, reason?: string | undefined): void {\n this.channel.postMessage({\n type: 'extraneous:close',\n payload: {\n clientId: this.id,\n code,\n reason,\n },\n } as WebSocketBroadcastChannelMessage)\n }\n\n addEventListener<EventType extends keyof WebSocketClientEventMap>(\n _type: EventType,\n _listener: (\n this: WebSocket,\n event: WebSocketClientEventMap[EventType],\n ) => void,\n _options?: AddEventListenerOptions | boolean,\n ): void {\n throw new Error(\n 'WebSocketRemoteClientConnection.addEventListener is not supported',\n )\n }\n\n removeEventListener<EventType extends keyof WebSocketClientEventMap>(\n _event: EventType,\n _listener: (\n this: WebSocket,\n event: WebSocketClientEventMap[EventType],\n ) => void,\n _options?: EventListenerOptions | boolean,\n ): void {\n throw new Error(\n 'WebSocketRemoteClientConnection.removeEventListener is not supported',\n )\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -121,6 +121,16 @@ class WebSocketRemoteClientConnection {
|
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
123
|
}
|
|
124
|
+
addEventListener(_type, _listener, _options) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
"WebSocketRemoteClientConnection.addEventListener is not supported"
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
removeEventListener(_event, _listener, _options) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
"WebSocketRemoteClientConnection.removeEventListener is not supported"
|
|
132
|
+
);
|
|
133
|
+
}
|
|
124
134
|
}
|
|
125
135
|
export {
|
|
126
136
|
WebSocketClientManager,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/ws/WebSocketClientManager.ts"],"sourcesContent":["import type {\n WebSocketData,\n
|
|
1
|
+
{"version":3,"sources":["../../../src/core/ws/WebSocketClientManager.ts"],"sourcesContent":["import type {\n WebSocketData,\n WebSocketClientConnectionProtocol,\n WebSocketClientEventMap,\n} from '@mswjs/interceptors/WebSocket'\nimport { WebSocketClientStore } from './WebSocketClientStore'\nimport { WebSocketMemoryClientStore } from './WebSocketMemoryClientStore'\nimport { WebSocketIndexedDBClientStore } from './WebSocketIndexedDBClientStore'\n\nexport type WebSocketBroadcastChannelMessage =\n | {\n type: 'extraneous:send'\n payload: {\n clientId: string\n data: WebSocketData\n }\n }\n | {\n type: 'extraneous:close'\n payload: {\n clientId: string\n code?: number\n reason?: string\n }\n }\n\n/**\n * A manager responsible for accumulating WebSocket client\n * connections across different browser runtimes.\n */\nexport class WebSocketClientManager {\n private store: WebSocketClientStore\n private runtimeClients: Map<string, WebSocketClientConnectionProtocol>\n private allClients: Set<WebSocketClientConnectionProtocol>\n\n constructor(private channel: BroadcastChannel) {\n // Store the clients in the IndexedDB in the browser,\n // otherwise, store the clients in memory.\n this.store =\n typeof indexedDB !== 'undefined'\n ? new WebSocketIndexedDBClientStore()\n : new WebSocketMemoryClientStore()\n\n this.runtimeClients = new Map()\n this.allClients = new Set()\n\n this.channel.addEventListener('message', (message) => {\n if (message.data?.type === 'db:update') {\n this.flushDatabaseToMemory()\n }\n })\n\n if (typeof window !== 'undefined') {\n window.addEventListener('message', async (message) => {\n if (message.data?.type === 'msw/worker:stop') {\n await this.removeRuntimeClients()\n }\n })\n }\n }\n\n private async flushDatabaseToMemory() {\n const storedClients = await this.store.getAll()\n\n this.allClients = new Set(\n storedClients.map((client) => {\n const runtimeClient = this.runtimeClients.get(client.id)\n\n /**\n * @note For clients originating in this runtime, use their\n * direct references. No need to wrap them in a remote connection.\n */\n if (runtimeClient) {\n return runtimeClient\n }\n\n return new WebSocketRemoteClientConnection(\n client.id,\n new URL(client.url),\n this.channel,\n )\n }),\n )\n }\n\n private async removeRuntimeClients(): Promise<void> {\n await this.store.deleteMany(Array.from(this.runtimeClients.keys()))\n this.runtimeClients.clear()\n await this.flushDatabaseToMemory()\n this.notifyOthersAboutDatabaseUpdate()\n }\n\n /**\n * All active WebSocket client connections.\n */\n get clients(): Set<WebSocketClientConnectionProtocol> {\n return this.allClients\n }\n\n /**\n * Notify other runtimes about the database update\n * using the shared `BroadcastChannel` instance.\n */\n private notifyOthersAboutDatabaseUpdate(): void {\n this.channel.postMessage({ type: 'db:update' })\n }\n\n private async addClient(\n client: WebSocketClientConnectionProtocol,\n ): Promise<void> {\n await this.store.add(client)\n // Sync the in-memory clients in this runtime with the\n // updated database. This pulls in all the stored clients.\n await this.flushDatabaseToMemory()\n this.notifyOthersAboutDatabaseUpdate()\n }\n\n /**\n * Adds the given `WebSocket` client connection to the set\n * of all connections. The given connection is always the complete\n * connection object because `addConnection()` is called only\n * for the opened connections in the same runtime.\n */\n public async addConnection(\n client: WebSocketClientConnectionProtocol,\n ): Promise<void> {\n // Store this client in the map of clients created in this runtime.\n // This way, the manager can distinguish between this runtime clients\n // and extraneous runtime clients when synchronizing clients storage.\n this.runtimeClients.set(client.id, client)\n\n // Add the new client to the storage.\n await this.addClient(client)\n\n // Handle the incoming BroadcastChannel messages from other runtimes\n // that attempt to control this runtime (via a remote connection wrapper).\n // E.g. another runtime calling `client.send()` for the client in this runtime.\n const handleExtraneousMessage = (\n message: MessageEvent<WebSocketBroadcastChannelMessage>,\n ) => {\n const { type, payload } = message.data\n\n // Ignore broadcasted messages for other clients.\n if (\n typeof payload === 'object' &&\n 'clientId' in payload &&\n payload.clientId !== client.id\n ) {\n return\n }\n\n switch (type) {\n case 'extraneous:send': {\n client.send(payload.data)\n break\n }\n\n case 'extraneous:close': {\n client.close(payload.code, payload.reason)\n break\n }\n }\n }\n\n const abortController = new AbortController()\n\n this.channel.addEventListener('message', handleExtraneousMessage, {\n signal: abortController.signal,\n })\n\n // Once closed, this connection cannot be operated on.\n // This must include the extraneous runtimes as well.\n client.addEventListener('close', () => abortController.abort(), {\n once: true,\n })\n }\n}\n\n/**\n * A wrapper class to operate with WebSocket client connections\n * from other runtimes. This class maintains 1-1 public API\n * compatibility to the `WebSocketClientConnection` but relies\n * on the given `BroadcastChannel` to communicate instructions\n * with the client connections from other runtimes.\n */\nexport class WebSocketRemoteClientConnection\n implements WebSocketClientConnectionProtocol\n{\n constructor(\n public readonly id: string,\n public readonly url: URL,\n private channel: BroadcastChannel,\n ) {}\n\n send(data: WebSocketData): void {\n this.channel.postMessage({\n type: 'extraneous:send',\n payload: {\n clientId: this.id,\n data,\n },\n } as WebSocketBroadcastChannelMessage)\n }\n\n close(code?: number | undefined, reason?: string | undefined): void {\n this.channel.postMessage({\n type: 'extraneous:close',\n payload: {\n clientId: this.id,\n code,\n reason,\n },\n } as WebSocketBroadcastChannelMessage)\n }\n\n addEventListener<EventType extends keyof WebSocketClientEventMap>(\n _type: EventType,\n _listener: (\n this: WebSocket,\n event: WebSocketClientEventMap[EventType],\n ) => void,\n _options?: AddEventListenerOptions | boolean,\n ): void {\n throw new Error(\n 'WebSocketRemoteClientConnection.addEventListener is not supported',\n )\n }\n\n removeEventListener<EventType extends keyof WebSocketClientEventMap>(\n _event: EventType,\n _listener: (\n this: WebSocket,\n event: WebSocketClientEventMap[EventType],\n ) => void,\n _options?: EventListenerOptions | boolean,\n ): void {\n throw new Error(\n 'WebSocketRemoteClientConnection.removeEventListener is not supported',\n )\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -21,54 +21,41 @@ __export(handleWebSocketEvent_exports, {
|
|
|
21
21
|
handleWebSocketEvent: () => handleWebSocketEvent
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(handleWebSocketEvent_exports);
|
|
24
|
-
var import_WebSocketHandler = require("../handlers/WebSocketHandler");
|
|
25
24
|
var import_webSocketInterceptor = require("./webSocketInterceptor");
|
|
26
25
|
var import_onUnhandledRequest = require("../utils/request/onUnhandledRequest");
|
|
27
26
|
var import_isHandlerKind = require("../utils/internal/isHandlerKind");
|
|
28
27
|
function handleWebSocketEvent(options) {
|
|
29
28
|
import_webSocketInterceptor.webSocketInterceptor.on("connection", async (connection) => {
|
|
30
|
-
const handlers = options.getHandlers();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if ((0, import_isHandlerKind.isHandlerKind)("EventHandler")(handler) && handler.predicate({
|
|
37
|
-
event: connectionEvent,
|
|
38
|
-
parsedResult: handler.parse({
|
|
39
|
-
event: connectionEvent
|
|
29
|
+
const handlers = options.getHandlers().filter((0, import_isHandlerKind.isHandlerKind)("EventHandler"));
|
|
30
|
+
if (handlers.length > 0) {
|
|
31
|
+
options?.onMockedConnection(connection);
|
|
32
|
+
await Promise.all(
|
|
33
|
+
handlers.map((handler) => {
|
|
34
|
+
return handler.run(connection);
|
|
40
35
|
})
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
36
|
+
);
|
|
37
|
+
return;
|
|
44
38
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
const request = new Request(connection.client.url, {
|
|
40
|
+
headers: {
|
|
41
|
+
upgrade: "websocket",
|
|
42
|
+
connection: "upgrade"
|
|
49
43
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
).catch((error) => {
|
|
61
|
-
const errorEvent = new Event("error");
|
|
62
|
-
Object.defineProperty(errorEvent, "cause", {
|
|
63
|
-
enumerable: true,
|
|
64
|
-
configurable: false,
|
|
65
|
-
value: error
|
|
66
|
-
});
|
|
67
|
-
connection.client.socket.dispatchEvent(errorEvent);
|
|
44
|
+
});
|
|
45
|
+
await (0, import_onUnhandledRequest.onUnhandledRequest)(
|
|
46
|
+
request,
|
|
47
|
+
options.getUnhandledRequestStrategy()
|
|
48
|
+
).catch((error) => {
|
|
49
|
+
const errorEvent = new Event("error");
|
|
50
|
+
Object.defineProperty(errorEvent, "cause", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
configurable: false,
|
|
53
|
+
value: error
|
|
68
54
|
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
connection.client.socket.dispatchEvent(errorEvent);
|
|
56
|
+
});
|
|
57
|
+
options?.onPassthroughConnection(connection);
|
|
58
|
+
connection.server.connect();
|
|
72
59
|
});
|
|
73
60
|
}
|
|
74
61
|
//# sourceMappingURL=handleWebSocketEvent.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/ws/handleWebSocketEvent.ts"],"sourcesContent":["import type { WebSocketConnectionData } from '@mswjs/interceptors/lib/browser/interceptors/WebSocket'\nimport { RequestHandler } from '../handlers/RequestHandler'\nimport { WebSocketHandler
|
|
1
|
+
{"version":3,"sources":["../../../src/core/ws/handleWebSocketEvent.ts"],"sourcesContent":["import type { WebSocketConnectionData } from '@mswjs/interceptors/lib/browser/interceptors/WebSocket'\nimport { RequestHandler } from '../handlers/RequestHandler'\nimport { WebSocketHandler } from '../handlers/WebSocketHandler'\nimport { webSocketInterceptor } from './webSocketInterceptor'\nimport {\n onUnhandledRequest,\n UnhandledRequestStrategy,\n} from '../utils/request/onUnhandledRequest'\nimport { isHandlerKind } from '../utils/internal/isHandlerKind'\n\ninterface HandleWebSocketEventOptions {\n getUnhandledRequestStrategy: () => UnhandledRequestStrategy\n getHandlers: () => Array<RequestHandler | WebSocketHandler>\n onMockedConnection: (connection: WebSocketConnectionData) => void\n onPassthroughConnection: (onnection: WebSocketConnectionData) => void\n}\n\nexport function handleWebSocketEvent(options: HandleWebSocketEventOptions) {\n webSocketInterceptor.on('connection', async (connection) => {\n const handlers = options.getHandlers().filter(isHandlerKind('EventHandler'))\n\n // Ignore this connection if the user hasn't defined any handlers.\n if (handlers.length > 0) {\n options?.onMockedConnection(connection)\n\n await Promise.all(\n handlers.map((handler) => {\n // Iterate over the handlers and forward the connection\n // event to WebSocket event handlers. This is equivalent\n // to dispatching that event onto multiple listeners.\n return handler.run(connection)\n }),\n )\n\n return\n }\n\n // Construct a request representing this WebSocket connection.\n const request = new Request(connection.client.url, {\n headers: {\n upgrade: 'websocket',\n connection: 'upgrade',\n },\n })\n await onUnhandledRequest(\n request,\n options.getUnhandledRequestStrategy(),\n ).catch((error) => {\n const errorEvent = new Event('error')\n Object.defineProperty(errorEvent, 'cause', {\n enumerable: true,\n configurable: false,\n value: error,\n })\n connection.client.socket.dispatchEvent(errorEvent)\n })\n\n options?.onPassthroughConnection(connection)\n\n // If none of the \"ws\" handlers matched,\n // establish the WebSocket connection as-is.\n connection.server.connect()\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kCAAqC;AACrC,gCAGO;AACP,2BAA8B;AASvB,SAAS,qBAAqB,SAAsC;AACzE,mDAAqB,GAAG,cAAc,OAAO,eAAe;AAC1D,UAAM,WAAW,QAAQ,YAAY,EAAE,WAAO,oCAAc,cAAc,CAAC;AAG3E,QAAI,SAAS,SAAS,GAAG;AACvB,eAAS,mBAAmB,UAAU;AAEtC,YAAM,QAAQ;AAAA,QACZ,SAAS,IAAI,CAAC,YAAY;AAIxB,iBAAO,QAAQ,IAAI,UAAU;AAAA,QAC/B,CAAC;AAAA,MACH;AAEA;AAAA,IACF;AAGA,UAAM,UAAU,IAAI,QAAQ,WAAW,OAAO,KAAK;AAAA,MACjD,SAAS;AAAA,QACP,SAAS;AAAA,QACT,YAAY;AAAA,MACd;AAAA,IACF,CAAC;AACD,cAAM;AAAA,MACJ;AAAA,MACA,QAAQ,4BAA4B;AAAA,IACtC,EAAE,MAAM,CAAC,UAAU;AACjB,YAAM,aAAa,IAAI,MAAM,OAAO;AACpC,aAAO,eAAe,YAAY,SAAS;AAAA,QACzC,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,OAAO;AAAA,MACT,CAAC;AACD,iBAAW,OAAO,OAAO,cAAc,UAAU;AAAA,IACnD,CAAC;AAED,aAAS,wBAAwB,UAAU;AAI3C,eAAW,OAAO,QAAQ;AAAA,EAC5B,CAAC;AACH;","names":[]}
|