msw 2.3.0-ws.rc-5 → 2.3.0-ws.rc-7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +3 -9
  2. package/lib/browser/index.js +160 -62
  3. package/lib/browser/index.js.map +1 -1
  4. package/lib/browser/index.mjs +160 -62
  5. package/lib/browser/index.mjs.map +1 -1
  6. package/lib/core/handlers/WebSocketHandler.js +1 -2
  7. package/lib/core/handlers/WebSocketHandler.js.map +1 -1
  8. package/lib/core/handlers/WebSocketHandler.mjs +1 -2
  9. package/lib/core/handlers/WebSocketHandler.mjs.map +1 -1
  10. package/lib/core/utils/internal/Disposable.d.mts +2 -2
  11. package/lib/core/utils/internal/Disposable.d.ts +2 -2
  12. package/lib/core/utils/internal/Disposable.js +5 -2
  13. package/lib/core/utils/internal/Disposable.js.map +1 -1
  14. package/lib/core/utils/internal/Disposable.mjs +5 -2
  15. package/lib/core/utils/internal/Disposable.mjs.map +1 -1
  16. package/lib/core/utils/internal/devUtils.d.mts +10 -1
  17. package/lib/core/utils/internal/devUtils.d.ts +10 -1
  18. package/lib/core/utils/internal/devUtils.js +7 -0
  19. package/lib/core/utils/internal/devUtils.js.map +1 -1
  20. package/lib/core/utils/internal/devUtils.mjs +7 -0
  21. package/lib/core/utils/internal/devUtils.mjs.map +1 -1
  22. package/lib/core/utils/matching/normalizePath.d.mts +1 -0
  23. package/lib/core/utils/matching/normalizePath.d.ts +1 -0
  24. package/lib/core/utils/matching/normalizePath.js.map +1 -1
  25. package/lib/core/utils/matching/normalizePath.mjs.map +1 -1
  26. package/lib/core/utils/request/onUnhandledRequest.js +3 -3
  27. package/lib/core/utils/request/onUnhandledRequest.js.map +1 -1
  28. package/lib/core/utils/request/onUnhandledRequest.mjs +4 -4
  29. package/lib/core/utils/request/onUnhandledRequest.mjs.map +1 -1
  30. package/lib/core/utils/url/cleanUrl.d.mts +2 -1
  31. package/lib/core/utils/url/cleanUrl.d.ts +2 -1
  32. package/lib/core/utils/url/cleanUrl.js +3 -0
  33. package/lib/core/utils/url/cleanUrl.js.map +1 -1
  34. package/lib/core/utils/url/cleanUrl.mjs +3 -0
  35. package/lib/core/utils/url/cleanUrl.mjs.map +1 -1
  36. package/lib/core/ws/WebSocketClientManager.d.mts +9 -15
  37. package/lib/core/ws/WebSocketClientManager.d.ts +9 -15
  38. package/lib/core/ws/WebSocketClientManager.js +73 -34
  39. package/lib/core/ws/WebSocketClientManager.js.map +1 -1
  40. package/lib/core/ws/WebSocketClientManager.mjs +73 -34
  41. package/lib/core/ws/WebSocketClientManager.mjs.map +1 -1
  42. package/lib/core/ws.js +4 -2
  43. package/lib/core/ws.js.map +1 -1
  44. package/lib/core/ws.mjs +4 -2
  45. package/lib/core/ws.mjs.map +1 -1
  46. package/lib/iife/index.js +278 -113
  47. package/lib/iife/index.js.map +1 -1
  48. package/lib/mockServiceWorker.js +1 -1
  49. package/lib/native/index.js +5 -0
  50. package/lib/native/index.js.map +1 -1
  51. package/lib/native/index.mjs +6 -1
  52. package/lib/native/index.mjs.map +1 -1
  53. package/lib/node/index.js +5 -0
  54. package/lib/node/index.js.map +1 -1
  55. package/lib/node/index.mjs +6 -1
  56. package/lib/node/index.mjs.map +1 -1
  57. package/package.json +17 -5
  58. package/src/browser/setupWorker/start/createStartHandler.ts +6 -0
  59. package/src/browser/setupWorker/stop/createStop.ts +4 -0
  60. package/src/core/handlers/WebSocketHandler.ts +1 -2
  61. package/src/core/utils/internal/Disposable.ts +6 -3
  62. package/src/core/utils/internal/devUtils.test.ts +21 -0
  63. package/src/core/utils/internal/devUtils.ts +13 -0
  64. package/src/core/utils/matching/matchRequestUrl.test.ts +11 -0
  65. package/src/core/utils/matching/normalizePath.test.ts +7 -1
  66. package/src/core/utils/matching/normalizePath.ts +1 -0
  67. package/src/core/utils/request/onUnhandledRequest.test.ts +30 -4
  68. package/src/core/utils/request/onUnhandledRequest.ts +4 -4
  69. package/src/core/utils/url/cleanUrl.test.ts +8 -3
  70. package/src/core/utils/url/cleanUrl.ts +9 -1
  71. package/src/core/utils/url/getAbsoluteUrl.node.test.ts +3 -3
  72. package/src/core/utils/url/getAbsoluteUrl.test.ts +5 -5
  73. package/src/core/utils/url/isAbsoluteUrl.test.ts +7 -7
  74. package/src/core/ws/WebSocketClientManager.test.ts +43 -45
  75. package/src/core/ws/WebSocketClientManager.ts +107 -44
  76. package/src/core/ws.ts +4 -2
  77. package/src/node/SetupServerCommonApi.ts +7 -1
@@ -18,30 +18,88 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var WebSocketClientManager_exports = {};
20
20
  __export(WebSocketClientManager_exports, {
21
+ MSW_WEBSOCKET_CLIENTS_KEY: () => MSW_WEBSOCKET_CLIENTS_KEY,
21
22
  WebSocketClientManager: () => WebSocketClientManager,
22
- WebSocketRemoteClientConnection: () => WebSocketRemoteClientConnection,
23
- kAddByClientId: () => kAddByClientId
23
+ WebSocketRemoteClientConnection: () => WebSocketRemoteClientConnection
24
24
  });
25
25
  module.exports = __toCommonJS(WebSocketClientManager_exports);
