Home | Send Feedback

A closer look at the Beacon API

Published: 5. September 2018  •  Updated: 11. November 2024  •  javascript

The Beacon API is a browser API that maybe not many people heard of or us it in their daily work, unless they do a lot of analytics reporting in the browser.

The Beacon API is very well-supported in modern browsers, including Chrome, Edge, Firefox, Safari, and many mobile browsers. For detailed browser support information, visit the caniuse.com site: https://caniuse.com/#feat=beacon

The API is straightforward and consists of just one method in the navigator object.

  navigator.sendBeacon(url [, data]);

The Beacon API sends a POST request to the URL you provide as the first parameter with optional data that you pass as the second parameter. The Beacon API supports ArrayBufferView, Blob, String and FormData as second parameter. The API does not expect a response; if your back end sends one, the browser ignores it.

The reason why the Beacon API exists was to solve a specific problem analytics software had in the browser. To collect as much data as possible, analytics libraries tried to send the collected data at the last possible moment to the server. This moment is the unload handler. Unfortunately, asynchronous requests that are initiated in the unload handler are canceled by the browser. As a workaround, the libraries sent synchronous XMLHTTPRequest requests, but this had the problem that it delayed the navigation to the next page.

The solution is the Beacon API. Requests sent with this API are going to be sent to the server even when the user already navigated away to the next page.

The method sendBeacon() returns true if the browser was successfully able to queue the request. false seems to be returned when browser-specific limits were hit. See this Stack Overflow answer for more information. During my tests, I've never reached these limits and, therefore, never encountered the false return value.

The browser tries to send the request immediately after internally queueing it. The Beacon API only tries to send the request once, and it does not care if the URL is wrong, the server is not responding, sends an error back or if the browser is offline. The Chrome browser prints an error message into the console and then deletes the request from the queue. There is no retry mechanism built-in, nor does the browser queues these requests while offline and then sends them the next time the device is online.

Usage

An application can call the sendBeacon method without any data.

    navigator.sendBeacon('heartbeat');

usage.html

The first parameter represents a standard URL. You can send data with query parameters

    navigator.sendBeacon('heartbeat?id=123');

usage.html

The second parameter of the sendBeacon method data is either a string

    // String
    const data = JSON.stringify({
      location: window.location,
      time: Date()
    });
    navigator.sendBeacon('usageString', data);

usage.html

or a FormData object

    // FormData
    const formData = new FormData();
    formData.append("session", "12345");
    formData.append("id", 11);
    navigator.sendBeacon('usageFormData', formData);

usage.html

or a Blob

    // Blob
    const ua = JSON.stringify({ ua: navigator.userAgent, now: performance.now() });
    const headers = { type: 'application/json' };
    const blob = new Blob([ua], headers);
    navigator.sendBeacon('usageBlob', blob);

usage.html

or an ArrayBufferView. In this example, a Uint8Array.

    // ArrayBufferView
    const string = '=======This is a text, sent compressed to the server=======';
    const enc = new TextEncoder();
    const encoded = enc.encode(string);
    const compressed = pako.deflate(encoded);

    navigator.sendBeacon('usageArrayBufferView', compressed);

usage.html

sendBeacon() sends a POST request with the data as the body to the server. There is no way to change the HTTP method, the Beacon API only supports POST requests, and it does not expect a response.

I wrote a Spring Boot backend for testing these examples. You find the source code that handles these requests here: https://github.com/ralscha/blog/blob/master/beacon/src/main/java/ch/rasc/beacon/UsageController.java

Examples

Next, we look at a few examples that use the Beacon API to send data from a browser to a server. For all these examples, I use a Spring Boot backend. You find the source code here:
https://github.com/ralscha/blog/blob/master/beacon/src/main/java/ch/rasc/beacon/ExamplesController.java

Unless we want to send data in the unload event handler, the Beacon API does not offer any additional benefits. Only in the first two examples, the Beacon API is required because the application sends data in the unload handler. In all the other examples, we could use the Fetch API or the XMLHTTPRequest object.


Page Lifecycle

First, we look at an example that sends data in the unload handler. We have a simple index.html page

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Lifecycle: Page 1</title>
</head>

<body>
  <h1>Lifecycle: This is Page 1</h1>

  <div>
    <a href="page2.html">Go to Page 2</a>
  </div>
  <script src="main.js"></script>
</body>

</html>

index.html

that runs this JavaScript code

const analytics = { start: performance.now(), visibility: [] };

window.addEventListener('unload', event => {
  analytics.stop = performance.now();
  navigator.sendBeacon('../lifecycle', JSON.stringify(analytics));
});

document.addEventListener('visibilitychange', event => {
  analytics.visibility.push({ state: document.visibilityState, ts: event.timeStamp });
});


main.js

When the user clicks on the "Go to Page 2" link, the browser emits the unload event, and our code sends some analytics data to the server. Because we use the Beacon API, we make sure that the browser does not cancel the call and sends the data to the server even we have already navigated away from the page. Because the sendBeacon() call is asynchronous, the user does not have to wait for the page navigation to happen.

