Skip to content
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 SendBeaconInterceptor #284

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

hpohlmeyer
Copy link
Contributor

Description

This PR adds a SendBeaconInterceptor for intercepting navigator.sendBeacon() calls.

Important Details

Response data does not matter

sendBeacon is a fire-and-forget call. It is a synchronous function that queues a network request, but does not wait for the response. It also does not provide any way to access the response. But mocking the response with request.respondWith() still makes sense, if you do not want the request to be passed on to the original sendBeacon.

interceptor.on('request', (request) => {
  if (request.url.pathname === '/blocked') {
    // The `respondWith()` call will prevent the request from being
    // passed to the original `sendBeacon`. The response itself does
    // not matter, since it can not be accessed.
    request.respondWith({ status: 204 })
    return
  }
  // Call to other paths will be passed on to the original `sendBeacon`
})

No response events

As described above, there is no way to access the response of sendBeacon, which means we have no way to trigger response events.

sendBeacon always returns true when an interceptor is applied

The original sendBeacon returns true in case it sucessfully queued the network request and false in case it did not. The criteria for queuing a request vary by the user agent and are not accessible in JavaScript. That means we can not know if queuing is possible before doing it. Therefore we can not return a sound value from our sendBeacon mock.

That problem does also affect routes, that do not mock the response, because we need to check if a mocked response has been defined. This is done asynchronously, but since sendBeacon needs to run synchronously, we can not wait until we know if the response has to be mocked.

Usage in MSW

I am using @msw/interceptors for different purposes, but as stated in the readme, the main aim for this package is to provide interceptors for MSW. So, here are some words on that topic.

Since sendBeacon is not implemented in Node, the SendBeaconInterceptor is only relevant in Browsers. As far as I have seen, MSW handles the request interception in the service worker by listening to fetch events. The fetch event is also able to intercept sendBeacon calls, so MSW already supports beacon interception without the need for SendBeaconInterceptor. But the same is true for FetchInterceptor and XMLHttpRequestInterceptor and they are still part of this package. I think the SendBeaconInterceptor would make this package more feature-complete.

I may have misunderstood the role of @msw/interceptors in MSW, if so, please let me know. 🙏

@kettanaito
Copy link
Member

kettanaito commented Sep 9, 2022

Hey, @hpohlmeyer! Thank you for working on this!

Please, allow me some time to process the changes. Generally speaking, the Interceptors library is designed to intercept requests from various request clients (http, fetch, etc). It tries to be as low-level as possible so we don't have to patch high-level clients like axios and depend on low-level network modules like http. I'm not familiar with navigator.sendBeacon but I have a suspicion that it's not really sending requests but rather signals events. I'm not sure at this stage whether such kind of interception falls under the promise this library makes. I need to study the API more.

But the same is true for FetchInterceptor and XMLHttpRequestInterceptor and they are still part of this package.

Not entirely. XHR interceptor is here because there are DOM-like environments in Node.js, like JSDOM, which ship with XHR but are not browser. That's why we have a higher-level XHR interception implemented. I think there's also React Native that supports native XHR while not having global fetch but I may be mistaken. Since React Native is not Node.js, we cannot rely on http alone to intercept XHR requests because different environments may implement XHR differently (XHR uses http in JSDOM but is implemented differently in React Native, I believe). These decisions were made some years ago so I apologize if I'm saying gibberish.

The Fetch interceptor is a unique snowflake because it was designed for a single purpose: enable in-browser interception for scenarios where Service Worker cannot be registered in the browser (serving local file:// or using a really old browser). Although we aim to use it for Node.js native fetch interception in the future, it's not applicable for anything else at this point and is a rather poor example of what this library tries to achieve.

@hpohlmeyer
Copy link
Contributor Author

Hey @kettanaito,

sorry for the delayed response. I was on vacation last week.

Please, allow me some time to process the changes.

Of course! Sorry for coming in with this rouge-PR instead of clarifying if you are open to see this implemented in the library. I wanted to see if it would be possible to intercept sendBeacon and what roadblocks I might hit, so I started with the work already.

I'm not familiar with navigator.sendBeacon but I have a suspicion that it's not really sending requests but rather signals events

Essentially it is an API to send http requests, that outlive the page. It is similar to a fetch call with the keepalive option, but way more limited.

Thank you for explaining the motiviation behind the other interceptors, I was sure I did not get the full picture there. Your explanations helped a lot.

The Fetch interceptor is a unique snowflake because it was designed for a single purpose: enable in-browser interception for scenarios where Service Worker cannot be registered in the browser

I think the SendBeaconInterceptor would be a similar thing, viewing it from the angle of MSW. Since Service Workers are able to intercept sendBeacon calls through thefetch, event the use case for the interceptor in MSW would be environments where you can not register a Service Worker.


The main motivation for this PR is, that I am working on a project where I am in need for low-level request interception. The library is well written and maintained and possibly the best low level interception library out there. But without the possibility to intercept sendBeacon calls I would not catch all the requests. So adding support for sendBeacon-interception would make it more feature-complete.

I totally understand that this library is mainly a building block for MSW though, where the new Interceptor might not be as important. Let me know when you made a decision or need any information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants