msw 2.2.13 → 2.3.0-ws.rc-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/cli/init.js +0 -0
  2. package/config/scripts/postinstall.js +0 -0
  3. package/lib/browser/index.d.mts +7 -6
  4. package/lib/browser/index.d.ts +7 -6
  5. package/lib/browser/index.js +26 -7
  6. package/lib/browser/index.js.map +1 -1
  7. package/lib/browser/index.mjs +26 -7
  8. package/lib/browser/index.mjs.map +1 -1
  9. package/lib/core/{GraphQLHandler-bom2Dn82.d.ts → GraphQLHandler-3gvpA65n.d.ts} +1 -1
  10. package/lib/core/{GraphQLHandler-yJ_I6j54.d.mts → GraphQLHandler-4DPdxG0R.d.mts} +1 -1
  11. package/lib/core/{HttpResponse-vQNlixkj.d.ts → HttpResponse-aJY-D0oG.d.ts} +2 -2
  12. package/lib/core/{HttpResponse-3-NyzyF7.d.mts → HttpResponse-xuSipbNt.d.mts} +2 -2
  13. package/lib/core/HttpResponse.d.mts +1 -1
  14. package/lib/core/HttpResponse.d.ts +1 -1
  15. package/lib/core/SetupApi.d.mts +15 -12
  16. package/lib/core/SetupApi.d.ts +15 -12
  17. package/lib/core/SetupApi.js +3 -1
  18. package/lib/core/SetupApi.js.map +1 -1
  19. package/lib/core/SetupApi.mjs +3 -1
  20. package/lib/core/SetupApi.mjs.map +1 -1
  21. package/lib/core/getResponse.d.mts +1 -1
  22. package/lib/core/getResponse.d.ts +1 -1
  23. package/lib/core/graphql.d.mts +2 -2
  24. package/lib/core/graphql.d.ts +2 -2
  25. package/lib/core/handlers/GraphQLHandler.d.mts +2 -2
  26. package/lib/core/handlers/GraphQLHandler.d.ts +2 -2
  27. package/lib/core/handlers/HttpHandler.d.mts +1 -1
  28. package/lib/core/handlers/HttpHandler.d.ts +1 -1
  29. package/lib/core/handlers/RequestHandler.d.mts +1 -1
  30. package/lib/core/handlers/RequestHandler.d.ts +1 -1
  31. package/lib/core/handlers/WebSocketHandler.d.mts +32 -0
  32. package/lib/core/handlers/WebSocketHandler.d.ts +32 -0
  33. package/lib/core/handlers/WebSocketHandler.js +62 -0
  34. package/lib/core/handlers/WebSocketHandler.js.map +1 -0
  35. package/lib/core/handlers/WebSocketHandler.mjs +44 -0
  36. package/lib/core/handlers/WebSocketHandler.mjs.map +1 -0
  37. package/lib/core/http.d.mts +1 -1
  38. package/lib/core/http.d.ts +1 -1
  39. package/lib/core/index.d.mts +5 -2
  40. package/lib/core/index.d.ts +5 -2
  41. package/lib/core/index.js +5 -1
  42. package/lib/core/index.js.map +1 -1
  43. package/lib/core/index.mjs +7 -1
  44. package/lib/core/index.mjs.map +1 -1
  45. package/lib/core/passthrough.d.mts +1 -1
  46. package/lib/core/passthrough.d.ts +1 -1
  47. package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
  48. package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
  49. package/lib/core/utils/executeHandlers.d.mts +1 -1
  50. package/lib/core/utils/executeHandlers.d.ts +1 -1
  51. package/lib/core/utils/executeHandlers.js +4 -0
  52. package/lib/core/utils/executeHandlers.js.map +1 -1
  53. package/lib/core/utils/executeHandlers.mjs +6 -0
  54. package/lib/core/utils/executeHandlers.mjs.map +1 -1
  55. package/lib/core/utils/handleRequest.d.mts +2 -2
  56. package/lib/core/utils/handleRequest.d.ts +2 -2
  57. package/lib/core/utils/handleRequest.js.map +1 -1
  58. package/lib/core/utils/handleRequest.mjs.map +1 -1
  59. package/lib/core/utils/handleWebSocketEvent.d.mts +16 -0
  60. package/lib/core/utils/handleWebSocketEvent.d.ts +16 -0
  61. package/lib/core/utils/handleWebSocketEvent.js +59 -0
  62. package/lib/core/utils/handleWebSocketEvent.js.map +1 -0
  63. package/lib/core/utils/handleWebSocketEvent.mjs +39 -0
  64. package/lib/core/utils/handleWebSocketEvent.mjs.map +1 -0
  65. package/lib/core/utils/internal/parseGraphQLRequest.d.mts +2 -2
  66. package/lib/core/utils/internal/parseGraphQLRequest.d.ts +2 -2
  67. package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
  68. package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
  69. package/lib/core/utils/internal/requestHandlerUtils.d.mts +1 -1
  70. package/lib/core/utils/internal/requestHandlerUtils.d.ts +1 -1
  71. package/lib/core/utils/logging/getTimestamp.d.mts +4 -1
  72. package/lib/core/utils/logging/getTimestamp.d.ts +4 -1
  73. package/lib/core/utils/logging/getTimestamp.js +6 -2
  74. package/lib/core/utils/logging/getTimestamp.js.map +1 -1
  75. package/lib/core/utils/logging/getTimestamp.mjs +6 -2
  76. package/lib/core/utils/logging/getTimestamp.mjs.map +1 -1
  77. package/lib/core/utils/matching/matchRequestUrl.d.mts +2 -1
  78. package/lib/core/utils/matching/matchRequestUrl.d.ts +2 -1
  79. package/lib/core/utils/matching/matchRequestUrl.js +4 -0
  80. package/lib/core/utils/matching/matchRequestUrl.js.map +1 -1
  81. package/lib/core/utils/matching/matchRequestUrl.mjs +4 -0
  82. package/lib/core/utils/matching/matchRequestUrl.mjs.map +1 -1
  83. package/lib/core/ws/WebSocketClientManager.d.mts +64 -0
  84. package/lib/core/ws/WebSocketClientManager.d.ts +64 -0
  85. package/lib/core/ws/WebSocketClientManager.js +123 -0
  86. package/lib/core/ws/WebSocketClientManager.js.map +1 -0
  87. package/lib/core/ws/WebSocketClientManager.mjs +103 -0
  88. package/lib/core/ws/WebSocketClientManager.mjs.map +1 -0
  89. package/lib/core/ws/utils/attachWebSocketLogger.d.mts +34 -0
  90. package/lib/core/ws/utils/attachWebSocketLogger.d.ts +34 -0
  91. package/lib/core/ws/utils/attachWebSocketLogger.js +211 -0
  92. package/lib/core/ws/utils/attachWebSocketLogger.js.map +1 -0
  93. package/lib/core/ws/utils/attachWebSocketLogger.mjs +191 -0
  94. package/lib/core/ws/utils/attachWebSocketLogger.mjs.map +1 -0
  95. package/lib/core/ws/utils/getMessageLength.d.mts +11 -0
  96. package/lib/core/ws/utils/getMessageLength.d.ts +11 -0
  97. package/lib/core/ws/utils/getMessageLength.js +33 -0
  98. package/lib/core/ws/utils/getMessageLength.js.map +1 -0
  99. package/lib/core/ws/utils/getMessageLength.mjs +13 -0
  100. package/lib/core/ws/utils/getMessageLength.mjs.map +1 -0
  101. package/lib/core/ws/utils/getPublicData.d.mts +5 -0
  102. package/lib/core/ws/utils/getPublicData.d.ts +5 -0
  103. package/lib/core/ws/utils/getPublicData.js +36 -0
  104. package/lib/core/ws/utils/getPublicData.js.map +1 -0
  105. package/lib/core/ws/utils/getPublicData.mjs +16 -0
  106. package/lib/core/ws/utils/getPublicData.mjs.map +1 -0
  107. package/lib/core/ws/utils/truncateMessage.d.mts +3 -0
  108. package/lib/core/ws/utils/truncateMessage.d.ts +3 -0
  109. package/lib/core/ws/utils/truncateMessage.js +31 -0
  110. package/lib/core/ws/utils/truncateMessage.js.map +1 -0
  111. package/lib/core/ws/utils/truncateMessage.mjs +11 -0
  112. package/lib/core/ws/utils/truncateMessage.mjs.map +1 -0
  113. package/lib/core/ws/webSocketInterceptor.d.mts +5 -0
  114. package/lib/core/ws/webSocketInterceptor.d.ts +5 -0
  115. package/lib/core/ws/webSocketInterceptor.js +26 -0
  116. package/lib/core/ws/webSocketInterceptor.js.map +1 -0
  117. package/lib/core/ws/webSocketInterceptor.mjs +6 -0
  118. package/lib/core/ws/webSocketInterceptor.mjs.map +1 -0
  119. package/lib/core/ws/ws.d.mts +44 -0
  120. package/lib/core/ws/ws.d.ts +44 -0
  121. package/lib/core/ws/ws.js +82 -0
  122. package/lib/core/ws/ws.js.map +1 -0
  123. package/lib/core/ws/ws.mjs +65 -0
  124. package/lib/core/ws/ws.mjs.map +1 -0
  125. package/lib/iife/index.js +967 -11
  126. package/lib/iife/index.js.map +1 -1
  127. package/lib/mockServiceWorker.js +1 -1
  128. package/lib/native/index.d.mts +6 -5
  129. package/lib/native/index.d.ts +6 -5
  130. package/lib/native/index.js +13 -0
  131. package/lib/native/index.js.map +1 -1
  132. package/lib/native/index.mjs +13 -0
  133. package/lib/native/index.mjs.map +1 -1
  134. package/lib/node/index.d.mts +8 -7
  135. package/lib/node/index.d.ts +8 -7
  136. package/lib/node/index.js +13 -0
  137. package/lib/node/index.js.map +1 -1
  138. package/lib/node/index.mjs +13 -0
  139. package/lib/node/index.mjs.map +1 -1
  140. package/package.json +26 -21
  141. package/src/browser/setupWorker/glossary.ts +10 -10
  142. package/src/browser/setupWorker/setupWorker.ts +34 -3
  143. package/src/core/SetupApi.ts +28 -20
  144. package/src/core/handlers/WebSocketHandler.ts +71 -0
  145. package/src/core/index.ts +8 -1
  146. package/src/core/utils/executeHandlers.ts +6 -2
  147. package/src/core/utils/handleRequest.ts +1 -2
  148. package/src/core/utils/handleWebSocketEvent.ts +59 -0
  149. package/src/core/utils/logging/getTimestamp.test.ts +20 -6
  150. package/src/core/utils/logging/getTimestamp.ts +11 -6
  151. package/src/core/utils/matching/matchRequestUrl.test.ts +44 -0
  152. package/src/core/utils/matching/matchRequestUrl.ts +4 -0
  153. package/src/core/ws/WebSocketClientManager.test.ts +159 -0
  154. package/src/core/ws/WebSocketClientManager.ts +170 -0
  155. package/src/core/ws/utils/attachWebSocketLogger.ts +262 -0
  156. package/src/core/ws/utils/getMessageLength.test.ts +16 -0
  157. package/src/core/ws/utils/getMessageLength.ts +19 -0
  158. package/src/core/ws/utils/getPublicData.test.ts +38 -0
  159. package/src/core/ws/utils/getPublicData.ts +17 -0
  160. package/src/core/ws/utils/truncateMessage.test.ts +12 -0
  161. package/src/core/ws/utils/truncateMessage.ts +9 -0
  162. package/src/core/ws/webSocketInterceptor.ts +3 -0
  163. package/src/core/ws/ws.test.ts +23 -0
  164. package/src/core/ws/ws.ts +108 -0
  165. package/src/node/SetupServerApi.ts +8 -7
  166. package/src/node/SetupServerCommonApi.ts +14 -1
  167. package/src/node/glossary.ts +5 -7
  168. package/src/node/setupServer.ts +2 -1
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @vitest-environment node-websocket
3
+ */
4
+ import { randomUUID } from 'node:crypto'
5
+ import {
6
+ WebSocketClientConnection,
7
+ WebSocketTransport,
8
+ } from '@mswjs/interceptors/WebSocket'
9
+ import {
10
+ WebSocketClientManager,
11
+ WebSocketBroadcastChannelMessage,
12
+ WebSocketRemoteClientConnection,
13
+ } from './WebSocketClientManager'
14
+
15
+ const channel = new BroadcastChannel('test:channel')
16
+ vi.spyOn(channel, 'postMessage')
17
+
18
+ const socket = new WebSocket('ws://localhost')
19
+ const transport = {
20
+ onOutgoing: vi.fn(),
21
+ onIncoming: vi.fn(),
22
+ onClose: vi.fn(),
23
+ send: vi.fn(),
24
+ close: vi.fn(),
25
+ } satisfies WebSocketTransport
26
+
27
+ afterEach(() => {
28
+ vi.resetAllMocks()
29
+ })
30
+
31
+ it('adds a client from this runtime to the list of clients', () => {
32
+ const manager = new WebSocketClientManager(channel)
33
+ const connection = new WebSocketClientConnection(socket, transport)
34
+
35
+ manager.addConnection(connection)
36
+
37
+ // Must add the client to the list of clients.
38
+ expect(Array.from(manager.clients.values())).toEqual([connection])
39
+
40
+ // Must emit the connection open event to notify other runtimes.
41
+ expect(channel.postMessage).toHaveBeenCalledWith({
42
+ type: 'connection:open',
43
+ payload: {
44
+ clientId: connection.id,
45
+ url: socket.url,
46
+ },
47
+ } satisfies WebSocketBroadcastChannelMessage)
48
+ })
49
+
50
+ it('adds a client from another runtime to the list of clients', async () => {
51
+ const clientId = randomUUID()
52
+ const url = new URL('ws://localhost')
53
+ const manager = new WebSocketClientManager(channel)
54
+
55
+ channel.dispatchEvent(
56
+ new MessageEvent<WebSocketBroadcastChannelMessage>('message', {
57
+ data: {
58
+ type: 'connection:open',
59
+ payload: {
60
+ clientId,
61
+ url: url.href,
62
+ },
63
+ },
64
+ }),
65
+ )
66
+
67
+ await vi.waitFor(() => {
68
+ expect(Array.from(manager.clients.values())).toEqual([
69
+ new WebSocketRemoteClientConnection(clientId, url, channel),
70
+ ])
71
+ })
72
+ })
73
+
74
+ it('replays a "send" event coming from another runtime', async () => {
75
+ const manager = new WebSocketClientManager(channel)
76
+ const connection = new WebSocketClientConnection(socket, transport)
77
+ manager.addConnection(connection)
78
+ vi.spyOn(connection, 'send')
79
+
80
+ // Emulate another runtime signaling this connection to receive data.
81
+ channel.dispatchEvent(
82
+ new MessageEvent<WebSocketBroadcastChannelMessage>('message', {
83
+ data: {
84
+ type: 'extraneous:send',
85
+ payload: {
86
+ clientId: connection.id,
87
+ data: 'hello',
88
+ },
89
+ },
90
+ }),
91
+ )
92
+
93
+ await vi.waitFor(() => {
94
+ // Must execute the requested operation on the connection.
95
+ expect(connection.send).toHaveBeenCalledWith('hello')
96
+ expect(connection.send).toHaveBeenCalledTimes(1)
97
+ })
98
+ })
99
+
100
+ it('replays a "close" event coming from another runtime', async () => {
101
+ const manager = new WebSocketClientManager(channel)
102
+ const connection = new WebSocketClientConnection(socket, transport)
103
+ manager.addConnection(connection)
104
+ vi.spyOn(connection, 'close')
105
+
106
+ // Emulate another runtime signaling this connection to close.
107
+ channel.dispatchEvent(
108
+ new MessageEvent<WebSocketBroadcastChannelMessage>('message', {
109
+ data: {
110
+ type: 'extraneous:close',
111
+ payload: {
112
+ clientId: connection.id,
113
+ code: 1000,
114
+ reason: 'Normal closure',
115
+ },
116
+ },
117
+ }),
118
+ )
119
+
120
+ await vi.waitFor(() => {
121
+ // Must execute the requested operation on the connection.
122
+ expect(connection.close).toHaveBeenCalledWith(1000, 'Normal closure')
123
+ expect(connection.close).toHaveBeenCalledTimes(1)
124
+ })
125
+ })
126
+
127
+ it('removes the extraneous message listener when the connection closes', async () => {
128
+ const manager = new WebSocketClientManager(channel)
129
+ const connection = new WebSocketClientConnection(socket, transport)
130
+ vi.spyOn(connection, 'close').mockImplementationOnce(() => {
131
+ /**
132
+ * @note This is a nasty hack so we don't have to uncouple
133
+ * the connection from transport. Creating a mock transport
134
+ * is difficult because it relies on the `WebSocketOverride` class.
135
+ * All we care here is that closing the connection triggers
136
+ * the transport closure, which it always does.
137
+ */
138
+ connection['transport'].onClose()
139
+ })
140
+ vi.spyOn(connection, 'send')
141
+
142
+ manager.addConnection(connection)
143
+ connection.close()
144
+
145
+ // Signals from other runtimes have no effect on the closed connection.
146
+ channel.dispatchEvent(
147
+ new MessageEvent<WebSocketBroadcastChannelMessage>('message', {
148
+ data: {
149
+ type: 'extraneous:send',
150
+ payload: {
151
+ clientId: connection.id,
152
+ data: 'hello',
153
+ },
154
+ },
155
+ }),
156
+ )
157
+
158
+ expect(connection.send).not.toHaveBeenCalled()
159
+ })
@@ -0,0 +1,170 @@
1
+ import type {
2
+ WebSocketData,
3
+ WebSocketClientConnection,
4
+ WebSocketClientConnectionProtocol,
5
+ } from '@mswjs/interceptors/WebSocket'
6
+
7
+ export type WebSocketBroadcastChannelMessage =
8
+ | {
9
+ type: 'connection:open'
10
+ payload: {
11
+ clientId: string
12
+ url: string
13
+ }
14
+ }
15
+ | {
16
+ type: 'extraneous:send'
17
+ payload: {
18
+ clientId: string
19
+ data: WebSocketData
20
+ }
21
+ }
22
+ | {
23
+ type: 'extraneous:close'
24
+ payload: {
25
+ clientId: string
26
+ code?: number
27
+ reason?: string
28
+ }
29
+ }
30
+
31
+ export const kAddByClientId = Symbol('kAddByClientId')
32
+
33
+ /**
34
+ * A manager responsible for accumulating WebSocket client
35
+ * connections across different browser runtimes.
36
+ */
37
+ export class WebSocketClientManager {
38
+ /**
39
+ * All active WebSocket client connections.
40
+ */
41
+ public clients: Set<WebSocketClientConnectionProtocol>
42
+
43
+ constructor(private channel: BroadcastChannel) {
44
+ this.clients = new Set()
45
+
46
+ this.channel.addEventListener('message', (message) => {
47
+ const { type, payload } = message.data as WebSocketBroadcastChannelMessage
48
+
49
+ switch (type) {
50
+ case 'connection:open': {
51
+ // When another runtime notifies about a new connection,
52
+ // create a connection wrapper class and add it to the set.
53
+ this.onRemoteConnection(payload.clientId, new URL(payload.url))
54
+ break
55
+ }
56
+ }
57
+ })
58
+ }
59
+
60
+ /**
61
+ * Adds the given `WebSocket` client connection to the set
62
+ * of all connections. The given connection is always the complete
63
+ * connection object because `addConnection()` is called only
64
+ * for the opened connections in the same runtime.
65
+ */
66
+ public addConnection(client: WebSocketClientConnection): void {
67
+ this.clients.add(client)
68
+
69
+ // Signal to other runtimes about this connection.
70
+ this.channel.postMessage({
71
+ type: 'connection:open',
72
+ payload: {
73
+ clientId: client.id,
74
+ url: client.url.toString(),
75
+ },
76
+ } as WebSocketBroadcastChannelMessage)
77
+
78
+ // Instruct the current client how to handle events
79
+ // coming from other runtimes (e.g. when calling `.broadcast()`).
80
+ const handleExtraneousMessage = (
81
+ message: MessageEvent<WebSocketBroadcastChannelMessage>,
82
+ ) => {
83
+ const { type, payload } = message.data
84
+
85
+ // Ignore broadcasted messages for other clients.
86
+ if (
87
+ typeof payload === 'object' &&
88
+ 'clientId' in payload &&
89
+ payload.clientId !== client.id
90
+ ) {
91
+ return
92
+ }
93
+
94
+ switch (type) {
95
+ case 'extraneous:send': {
96
+ client.send(payload.data)
97
+ break
98
+ }
99
+
100
+ case 'extraneous:close': {
101
+ client.close(payload.code, payload.reason)
102
+ break
103
+ }
104
+ }
105
+ }
106
+
107
+ const abortController = new AbortController()
108
+
109
+ this.channel.addEventListener('message', handleExtraneousMessage, {
110
+ signal: abortController.signal,
111
+ })
112
+
113
+ // Once closed, this connection cannot be operated on.
114
+ // This must include the extraneous runtimes as well.
115
+ client.addEventListener('close', () => abortController.abort(), {
116
+ once: true,
117
+ })
118
+ }
119
+
120
+ /**
121
+ * Adds a client connection wrapper to operate with
122
+ * WebSocket client connections in other runtimes.
123
+ */
124
+ private onRemoteConnection(id: string, url: URL): void {
125
+ this.clients.add(
126
+ // Create a connection-compatible instance that can
127
+ // operate with this client from a different runtime
128
+ // using the BroadcastChannel messages.
129
+ new WebSocketRemoteClientConnection(id, url, this.channel),
130
+ )
131
+ }
132
+ }
133
+
134
+ /**
135
+ * A wrapper class to operate with WebSocket client connections
136
+ * from other runtimes. This class maintains 1-1 public API
137
+ * compatibility to the `WebSocketClientConnection` but relies
138
+ * on the given `BroadcastChannel` to communicate instructions
139
+ * with the client connections from other runtimes.
140
+ */
141
+ export class WebSocketRemoteClientConnection
142
+ implements WebSocketClientConnectionProtocol
143
+ {
144
+ constructor(
145
+ public readonly id: string,
146
+ public readonly url: URL,
147
+ private channel: BroadcastChannel,
148
+ ) {}
149
+
150
+ send(data: WebSocketData): void {
151
+ this.channel.postMessage({
152
+ type: 'extraneous:send',
153
+ payload: {
154
+ clientId: this.id,
155
+ data,
156
+ },
157
+ } as WebSocketBroadcastChannelMessage)
158
+ }
159
+
160
+ close(code?: number | undefined, reason?: string | undefined): void {
161
+ this.channel.postMessage({
162
+ type: 'extraneous:close',
163
+ payload: {
164
+ clientId: this.id,
165
+ code,
166
+ reason,
167
+ },
168
+ } as WebSocketBroadcastChannelMessage)
169
+ }
170
+ }
@@ -0,0 +1,262 @@
1
+ import type {
2
+ WebSocketClientConnection,
3
+ WebSocketConnectionData,
4
+ WebSocketData,
5
+ } from '@mswjs/interceptors/WebSocket'
6
+ import { devUtils } from '../../utils/internal/devUtils'
7
+ import { getTimestamp } from '../../utils/logging/getTimestamp'
8
+ import { toPublicUrl } from '../../utils/request/toPublicUrl'
9
+ import { getMessageLength } from './getMessageLength'
10
+ import { getPublicData } from './getPublicData'
11
+
12
+ export function attachWebSocketLogger(
13
+ connection: WebSocketConnectionData,
14
+ ): void {
15
+ const { client, server } = connection
16
+
17
+ logConnectionOpen(client)
18
+
19
+ // Log the events sent from the WebSocket client.
20
+ // WebSocket client connection object is written from the
21
+ // server's perspective so these message events are outgoing.
22
+ /**
23
+ * @todo Provide the reference to the exact event handler
24
+ * that called this `client.send()`.
25
+ */
26
+ client.addEventListener('message', (event) => {
27
+ logOutgoingClientMessage(event)
28
+ })
29
+
30
+ client.addEventListener('close', (event) => {
31
+ logConnectionClose(event)
32
+ })
33
+
34
+ // Log the events received by the WebSocket client.
35
+ // "client.socket" references the actual WebSocket instance
36
+ // so these message events are incoming messages.
37
+ client.socket.addEventListener('message', (event) => {
38
+ logIncomingClientMessage(event)
39
+ })
40
+
41
+ // Log client errors (connection closures due to errors).
42
+ client.socket.addEventListener('error', (event) => {
43
+ logClientError(event)
44
+ })
45
+
46
+ client.send = new Proxy(client.send, {
47
+ apply(target, thisArg, args) {
48
+ const [data] = args
49
+ const messageEvent = new MessageEvent('message', { data })
50
+ Object.defineProperties(messageEvent, {
51
+ currentTarget: {
52
+ enumerable: true,
53
+ writable: false,
54
+ value: client.socket,
55
+ },
56
+ target: {
57
+ enumerable: true,
58
+ writable: false,
59
+ value: client.socket,
60
+ },
61
+ })
62
+ logIncomingMockedClientMessage(messageEvent)
63
+
64
+ return Reflect.apply(target, thisArg, args)
65
+ },
66
+ })
67
+
68
+ server.addEventListener(
69
+ 'open',
70
+ () => {
71
+ server.addEventListener('message', (event) => {
72
+ logIncomingServerMessage(event)
73
+ })
74
+ },
75
+ { once: true },
76
+ )
77
+
78
+ // Log outgoing client events initiated by the event handler.
79
+ // The actual client never sent these but the handler did.
80
+ server.send = new Proxy(server.send, {
81
+ apply(target, thisArg, args) {
82
+ const [data] = args
83
+ const messageEvent = new MessageEvent('message', { data })
84
+ Object.defineProperties(messageEvent, {
85
+ currentTarget: {
86
+ enumerable: true,
87
+ writable: false,
88
+ value: server['realWebSocket'],
89
+ },
90
+ target: {
91
+ enumerable: true,
92
+ writable: false,
93
+ value: server['realWebSocket'],
94
+ },
95
+ })
96
+
97
+ logOutgoingMockedClientMessage(messageEvent)
98
+
99
+ return Reflect.apply(target, thisArg, args)
100
+ },
101
+ })
102
+ }
103
+
104
+ /**
105
+ * Prints the WebSocket connection.
106
+ * This is meant to be logged by every WebSocket handler
107
+ * that intercepted this connection. This helps you see
108
+ * what handlers observe this connection.
109
+ */
110
+ export function logConnectionOpen(client: WebSocketClientConnection) {
111
+ const publicUrl = toPublicUrl(client.url)
112
+
113
+ console.groupCollapsed(
114
+ devUtils.formatMessage(`${getTimestamp()} %c▸%c ${publicUrl}`),
115
+ 'color:blue',
116
+ 'color:inherit',
117
+ )
118
+ console.log('Client:', client.socket)
119
+ console.groupEnd()
120
+ }
121
+
122
+ /**
123
+ * Prints the outgoing client message.
124
+ */
125
+ export async function logOutgoingClientMessage(
126
+ event: MessageEvent<WebSocketData>,
127
+ ) {
128
+ const byteLength = getMessageLength(event.data)
129
+ const publicData = await getPublicData(event.data)
130
+
131
+ console.groupCollapsed(
132
+ devUtils.formatMessage(
133
+ `${getTimestamp({ milliseconds: true })} %c↑%c ${publicData} %c${byteLength}%c`,
134
+ ),
135
+ 'color:green',
136
+ 'color:inherit',
137
+ 'color:gray;font-weight:normal',
138
+ 'color:inherit;font-weight:inherit',
139
+ )
140
+ console.log(event)
141
+ console.groupEnd()
142
+ }
143
+
144
+ /**
145
+ * Prints the outgoing client message initiated
146
+ * by `server.send()` in the event handler.
147
+ */
148
+ export async function logOutgoingMockedClientMessage(
149
+ event: MessageEvent<WebSocketData>,
150
+ ) {
151
+ const byteLength = getMessageLength(event.data)
152
+ const publicData = await getPublicData(event.data)
153
+
154
+ console.groupCollapsed(
155
+ devUtils.formatMessage(
156
+ `${getTimestamp({ milliseconds: true })} %c⇡%c ${publicData} %c${byteLength}%c`,
157
+ ),
158
+ 'color:orangered',
159
+ 'color:inherit',
160
+ 'color:gray;font-weight:normal',
161
+ 'color:inherit;font-weight:inherit',
162
+ )
163
+ console.log(event)
164
+ console.groupEnd()
165
+ }
166
+
167
+ /**
168
+ * Prings the message received by the WebSocket client.
169
+ * This is fired when the "message" event is dispatched
170
+ * on the actual WebSocket client instance, and translates to
171
+ * the client receiving a message from the server.
172
+ */
173
+ export async function logIncomingClientMessage(
174
+ event: MessageEvent<WebSocketData>,
175
+ ) {
176
+ const byteLength = getMessageLength(event.data)
177
+ const publicData = await getPublicData(event.data)
178
+
179
+ console.groupCollapsed(
180
+ devUtils.formatMessage(
181
+ `${getTimestamp({ milliseconds: true })} %c↓%c ${publicData} %c${byteLength}%c`,
182
+ ),
183
+ 'color:red',
184
+ 'color:inherit',
185
+ 'color:gray;font-weight:normal',
186
+ 'color:inherit;font-weight:inherit',
187
+ )
188
+ console.log(event)
189
+ console.groupEnd()
190
+ }
191
+
192
+ /**
193
+ * Prints the outgoing client message initiated
194
+ * by `client.send()` in the event handler.
195
+ */
196
+ export async function logIncomingMockedClientMessage(
197
+ event: MessageEvent<WebSocketData>,
198
+ ) {
199
+ const byteLength = getMessageLength(event.data)
200
+ const publicData = await getPublicData(event.data)
201
+
202
+ console.groupCollapsed(
203
+ devUtils.formatMessage(
204
+ `${getTimestamp({ milliseconds: true })} %c⇣%c ${publicData} %c${byteLength}%c`,
205
+ ),
206
+ 'color:orangered',
207
+ 'color:inherit',
208
+ 'color:gray;font-weight:normal',
209
+ 'color:inherit;font-weight:inherit',
210
+ )
211
+ console.log(event)
212
+ console.groupEnd()
213
+ }
214
+
215
+ function logConnectionClose(event: CloseEvent) {
216
+ const target = event.target as WebSocket
217
+ const publicUrl = toPublicUrl(target.url)
218
+
219
+ console.groupCollapsed(
220
+ devUtils.formatMessage(
221
+ `${getTimestamp({ milliseconds: true })} %c■%c ${publicUrl}`,
222
+ ),
223
+ 'color:blue',
224
+ 'color:inherit',
225
+ )
226
+ console.log(event)
227
+ console.groupEnd()
228
+ }
229
+
230
+ export async function logIncomingServerMessage(
231
+ event: MessageEvent<WebSocketData>,
232
+ ) {
233
+ const byteLength = getMessageLength(event.data)
234
+ const publicData = await getPublicData(event.data)
235
+
236
+ console.groupCollapsed(
237
+ devUtils.formatMessage(
238
+ `${getTimestamp({ milliseconds: true })} %c⇣%c ${publicData} %c${byteLength}%c`,
239
+ ),
240
+ 'color:orangered',
241
+ 'color:inherit',
242
+ 'color:gray;font-weight:normal',
243
+ 'color:inherit;font-weight:inherit',
244
+ )
245
+ console.log(event)
246
+ console.groupEnd()
247
+ }
248
+
249
+ function logClientError(event: Event) {
250
+ const socket = event.target as WebSocket
251
+ const publicUrl = toPublicUrl(socket.url)
252
+
253
+ console.groupCollapsed(
254
+ devUtils.formatMessage(
255
+ `${getTimestamp({ milliseconds: true })} %c\u00D7%c ${publicUrl}`,
256
+ ),
257
+ 'color:red',
258
+ 'color:inherit',
259
+ )
260
+ console.log(event)
261
+ console.groupEnd()
262
+ }
@@ -0,0 +1,16 @@
1
+ import { getMessageLength } from './getMessageLength'
2
+
3
+ it('returns the length of the string', () => {
4
+ expect(getMessageLength('')).toBe(0)
5
+ expect(getMessageLength('hello')).toBe(5)
6
+ })
7
+
8
+ it('returns the size of the Blob', () => {
9
+ expect(getMessageLength(new Blob())).toBe(0)
10
+ expect(getMessageLength(new Blob(['hello']))).toBe(5)
11
+ })
12
+
13
+ it('returns the byte length of ArrayBuffer', () => {
14
+ expect(getMessageLength(new ArrayBuffer(0))).toBe(0)
15
+ expect(getMessageLength(new ArrayBuffer(5))).toBe(5)
16
+ })
@@ -0,0 +1,19 @@
1
+ import type { WebSocketData } from '@mswjs/interceptors/lib/browser/interceptors/WebSocket'
2
+
3
+ /**
4
+ * Returns the byte length of the given WebSocket message.
5
+ * @example
6
+ * getMessageLength('hello') // 5
7
+ * getMessageLength(new Blob(['hello'])) // 5
8
+ */
9
+ export function getMessageLength(data: WebSocketData): number {
10
+ if (data instanceof Blob) {
11
+ return data.size
12
+ }
13
+
14
+ if (data instanceof ArrayBuffer) {
15
+ return data.byteLength
16
+ }
17
+
18
+ return new Blob([data]).size
19
+ }
@@ -0,0 +1,38 @@
1
+ import { getPublicData } from './getPublicData'
2
+
3
+ it('returns a short string as-is', async () => {
4
+ expect(await getPublicData('')).toBe('')
5
+ expect(await getPublicData('hello')).toBe('hello')
6
+ })
7
+
8
+ it('returns a truncated long string', async () => {
9
+ expect(await getPublicData('this is a very long string')).toBe(
10
+ 'this is a very long stri…',
11
+ )
12
+ })
13
+
14
+ it('returns a short Blob text as-is', async () => {
15
+ expect(await getPublicData(new Blob(['']))).toBe('Blob()')
16
+ expect(await getPublicData(new Blob(['hello']))).toBe('Blob(hello)')
17
+ })
18
+
19
+ it('returns a truncated long Blob text', async () => {
20
+ expect(await getPublicData(new Blob(['this is a very long string']))).toBe(
21
+ 'Blob(this is a very long stri…)',
22
+ )
23
+ })
24
+
25
+ it('returns a short ArrayBuffer text as-is', async () => {
26
+ expect(await getPublicData(new TextEncoder().encode(''))).toBe(
27
+ 'ArrayBuffer()',
28
+ )
29
+ expect(await getPublicData(new TextEncoder().encode('hello'))).toBe(
30
+ 'ArrayBuffer(hello)',
31
+ )
32
+ })
33
+
34
+ it('returns a truncated ArrayBuffer text', async () => {
35
+ expect(
36
+ await getPublicData(new TextEncoder().encode('this is a very long string')),
37
+ ).toBe('ArrayBuffer(this is a very long stri…)')
38
+ })
@@ -0,0 +1,17 @@
1
+ import { WebSocketData } from '@mswjs/interceptors/WebSocket'
2
+ import { truncateMessage } from './truncateMessage'
3
+
4
+ export async function getPublicData(data: WebSocketData): Promise<string> {
5
+ if (data instanceof Blob) {
6
+ const text = await data.text()
7
+ return `Blob(${truncateMessage(text)})`
8
+ }
9
+
10
+ // Handle all ArrayBuffer-like objects.
11
+ if (typeof data === 'object' && 'byteLength' in data) {
12
+ const text = new TextDecoder().decode(data)
13
+ return `ArrayBuffer(${truncateMessage(text)})`
14
+ }
15
+
16
+ return truncateMessage(data)
17
+ }
@@ -0,0 +1,12 @@
1
+ import { truncateMessage } from './truncateMessage'
2
+
3
+ it('returns a short string as-is', () => {
4
+ expect(truncateMessage('')).toBe('')
5
+ expect(truncateMessage('hello')).toBe('hello')
6
+ })
7
+
8
+ it('truncates a long string', () => {
9
+ expect(truncateMessage('this is a very long string')).toBe(
10
+ 'this is a very long stri…',
11
+ )
12
+ })
@@ -0,0 +1,9 @@
1
+ const MAX_LENGTH = 24
2
+
3
+ export function truncateMessage(message: string): string {
4
+ if (message.length <= MAX_LENGTH) {
5
+ return message
6
+ }
7
+
8
+ return `${message.slice(0, MAX_LENGTH)}…`
9
+ }