Home | Send Feedback

A closer look at the Beacon API

Published: 5. September 2018  •  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 well-supported in most browsers, except (you already guess it) IE11. Visit the caniuse.com site to see the current support for this API: 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 could, therefore, 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 back end. 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.

If you want to learn more about the Page Lifecycle API, visit this article:
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

More information about the Performance API:
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.

To fill this gap, Google added a new API to Chrome 69: ReportingObserver. It provides a programmatic way to access these browser warnings.

    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

Another experimental API and, at the time of writing this blog post, only available in Google's Chrome browser is the Generic Sensor API. It gives web applications access to all kinds of sensors that a modern smartphone has built-in.

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

Visit this page for more information:
https://developer.chrome.com/docs/capabilities/web-apis/generic-sensor