Prerequisites
Scope
Adds a new behavior
Compatibility
Context
msw@2.6.0 added support to intercept and mock WebSocket connections. This opens up a path for a Zimic WebSocket interceptor.
Feature description
Add support to intercept and mock WebSocket connections in Zimic. To do this, I believe we need to create a new entity, WebSocketInterceptor, which is independent from the current HttpInterceptor. We also need a different set of schema primitives, as WebSocket is based on messages, not requests and responses as HTTP.
Requirements
Questions
Development
Code snippets (draft)
import { expect } from 'vitest';
import { WebSocketSchema, WebSocketClient } from '@zimic/ws';
import { WebSocketServer } from '@zimic/ws/server';
import { createWebSocketInterceptor } from '@zimic/interceptor/ws';
interface Post {
id: string;
content: string;
}
type Schema = WebSocketSchema<
| { type: 'ping' }
| { type: 'pong' }
| { type: 'post:create'; data: { post: Post } }
| { type: 'post:update'; data: { post: Post; before: Post } }
| { type: 'post:delete'; data: { post: Post } }
>;
const interceptor = createWebSocketInterceptor<Schema>({
type: 'local',
baseURL: 'ws://localhost:3000',
messageSaving: {
enabled: true,
safeLimit: 1000
},
});
async function main() {
expect(interceptor.isRunning).toBe(false);
expect(interceptor.server).instanceOf(WebSocketServer)
expect(interceptor.server.readyState).toBe(WebSocket.CLOSED););
expect(interceptor.server.messages).toHaveLength(0);
expect(interceptor.clients).toHaveLength(0);
await interceptor.start();
expect(interceptor.isRunning).toBe(true);
expect(interceptor.server).instanceOf(WebSocketServer);
expect(interceptor.server.messages).toHaveLength(0);
expect(interceptor.clients).toHaveLength(0);
// Simulating connections from an application...
// await application.connect({ ws: interceptor.baseURL });
// await application.connect({ ws: interceptor.baseURL });
expect(interceptor.server.messages).toHaveLength(0);
expect(interceptor.clients).toHaveLength(2);
for (const client of interceptor.clients) {
expect(client).instanceOf(WebSocketClient);
expect(client.readyState).toBe(WebSocket.OPEN);
expect(client.messages).toHaveLength(0);
// `socket.messages` is only available in WebSocketInterceptorClient, not WebSocketClient
}
// `server.send(data)` broadcasts to all clients
// `server.send(data, { to: clients })` sends a message to specific clients
// `client.send(data)` sends a message to the server
interceptor.server
.message()
.with({ type: 'ping' })
.run((message) => message.receiver.send({ type: 'pong' }, { to: message.sender }))
.times(1);
interceptor.server
.message()
.with({ type: 'ping' })
.send({ type: 'pong' })
.times(1);
interceptor.clients[0]
.message()
.with({ type: 'pong' })
.run((message) => message.sender.send({ type: 'ping' }, { to: message.receiver }));
interceptor.clients[0]
.message()
.with({ type: 'pong' })
.send({ type: 'ping' })
.delay(500)
.times(1);
await interceptor.server
.send(
{ type: 'ping' },
{ to: interceptor.clients[0] },
);
await interceptor.clients[0]
.send({
type: 'post:create',
data: { post: { id: crypto.randomUUID(), content: '...' } },
});
interceptor.server
.message()
.from(interceptor.clients[0])
.with({ type: 'post:create' })
.send({
type: 'post:update',
data: {
post: { id: crypto.randomUUID(), content: 'Updated content' },
before: { id: crypto.randomUUID(), content: '...' },
},
})
.delay(200)
.times(1);
interceptor.server.on('connection', (client, request) => {
console.log(client.readyState, request.headers['sec-websocket-protocol']);
});
await waitFor(() => {
expect(interceptor.clients[0].messages).toHaveLength(2);
expect(interceptor.clients[1].messages).toHaveLength(1);
});
// Check the received message data.
expect(interceptor.clients[0].messages[0]).toEqual({
sender: interceptor.clients[0],
receiver: interceptor.server,
data: { type: 'ping' },
});
await interceptor.stop();
expect(interceptor.isRunning).toBe(false);
expect(interceptor.server.messages).toHaveLength(0);
expect(interceptor.clients).toHaveLength(0);
}
void main();
Prerequisites
Scope
Adds a new behavior
Compatibility
Context
msw@2.6.0 added support to intercept and mock WebSocket connections. This opens up a path for a Zimic WebSocket interceptor.
Feature description
Add support to intercept and mock WebSocket connections in Zimic. To do this, I believe we need to create a new entity,
WebSocketInterceptor, which is independent from the currentHttpInterceptor. We also need a different set of schema primitives, as WebSocket is based on messages, not requests and responses as HTTP.Requirements
handler.requests.connectionevent).WebSocketentities and not be coupled to library-specific implementations.socket.iosupport may be considered and included if not too distant from the native Web Socket entities.@zimic/wsshould not automatically parse the event data. If the data is a JSON-compatible type, type the string in such a way that callingJSON.parse()over it yields the correct type (use type branding).Questions
transports: ['websocket']?Development
@zimic/interceptor/ws] Create@zimic/interceptor/ws@zimic/interceptor/ws] CreateLocalWebSocketInterceptor@zimic/interceptor/ws] CreateRemoteWebSocketInterceptor@zimic/interceptor/ws] CreatecreateWebSocketInterceptor@zimic/interceptor/ws]WebSocketInterceptor#start()@zimic/interceptor/ws]WebSocketInterceptor#stop()@zimic/interceptor/ws]WebSocketInterceptor#isRunning@zimic/interceptor/ws]WebSocketInterceptor#server@zimic/interceptor/ws]WebSocketInterceptor#clients@zimic/interceptor/ws]WebSocketInterceptor#server#messages@zimic/interceptor/ws]WebSocketInterceptor#clients#messages@zimic/interceptor/ws]WebSocketInterceptor#message()@zimic/interceptor/ws]WebSocketMessageHandler#with()@zimic/interceptor/ws]WebSocketMessageHandler#times()@zimic/interceptor/ws]WebSocketMessageHandler#send()@zimic/interceptor/ws]WebSocketMessageHandler#send()#to()@zimic/interceptor/ws]WebSocketInterceptor#on('connection')@zimic/ws] Create package@zimic/ws] CreateWebSocketSchema@zimic/ws] Configure deployments@zimic/ws] CreateWebSocketClient@zimic/ws] CreateWebSocketClient#start()@zimic/ws] CreateWebSocketClient#stop()@zimic/ws] CreateWebSocketClient#addEventListener()@zimic/ws] CreateWebSocketClient#removeEventListener()@zimic/ws] CreateWebSocketClient#send()@zimic/ws] CreateWebSocketServer@zimic/ws] CreateWebSocketServer#start()@zimic/ws] CreateWebSocketServer#stop()@zimic/ws] CreateWebSocketServer#addEventListener()@zimic/ws] CreateWebSocketServer#removeEventListener()@zimic/ws] CreateWebSocketServer#send()@zimic/interceptor/ws] RefactorInterceptorServerto use@zimic/wsCode snippets (draft)