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.
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");});