The Page Lifecycle API, which defines events like unload, is not recommended for sending analytics data due to its unreliability, especially on mobile devices. The pagehide event is generally a better choice for detecting page unloads. More details about the Page Lifecycle API and its caveats are available here:
https://developer.chrome.com/docs/web-platform/page-lifecycle-api


Performance

The second example is almost identical to the first. This time we send the built-in performance.timing object to the server. The Performance interface provides access to performance-related information for the current page.

window.addEventListener('unload', event => {
  navigator.sendBeacon('../performance', JSON.stringify(performance.timing));
});


main.js

Using performance.timing is deprecated. Use the PerformanceObserver API for collecting performance metrics. For more details on the Performance API, refer to:
https://developer.mozilla.org/en-US/docs/Web/API/Performance


Global Error Handling

When a browser encounters an error, he prints a message into the browser console. For the developers, this is not very useful because they usually cannot look on the user's computer. To solve that, we can send the messages that the browser emits to the server. On the server, we can store them into a database, send emails to the developers, or automatically open a ticket in an issue management system.

To install a global error handler, we only have to listen for the error event on the window object.

    window.onerror = function (msg, url, line, col, error) {
      const formData = new FormData();
      formData.append('url', url);
      formData.append('line', line);
      formData.append('col', col);
      formData.append('error', error);
      navigator.sendBeacon('../clientError', formData);
    };

index.html

The error handler receives these parameters that describe the error.

Alternatively, we can install a global error handler with addEventListener. In this case, the handler method has a different signature and only receives an event object.

   window.addEventListener('error', event => { ... })

The event object contains these read-only properties:
event.message, event.filename, event.lineno, event.colno and event.error. They correspond to the parameters of the onerror handler.


Position

With the Geolocation API, a web application can access the position of a user. For privacy reasons, the browser must ask the user if he wants to permit to report his location data.

If the user gives permission, we can listen on location changes with the navigator.geolocation.watchPosition method. The example then sends the latitude, longitude, and timestamp with the Beacon API to the server.

      navigator.geolocation.watchPosition(pos => {

        document.getElementById('latitude').innerHTML = pos.coords.latitude;
        document.getElementById('longitude').innerHTML = pos.coords.longitude;

        const position = {
          latitude: pos.coords.latitude,
          longitude: pos.coords.longitude,
          ts: pos.timestamp
        };
        navigator.sendBeacon('../position', JSON.stringify(position));
      });

index.html

Visit MDN for more information about the GeoLocation API:
https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API


Reporting Observer

If you are surfing the web with Chrome and you look into the browser console, you maybe already noticed some [Intervention] and [Deprecation] warnings.

Like error messages, these warnings could be useful for a developer to improve his application. Unfortunately we cannot see them on the users' browsers, and users do not notice them because they usually don't open the browser console.

Unfortunately, the error event handler does not capture these messages, because these warnings are generated directly by the browser. The error handler is only called for runtime errors caused by our code.

The ReportingObserver API offers a way to programmatically access browser warnings and interventions. For further details, refer to: https://developer.chrome.com/docs/capabilities/web-apis/reporting-observer

    const observer = new ReportingObserver((reports, observer) => {
      for (const report of reports) {
        navigator.sendBeacon('../reportObserver', JSON.stringify(report.body, ['id', 'columnNumber', 'lineNumber', 'message', 'sourceFile']));
      }
    }, { types: ['intervention', 'deprecation'], buffered: true });

    observer.observe();

index.html

Each time the browser issues a warning, the call-back function passed to the ReportingObserver constructor is called.

Visit this page to learn more about the ReportingObserver API:
https://developer.chrome.com/docs/capabilities/web-apis/reporting-observer


Generic Sensor API

The Generic Sensor API provides access to device sensors like accelerometers. For current browser support and more information, see:
https://developer.chrome.com/docs/capabilities/web-apis/generic-sensor

In this example, we access the accelerometer, listen for the reading event, and send the sensor data to the server.

    const sensor = new Accelerometer();
    sensor.start();

    sensor.onreading = () => {
      const formData = new FormData();
      formData.append('x', sensor.x);
      formData.append('y', sensor.y);
      formData.append('z', sensor.y);
      navigator.sendBeacon('../sensor', formData);

      document.getElementById('x').innerText = sensor.x;
      document.getElementById('y').innerText = sensor.y;
      document.getElementById('z').innerText = sensor.z;
    };

index.html

Conclusion

This concludes our exploration of the Beacon API and its use cases. The Beacon API is useful for sending data in the background from the browser to a server.

Browser Compatibility: The Beacon API has excellent support in modern browsers. However, always test your implementation thoroughly across different browsers and versions to ensure consistent functionality.

Data Limits: The Beacon API is designed for sending small amounts of data. There are limits to the size of data that can be sent in a single beacon request. While the exact limits vary by browser, they are typically around 64KB.

Security: If you are sending sensitive data using the Beacon API, ensure appropriate security measures are in place on your server.

Alternative: In many cases, instead of the Beacon API you can send data with the Fetch API. Consider this if you need more control over the request or require a response from the server.