This article describes how to write JavaScript scripts to send user data back to the server.

I made a code repository containing all the examples below, which can be run to see the results.

1. Synchronization AJAX

The common practice of sending data back to the server is to put the collected user data inside the unload event and send it back to the server with an AJAX request.

However, asynchronous AJAX inside the unload event may not always work, because the page is already in unload and the browser may or may not send it. So, change to synchronous AJAX requests.

1
2
3
4
5
6
window.addEventListener('unload', function (event) {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/log', false);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.send('foo=bar');
});

In the above code, the third parameter of the xhr.open() method is false, which means synchronous request.

The biggest problem with this approach is that the browser will gradually disallow the use of synchronous AJAX on top of the main thread. So, the above code doesn’t actually work.

2. Asynchronous AJAX

Asynchronous AJAX is actually available. The premise is that inside the unload event, there must be some very time-consuming synchronization operations. This allows enough time to ensure that asynchronous AJAX can be sent successfully.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function log() {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/log', true);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.send('foo=bar');
}

window.addEventListener('unload', function(event) {
  log();

  // a time-consuming operation
  for (let i = 1; i < 10000; i++) {
    for (let m = 1; m < 10000; m++) { continue; }
  }
});

In the above code, a double loop is forced, which delays the execution of the unload event and causes the asynchronous AJAX to be sent successfully.

3. Track user clicks

setTimeout can also delay page offloading and ensure that asynchronous requests are sent successfully. Here is an example that tracks user clicks.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// The HTML code is as follows
// <a id="target" href="https://baidu.com">click</a>
const clickTime = 350;
const theLink = document.getElementById('target');

function log() {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/log', true);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.send('foo=bar');
}

theLink.addEventListener('click', function (event) {
  event.preventDefault();
  log();

  setTimeout(function () {
    window.location.href = theLink.getAttribute('href');
  }, clickTime);
});

The above code uses setTimeout, which delays 350 milliseconds before allowing the page to jump, thus giving the asynchronous AJAX time to emit.

4. Bounce Tracking

Bounce tracking can also be used to track user clicks.

The so-called “bounce tracking” is a web page jump to one or more intermediate URLs in order to collect information before jumping to the original target URL.

1
2
3
4
5
6
7
8
9
// The HTML code is as follows
// <a id="target" href="https://baidu.com">click</a>
const theLink = document.getElementById('target');

theLink.addEventListener('click', function (event) {
  event.preventDefault();
  window.location.href = '/jump?url=' + 
    encodeURIComponent(theLink.getAttribute('href'));
});

In the code above, when the user clicks, it forces a jump to an intermediate URL that carries the information over, and then jumps to the original target URL after processing is complete.

Both Google and Baidu are now doing this, and when clicking on the search results, they will bounce several times before jumping to the target URL.

5. Beacon API

All of the above practices will delay the uninstallation of web pages and seriously affect the user experience.

In order to solve the problem of asynchronous requests not succeeding when the web page is unloaded, the browser has implemented a special Beacon API that allows asynchronous requests to be sent out of the current main thread and placed inside the browser process, so that they can be guaranteed to be sent out.

1
2
3
window.addEventListener('unload', function (event) {
  navigator.sendBeacon('/log', 'foo=bar');
});

In the above code, the navigator.sendBeacon() method ensures that the asynchronous request will definitely be sent. The first parameter is the requested URL and the second parameter is the data to be sent.

Note that the Beacon API sends a POST request.

6. Ping Property

The <a> tag of HTML has a ping attribute that sends a POST request to the URL specified by the attribute whenever the user clicks on it.

1
2
3
<a href="https://baidu.com" ping="/log?foo=bar">
  click
</a>

In the above code, when the user clicks on the jump, a POST request is sent to the URL /log.

The ping attribute cannot specify a data body, it seems to carry information only through the query string of the URL.


Reference https://www.ruanyifeng.com/blog/2019/04/user-tracking.html