Skip to content

Commit 3b05a61

Browse files
committed
fix: support stopping event propagation
1 parent b6c8374 commit 3b05a61

File tree

5 files changed

+575
-8
lines changed

5 files changed

+575
-8
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@
137137
"@bundled-es-modules/statuses": "^1.0.1",
138138
"@bundled-es-modules/tough-cookie": "^0.1.6",
139139
"@inquirer/confirm": "^3.0.0",
140-
"@mswjs/interceptors": "^0.36.1",
140+
"@mswjs/interceptors": "^0.36.4",
141141
"@open-draft/deferred-promise": "^2.2.0",
142142
"@open-draft/until": "^2.1.0",
143143
"@types/cookie": "^0.6.0",

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/handlers/WebSocketHandler.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Emitter } from 'strict-event-emitter'
2+
import { createRequestId } from '@mswjs/interceptors'
23
import type { WebSocketConnectionData } from '@mswjs/interceptors/WebSocket'
34
import {
45
type Match,
@@ -23,13 +24,18 @@ interface WebSocketHandlerConnection extends WebSocketConnectionData {
2324
export const kEmitter = Symbol('kEmitter')
2425
export const kDispatchEvent = Symbol('kDispatchEvent')
2526
export const kSender = Symbol('kSender')
27+
const kStopPropagationPatched = Symbol('kStopPropagationPatched')
28+
const KOnStopPropagation = Symbol('KOnStopPropagation')
2629

2730
export class WebSocketHandler {
31+
public id: string
2832
public callFrame?: string
2933

3034
protected [kEmitter]: Emitter<WebSocketHandlerEventMap>
3135

3236
constructor(private readonly url: Path) {
37+
this.id = createRequestId()
38+
3339
this[kEmitter] = new Emitter()
3440
this.callFrame = getCallFrame(new Error())
3541
}
@@ -63,8 +69,74 @@ export class WebSocketHandler {
6369
params: parsedResult.match.params || {},
6470
}
6571

72+
// Support `event.stopPropagation()` for various client/server events.
73+
connection.client.addEventListener(
74+
'message',
75+
createStopPropagationListener(this),
76+
)
77+
connection.client.addEventListener(
78+
'close',
79+
createStopPropagationListener(this),
80+
)
81+
82+
connection.server.addEventListener(
83+
'open',
84+
createStopPropagationListener(this),
85+
)
86+
connection.server.addEventListener(
87+
'message',
88+
createStopPropagationListener(this),
89+
)
90+
connection.server.addEventListener(
91+
'error',
92+
createStopPropagationListener(this),
93+
)
94+
connection.server.addEventListener(
95+
'close',
96+
createStopPropagationListener(this),
97+
)
98+
6699
// Emit the connection event on the handler.
67100
// This is what the developer adds listeners for.
68101
this[kEmitter].emit('connection', resolvedConnection)
69102
}
70103
}
104+
105+
function createStopPropagationListener(handler: WebSocketHandler) {
106+
return function stopPropagationListener(event: Event) {
107+
const propagationStoppedAt = Reflect.get(event, 'kPropagationStoppedAt') as
108+
| string
109+
| undefined
110+
111+
if (propagationStoppedAt && handler.id !== propagationStoppedAt) {
112+
event.stopImmediatePropagation()
113+
return
114+
}
115+
116+
Object.defineProperty(event, KOnStopPropagation, {
117+
value(this: WebSocketHandler) {
118+
Object.defineProperty(event, 'kPropagationStoppedAt', {
119+
value: handler.id,
120+
})
121+
},
122+
configurable: true,
123+
})
124+
125+
// Since the same event instance is shared between all client/server objects,
126+
// make sure to patch its `stopPropagation` method only once.
127+
if (!Reflect.get(event, kStopPropagationPatched)) {
128+
event.stopPropagation = new Proxy(event.stopPropagation, {
129+
apply: (target, thisArg, args) => {
130+
Reflect.get(event, KOnStopPropagation)?.call(handler)
131+
return Reflect.apply(target, thisArg, args)
132+
},
133+
})
134+
135+
Object.defineProperty(event, kStopPropagationPatched, {
136+
value: true,
137+
// If something else attempts to redefine this, throw.
138+
configurable: false,
139+
})
140+
}
141+
}
142+
}

test/node/ws-api/on-unhandled-request/error.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,20 @@ afterAll(() => {
2121
vi.restoreAllMocks()
2222
})
2323

24-
it(
24+
it.only(
2525
'errors on unhandled WebSocket connection',
2626
server.boundary(async () => {
2727
const socket = new WebSocket('wss://localhost:4321')
2828
const errorListener = vi.fn()
2929

30-
await vi.waitFor(() => {
30+
await vi.waitUntil(() => {
3131
return new Promise((resolve, reject) => {
3232
// These are intentionally swapped. The connection MUST error.
3333
socket.addEventListener('error', errorListener)
3434
socket.addEventListener('error', resolve)
35-
socket.onopen = reject
35+
socket.onopen = () => {
36+
reject(new Error('WebSocket connection opened unexpectedly'))
37+
}
3638
})
3739
})
3840

0 commit comments

Comments
 (0)