-
-
Notifications
You must be signed in to change notification settings - Fork 504
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
Support mocking WebSocket APIs #156
Comments
Hey, @Doesntmeananything, thanks for bringing this topic up. I'd love to bring SSE and WS support to the users of MSW. I admit I haven't researched the topic yet, but would use this thread for this. Technically, it comes down to the ability of Service Worker to intercept those requests/events. If the spec supports it, there shouldn't be much changes needed on the MSW side. Here's some useful resources:
Could you please try to set up a proof of concept, if those events can be intercepted in the worker's You're right about the custom request handler, we can use it to log all intercepted requests: setupWorker(
{
predicate(req) {
console.log(req)
// log captured requests, but bypass them
return false
},
resolver: () => null
}
) If we confirm it working, I'd be glad to discuss the API for a dedicated request handler for WebSocket/SSE. I can read on their specification meanwhile. |
Sounds like a plan! At a cursory glance, it does indeed seem quite doable. Let me PoC this, and I'll get back to you with my results as soon as I can. |
Hi, @kettanaito! I've set up a (very quick and dirty) repository to test these interactions over at https://github.com/Doesntmeananything/msw-sse-ws. My initial findings are the following:
I'm a bit concerned about WS events, although I hope that with some additional work it'd possible to intercept them. |
@Doesntmeananything, thank you for the investigation! I'm excited to hear that SSE can be intercepted! Wonder if there's anything we can do it intercept events as they go. I'm currently working on a NodeJS support, but can switch to this issue to help you once I'm done. I'm always open to questions or discussions, so please don't hesitate to raise those here.
|
I'm trying to get my head around the SSE example. It seems MSW should intercept the
|
One of the most useful pieces of code I've found in the w3c discussion (w3c/ServiceWorker#947 (comment)) was that the Service Worker file can establish a WebSocket connection. It appears that the WS events are not subjected to be intercepted in the If it comes down to the manual WS connection, I'd suggest to do that on the client's side, not in the worker file. There's no technical reason to move this logic to worker, at least as of how I understand such implementation now. |
Thanks very much for taking the time to look further into this! Since I've hit the wall in regards to intercepting WS connections, your suggestions come in handy. Will definitely look into this. To be clear, are you saying that mocking WS connections falls strictly outside of MSW concerns? My investigations lead me to believe this, and I would certainly not want to push for something that doesn't make sense neither on technical nor on conceptual level. |
Not necessarily. What I was trying to say is that a WebSocket event is not intercepted by the |
I've received a suggestion to look at |
Update: I've started with the WebSocket support and will keep you updated as I progress. For those interested I will post some technical insights into what that support means, what technical challenges I've faced, and what API to expect as the result. Session 1: It's all about socketsNo service for the workerUnfortunately, WebSocket events cannot be intercepted in the
Goodbye, handlers!WebSocket operates with events, not requests, making the concept of request handler in this context redundant. Instead, you should be able to receive and send messages from import { rest, ws, setupWorker } from 'msw'
// Create an interception "server" at the given URL.
const todos = ws.link('wss://api.github.com/todos')
setupWorker(
rest.put('/todo', (req, res, ctx) => {
const nextTodos = prevTodos.concat(req.body)
// Send the data to all WebSocket clients,
// for example from within a request handler.
todos.send(nextTodos)
return res(ctx.json(nextTodos))
})
)
// Or as a part of arbitrary logic.
setInterval(() => todos.send(Math.random()), 5000) URL that got awayWhen constructing a I've ended up re-implementing a Persisting WebSocket clientsThe entire idea of WebSocket is to sync data between multiple clients in real time. When you dispatch a mocked Usually a solution to this kind of problems is to lift the state up and maintain a record of clients in the upper context shared with all the clients (pages). However, in JavaScript there isn't that may ways to share and persist data between clients. In case of WebSocket clients one needs to store references to
const channel = new BroadcastChannel('ws-support')
// One client sends a data.
channel.send('some-data')
// All clients can react to it.
channel.addEventListener('message', (event) => {
event.data // "some-data"
}) I find |
You could use an ES6 Proxy. It can mess with ctors. |
SSE and WebSockets are different issues. |
@BlackGlory, MSW should support |
@kettanaito Although export const worker = setupWorker(
rest.get('/sse', (req, res, ctx) => {
return res(
ctx.status(200)
, ctx.set('Content-Type', 'text/event-stream')
, ctx.body(sse(function* () {
yield 'message1'
yield 'message2'
}))
)
})
)
function sse(gfn) {
let iter
return new ReadableStream({
start() {
iter = gfn()
}
, pull(controller) {
controller.enqueue(`data: ${iter.next().value}\n\n`)
}
})
} Browser:
Node.js:
|
Hey, @BlackGlory. Could you please report this as a separate issue? Thanks. |
The scope of WebSocket support has grown tremendously since the last time I've looked into it. It's not really a matter of spying on the I've also learn that there are multiple ways to implement web sockets in your application. The SocketIO itself illustrates them rather descriptively with their
And all transports must be accounted for if we wish for MSW to intercept WebSocket events reliably regardless of implementation detail. The I've split the work into chunks, and now I'm working on the transports that utilize I haven't assessed yet what changes would be required to support all transports. I've anticipated that we'd have to rework the way we establish interceptors but the concept that a single interceptor can accept requests from various origins (http/xhr/service worker) has never been explored. Until now, all interceptors have been self-contained. |
Looks great, on my end I feel the API looks good and really resembles how I would use a sockets server. I do have a use case that I wanted to ask for. Let's say I have a message that needs to be sent after X amount of time after a REST endpoint is hit, I handle the REST endpoint on my MSW mocks, but how would I generate a WS response for my client based on that REST endpoint being hit first. Seems to me that the current API is not capable of doing that with ease. |
@Jhony0311, that's a great use case! Originally, I planned for the import { ws } from 'msw'
export const service = ws.link('wss://my-service.com') This way you can reference it in any other handler you like, for example, a REST handler: import { service } from './ws.mocks'
rest.post('/user', (req, res, ctx) => {
// Emit event from the WebSocket server upon hitting this REST API route.
service.send(`welcome a new user ${req.body.username}`)
}) My concern with this is that the WebSocket handlers suddenly become their own special thing, while I'd much prefer them to be alongside the rest of your handlers, keeping the entire network behavior collocated. At the same time, WebSocket handlers fall out of the response resolution loop anyway: they won't be signaled incoming requests because they are event-based. So, from the user experience, I feel it'd be great to colocate all handlers together. Internally, WebSocket handlers are special because they represent a mock server of an entirely different protocol. I'll try to summarize my points below. Why I think
|
How would you consume this? |
the suggestion by @mariothedev works for me, for a single message. After that, the connection drops and I get an 'error' event on the client side. Still hoping to be able to stream events or at least keep the connection open for a while. I don't do much other than |
note if you modify mockServiceWorker.js to comment out the following code as @mariothedev suggested:
make sure to remove the In other words, remove this config from package.json if you have it:
this will allow you to manually choose when to update the service worker. |
Hey guys, thanks for the hard work into supporting more than just rest and graphql. May I ask if there's something we can start using (even if experimental) to explore the current API and use in our projects? We'd love to help with testing this feature! |
Hey, folks. I wanted to give a quick update on the state of the WebSocket support in MSW. Short, the browser side of this feature is implemented in mswjs/interceptors#236. The Node side remains as there's no WebSocket API in Node, and we need to find a suitable way to implement request interception provisioned by third-party libraries without shipping any library-specific code. This likely means diving into I'm not planning on working on WebSocket support in the foreseeable future. My focus goes to the Fetch API support (#1436) because it's something absolutely everybody using the library will benefit from. Help is welcome to make WebSocket happen! |
@kettanaito it is possible to merge the browser part into |
@SerkanSipahi, the issue is that only browser-side interception is not a finite feature. I wouldn't merge things that don't make sense on their own into The best I can do is release the browser implementation under a Honestly, this makes little sense to me, and if you wish to give this a try, consider using GitHub pull requests as dependencies in your project. Add the Contributing to the |
Hello @kettanaito! My team and I have been following this thread, and we saw that the change has been merged in so that msw will support EventSource. I was hoping to inquire when an official release might be happening that includes this update? Thank you in advance! |
Hey, @wade-gooch-kr. Excited to hear that. I'm in the middle of some test rewrites, I will publish that branch when I have a minute. Meanwhile, you can specify that PR as your dependency in package.json and should be able to try it out. |
Hey, whats the status on this? It seems like some part of the job was done in this merged PR 🤔 Should this be marked closed / done or is this still WIP? |
September 2023 Status Updatemswjs/interceptors#236 (comment) @Stackustack, supporting SSE is unfortunately not enough to ship WebSocket support. See the status update in the linked comment above. |
UpdateI've had some success implementing a WebSocket class-based interceptor (mswjs/interceptors#501). This means that the WebSocket support is coming to MSW rather soon! The browser test suite is currently passing. The Node.js test suite using Undici's Now, before anyone gets overly excited about this, let me clarify a few things.
What's left?Feedback. You can help land this API sooner by helping with the following:
Meanwhile, I will improve the test coverage of the interceptor to make sure it's fully compatible with the standard when you're using it. |
Turns out that the initial WebSocket implementation will support SocketIO also! If you want to be able to mock SocketIO with MSW, please read and upvote this: Thank you. |
WebSocket Support BetaPlease participate in the discussion about the upcoming WebSocket API to help us shape it and ship it faster: Thank you! |
I'm renaming this issue so it focuses on the WebSocket API mocking exclusively. Server-Sent Events (SSE) are quite different, and to my best knowledge, they can be intercepted by the Service Worker. Their implementation will be different. If someone needs it, I encourage you to open a new feature proposal and describe it in more detail (e.g. how you are using SSE in your application, how you imagine mocking to look like, etc). |
Update: Give the RC a try!You can install the RC with the WebSocket support today: Please participate and share your feedback! The more feedback we get, the faster and better the end API will be released. Thank you! |
@kettanaito I am getting when using websocket mocks:
Code I am trying:
Platform: Node |
@95th, hi! What version of Node.js are you running? It looks like it's older than v18. MSW itself doesn't support Node.js <18. Please update and have the global |
I have a regression with Resolved: looks like I can listen for events directly on the
|
@johnhunter, hi. That is not a regression but a change in the API. I've removed |
Is it possible to use msw to mock server-sent events (SSE) and WebSocket (WS) connections?
My use case is to have a somewhat full client-side mocking story (including REST, SSE, and WS functionality), and since msw is such a joy to use when mocking out REST APIs, I was wondering if it makes sense to use it to mock more specialised server interactions.
Have you thought about this? I admit that I haven't looked much into whether it's possible just to use custom request handlers to add this functionality, emulating SSE and WS behaviour in some way. I wanted to know if you already had something in mind regarding this question. Thanks!
The text was updated successfully, but these errors were encountered: