feat: support cross-process interception via setupRemoteServer#1617
feat: support cross-process interception via setupRemoteServer#1617kettanaito wants to merge 293 commits into
setupRemoteServer#1617Conversation
* docs(migrating): fix example for response composition * docs(migrating): change to response generation example instead of creation
535f280 to
209b65c
Compare
b7d905e to
837f141
Compare
04e8e2f to
c5a58da
Compare
|
Hey @kettanaito! Sorry to ask the age-old annoying question... This would be suuuuch a game changer for our team. Are there any plans to merge this functionality? I can see it was added to the 3.0 milestone but can't see it in the list! |
|
Hi @jonlambert. No worries. This is going to be a gamechanger for all teams, I can imagine! This pull request as-is is likely dead weight. It was foundational in figuring out the Instead of continuing with this branch, I've opened another feature branch that rebuilds this on top of the new architecture. There are a couple of decisions that were not addressed with this feature that I'm still working on:
And a few dozens smaller decisions sprinkled all around. AvailabilityThe biggest blocker right now is my availability. I have a ton of work at Epic Web, I have a few pull request I really need to finish. Realistically, this is coming in 3.0, which might release sometime later this year if all goes smoothly. What you can do to helpIf this feature would be beneficial to your team, consider sponsoring my work on it! That would allow me to dedicate proper time to the cross-process interception and finish it for everybody, with your company as a proud patron (I will make sure everyone knows who made this possible). I'd then release this under an experimental version so you can start using it without having to wait for 3.0. Reach out to me on Discord or Twitter if this is something you would be interested in. Thank you! |
Intention
Introduce an API that allows one process to modify the traffic of another process. The most apparent application for this is testing server-side behaviors of a JavaScript application:
This API is designed exclusively for use cases when the request-issuing process and the request-resolving process (i.e. where you run MSW) are two different processes.
Important
While the feature itself is unopinionated in how it's used, in practice it implies that you either have a fixed list of handlers OR you provide test-app isolation by spawning an app instance per test case. That is so handler overrides in one test don't affect the remote network for other tests. The app becomes a shared state here with no means to distinguish who is the consumer (test -> client -> server chain loses any identifiers).
Proposed API
With consideration to the existing MSW user experience, I suggest we add a
setupRemoteServer()API that implements theSetupApiinterface and has a similar API tosetupServer. The main user-facing distinction here is thatsetupRemoteServeris affecting a remote process, as indicated by the name.The
.listen()and.close()methods of the remote server become async since they now establish and terminate an internal server instance respectively.You can then operate with the
remoteserver as you would with a regularsetupServer, keeping in mind that it doesn't affect the current process (your test) but instead, any remote process that runssetupServer(your app).By fully extending the
SetupApi, thesetupRemoteServerAPI provides the user with full network-managing capabilities. This includes defining initial and runtime request handlers, as well as observing the outgoing traffic of a remote process using the Life-cycle API (remote.events.on(event, listener)). I think this is a nice familiarity that also provides the user with more power when it comes to controlling the network.Implementation
I've considered multiple ways of implementing this feature. Listing them below.
(Chosen) WebSocket server
The
setupRemoteServerAPI can establish an internal WebSocket server that can route the outgoing traffic from any server-side MSW instance anywhere and deliver it to the remote server to potentially resolve.Technically, the WebSocket server acts as a resolution point (i.e. your handlers) while the remote MSW process acts as a request supplier (similar to how the Service Worker acts in the browser).
Very roughly, this implies that the regular
setupServerinstances now have a fixed request handler that tries to check if any outgoing request is potentially handled by an existing remote WebSocket server:If no WebSocket server was found or establishing a connection with it fails within a sensible timeout period (~500ms), the
setupServerinstance of the app continues to operate as normal.IPC
The test process and the app process can utilize IPC (interprocess communication) to implement a messaging protocol. Using that protocol, the app can signal back any outgoing requests and the test can try resolving them against the request handlers you defined immediately in the test.
This approach is similar to the WebSocket approach above with the exception that it relies on IPC instead of a standalone running server. With that, it also gains its biggest disadvantage: the app process must be a child process of the test process. This is not easy to guarantee. Depending on the framework's internal implementation, the user may not achieve this parent/child relationship, and the IPC implementation will not work.
Given such a demanding requirement, I've decided not to use this implementation.
Limitations
useRemoteServer()affects the network resolution for the entire app. This means that you cannot have multiple tests that override request handlers for the same app at the same time. I think this is more than reasonable since you know you're running 1 app instance that can only behave in a single way at a single point in time. Still, I expect users to be confused when they parallelize their E2E tests and suddenly see some network behaviors leaking across the test cases.Concerns
setupRemoteServeronly affects the server-side network behavior of any running application process with the server-side MSW integration? To affect the client-side network behavior from a test you have to 1) havesetupWorkerintegration in the app; 2) set a globalwindow.workerinstance; 3) usewindow.worker.use()to add runtime request handlers. This stays as it is right now, no changes here.The API is TBD and is subjected to change.
Roadmap
rest.all()handler.responseinsetupServer.ReadableStreamfrom the remote request handler (may consider transferringReadableStreamover the WS messages instead ofArrayBuffer, if that's allowed).ReadableStreamtransfer over WebSockets that would be great.remotePortandportan implementation detail ofsetupRemoteServerandsetupServer({ remote: true }). The developer mustn't care about those.use()test (may have something to do with the handlers management refactoring as a part of theserver.boundary()).setupServer(that it doesn't emit them for internal requests).setupWorkersupport (see feat: support cross-process interception viasetupRemoteServer#1617 (comment)).socket.ioin favor ofwsif we don't need anything socketio-specificforwardLifeCycleEvents()).ListenOptionsonsetupRemoteServer(), likeonUnhandledRequest.spyOnLifeCycleEventstest utility importingvitestwhen it can also be run in the browser.setupRemoteServer#1617 (comment)setupRemoteServer#1617 (review)).RemoteInterceptor. Then, use it in MSW viaInterceptorNetworkSource./newhas to be merged with/core.Blockers
socket.io-parserare broken for the CJS build).setupRemoteServer#1617 (comment)).