msw 2.2.2 → 2.3.0-ws.rc-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/cli/init.js +0 -0
- package/config/scripts/postinstall.js +0 -0
- package/lib/browser/index.d.mts +7 -6
- package/lib/browser/index.d.ts +7 -6
- package/lib/browser/index.js +32 -15
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/index.mjs +32 -15
- package/lib/browser/index.mjs.map +1 -1
- package/lib/core/{GraphQLHandler-jOzqbxSK.d.ts → GraphQLHandler-Cbu12sb0.d.ts} +1 -1
- package/lib/core/{GraphQLHandler-AenIUdwE.d.mts → GraphQLHandler-QGQY_9Rc.d.mts} +1 -1
- package/lib/core/{HttpResponse-wcp03c7-.d.mts → HttpResponse-BWB1yDNM.d.mts} +2 -2
- package/lib/core/{HttpResponse-_514VQ9z.d.ts → HttpResponse-DeJBWGN5.d.ts} +2 -2
- package/lib/core/HttpResponse.d.mts +1 -1
- package/lib/core/HttpResponse.d.ts +1 -1
- package/lib/core/SetupApi.d.mts +15 -12
- package/lib/core/SetupApi.d.ts +15 -12
- package/lib/core/SetupApi.js +3 -1
- package/lib/core/SetupApi.js.map +1 -1
- package/lib/core/SetupApi.mjs +3 -1
- package/lib/core/SetupApi.mjs.map +1 -1
- package/lib/core/getResponse.d.mts +1 -1
- package/lib/core/getResponse.d.ts +1 -1
- package/lib/core/graphql.d.mts +2 -2
- package/lib/core/graphql.d.ts +2 -2
- package/lib/core/handlers/GraphQLHandler.d.mts +2 -2
- package/lib/core/handlers/GraphQLHandler.d.ts +2 -2
- package/lib/core/handlers/HttpHandler.d.mts +1 -1
- package/lib/core/handlers/HttpHandler.d.ts +1 -1
- package/lib/core/handlers/RequestHandler.d.mts +1 -1
- package/lib/core/handlers/RequestHandler.d.ts +1 -1
- package/lib/core/handlers/WebSocketHandler.d.mts +38 -0
- package/lib/core/handlers/WebSocketHandler.d.ts +38 -0
- package/lib/core/handlers/WebSocketHandler.js +65 -0
- package/lib/core/handlers/WebSocketHandler.js.map +1 -0
- package/lib/core/handlers/WebSocketHandler.mjs +47 -0
- package/lib/core/handlers/WebSocketHandler.mjs.map +1 -0
- package/lib/core/http.d.mts +1 -1
- package/lib/core/http.d.ts +1 -1
- package/lib/core/index.d.mts +5 -2
- package/lib/core/index.d.ts +5 -2
- package/lib/core/index.js +3 -1
- package/lib/core/index.js.map +1 -1
- package/lib/core/index.mjs +3 -1
- package/lib/core/index.mjs.map +1 -1
- package/lib/core/passthrough.d.mts +1 -1
- package/lib/core/passthrough.d.ts +1 -1
- package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
- package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
- package/lib/core/utils/executeHandlers.d.mts +1 -1
- package/lib/core/utils/executeHandlers.d.ts +1 -1
- package/lib/core/utils/executeHandlers.js +4 -0
- package/lib/core/utils/executeHandlers.js.map +1 -1
- package/lib/core/utils/executeHandlers.mjs +6 -0
- package/lib/core/utils/executeHandlers.mjs.map +1 -1
- package/lib/core/utils/handleRequest.d.mts +2 -2
- package/lib/core/utils/handleRequest.d.ts +2 -2
- package/lib/core/utils/handleRequest.js.map +1 -1
- package/lib/core/utils/handleRequest.mjs.map +1 -1
- package/lib/core/utils/handleWebSocketEvent.d.mts +10 -0
- package/lib/core/utils/handleWebSocketEvent.d.ts +10 -0
- package/lib/core/utils/handleWebSocketEvent.js +56 -0
- package/lib/core/utils/handleWebSocketEvent.js.map +1 -0
- package/lib/core/utils/handleWebSocketEvent.mjs +40 -0
- package/lib/core/utils/handleWebSocketEvent.mjs.map +1 -0
- package/lib/core/utils/internal/parseGraphQLRequest.d.mts +2 -2
- package/lib/core/utils/internal/parseGraphQLRequest.d.ts +2 -2
- package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
- package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
- package/lib/core/utils/internal/requestHandlerUtils.d.mts +1 -1
- package/lib/core/utils/internal/requestHandlerUtils.d.ts +1 -1
- package/lib/core/utils/matching/matchRequestUrl.d.mts +2 -1
- package/lib/core/utils/matching/matchRequestUrl.d.ts +2 -1
- package/lib/core/utils/matching/matchRequestUrl.js +4 -0
- package/lib/core/utils/matching/matchRequestUrl.js.map +1 -1
- package/lib/core/utils/matching/matchRequestUrl.mjs +4 -0
- package/lib/core/utils/matching/matchRequestUrl.mjs.map +1 -1
- package/lib/core/ws/WebSocketClientManager.d.mts +64 -0
- package/lib/core/ws/WebSocketClientManager.d.ts +64 -0
- package/lib/core/ws/WebSocketClientManager.js +123 -0
- package/lib/core/ws/WebSocketClientManager.js.map +1 -0
- package/lib/core/ws/WebSocketClientManager.mjs +103 -0
- package/lib/core/ws/WebSocketClientManager.mjs.map +1 -0
- package/lib/core/ws/webSocketInterceptor.d.mts +5 -0
- package/lib/core/ws/webSocketInterceptor.d.ts +5 -0
- package/lib/core/ws/webSocketInterceptor.js +26 -0
- package/lib/core/ws/webSocketInterceptor.js.map +1 -0
- package/lib/core/ws/webSocketInterceptor.mjs +6 -0
- package/lib/core/ws/webSocketInterceptor.mjs.map +1 -0
- package/lib/core/ws/ws.d.mts +44 -0
- package/lib/core/ws/ws.d.ts +44 -0
- package/lib/core/ws/ws.js +82 -0
- package/lib/core/ws/ws.js.map +1 -0
- package/lib/core/ws/ws.mjs +65 -0
- package/lib/core/ws/ws.mjs.map +1 -0
- package/lib/iife/index.js +703 -17
- package/lib/iife/index.js.map +1 -1
- package/lib/mockServiceWorker.js +1 -1
- package/lib/native/index.d.mts +6 -5
- package/lib/native/index.d.ts +6 -5
- package/lib/native/index.js +7 -0
- package/lib/native/index.js.map +1 -1
- package/lib/native/index.mjs +7 -0
- package/lib/native/index.mjs.map +1 -1
- package/lib/node/index.d.mts +8 -7
- package/lib/node/index.d.ts +8 -7
- package/lib/node/index.js +7 -0
- package/lib/node/index.js.map +1 -1
- package/lib/node/index.mjs +7 -0
- package/lib/node/index.mjs.map +1 -1
- package/package.json +29 -24
- package/src/browser/setupWorker/glossary.ts +10 -10
- package/src/browser/setupWorker/setupWorker.ts +17 -3
- package/src/core/SetupApi.ts +28 -20
- package/src/core/handlers/WebSocketHandler.ts +89 -0
- package/src/core/index.ts +3 -0
- package/src/core/utils/executeHandlers.ts +6 -2
- package/src/core/utils/handleRequest.ts +1 -2
- package/src/core/utils/handleWebSocketEvent.ts +49 -0
- package/src/core/utils/matching/matchRequestUrl.test.ts +44 -0
- package/src/core/utils/matching/matchRequestUrl.ts +4 -0
- package/src/core/ws/WebSocketClientManager.test.ts +159 -0
- package/src/core/ws/WebSocketClientManager.ts +170 -0
- package/src/core/ws/webSocketInterceptor.ts +3 -0
- package/src/core/ws/ws.test.ts +23 -0
- package/src/core/ws/ws.ts +108 -0
- package/src/node/SetupServerApi.ts +8 -7
- package/src/node/SetupServerCommonApi.ts +10 -1
- package/src/node/glossary.ts +5 -7
- package/src/node/setupServer.ts +2 -1
|
@@ -5,13 +5,11 @@ import {
|
|
|
5
5
|
SharedOptions,
|
|
6
6
|
} from '~/core/sharedOptions'
|
|
7
7
|
import { ServiceWorkerMessage } from './start/utils/createMessageChannel'
|
|
8
|
-
import {
|
|
9
|
-
RequestHandler,
|
|
10
|
-
RequestHandlerDefaultInfo,
|
|
11
|
-
} from '~/core/handlers/RequestHandler'
|
|
8
|
+
import { RequestHandler } from '~/core/handlers/RequestHandler'
|
|
12
9
|
import type { HttpRequestEventMap, Interceptor } from '@mswjs/interceptors'
|
|
13
|
-
import { Path } from '~/core/utils/matching/matchRequestUrl'
|
|
14
|
-
import { RequiredDeep } from '~/core/typeUtils'
|
|
10
|
+
import type { Path } from '~/core/utils/matching/matchRequestUrl'
|
|
11
|
+
import type { RequiredDeep } from '~/core/typeUtils'
|
|
12
|
+
import type { WebSocketHandler } from '~/core/handlers/WebSocketHandler'
|
|
15
13
|
|
|
16
14
|
export type ResolvedPath = Path | URL
|
|
17
15
|
|
|
@@ -84,7 +82,7 @@ export interface SetupWorkerInternalContext {
|
|
|
84
82
|
startOptions: RequiredDeep<StartOptions>
|
|
85
83
|
worker: ServiceWorker | null
|
|
86
84
|
registration: ServiceWorkerRegistration | null
|
|
87
|
-
getRequestHandlers(): Array<RequestHandler>
|
|
85
|
+
getRequestHandlers(): Array<RequestHandler | WebSocketHandler>
|
|
88
86
|
requests: Map<string, Request>
|
|
89
87
|
emitter: Emitter<LifeCycleEventsMap>
|
|
90
88
|
keepAliveInterval?: number
|
|
@@ -208,7 +206,7 @@ export interface SetupWorker {
|
|
|
208
206
|
*
|
|
209
207
|
* @see {@link https://mswjs.io/docs/api/setup-worker/use `worker.use()` API reference}
|
|
210
208
|
*/
|
|
211
|
-
use: (...handlers: RequestHandler
|
|
209
|
+
use: (...handlers: Array<RequestHandler | WebSocketHandler>) => void
|
|
212
210
|
|
|
213
211
|
/**
|
|
214
212
|
* Marks all request handlers that respond using `res.once()` as unused.
|
|
@@ -223,14 +221,16 @@ export interface SetupWorker {
|
|
|
223
221
|
*
|
|
224
222
|
* @see {@link https://mswjs.io/docs/api/setup-worker/reset-handlers `worker.resetHandlers()` API reference}
|
|
225
223
|
*/
|
|
226
|
-
resetHandlers: (
|
|
224
|
+
resetHandlers: (
|
|
225
|
+
...nextHandlers: Array<RequestHandler | WebSocketHandler>
|
|
226
|
+
) => void
|
|
227
227
|
|
|
228
228
|
/**
|
|
229
229
|
* Returns a readonly list of currently active request handlers.
|
|
230
230
|
*
|
|
231
231
|
* @see {@link https://mswjs.io/docs/api/setup-worker/list-handlers `worker.listHandlers()` API reference}
|
|
232
232
|
*/
|
|
233
|
-
listHandlers(): ReadonlyArray<RequestHandler
|
|
233
|
+
listHandlers(): ReadonlyArray<RequestHandler | WebSocketHandler>
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
236
|
* Life-cycle events.
|
|
@@ -18,9 +18,12 @@ import { createFallbackStop } from './stop/createFallbackStop'
|
|
|
18
18
|
import { devUtils } from '~/core/utils/internal/devUtils'
|
|
19
19
|
import { SetupApi } from '~/core/SetupApi'
|
|
20
20
|
import { mergeRight } from '~/core/utils/internal/mergeRight'
|
|
21
|
-
import { LifeCycleEventsMap } from '~/core/sharedOptions'
|
|
21
|
+
import type { LifeCycleEventsMap } from '~/core/sharedOptions'
|
|
22
|
+
import type { WebSocketHandler } from '~/core/handlers/WebSocketHandler'
|
|
22
23
|
import { SetupWorker } from './glossary'
|
|
23
24
|
import { supportsReadableStreamTransfer } from '../utils/supportsReadableStreamTransfer'
|
|
25
|
+
import { webSocketInterceptor } from '~/core/ws/webSocketInterceptor'
|
|
26
|
+
import { handleWebSocketEvent } from '~/core/utils/handleWebSocketEvent'
|
|
24
27
|
|
|
25
28
|
interface Listener {
|
|
26
29
|
target: EventTarget
|
|
@@ -37,7 +40,7 @@ export class SetupWorkerApi
|
|
|
37
40
|
private stopHandler: StopHandler = null as any
|
|
38
41
|
private listeners: Array<Listener>
|
|
39
42
|
|
|
40
|
-
constructor(...handlers: Array<RequestHandler>) {
|
|
43
|
+
constructor(...handlers: Array<RequestHandler | WebSocketHandler>) {
|
|
41
44
|
super(...handlers)
|
|
42
45
|
|
|
43
46
|
invariant(
|
|
@@ -176,6 +179,15 @@ export class SetupWorkerApi
|
|
|
176
179
|
options,
|
|
177
180
|
) as SetupWorkerInternalContext['startOptions']
|
|
178
181
|
|
|
182
|
+
handleWebSocketEvent(() => {
|
|
183
|
+
return this.handlersController.currentHandlers()
|
|
184
|
+
})
|
|
185
|
+
webSocketInterceptor.apply()
|
|
186
|
+
|
|
187
|
+
this.subscriptions.push(() => {
|
|
188
|
+
webSocketInterceptor.dispose()
|
|
189
|
+
})
|
|
190
|
+
|
|
179
191
|
return await this.startHandler(this.context.startOptions, options)
|
|
180
192
|
}
|
|
181
193
|
|
|
@@ -193,6 +205,8 @@ export class SetupWorkerApi
|
|
|
193
205
|
*
|
|
194
206
|
* @see {@link https://mswjs.io/docs/api/setup-worker `setupWorker()` API reference}
|
|
195
207
|
*/
|
|
196
|
-
export function setupWorker(
|
|
208
|
+
export function setupWorker(
|
|
209
|
+
...handlers: Array<RequestHandler | WebSocketHandler>
|
|
210
|
+
): SetupWorker {
|
|
197
211
|
return new SetupWorkerApi(...handlers)
|
|
198
212
|
}
|
package/src/core/SetupApi.ts
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
import { invariant } from 'outvariant'
|
|
2
2
|
import { EventMap, Emitter } from 'strict-event-emitter'
|
|
3
|
-
import {
|
|
4
|
-
RequestHandler,
|
|
5
|
-
RequestHandlerDefaultInfo,
|
|
6
|
-
} from './handlers/RequestHandler'
|
|
3
|
+
import { RequestHandler } from './handlers/RequestHandler'
|
|
7
4
|
import { LifeCycleEventEmitter } from './sharedOptions'
|
|
8
5
|
import { devUtils } from './utils/internal/devUtils'
|
|
9
6
|
import { pipeEvents } from './utils/internal/pipeEvents'
|
|
10
7
|
import { toReadonlyArray } from './utils/internal/toReadonlyArray'
|
|
11
8
|
import { Disposable } from './utils/internal/Disposable'
|
|
9
|
+
import type { WebSocketHandler } from './handlers/WebSocketHandler'
|
|
12
10
|
|
|
13
11
|
export abstract class HandlersController {
|
|
14
|
-
abstract prepend(
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
abstract prepend(
|
|
13
|
+
runtimeHandlers: Array<RequestHandler | WebSocketHandler>,
|
|
14
|
+
): void
|
|
15
|
+
abstract reset(nextHandles: Array<RequestHandler | WebSocketHandler>): void
|
|
16
|
+
abstract currentHandlers(): Array<RequestHandler | WebSocketHandler>
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export class InMemoryHandlersController implements HandlersController {
|
|
20
|
-
private handlers: Array<RequestHandler>
|
|
20
|
+
private handlers: Array<RequestHandler | WebSocketHandler>
|
|
21
21
|
|
|
22
|
-
constructor(
|
|
22
|
+
constructor(
|
|
23
|
+
private initialHandlers: Array<RequestHandler | WebSocketHandler>,
|
|
24
|
+
) {
|
|
23
25
|
this.handlers = [...initialHandlers]
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
public prepend(
|
|
28
|
+
public prepend(
|
|
29
|
+
runtimeHandles: Array<RequestHandler | WebSocketHandler>,
|
|
30
|
+
): void {
|
|
27
31
|
this.handlers.unshift(...runtimeHandles)
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
public reset(nextHandlers: Array<RequestHandler>): void {
|
|
34
|
+
public reset(nextHandlers: Array<RequestHandler | WebSocketHandler>): void {
|
|
31
35
|
this.handlers =
|
|
32
36
|
nextHandlers.length > 0 ? [...nextHandlers] : [...this.initialHandlers]
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
public currentHandlers(): Array<RequestHandler> {
|
|
39
|
+
public currentHandlers(): Array<RequestHandler | WebSocketHandler> {
|
|
36
40
|
return this.handlers
|
|
37
41
|
}
|
|
38
42
|
}
|
|
@@ -47,7 +51,7 @@ export abstract class SetupApi<EventsMap extends EventMap> extends Disposable {
|
|
|
47
51
|
|
|
48
52
|
public readonly events: LifeCycleEventEmitter<EventsMap>
|
|
49
53
|
|
|
50
|
-
constructor(...initialHandlers: Array<RequestHandler>) {
|
|
54
|
+
constructor(...initialHandlers: Array<RequestHandler | WebSocketHandler>) {
|
|
51
55
|
super()
|
|
52
56
|
|
|
53
57
|
invariant(
|
|
@@ -71,12 +75,14 @@ export abstract class SetupApi<EventsMap extends EventMap> extends Disposable {
|
|
|
71
75
|
})
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
private validateHandlers(handlers: ReadonlyArray<
|
|
78
|
+
private validateHandlers(handlers: ReadonlyArray<unknown>): boolean {
|
|
75
79
|
// Guard against incorrect call signature of the setup API.
|
|
76
80
|
return handlers.every((handler) => !Array.isArray(handler))
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
public use(
|
|
83
|
+
public use(
|
|
84
|
+
...runtimeHandlers: Array<RequestHandler | WebSocketHandler>
|
|
85
|
+
): void {
|
|
80
86
|
invariant(
|
|
81
87
|
this.validateHandlers(runtimeHandlers),
|
|
82
88
|
devUtils.formatMessage(
|
|
@@ -89,17 +95,19 @@ export abstract class SetupApi<EventsMap extends EventMap> extends Disposable {
|
|
|
89
95
|
|
|
90
96
|
public restoreHandlers(): void {
|
|
91
97
|
this.handlersController.currentHandlers().forEach((handler) => {
|
|
92
|
-
|
|
98
|
+
if ('isUsed' in handler) {
|
|
99
|
+
handler.isUsed = false
|
|
100
|
+
}
|
|
93
101
|
})
|
|
94
102
|
}
|
|
95
103
|
|
|
96
|
-
public resetHandlers(
|
|
104
|
+
public resetHandlers(
|
|
105
|
+
...nextHandlers: Array<RequestHandler | WebSocketHandler>
|
|
106
|
+
): void {
|
|
97
107
|
this.handlersController.reset(nextHandlers)
|
|
98
108
|
}
|
|
99
109
|
|
|
100
|
-
public listHandlers(): ReadonlyArray<
|
|
101
|
-
RequestHandler<RequestHandlerDefaultInfo, any, any>
|
|
102
|
-
> {
|
|
110
|
+
public listHandlers(): ReadonlyArray<RequestHandler | WebSocketHandler> {
|
|
103
111
|
return toReadonlyArray(this.handlersController.currentHandlers())
|
|
104
112
|
}
|
|
105
113
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Emitter } from 'strict-event-emitter'
|
|
2
|
+
import type {
|
|
3
|
+
WebSocketClientConnection,
|
|
4
|
+
WebSocketServerConnection,
|
|
5
|
+
} from '@mswjs/interceptors/WebSocket'
|
|
6
|
+
import {
|
|
7
|
+
type Match,
|
|
8
|
+
type Path,
|
|
9
|
+
type PathParams,
|
|
10
|
+
matchRequestUrl,
|
|
11
|
+
} from '../utils/matching/matchRequestUrl'
|
|
12
|
+
|
|
13
|
+
type WebSocketHandlerParsedResult = {
|
|
14
|
+
match: Match
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type WebSocketHandlerEventMap = {
|
|
18
|
+
connection: [
|
|
19
|
+
args: {
|
|
20
|
+
client: WebSocketClientConnection
|
|
21
|
+
server: WebSocketServerConnection
|
|
22
|
+
params: PathParams
|
|
23
|
+
},
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type WebSocketHandlerIncomingEvent = MessageEvent<{
|
|
28
|
+
client: WebSocketClientConnection
|
|
29
|
+
server: WebSocketServerConnection
|
|
30
|
+
}>
|
|
31
|
+
|
|
32
|
+
export const kEmitter = Symbol('kEmitter')
|
|
33
|
+
export const kDispatchEvent = Symbol('kDispatchEvent')
|
|
34
|
+
export const kDefaultPrevented = Symbol('kDefaultPrevented')
|
|
35
|
+
|
|
36
|
+
export class WebSocketHandler {
|
|
37
|
+
protected [kEmitter]: Emitter<WebSocketHandlerEventMap>
|
|
38
|
+
|
|
39
|
+
constructor(private readonly url: Path) {
|
|
40
|
+
this[kEmitter] = new Emitter()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public parse(args: {
|
|
44
|
+
event: WebSocketHandlerIncomingEvent
|
|
45
|
+
}): WebSocketHandlerParsedResult {
|
|
46
|
+
const connection = args.event.data
|
|
47
|
+
const match = matchRequestUrl(connection.client.url, this.url)
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
match,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public predicate(args: {
|
|
55
|
+
event: WebSocketHandlerIncomingEvent
|
|
56
|
+
parsedResult: WebSocketHandlerParsedResult
|
|
57
|
+
}): boolean {
|
|
58
|
+
return args.parsedResult.match.matches
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async [kDispatchEvent](event: MessageEvent<any>): Promise<void> {
|
|
62
|
+
const parsedResult = this.parse({ event })
|
|
63
|
+
const shouldIntercept = this.predicate({ event, parsedResult })
|
|
64
|
+
|
|
65
|
+
if (!shouldIntercept) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Account for other matching event handlers that've already prevented this event.
|
|
70
|
+
if (!Reflect.get(event, kDefaultPrevented)) {
|
|
71
|
+
// At this point, the WebSocket connection URL has matched the handler.
|
|
72
|
+
// Prevent the default behavior of establishing the connection as-is.
|
|
73
|
+
// Use internal symbol because we aren't actually dispatching this
|
|
74
|
+
// event. Events can only marked as cancelable and can be prevented
|
|
75
|
+
// when dispatched on an EventTarget.
|
|
76
|
+
Reflect.set(event, kDefaultPrevented, true)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const connection = event.data
|
|
80
|
+
|
|
81
|
+
// Emit the connection event on the handler.
|
|
82
|
+
// This is what the developer adds listeners for.
|
|
83
|
+
this[kEmitter].emit('connection', {
|
|
84
|
+
client: connection.client,
|
|
85
|
+
server: connection.server,
|
|
86
|
+
params: parsedResult.match.params || {},
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/core/index.ts
CHANGED
|
@@ -9,6 +9,9 @@ export { HttpHandler, HttpMethods } from './handlers/HttpHandler'
|
|
|
9
9
|
export { graphql } from './graphql'
|
|
10
10
|
export { GraphQLHandler } from './handlers/GraphQLHandler'
|
|
11
11
|
|
|
12
|
+
/* WebSocket */
|
|
13
|
+
export { ws } from './ws/ws'
|
|
14
|
+
|
|
12
15
|
/* Utils */
|
|
13
16
|
export { matchRequestUrl } from './utils/matching/matchRequestUrl'
|
|
14
17
|
export * from './utils/handleRequest'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RequestHandler,
|
|
3
|
-
RequestHandlerExecutionResult,
|
|
3
|
+
type RequestHandlerExecutionResult,
|
|
4
4
|
} from '../handlers/RequestHandler'
|
|
5
5
|
|
|
6
6
|
export interface HandlersExecutionResult {
|
|
@@ -18,7 +18,7 @@ export interface ResponseResolutionContext {
|
|
|
18
18
|
* Returns the execution result object containing any matching request
|
|
19
19
|
* handler and any mocked response it returned.
|
|
20
20
|
*/
|
|
21
|
-
export const executeHandlers = async <Handlers extends Array<
|
|
21
|
+
export const executeHandlers = async <Handlers extends Array<unknown>>({
|
|
22
22
|
request,
|
|
23
23
|
requestId,
|
|
24
24
|
handlers,
|
|
@@ -33,6 +33,10 @@ export const executeHandlers = async <Handlers extends Array<RequestHandler>>({
|
|
|
33
33
|
let result: RequestHandlerExecutionResult<any> | null = null
|
|
34
34
|
|
|
35
35
|
for (const handler of handlers) {
|
|
36
|
+
if (!(handler instanceof RequestHandler)) {
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
result = await handler.run({ request, requestId, resolutionContext })
|
|
37
41
|
|
|
38
42
|
// If the handler produces some result for this request,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { until } from '@open-draft/until'
|
|
2
2
|
import { Emitter } from 'strict-event-emitter'
|
|
3
|
-
import { RequestHandler } from '../handlers/RequestHandler'
|
|
4
3
|
import { LifeCycleEventsMap, SharedOptions } from '../sharedOptions'
|
|
5
4
|
import { RequiredDeep } from '../typeUtils'
|
|
6
5
|
import { HandlersExecutionResult, executeHandlers } from './executeHandlers'
|
|
@@ -45,7 +44,7 @@ export interface HandleRequestOptions {
|
|
|
45
44
|
export async function handleRequest(
|
|
46
45
|
request: Request,
|
|
47
46
|
requestId: string,
|
|
48
|
-
handlers: Array<
|
|
47
|
+
handlers: Array<unknown>,
|
|
49
48
|
options: RequiredDeep<SharedOptions>,
|
|
50
49
|
emitter: Emitter<LifeCycleEventsMap>,
|
|
51
50
|
handleRequestOptions?: HandleRequestOptions,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RequestHandler } from '../handlers/RequestHandler'
|
|
2
|
+
import {
|
|
3
|
+
WebSocketHandler,
|
|
4
|
+
kDefaultPrevented,
|
|
5
|
+
kDispatchEvent,
|
|
6
|
+
} from '../handlers/WebSocketHandler'
|
|
7
|
+
import { webSocketInterceptor } from '../ws/webSocketInterceptor'
|
|
8
|
+
|
|
9
|
+
export function handleWebSocketEvent(
|
|
10
|
+
getCurrentHandlers: () => Array<RequestHandler | WebSocketHandler>,
|
|
11
|
+
) {
|
|
12
|
+
webSocketInterceptor.on('connection', (connection) => {
|
|
13
|
+
const handlers = getCurrentHandlers()
|
|
14
|
+
|
|
15
|
+
const connectionEvent = new MessageEvent('connection', {
|
|
16
|
+
data: connection,
|
|
17
|
+
/**
|
|
18
|
+
* @note This message event should be marked as "cancelable"
|
|
19
|
+
* to have its default prevented using "event.preventDefault()".
|
|
20
|
+
* There's a bug in Node.js that breaks the "cancelable" flag.
|
|
21
|
+
* @see https://github.com/nodejs/node/issues/51767
|
|
22
|
+
*/
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
Object.defineProperty(connectionEvent, kDefaultPrevented, {
|
|
26
|
+
enumerable: false,
|
|
27
|
+
writable: true,
|
|
28
|
+
value: false,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Iterate over the handlers and forward the connection
|
|
32
|
+
// event to WebSocket event handlers. This is equivalent
|
|
33
|
+
// to dispatching that event onto multiple listeners.
|
|
34
|
+
for (const handler of handlers) {
|
|
35
|
+
if (handler instanceof WebSocketHandler) {
|
|
36
|
+
handler[kDispatchEvent](connectionEvent)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// If none of the "ws" handlers matched,
|
|
41
|
+
// establish the WebSocket connection as-is.
|
|
42
|
+
if (!Reflect.get(connectionEvent, kDefaultPrevented)) {
|
|
43
|
+
connection.server.connect()
|
|
44
|
+
connection.client.addEventListener('message', (event) => {
|
|
45
|
+
connection.server.send(event.data)
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
@@ -61,6 +61,50 @@ describe('matchRequestUrl', () => {
|
|
|
61
61
|
expect(match).toHaveProperty('matches', false)
|
|
62
62
|
expect(match).toHaveProperty('params', {})
|
|
63
63
|
})
|
|
64
|
+
|
|
65
|
+
test('returns true for matching WebSocket URL', () => {
|
|
66
|
+
expect(
|
|
67
|
+
matchRequestUrl(new URL('ws://test.mswjs.io'), 'ws://test.mswjs.io'),
|
|
68
|
+
).toEqual({
|
|
69
|
+
matches: true,
|
|
70
|
+
params: {},
|
|
71
|
+
})
|
|
72
|
+
expect(
|
|
73
|
+
matchRequestUrl(new URL('wss://test.mswjs.io'), 'wss://test.mswjs.io'),
|
|
74
|
+
).toEqual({
|
|
75
|
+
matches: true,
|
|
76
|
+
params: {},
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('returns false for non-matching WebSocket URL', () => {
|
|
81
|
+
expect(
|
|
82
|
+
matchRequestUrl(new URL('ws://test.mswjs.io'), 'ws://foo.mswjs.io'),
|
|
83
|
+
).toEqual({
|
|
84
|
+
matches: false,
|
|
85
|
+
params: {},
|
|
86
|
+
})
|
|
87
|
+
expect(
|
|
88
|
+
matchRequestUrl(new URL('wss://test.mswjs.io'), 'wss://completely.diff'),
|
|
89
|
+
).toEqual({
|
|
90
|
+
matches: false,
|
|
91
|
+
params: {},
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('returns path parameters when matched a WebSocket URL', () => {
|
|
96
|
+
expect(
|
|
97
|
+
matchRequestUrl(
|
|
98
|
+
new URL('wss://test.mswjs.io'),
|
|
99
|
+
'wss://:service.mswjs.io',
|
|
100
|
+
),
|
|
101
|
+
).toEqual({
|
|
102
|
+
matches: true,
|
|
103
|
+
params: {
|
|
104
|
+
service: 'test',
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
})
|
|
64
108
|
})
|
|
65
109
|
|
|
66
110
|
describe('coercePath', () => {
|
|
@@ -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
|
+
})
|