0

I'm working on a tracker that should collect some data on the websites of our clients and send it to our API using fetch request when site users leave the page.

The idea was to use beforeunload event handler to send the request, but I've read here that In order to cover most browsers I also need to use unload event handler.

This is the relevant part of tracker code that our clients will put on their web sites:

var requestSent = false;
function submitData(element_id, url) {
    
    if (!requestSent) {
        
        var data = JSON.stringify({ourobject});
    
    
        fetch(url, {
           method: 'POST',
           headers: {
             'Accept': 'application/json',
             'Content-Type':'application/x-www-form-urlencoded',
           },
          body: data,})
        .then(response => response.json())
        .then((data) => {
           console.log('Hello?');
           requestSent = true;
        });
    } 
}

window.addEventListener('beforeunload', function (e) { submitData(1, "https://oursiteurl/metrics");});
window.addEventListener('unload', function(event) {submitData(1, "https://oursiteurl/metrics"); });

I've tested this on chrome and both requests pass, instead of just the first one that is successful, this leads to duplicate data in our database.

After putting console log in next to the part where requestSent flag is set to true, I realized that part of the code never gets executed.

If I preserve logs in network tab, it says that both requests are cancelled, even though the data gets to our endpoint.

Network tab result

Our API is created in Codeigniter, here is the /metrics endpoint:

public function submit () {
      
      $this->cors();

      $response = [
          'status' => 'error',
          'message' => 'No data',
      ];
      
      $data = json_decode(file_get_contents('php://input'), true);
      if (empty($data)) {
          echo json_encode($response);exit();
      }
      // process data and do other stuff ...

Cors function:

private function cors() {

      // Allow from any origin
      if (isset($_SERVER['HTTP_ORIGIN'])) {
          // Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
          // you want to allow, and if so:
          header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
          header('Access-Control-Allow-Credentials: true');
          header('Access-Control-Max-Age: 86400');    // cache for 1 day
      }

      // Access-Control headers are received during OPTIONS requests
      if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {

          if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
              // may also be using PUT, PATCH, HEAD etc
              header("Access-Control-Allow-Methods: GET, POST, OPTIONS");         

          if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
              header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");

      }

  }

EDIT:

Thanks to @CBroe for suggesting to use the Beacon API, using it removed the need for both unload and beforeunload event handlers:

submitData now looks like this:

...
if (navigator.sendBeacon) {
        let beacon = navigator.sendBeacon(url, data);
        console.log( 'Beacon', beacon );
    } else { // fallback for older browsers
        if (!requestSent) {
            console.log( 'Data object from fallback', data );
            var xhr = new XMLHttpRequest();
            xhr.open("POST", url, false); // third parameter of `false` means synchronous
            xhr.send(data);
    }
 ... 

Doing it this way allowed me to only keep beforeunload event handler because it works both on IE and Chrome:

 window.addEventListener('beforeunload', function (e) { submitData(1, "https://oursiteurl/metrics");});
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
failedCoder
  • 1,346
  • 1
  • 14
  • 38
  • 1
    What you should be using rather than AJAX or fetch, is https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API (If you can do with lacking IE support. If you can’t, then I’d still use this for the browsers that support it, and maybe implement AJAX/fetch as a fallback for IE.) – CBroe Jun 25 '20 at 14:20
  • @CBroe Thanks for the suggestion, I will switch to beacon api for the browsers that support it, but I need this to work on as many browsers as possible, so I still need to use this as a backup. – failedCoder Jun 25 '20 at 14:24
  • @CBroe Your suggestion basically solved the problem for me, I've edited my question if you're interested in my solution, if you post it as an answer I will accept it. Thank you! – failedCoder Jun 25 '20 at 15:48

1 Answers1

1

The idea was to use beforeunload event handler to send the request, but I've read here that In order to cover most browsers I also need to use unload event handler.

Both are not terribly suited to make AJAX/fetch requests, they are likely to get cancelled when the page actually unloads.

You should rather use the Beacon API, that was specifically made for this kind of tracking / keep-alive requests.

According to the browser compability list there on MDN, it is not supported by Internet Explorer yet though. If you need tracking for that as well, maybe go with a two-pronged approach - Beacon for the browsers that support it, an AJAX/fetch fallback for IE.

CBroe
  • 91,630
  • 14
  • 92
  • 150