-
-
Notifications
You must be signed in to change notification settings - Fork 128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add WebSocket class interceptor #501
Conversation
bd761c9
to
7c542cf
Compare
7c542cf
to
fb95d06
Compare
e41e442
to
d6feba3
Compare
bd7f7f8
to
6df9a3a
Compare
src/interceptors/WebSocket/index.ts
Outdated
constructor() { | ||
super({ | ||
name: 'websocket', | ||
interceptors: [new WebSocketClassInterceptor()], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The WebSocketInterceptor
itself has started as a batched interceptor on a premise that in the future it will combine multiple, transport-related interceptors (WebSocket, XHR polling).
I believe that's redundant now. I don't plan to support any non-standard WebSocket implementations, and this pull request implements the interceptor for the standard implementation already.
protected checkEnvironment(): boolean { | ||
// Enable this interceptor in any environment | ||
// that has a global WebSocket API. | ||
return typeof globalThis.WebSocket !== 'undefined' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The WebSocket interceptor will be available anywhere where the global WebSocket
class is present.
For testing, we are setting that class in a custom Vitest environment, taking it from undici
and making it a global class.
// All WebSocket instances are mocked and don't forward | ||
// any events to the original server (no connection established). | ||
// To forward the events, the user must use the "server.send()" API. | ||
const mockWs = new WebSocketClassOverride(url, protocols) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All constructed WebSocket instances will point to a "dummy" implementation of the WebSocket class (i.e. mock). This makes all connections mocked by default (will never reach the actual server unless you tell them to). I find this to be a good default when developing against non-existing WebSocket servers.
Reason
new WebSocket(url)
connections to non-existing addresses throw a special kind of error in the browser that cannot be caught by any means.
data: WebSocketSendData | ||
) => void | ||
|
||
export abstract class WebSocketTransport { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Transport is an abstract class responsible for handling incoming and outgoing events from a WebSocket instance.
At its core, the transport is the barebones implementation of how to apply client.send()
events and how to listen to client.on()
events.
Later, the transport is wrapped in the Connection instance to acquire a nicer public API.
* client connection. The user can control the connection, | ||
* send and receive events. | ||
*/ | ||
export class WebSocketClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a WebSocket client connection class. It represents a single WebSocket client connected to the "server" (the interceptor).
This API is implementation-agnostic and provides the public methods to interact with the intercepted WebSocket client.
* WebSocket server connection. It's idle by default but you can | ||
* establish it by calling `server.connect()`. | ||
*/ | ||
export class WebSocketServer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the WebSocket client representation, this is the actual WebSocket server connection representation. It's used to:
- Establish the original connection to the server;
- Intercept the incoming server events (the real ones);
- Forward any (intercepted) outgoing client events to the actual server (if you wish a full passthrough interception).
8c261f2
to
d19208c
Compare
Socket.IO supportimport { io } from '@mswjs/socket.io-parser'
interceptor.on('connection', (args) => {
const client = io(args.client)
const server = io(args.server)
// MessageEvent data is decoded.
client.on('message', (event) => {
console.log(event.data) // "hello"
})
// Custom events are supported
// (decoded from the generic MessageEvent).
client.on('custom-event', (data) => {})
// Outgoing messages are encoded.
client.send('hello')
// Custom events are encoded and transformed
// into the generic MessageEvent.
client.emit('custom-event',new Blob(['hello']))
// Incoming original server events are decoded.
server.on('server-event', (data) => {})
// Sent messages to the server are encoded.
// Also sent in 2 packets as Socket.IO does it.
server.send(new Blob(['hello']))
}) |
The API itself is complete. One thing remaining is proper test coverage, specifically addressing the bug in the |
Released: v0.26.0 🎉This has been released in v0.26.0! Make sure to always update to the latest version ( Predictable release automation by @ossjs/release. |
README.md
for the feature documentation.Changes
globalThis.WebSocket
class.client
and theserver
APIs to represent the incoming client connection and the original server connection respectively in the connection listener of the interceptor.WebSocketClassClient
,WebSocketClassServer
,WebSocketClassTransport
.Todo
ws
module we use for testing doesn't pass theinstanceof
check on dispatched events in Node, causing some tests to fail (Use Node.js 15 nativeEventTarget
object websockets/ws#1818).test/modules/WebSocket/WebSocketClass/exchange/websocket.server.connect.test.ts
Event
..send()
and.close()
and its input).onopen
and etc)origin
on theMessageEvent
from the socket. Check if the interceptor should do that too (inspect the original WebSocket event).error
event correctly. It's emitted in the following scenarios:> 1000 <= 1015
(https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1)WebSocketClient#emit
. It's non-standard.Support custom encoder/decoder for (a) JSON transfer; (b) to support custom event format (like SocketIO).client.addEventListener()
.Third-party support