26
- const kAddByClientId = Symbol("kAddByClientId");
26
+ var import_outvariant = require("outvariant");
27
+ var import_matchRequestUrl = require("../utils/matching/matchRequestUrl.js");
28
+ const MSW_WEBSOCKET_CLIENTS_KEY = "msw:ws:clients";
27
29
  class WebSocketClientManager {
28
- constructor(channel) {
30
+ constructor(channel, url) {
29
31
  this.channel = channel;
30
- this.clients = /* @__PURE__ */ new Set();
31
- this.channel.addEventListener("message", (message) => {
32
- const { type, payload } = message.data;
33
- switch (type) {
34
- case "connection:open": {
35
- this.onRemoteConnection(payload.clientId, new URL(payload.url));
36
- break;
32
+ this.url = url;
33
+ this.inMemoryClients = /* @__PURE__ */ new Set();
34
+ if (typeof localStorage !== "undefined") {
35
+ localStorage.removeItem = new Proxy(localStorage.removeItem, {
36
+ apply: (target, thisArg, args) => {
37
+ const [key] = args;
38
+ if (key === MSW_WEBSOCKET_CLIENTS_KEY) {
39
+ this.inMemoryClients.clear();
40
+ }
41
+ return Reflect.apply(target, thisArg, args);
37
42
  }
38
- }
39
- });
43
+ });
44
+ }
40
45
  }
46
+ inMemoryClients;
41
47
  /**
42
48
  * All active WebSocket client connections.
43
49
  */
44
- clients;
50
+ get clients() {
51
+ if (typeof localStorage !== "undefined") {
52
+ const inMemoryClients = Array.from(this.inMemoryClients);
53
+ console.log("get clients()", inMemoryClients, this.getSerializedClients());
54
+ return new Set(
55
+ inMemoryClients.concat(
56
+ this.getSerializedClients().filter((serializedClient) => {
57
+ if (inMemoryClients.every(
58
+ (client) => client.id !== serializedClient.clientId
59
+ )) {
60
+ return serializedClient;
61
+ }
62
+ }).map((serializedClient) => {
63
+ return new WebSocketRemoteClientConnection(
64
+ serializedClient.clientId,
65
+ new URL(serializedClient.url),
66
+ this.channel
67
+ );
68
+ })
69
+ )
70
+ );
71
+ }
72
+ return this.inMemoryClients;
73
+ }
74
+ getSerializedClients() {
75
+ (0, import_outvariant.invariant)(
76
+ typeof localStorage !== "undefined",
77
+ "Failed to call WebSocketClientManager#getSerializedClients() in a non-browser environment. This is likely a bug in MSW. Please, report it on GitHub: https://github.com/mswjs/msw"
78
+ );
79
+ const clientsJson = localStorage.getItem(MSW_WEBSOCKET_CLIENTS_KEY);
80
+ if (!clientsJson) {
81
+ return [];
82
+ }
83
+ const allClients = JSON.parse(clientsJson);
84
+ const matchingClients = allClients.filter((client) => {
85
+ return (0, import_matchRequestUrl.matchRequestUrl)(new URL(client.url), this.url).matches;
86
+ });
87
+ return matchingClients;
88
+ }
89
+ addClient(client) {
90
+ this.inMemoryClients.add(client);
91
+ if (typeof localStorage !== "undefined") {
92
+ const serializedClients = this.getSerializedClients();
93
+ const nextSerializedClients = serializedClients.concat({
94
+ clientId: client.id,
95
+ url: client.url.href
96
+ });
97
+ localStorage.setItem(
98
+ MSW_WEBSOCKET_CLIENTS_KEY,
99
+ JSON.stringify(nextSerializedClients)
100
+ );
101
+ }
102
+ }
45
103
  /**
46
104
  * Adds the given `WebSocket` client connection to the set
47
105
  * of all connections. The given connection is always the complete
@@ -49,14 +107,7 @@ class WebSocketClientManager {
49
107
  * for the opened connections in the same runtime.
50
108
  */
51
109
  addConnection(client) {
52
- this.clients.add(client);
53
- this.channel.postMessage({
54
- type: "connection:open",
55
- payload: {
56
- clientId: client.id,
57
- url: client.url.toString()
58
- }
59
- });
110
+ this.addClient(client);
60
111
  const handleExtraneousMessage = (message) => {
61
112
  const { type, payload } = message.data;
62
113
  if (typeof payload === "object" && "clientId" in payload && payload.clientId !== client.id) {
@@ -81,18 +132,6 @@ class WebSocketClientManager {
81
132
  once: true
82
133
  });
83
134
  }
84
- /**
85
- * Adds a client connection wrapper to operate with
86
- * WebSocket client connections in other runtimes.
87
- */
88
- onRemoteConnection(id, url) {
89
- this.clients.add(
90
- // Create a connection-compatible instance that can
91
- // operate with this client from a different runtime
92
- // using the BroadcastChannel messages.
93
- new WebSocketRemoteClientConnection(id, url, this.channel)
94
- );
95
- }
96
135
  }
97
136
  class WebSocketRemoteClientConnection {
98
137
  constructor(id, url, channel) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/ws/WebSocketClientManager.ts"],"sourcesContent":["import type {\n WebSocketData,\n WebSocketClientConnection,\n WebSocketClientConnectionProtocol,\n} from '@mswjs/interceptors/WebSocket'\n\nexport type WebSocketBroadcastChannelMessage =\n | {\n type: 'connection:open'\n payload: {\n clientId: string\n url: string\n }\n }\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\nexport const kAddByClientId = Symbol('kAddByClientId')\n\n/**\n * A manager responsible for accumulating WebSocket client\n * connections across different browser runtimes.\n */\nexport class WebSocketClientManager {\n /**\n * All active WebSocket client connections.\n */\n public clients: Set<WebSocketClientConnectionProtocol>\n\n constructor(private channel: BroadcastChannel) {\n this.clients = new Set()\n\n this.channel.addEventListener('message', (message) => {\n const { type, payload } = message.data as WebSocketBroadcastChannelMessage\n\n switch (type) {\n case 'connection:open': {\n // When another runtime notifies about a new connection,\n // create a connection wrapper class and add it to the set.\n this.onRemoteConnection(payload.clientId, new URL(payload.url))\n break\n }\n }\n })\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 addConnection(client: WebSocketClientConnection): void {\n this.clients.add(client)\n\n // Signal to other runtimes about this connection.\n this.channel.postMessage({\n type: 'connection:open',\n payload: {\n clientId: client.id,\n url: client.url.toString(),\n },\n } as WebSocketBroadcastChannelMessage)\n\n // Instruct the current client how to handle events\n // coming from other runtimes (e.g. when calling `.broadcast()`).\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 * Adds a client connection wrapper to operate with\n * WebSocket client connections in other runtimes.\n */\n private onRemoteConnection(id: string, url: URL): void {\n this.clients.add(\n // Create a connection-compatible instance that can\n // operate with this client from a different runtime\n // using the BroadcastChannel messages.\n new WebSocketRemoteClientConnection(id, url, this.channel),\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"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BO,MAAM,iBAAiB,OAAO,gBAAgB;AAM9C,MAAM,uBAAuB;AAAA,EAMlC,YAAoB,SAA2B;AAA3B;AAClB,SAAK,UAAU,oBAAI,IAAI;AAEvB,SAAK,QAAQ,iBAAiB,WAAW,CAAC,YAAY;AACpD,YAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ;AAElC,cAAQ,MAAM;AAAA,QACZ,KAAK,mBAAmB;AAGtB,eAAK,mBAAmB,QAAQ,UAAU,IAAI,IAAI,QAAQ,GAAG,CAAC;AAC9D;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAjBO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBA,cAAc,QAAyC;AAC5D,SAAK,QAAQ,IAAI,MAAM;AAGvB,SAAK,QAAQ,YAAY;AAAA,MACvB,MAAM;AAAA,MACN,SAAS;AAAA,QACP,UAAU,OAAO;AAAA,QACjB,KAAK,OAAO,IAAI,SAAS;AAAA,MAC3B;AAAA,IACF,CAAqC;AAIrC,UAAM,0BAA0B,CAC9B,YACG;AACH,YAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ;AAGlC,UACE,OAAO,YAAY,YACnB,cAAc,WACd,QAAQ,aAAa,OAAO,IAC5B;AACA;AAAA,MACF;AAEA,cAAQ,MAAM;AAAA,QACZ,KAAK,mBAAmB;AACtB,iBAAO,KAAK,QAAQ,IAAI;AACxB;AAAA,QACF;AAAA,QAEA,KAAK,oBAAoB;AACvB,iBAAO,MAAM,QAAQ,MAAM,QAAQ,MAAM;AACzC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,kBAAkB,IAAI,gBAAgB;AAE5C,SAAK,QAAQ,iBAAiB,WAAW,yBAAyB;AAAA,MAChE,QAAQ,gBAAgB;AAAA,IAC1B,CAAC;AAID,WAAO,iBAAiB,SAAS,MAAM,gBAAgB,MAAM,GAAG;AAAA,MAC9D,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,mBAAmB,IAAY,KAAgB;AACrD,SAAK,QAAQ;AAAA;AAAA;AAAA;AAAA,MAIX,IAAI,gCAAgC,IAAI,KAAK,KAAK,OAAO;AAAA,IAC3D;AAAA,EACF;AACF;AASO,MAAM,gCAEb;AAAA,EACE,YACkB,IACA,KACR,SACR;AAHgB;AACA;AACR;AAAA,EACP;AAAA,EAEH,KAAK,MAA2B;AAC9B,SAAK,QAAQ,YAAY;AAAA,MACvB,MAAM;AAAA,MACN,SAAS;AAAA,QACP,UAAU,KAAK;AAAA,QACf;AAAA,MACF;AAAA,IACF,CAAqC;AAAA,EACvC;AAAA,EAEA,MAAM,MAA2B,QAAmC;AAClE,SAAK,QAAQ,YAAY;AAAA,MACvB,MAAM;AAAA,MACN,SAAS;AAAA,QACP,UAAU,KAAK;AAAA,QACf;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAqC;AAAA,EACvC;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/core/ws/WebSocketClientManager.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport type {\n WebSocketData,\n WebSocketClientConnection,\n WebSocketClientConnectionProtocol,\n} from '@mswjs/interceptors/WebSocket'\nimport { matchRequestUrl, type Path } from '../utils/matching/matchRequestUrl'\n\nexport const MSW_WEBSOCKET_CLIENTS_KEY = 'msw:ws:clients'\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\ntype SerializedClient = {\n clientId: string\n url: string\n}\n\n/**\n * A manager responsible for accumulating WebSocket client\n * connections across different browser runtimes.\n */\nexport class WebSocketClientManager {\n private inMemoryClients: Set<WebSocketClientConnectionProtocol>\n\n constructor(\n private channel: BroadcastChannel,\n private url: Path,\n ) {\n this.inMemoryClients = new Set()\n\n // Purge in-memory clients when the worker stops.\n if (typeof localStorage !== 'undefined') {\n localStorage.removeItem = new Proxy(localStorage.removeItem, {\n apply: (target, thisArg, args) => {\n const [key] = args\n\n if (key === MSW_WEBSOCKET_CLIENTS_KEY) {\n this.inMemoryClients.clear()\n }\n\n return Reflect.apply(target, thisArg, args)\n },\n })\n }\n }\n\n /**\n * All active WebSocket client connections.\n */\n get clients(): Set<WebSocketClientConnectionProtocol> {\n // In the browser, different runtimes use \"localStorage\"\n // as the shared source of all the clients.\n if (typeof localStorage !== 'undefined') {\n const inMemoryClients = Array.from(this.inMemoryClients)\n\n console.log('get clients()', inMemoryClients, this.getSerializedClients())\n\n return new Set(\n inMemoryClients.concat(\n this.getSerializedClients()\n // Filter out the serialized clients that are already present\n // in this runtime in-memory. This is crucial because a remote client\n // wrapper CANNOT send a message to the client in THIS runtime\n // (the \"message\" event on broadcast channel won't trigger).\n .filter((serializedClient) => {\n if (\n inMemoryClients.every(\n (client) => client.id !== serializedClient.clientId,\n )\n ) {\n return serializedClient\n }\n })\n .map((serializedClient) => {\n return new WebSocketRemoteClientConnection(\n serializedClient.clientId,\n new URL(serializedClient.url),\n this.channel,\n )\n }),\n ),\n )\n }\n\n // In Node.js, the manager acts as a singleton, and all clients\n // are kept in-memory.\n return this.inMemoryClients\n }\n\n private getSerializedClients(): Array<SerializedClient> {\n invariant(\n typeof localStorage !== 'undefined',\n 'Failed to call WebSocketClientManager#getSerializedClients() in a non-browser environment. This is likely a bug in MSW. Please, report it on GitHub: https://github.com/mswjs/msw',\n )\n\n const clientsJson = localStorage.getItem(MSW_WEBSOCKET_CLIENTS_KEY)\n\n if (!clientsJson) {\n return []\n }\n\n const allClients = JSON.parse(clientsJson) as Array<SerializedClient>\n const matchingClients = allClients.filter((client) => {\n return matchRequestUrl(new URL(client.url), this.url).matches\n })\n\n return matchingClients\n }\n\n private addClient(client: WebSocketClientConnection): void {\n this.inMemoryClients.add(client)\n\n if (typeof localStorage !== 'undefined') {\n const serializedClients = this.getSerializedClients()\n\n // Serialize the current client for other runtimes to create\n // a remote wrapper over it. This has no effect on the current runtime.\n const nextSerializedClients = serializedClients.concat({\n clientId: client.id,\n url: client.url.href,\n } as SerializedClient)\n\n localStorage.setItem(\n MSW_WEBSOCKET_CLIENTS_KEY,\n JSON.stringify(nextSerializedClients),\n )\n }\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 addConnection(client: WebSocketClientConnection): void {\n this.addClient(client)\n\n // Instruct the current client how to handle events\n // coming from other runtimes (e.g. when calling `.broadcast()`).\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"],"mappings":"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","names":[]}
@@ -1,22 +1,80 @@
1
- const kAddByClientId = Symbol("kAddByClientId");
1
+ import { invariant } from "outvariant";
2
+ import { matchRequestUrl } from '../utils/matching/matchRequestUrl.mjs';
3
+ const MSW_WEBSOCKET_CLIENTS_KEY = "msw:ws:clients";
2
4
  class WebSocketClientManager {
3
- constructor(channel) {
5
+ constructor(channel, url) {
4
6
  this.channel = channel;
5
- this.clients = /* @__PURE__ */ new Set();
6
- this.channel.addEventListener("message", (message) => {
7
- const { type, payload } = message.data;
8
- switch (type) {
9
- case "connection:open": {
10
- this.onRemoteConnection(payload.clientId, new URL(payload.url));
11
- break;
7
+ this.url = url;
8
+ this.inMemoryClients = /* @__PURE__ */ new Set();
9
+ if (typeof localStorage !== "undefined") {
10
+ localStorage.removeItem = new Proxy(localStorage.removeItem, {
11
+ apply: (target, thisArg, args) => {
12
+ const [key] = args;
13
+ if (key === MSW_WEBSOCKET_CLIENTS_KEY) {
14
+ this.inMemoryClients.clear();
15
+ }
16
+ return Reflect.apply(target, thisArg, args);
12
17
  }
13
- }
14
- });
18
+ });
19
+ }
15
20
  }
21
+ inMemoryClients;
16
22
  /**
17
23
  * All active WebSocket client connections.
18
24
  */
19
- clients;
25
+ get clients() {
26
+ if (typeof localStorage !== "undefined") {
27
+ const inMemoryClients = Array.from(this.inMemoryClients);
28
+ console.log("get clients()", inMemoryClients, this.getSerializedClients());
29
+ return new Set(
30
+ inMemoryClients.concat(
31
+ this.getSerializedClients().filter((serializedClient) => {
32
+ if (inMemoryClients.every(
33
+ (client) => client.id !== serializedClient.clientId
34
+ )) {
35
+ return serializedClient;
36
+ }
37
+ }).map((serializedClient) => {
38
+ return new WebSocketRemoteClientConnection(
39
+ serializedClient.clientId,
40
+ new URL(serializedClient.url),
41
+ this.channel
42
+ );
43
+ })
44
+ )
45
+ );
46
+ }
47
+ return this.inMemoryClients;
48
+ }
49
+ getSerializedClients() {
50
+ invariant(
51
+ typeof localStorage !== "undefined",
52
+ "Failed to call WebSocketClientManager#getSerializedClients() in a non-browser environment. This is likely a bug in MSW. Please, report it on GitHub: https://github.com/mswjs/msw"
53
+ );
54
+ const clientsJson = localStorage.getItem(MSW_WEBSOCKET_CLIENTS_KEY);
55
+ if (!clientsJson) {
56
+ return [];
57
+ }
58
+ const allClients = JSON.parse(clientsJson);
59
+ const matchingClients = allClients.filter((client) => {
60
+ return matchRequestUrl(new URL(client.url), this.url).matches;
61
+ });
62
+ return matchingClients;
63
+ }
64
+ addClient(client) {
65
+ this.inMemoryClients.add(client);
66
+ if (typeof localStorage !== "undefined") {
67
+ const serializedClients = this.getSerializedClients();
68
+ const nextSerializedClients = serializedClients.concat({
69
+ clientId: client.id,
70
+ url: client.url.href
71
+ });
72
+ localStorage.setItem(
73
+ MSW_WEBSOCKET_CLIENTS_KEY,
74
+ JSON.stringify(nextSerializedClients)
75
+ );
76
+ }
77
+ }
20
78
  /**
21
79
  * Adds the given `WebSocket` client connection to the set
22
80
  * of all connections. The given connection is always the complete
@@ -24,14 +82,7 @@ class WebSocketClientManager {
24
82
  * for the opened connections in the same runtime.
25
83
  */
26
84
  addConnection(client) {
27
- this.clients.add(client);
28
- this.channel.postMessage({
29
- type: "connection:open",
30
- payload: {
31
- clientId: client.id,
32
- url: client.url.toString()
33
- }
34
- });
85
+ this.addClient(client);
35
86
  const handleExtraneousMessage = (message) => {
36
87
  const { type, payload } = message.data;
37
88
  if (typeof payload === "object" && "clientId" in payload && payload.clientId !== client.id) {
@@ -56,18 +107,6 @@ class WebSocketClientManager {
56
107
  once: true
57
108
  });
58
109
  }
59
- /**
60
- * Adds a client connection wrapper to operate with
61
- * WebSocket client connections in other runtimes.
62
- */
63
- onRemoteConnection(id, url) {
64
- this.clients.add(
65
- // Create a connection-compatible instance that can
66
- // operate with this client from a different runtime
67
- // using the BroadcastChannel messages.
68
- new WebSocketRemoteClientConnection(id, url, this.channel)
69
- );
70
- }
71
110
  }
72
111
  class WebSocketRemoteClientConnection {
73
112
  constructor(id, url, channel) {
@@ -96,8 +135,8 @@ class WebSocketRemoteClientConnection {
96
135
  }
97
136
  }
98
137
  export {
138
+ MSW_WEBSOCKET_CLIENTS_KEY,
99
139
  WebSocketClientManager,
100
- WebSocketRemoteClientConnection,
101
- kAddByClientId
140
+ WebSocketRemoteClientConnection
102
141
  };
103
142
  //# sourceMappingURL=WebSocketClientManager.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/ws/WebSocketClientManager.ts"],"sourcesContent":["import type {\n WebSocketData,\n WebSocketClientConnection,\n WebSocketClientConnectionProtocol,\n} from '@mswjs/interceptors/WebSocket'\n\nexport type WebSocketBroadcastChannelMessage =\n | {\n type: 'connection:open'\n payload: {\n clientId: string\n url: string\n }\n }\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\nexport const kAddByClientId = Symbol('kAddByClientId')\n\n/**\n * A manager responsible for accumulating WebSocket client\n * connections across different browser runtimes.\n */\nexport class WebSocketClientManager {\n /**\n * All active WebSocket client connections.\n */\n public clients: Set<WebSocketClientConnectionProtocol>\n\n constructor(private channel: BroadcastChannel) {\n this.clients = new Set()\n\n this.channel.addEventListener('message', (message) => {\n const { type, payload } = message.data as WebSocketBroadcastChannelMessage\n\n switch (type) {\n case 'connection:open': {\n // When another runtime notifies about a new connection,\n // create a connection wrapper class and add it to the set.\n this.onRemoteConnection(payload.clientId, new URL(payload.url))\n break\n }\n }\n })\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 addConnection(client: WebSocketClientConnection): void {\n this.clients.add(client)\n\n // Signal to other runtimes about this connection.\n this.channel.postMessage({\n type: 'connection:open',\n payload: {\n clientId: client.id,\n url: client.url.toString(),\n },\n } as WebSocketBroadcastChannelMessage)\n\n // Instruct the current client how to handle events\n // coming from other runtimes (e.g. when calling `.broadcast()`).\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 * Adds a client connection wrapper to operate with\n * WebSocket client connections in other runtimes.\n */\n private onRemoteConnection(id: string, url: URL): void {\n this.clients.add(\n // Create a connection-compatible instance that can\n // operate with this client from a different runtime\n // using the BroadcastChannel messages.\n new WebSocketRemoteClientConnection(id, url, this.channel),\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"],"mappings":"AA8BO,MAAM,iBAAiB,OAAO,gBAAgB;AAM9C,MAAM,uBAAuB;AAAA,EAMlC,YAAoB,SAA2B;AAA3B;AAClB,SAAK,UAAU,oBAAI,IAAI;AAEvB,SAAK,QAAQ,iBAAiB,WAAW,CAAC,YAAY;AACpD,YAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ;AAElC,cAAQ,MAAM;AAAA,QACZ,KAAK,mBAAmB;AAGtB,eAAK,mBAAmB,QAAQ,UAAU,IAAI,IAAI,QAAQ,GAAG,CAAC;AAC9D;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAjBO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBA,cAAc,QAAyC;AAC5D,SAAK,QAAQ,IAAI,MAAM;AAGvB,SAAK,QAAQ,YAAY;AAAA,MACvB,MAAM;AAAA,MACN,SAAS;AAAA,QACP,UAAU,OAAO;AAAA,QACjB,KAAK,OAAO,IAAI,SAAS;AAAA,MAC3B;AAAA,IACF,CAAqC;AAIrC,UAAM,0BAA0B,CAC9B,YACG;AACH,YAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ;AAGlC,UACE,OAAO,YAAY,YACnB,cAAc,WACd,QAAQ,aAAa,OAAO,IAC5B;AACA;AAAA,MACF;AAEA,cAAQ,MAAM;AAAA,QACZ,KAAK,mBAAmB;AACtB,iBAAO,KAAK,QAAQ,IAAI;AACxB;AAAA,QACF;AAAA,QAEA,KAAK,oBAAoB;AACvB,iBAAO,MAAM,QAAQ,MAAM,QAAQ,MAAM;AACzC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,kBAAkB,IAAI,gBAAgB;AAE5C,SAAK,QAAQ,iBAAiB,WAAW,yBAAyB;AAAA,MAChE,QAAQ,gBAAgB;AAAA,IAC1B,CAAC;AAID,WAAO,iBAAiB,SAAS,MAAM,gBAAgB,MAAM,GAAG;AAAA,MAC9D,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,mBAAmB,IAAY,KAAgB;AACrD,SAAK,QAAQ;AAAA;AAAA;AAAA;AAAA,MAIX,IAAI,gCAAgC,IAAI,KAAK,KAAK,OAAO;AAAA,IAC3D;AAAA,EACF;AACF;AASO,MAAM,gCAEb;AAAA,EACE,YACkB,IACA,KACR,SACR;AAHgB;AACA;AACR;AAAA,EACP;AAAA,EAEH,KAAK,MAA2B;AAC9B,SAAK,QAAQ,YAAY;AAAA,MACvB,MAAM;AAAA,MACN,SAAS;AAAA,QACP,UAAU,KAAK;AAAA,QACf;AAAA,MACF;AAAA,IACF,CAAqC;AAAA,EACvC;AAAA,EAEA,MAAM,MAA2B,QAAmC;AAClE,SAAK,QAAQ,YAAY;AAAA,MACvB,MAAM;AAAA,MACN,SAAS;AAAA,QACP,UAAU,KAAK;AAAA,QACf;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAqC;AAAA,EACvC;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/core/ws/WebSocketClientManager.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport type {\n WebSocketData,\n WebSocketClientConnection,\n WebSocketClientConnectionProtocol,\n} from '@mswjs/interceptors/WebSocket'\nimport { matchRequestUrl, type Path } from '../utils/matching/matchRequestUrl'\n\nexport const MSW_WEBSOCKET_CLIENTS_KEY = 'msw:ws:clients'\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\ntype SerializedClient = {\n clientId: string\n url: string\n}\n\n/**\n * A manager responsible for accumulating WebSocket client\n * connections across different browser runtimes.\n */\nexport class WebSocketClientManager {\n private inMemoryClients: Set<WebSocketClientConnectionProtocol>\n\n constructor(\n private channel: BroadcastChannel,\n private url: Path,\n ) {\n this.inMemoryClients = new Set()\n\n // Purge in-memory clients when the worker stops.\n if (typeof localStorage !== 'undefined') {\n localStorage.removeItem = new Proxy(localStorage.removeItem, {\n apply: (target, thisArg, args) => {\n const [key] = args\n\n if (key === MSW_WEBSOCKET_CLIENTS_KEY) {\n this.inMemoryClients.clear()\n }\n\n return Reflect.apply(target, thisArg, args)\n },\n })\n }\n }\n\n /**\n * All active WebSocket client connections.\n */\n get clients(): Set<WebSocketClientConnectionProtocol> {\n // In the browser, different runtimes use \"localStorage\"\n // as the shared source of all the clients.\n if (typeof localStorage !== 'undefined') {\n const inMemoryClients = Array.from(this.inMemoryClients)\n\n console.log('get clients()', inMemoryClients, this.getSerializedClients())\n\n return new Set(\n inMemoryClients.concat(\n this.getSerializedClients()\n // Filter out the serialized clients that are already present\n // in this runtime in-memory. This is crucial because a remote client\n // wrapper CANNOT send a message to the client in THIS runtime\n // (the \"message\" event on broadcast channel won't trigger).\n .filter((serializedClient) => {\n if (\n inMemoryClients.every(\n (client) => client.id !== serializedClient.clientId,\n )\n ) {\n return serializedClient\n }\n })\n .map((serializedClient) => {\n return new WebSocketRemoteClientConnection(\n serializedClient.clientId,\n new URL(serializedClient.url),\n this.channel,\n )\n }),\n ),\n )\n }\n\n // In Node.js, the manager acts as a singleton, and all clients\n // are kept in-memory.\n return this.inMemoryClients\n }\n\n private getSerializedClients(): Array<SerializedClient> {\n invariant(\n typeof localStorage !== 'undefined',\n 'Failed to call WebSocketClientManager#getSerializedClients() in a non-browser environment. This is likely a bug in MSW. Please, report it on GitHub: https://github.com/mswjs/msw',\n )\n\n const clientsJson = localStorage.getItem(MSW_WEBSOCKET_CLIENTS_KEY)\n\n if (!clientsJson) {\n return []\n }\n\n const allClients = JSON.parse(clientsJson) as Array<SerializedClient>\n const matchingClients = allClients.filter((client) => {\n return matchRequestUrl(new URL(client.url), this.url).matches\n })\n\n return matchingClients\n }\n\n private addClient(client: WebSocketClientConnection): void {\n this.inMemoryClients.add(client)\n\n if (typeof localStorage !== 'undefined') {\n const serializedClients = this.getSerializedClients()\n\n // Serialize the current client for other runtimes to create\n // a remote wrapper over it. This has no effect on the current runtime.\n const nextSerializedClients = serializedClients.concat({\n clientId: client.id,\n url: client.url.href,\n } as SerializedClient)\n\n localStorage.setItem(\n MSW_WEBSOCKET_CLIENTS_KEY,\n JSON.stringify(nextSerializedClients),\n )\n }\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 addConnection(client: WebSocketClientConnection): void {\n this.addClient(client)\n\n // Instruct the current client how to handle events\n // coming from other runtimes (e.g. when calling `.broadcast()`).\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"],"mappings":"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","names":[]}
package/lib/core/ws.js CHANGED
@@ -33,9 +33,11 @@ function createWebSocketLinkHandler(url) {
33
33
  "Expected a WebSocket server URL to be a valid path but got %s",
34
34
  typeof url
35
35
  );
36
- const clientManager = new import_WebSocketClientManager.WebSocketClientManager(wsBroadcastChannel);
36
+ const clientManager = new import_WebSocketClientManager.WebSocketClientManager(wsBroadcastChannel, url);
37
37
  return {
38
- clients: clientManager.clients,
38
+ get clients() {
39
+ return clientManager.clients;
40
+ },
39
41
  on(event, listener) {
40
42
  const handler = new import_WebSocketHandler.WebSocketHandler(url);
41
43
  handler[import_WebSocketHandler.kEmitter].on("connection", ({ client }) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/ws.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport type {\n WebSocketClientConnectionProtocol,\n WebSocketData,\n} from '@mswjs/interceptors/WebSocket'\nimport {\n WebSocketHandler,\n kEmitter,\n type WebSocketHandlerEventMap,\n} from './handlers/WebSocketHandler'\nimport { Path, isPath } from './utils/matching/matchRequestUrl'\nimport { WebSocketClientManager } from './ws/WebSocketClientManager'\n\nconst wsBroadcastChannel = new BroadcastChannel('msw:ws-client-manager')\n\nexport type WebSocketLink = {\n /**\n * A set of all WebSocket clients connected\n * to this link.\n */\n clients: Set<WebSocketClientConnectionProtocol>\n\n on<EventType extends keyof WebSocketHandlerEventMap>(\n event: EventType,\n listener: (...args: WebSocketHandlerEventMap[EventType]) => void,\n ): WebSocketHandler\n\n /**\n * Broadcasts the given data to all WebSocket clients.\n *\n * @example\n * const service = ws.link('wss://example.com')\n * service.on('connection', () => {\n * service.broadcast('hello, everyone!')\n * })\n */\n broadcast(data: WebSocketData): void\n\n /**\n * Broadcasts the given data to all WebSocket clients\n * except the ones provided in the `clients` argument.\n *\n * @example\n * const service = ws.link('wss://example.com')\n * service.on('connection', ({ client }) => {\n * service.broadcastExcept(client, 'hi, the rest of you!')\n * })\n */\n broadcastExcept(\n clients:\n | WebSocketClientConnectionProtocol\n | Array<WebSocketClientConnectionProtocol>,\n data: WebSocketData,\n ): void\n}\n\n/**\n * Intercepts outgoing WebSocket connections to the given URL.\n *\n * @example\n * const chat = ws.link('wss://chat.example.com')\n * chat.on('connection', ({ client }) => {\n * client.send('hello from server!')\n * })\n */\nfunction createWebSocketLinkHandler(url: Path): WebSocketLink {\n invariant(url, 'Expected a WebSocket server URL but got undefined')\n\n invariant(\n isPath(url),\n 'Expected a WebSocket server URL to be a valid path but got %s',\n typeof url,\n )\n\n const clientManager = new WebSocketClientManager(wsBroadcastChannel)\n\n return {\n clients: clientManager.clients,\n on(event, listener) {\n const handler = new WebSocketHandler(url)\n\n // Add the connection event listener for when the\n // handler matches and emits a connection event.\n // When that happens, store that connection in the\n // set of all connections for reference.\n handler[kEmitter].on('connection', ({ client }) => {\n clientManager.addConnection(client)\n })\n\n // The \"handleWebSocketEvent\" function will invoke\n // the \"run()\" method on the WebSocketHandler.\n // If the handler matches, it will emit the \"connection\"\n // event. Attach the user-defined listener to that event.\n handler[kEmitter].on(event, listener)\n\n return handler\n },\n\n broadcast(data) {\n // This will invoke \"send()\" on the immediate clients\n // in this runtime and post a message to the broadcast channel\n // to trigger send for the clients in other runtimes.\n this.broadcastExcept([], data)\n },\n\n broadcastExcept(clients, data) {\n const ignoreClients = Array.prototype\n .concat(clients)\n .map((client) => client.id)\n\n clientManager.clients.forEach((otherClient) => {\n if (!ignoreClients.includes(otherClient.id)) {\n otherClient.send(data)\n }\n })\n },\n }\n}\n\nexport const ws = {\n link: createWebSocketLinkHandler,\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAA0B;AAK1B,8BAIO;AACP,6BAA6B;AAC7B,oCAAuC;AAEvC,MAAM,qBAAqB,IAAI,iBAAiB,uBAAuB;AAoDvE,SAAS,2BAA2B,KAA0B;AAC5D,mCAAU,KAAK,mDAAmD;AAElE;AAAA,QACE,+BAAO,GAAG;AAAA,IACV;AAAA,IACA,OAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,IAAI,qDAAuB,kBAAkB;AAEnE,SAAO;AAAA,IACL,SAAS,cAAc;AAAA,IACvB,GAAG,OAAO,UAAU;AAClB,YAAM,UAAU,IAAI,yCAAiB,GAAG;AAMxC,cAAQ,gCAAQ,EAAE,GAAG,cAAc,CAAC,EAAE,OAAO,MAAM;AACjD,sBAAc,cAAc,MAAM;AAAA,MACpC,CAAC;AAMD,cAAQ,gCAAQ,EAAE,GAAG,OAAO,QAAQ;AAEpC,aAAO;AAAA,IACT;AAAA,IAEA,UAAU,MAAM;AAId,WAAK,gBAAgB,CAAC,GAAG,IAAI;AAAA,IAC/B;AAAA,IAEA,gBAAgB,SAAS,MAAM;AAC7B,YAAM,gBAAgB,MAAM,UACzB,OAAO,OAAO,EACd,IAAI,CAAC,WAAW,OAAO,EAAE;AAE5B,oBAAc,QAAQ,QAAQ,CAAC,gBAAgB;AAC7C,YAAI,CAAC,cAAc,SAAS,YAAY,EAAE,GAAG;AAC3C,sBAAY,KAAK,IAAI;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,MAAM,KAAK;AAAA,EAChB,MAAM;AACR;","names":[]}
1
+ {"version":3,"sources":["../../src/core/ws.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport type {\n WebSocketClientConnectionProtocol,\n WebSocketData,\n} from '@mswjs/interceptors/WebSocket'\nimport {\n WebSocketHandler,\n kEmitter,\n type WebSocketHandlerEventMap,\n} from './handlers/WebSocketHandler'\nimport { Path, isPath } from './utils/matching/matchRequestUrl'\nimport { WebSocketClientManager } from './ws/WebSocketClientManager'\n\nconst wsBroadcastChannel = new BroadcastChannel('msw:ws-client-manager')\n\nexport type WebSocketLink = {\n /**\n * A set of all WebSocket clients connected\n * to this link.\n */\n clients: Set<WebSocketClientConnectionProtocol>\n\n on<EventType extends keyof WebSocketHandlerEventMap>(\n event: EventType,\n listener: (...args: WebSocketHandlerEventMap[EventType]) => void,\n ): WebSocketHandler\n\n /**\n * Broadcasts the given data to all WebSocket clients.\n *\n * @example\n * const service = ws.link('wss://example.com')\n * service.on('connection', () => {\n * service.broadcast('hello, everyone!')\n * })\n */\n broadcast(data: WebSocketData): void\n\n /**\n * Broadcasts the given data to all WebSocket clients\n * except the ones provided in the `clients` argument.\n *\n * @example\n * const service = ws.link('wss://example.com')\n * service.on('connection', ({ client }) => {\n * service.broadcastExcept(client, 'hi, the rest of you!')\n * })\n */\n broadcastExcept(\n clients:\n | WebSocketClientConnectionProtocol\n | Array<WebSocketClientConnectionProtocol>,\n data: WebSocketData,\n ): void\n}\n\n/**\n * Intercepts outgoing WebSocket connections to the given URL.\n *\n * @example\n * const chat = ws.link('wss://chat.example.com')\n * chat.on('connection', ({ client }) => {\n * client.send('hello from server!')\n * })\n */\nfunction createWebSocketLinkHandler(url: Path): WebSocketLink {\n invariant(url, 'Expected a WebSocket server URL but got undefined')\n\n invariant(\n isPath(url),\n 'Expected a WebSocket server URL to be a valid path but got %s',\n typeof url,\n )\n\n const clientManager = new WebSocketClientManager(wsBroadcastChannel, url)\n\n return {\n get clients() {\n return clientManager.clients\n },\n on(event, listener) {\n const handler = new WebSocketHandler(url)\n\n // Add the connection event listener for when the\n // handler matches and emits a connection event.\n // When that happens, store that connection in the\n // set of all connections for reference.\n handler[kEmitter].on('connection', ({ client }) => {\n clientManager.addConnection(client)\n })\n\n // The \"handleWebSocketEvent\" function will invoke\n // the \"run()\" method on the WebSocketHandler.\n // If the handler matches, it will emit the \"connection\"\n // event. Attach the user-defined listener to that event.\n handler[kEmitter].on(event, listener)\n\n return handler\n },\n\n broadcast(data) {\n // This will invoke \"send()\" on the immediate clients\n // in this runtime and post a message to the broadcast channel\n // to trigger send for the clients in other runtimes.\n this.broadcastExcept([], data)\n },\n\n broadcastExcept(clients, data) {\n const ignoreClients = Array.prototype\n .concat(clients)\n .map((client) => client.id)\n\n clientManager.clients.forEach((otherClient) => {\n if (!ignoreClients.includes(otherClient.id)) {\n otherClient.send(data)\n }\n })\n },\n }\n}\n\nexport const ws = {\n link: createWebSocketLinkHandler,\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAA0B;AAK1B,8BAIO;AACP,6BAA6B;AAC7B,oCAAuC;AAEvC,MAAM,qBAAqB,IAAI,iBAAiB,uBAAuB;AAoDvE,SAAS,2BAA2B,KAA0B;AAC5D,mCAAU,KAAK,mDAAmD;AAElE;AAAA,QACE,+BAAO,GAAG;AAAA,IACV;AAAA,IACA,OAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,IAAI,qDAAuB,oBAAoB,GAAG;AAExE,SAAO;AAAA,IACL,IAAI,UAAU;AACZ,aAAO,cAAc;AAAA,IACvB;AAAA,IACA,GAAG,OAAO,UAAU;AAClB,YAAM,UAAU,IAAI,yCAAiB,GAAG;AAMxC,cAAQ,gCAAQ,EAAE,GAAG,cAAc,CAAC,EAAE,OAAO,MAAM;AACjD,sBAAc,cAAc,MAAM;AAAA,MACpC,CAAC;AAMD,cAAQ,gCAAQ,EAAE,GAAG,OAAO,QAAQ;AAEpC,aAAO;AAAA,IACT;AAAA,IAEA,UAAU,MAAM;AAId,WAAK,gBAAgB,CAAC,GAAG,IAAI;AAAA,IAC/B;AAAA,IAEA,gBAAgB,SAAS,MAAM;AAC7B,YAAM,gBAAgB,MAAM,UACzB,OAAO,OAAO,EACd,IAAI,CAAC,WAAW,OAAO,EAAE;AAE5B,oBAAc,QAAQ,QAAQ,CAAC,gBAAgB;AAC7C,YAAI,CAAC,cAAc,SAAS,YAAY,EAAE,GAAG;AAC3C,sBAAY,KAAK,IAAI;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,MAAM,KAAK;AAAA,EAChB,MAAM;AACR;","names":[]}
package/lib/core/ws.mjs CHANGED
@@ -13,9 +13,11 @@ function createWebSocketLinkHandler(url) {
13
13
  "Expected a WebSocket server URL to be a valid path but got %s",
14
14
  typeof url
15
15
  );
16
- const clientManager = new WebSocketClientManager(wsBroadcastChannel);
16
+ const clientManager = new WebSocketClientManager(wsBroadcastChannel, url);
17
17
  return {
18
- clients: clientManager.clients,
18
+ get clients() {
19
+ return clientManager.clients;
20
+ },
19
21
  on(event, listener) {
20
22
  const handler = new WebSocketHandler(url);
21
23
  handler[kEmitter].on("connection", ({ client }) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/ws.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport type {\n WebSocketClientConnectionProtocol,\n WebSocketData,\n} from '@mswjs/interceptors/WebSocket'\nimport {\n WebSocketHandler,\n kEmitter,\n type WebSocketHandlerEventMap,\n} from './handlers/WebSocketHandler'\nimport { Path, isPath } from './utils/matching/matchRequestUrl'\nimport { WebSocketClientManager } from './ws/WebSocketClientManager'\n\nconst wsBroadcastChannel = new BroadcastChannel('msw:ws-client-manager')\n\nexport type WebSocketLink = {\n /**\n * A set of all WebSocket clients connected\n * to this link.\n */\n clients: Set<WebSocketClientConnectionProtocol>\n\n on<EventType extends keyof WebSocketHandlerEventMap>(\n event: EventType,\n listener: (...args: WebSocketHandlerEventMap[EventType]) => void,\n ): WebSocketHandler\n\n /**\n * Broadcasts the given data to all WebSocket clients.\n *\n * @example\n * const service = ws.link('wss://example.com')\n * service.on('connection', () => {\n * service.broadcast('hello, everyone!')\n * })\n */\n broadcast(data: WebSocketData): void\n\n /**\n * Broadcasts the given data to all WebSocket clients\n * except the ones provided in the `clients` argument.\n *\n * @example\n * const service = ws.link('wss://example.com')\n * service.on('connection', ({ client }) => {\n * service.broadcastExcept(client, 'hi, the rest of you!')\n * })\n */\n broadcastExcept(\n clients:\n | WebSocketClientConnectionProtocol\n | Array<WebSocketClientConnectionProtocol>,\n data: WebSocketData,\n ): void\n}\n\n/**\n * Intercepts outgoing WebSocket connections to the given URL.\n *\n * @example\n * const chat = ws.link('wss://chat.example.com')\n * chat.on('connection', ({ client }) => {\n * client.send('hello from server!')\n * })\n */\nfunction createWebSocketLinkHandler(url: Path): WebSocketLink {\n invariant(url, 'Expected a WebSocket server URL but got undefined')\n\n invariant(\n isPath(url),\n 'Expected a WebSocket server URL to be a valid path but got %s',\n typeof url,\n )\n\n const clientManager = new WebSocketClientManager(wsBroadcastChannel)\n\n return {\n clients: clientManager.clients,\n on(event, listener) {\n const handler = new WebSocketHandler(url)\n\n // Add the connection event listener for when the\n // handler matches and emits a connection event.\n // When that happens, store that connection in the\n // set of all connections for reference.\n handler[kEmitter].on('connection', ({ client }) => {\n clientManager.addConnection(client)\n })\n\n // The \"handleWebSocketEvent\" function will invoke\n // the \"run()\" method on the WebSocketHandler.\n // If the handler matches, it will emit the \"connection\"\n // event. Attach the user-defined listener to that event.\n handler[kEmitter].on(event, listener)\n\n return handler\n },\n\n broadcast(data) {\n // This will invoke \"send()\" on the immediate clients\n // in this runtime and post a message to the broadcast channel\n // to trigger send for the clients in other runtimes.\n this.broadcastExcept([], data)\n },\n\n broadcastExcept(clients, data) {\n const ignoreClients = Array.prototype\n .concat(clients)\n .map((client) => client.id)\n\n clientManager.clients.forEach((otherClient) => {\n if (!ignoreClients.includes(otherClient.id)) {\n otherClient.send(data)\n }\n })\n },\n }\n}\n\nexport const ws = {\n link: createWebSocketLinkHandler,\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAK1B;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAe,cAAc;AAC7B,SAAS,8BAA8B;AAEvC,MAAM,qBAAqB,IAAI,iBAAiB,uBAAuB;AAoDvE,SAAS,2BAA2B,KAA0B;AAC5D,YAAU,KAAK,mDAAmD;AAElE;AAAA,IACE,OAAO,GAAG;AAAA,IACV;AAAA,IACA,OAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,IAAI,uBAAuB,kBAAkB;AAEnE,SAAO;AAAA,IACL,SAAS,cAAc;AAAA,IACvB,GAAG,OAAO,UAAU;AAClB,YAAM,UAAU,IAAI,iBAAiB,GAAG;AAMxC,cAAQ,QAAQ,EAAE,GAAG,cAAc,CAAC,EAAE,OAAO,MAAM;AACjD,sBAAc,cAAc,MAAM;AAAA,MACpC,CAAC;AAMD,cAAQ,QAAQ,EAAE,GAAG,OAAO,QAAQ;AAEpC,aAAO;AAAA,IACT;AAAA,IAEA,UAAU,MAAM;AAId,WAAK,gBAAgB,CAAC,GAAG,IAAI;AAAA,IAC/B;AAAA,IAEA,gBAAgB,SAAS,MAAM;AAC7B,YAAM,gBAAgB,MAAM,UACzB,OAAO,OAAO,EACd,IAAI,CAAC,WAAW,OAAO,EAAE;AAE5B,oBAAc,QAAQ,QAAQ,CAAC,gBAAgB;AAC7C,YAAI,CAAC,cAAc,SAAS,YAAY,EAAE,GAAG;AAC3C,sBAAY,KAAK,IAAI;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,MAAM,KAAK;AAAA,EAChB,MAAM;AACR;","names":[]}
1
+ {"version":3,"sources":["../../src/core/ws.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport type {\n WebSocketClientConnectionProtocol,\n WebSocketData,\n} from '@mswjs/interceptors/WebSocket'\nimport {\n WebSocketHandler,\n kEmitter,\n type WebSocketHandlerEventMap,\n} from './handlers/WebSocketHandler'\nimport { Path, isPath } from './utils/matching/matchRequestUrl'\nimport { WebSocketClientManager } from './ws/WebSocketClientManager'\n\nconst wsBroadcastChannel = new BroadcastChannel('msw:ws-client-manager')\n\nexport type WebSocketLink = {\n /**\n * A set of all WebSocket clients connected\n * to this link.\n */\n clients: Set<WebSocketClientConnectionProtocol>\n\n on<EventType extends keyof WebSocketHandlerEventMap>(\n event: EventType,\n listener: (...args: WebSocketHandlerEventMap[EventType]) => void,\n ): WebSocketHandler\n\n /**\n * Broadcasts the given data to all WebSocket clients.\n *\n * @example\n * const service = ws.link('wss://example.com')\n * service.on('connection', () => {\n * service.broadcast('hello, everyone!')\n * })\n */\n broadcast(data: WebSocketData): void\n\n /**\n * Broadcasts the given data to all WebSocket clients\n * except the ones provided in the `clients` argument.\n *\n * @example\n * const service = ws.link('wss://example.com')\n * service.on('connection', ({ client }) => {\n * service.broadcastExcept(client, 'hi, the rest of you!')\n * })\n */\n broadcastExcept(\n clients:\n | WebSocketClientConnectionProtocol\n | Array<WebSocketClientConnectionProtocol>,\n data: WebSocketData,\n ): void\n}\n\n/**\n * Intercepts outgoing WebSocket connections to the given URL.\n *\n * @example\n * const chat = ws.link('wss://chat.example.com')\n * chat.on('connection', ({ client }) => {\n * client.send('hello from server!')\n * })\n */\nfunction createWebSocketLinkHandler(url: Path): WebSocketLink {\n invariant(url, 'Expected a WebSocket server URL but got undefined')\n\n invariant(\n isPath(url),\n 'Expected a WebSocket server URL to be a valid path but got %s',\n typeof url,\n )\n\n const clientManager = new WebSocketClientManager(wsBroadcastChannel, url)\n\n return {\n get clients() {\n return clientManager.clients\n },\n on(event, listener) {\n const handler = new WebSocketHandler(url)\n\n // Add the connection event listener for when the\n // handler matches and emits a connection event.\n // When that happens, store that connection in the\n // set of all connections for reference.\n handler[kEmitter].on('connection', ({ client }) => {\n clientManager.addConnection(client)\n })\n\n // The \"handleWebSocketEvent\" function will invoke\n // the \"run()\" method on the WebSocketHandler.\n // If the handler matches, it will emit the \"connection\"\n // event. Attach the user-defined listener to that event.\n handler[kEmitter].on(event, listener)\n\n return handler\n },\n\n broadcast(data) {\n // This will invoke \"send()\" on the immediate clients\n // in this runtime and post a message to the broadcast channel\n // to trigger send for the clients in other runtimes.\n this.broadcastExcept([], data)\n },\n\n broadcastExcept(clients, data) {\n const ignoreClients = Array.prototype\n .concat(clients)\n .map((client) => client.id)\n\n clientManager.clients.forEach((otherClient) => {\n if (!ignoreClients.includes(otherClient.id)) {\n otherClient.send(data)\n }\n })\n },\n }\n}\n\nexport const ws = {\n link: createWebSocketLinkHandler,\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAK1B;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAe,cAAc;AAC7B,SAAS,8BAA8B;AAEvC,MAAM,qBAAqB,IAAI,iBAAiB,uBAAuB;AAoDvE,SAAS,2BAA2B,KAA0B;AAC5D,YAAU,KAAK,mDAAmD;AAElE;AAAA,IACE,OAAO,GAAG;AAAA,IACV;AAAA,IACA,OAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,IAAI,uBAAuB,oBAAoB,GAAG;AAExE,SAAO;AAAA,IACL,IAAI,UAAU;AACZ,aAAO,cAAc;AAAA,IACvB;AAAA,IACA,GAAG,OAAO,UAAU;AAClB,YAAM,UAAU,IAAI,iBAAiB,GAAG;AAMxC,cAAQ,QAAQ,EAAE,GAAG,cAAc,CAAC,EAAE,OAAO,MAAM;AACjD,sBAAc,cAAc,MAAM;AAAA,MACpC,CAAC;AAMD,cAAQ,QAAQ,EAAE,GAAG,OAAO,QAAQ;AAEpC,aAAO;AAAA,IACT;AAAA,IAEA,UAAU,MAAM;AAId,WAAK,gBAAgB,CAAC,GAAG,IAAI;AAAA,IAC/B;AAAA,IAEA,gBAAgB,SAAS,MAAM;AAC7B,YAAM,gBAAgB,MAAM,UACzB,OAAO,OAAO,EACd,IAAI,CAAC,WAAW,OAAO,EAAE;AAE5B,oBAAc,QAAQ,QAAQ,CAAC,gBAAgB;AAC7C,YAAI,CAAC,cAAc,SAAS,YAAY,EAAE,GAAG;AAC3C,sBAAY,KAAK,IAAI;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,MAAM,KAAK;AAAA,EAChB,MAAM;AACR;","names":[]}