Skip to content

Instantly share code, notes, and snippets.

@danshearmur
Last active September 18, 2018 12:04
Show Gist options
  • Save danshearmur/ad8cbf2c1ef5c7596b27 to your computer and use it in GitHub Desktop.
Save danshearmur/ad8cbf2c1ef5c7596b27 to your computer and use it in GitHub Desktop.
(function () {
if (window.navigator.sendBeacon || !window.localStorage) return;
var TO_S = Object.prototype.toString;
var TYPE_RE = /\[object ([a-zA-Z0-9]*)\]/;
var LS_PFX = 'beaconfill-';
var LIMIT = 64 * 1024;
var WAIT = 1500;
var MAX_RETRIES = 3;
// helpers
function type(thing) {
return TYPE_RE.exec(TO_S.call(thing))[1].toLowerCase();
}
function escStr(str) {
return str.replace(/"/g, "\\\"");
}
function unescStr(str) {
return str.replace(/\\"/g, "\"");
}
// grab the next item in localStorage that has a key beginning with 'beaconfill-'
function nextItem() {
for (var i = 0, l = localStorage.length; i < l; i++) {
var key = localStorage.key(i);
if (key.indexOf(LS_PFX) === 0) {
return {key: key, payload: JSON.parse(localStorage.getItem(key))};
}
}
}
// posts relevant data to the server, if req successful removes data from localStorage
// always starts next `process()`
function send(item) {
item.payload.retried++;
localStorage.setItem(item.key, JSON.stringify(item.payload));
var client = new XMLHttpRequest();
if (!('withCredential' in client)) {
if (typeof XDomainRequest === 'undefined') return;
client = new XDomainRequest();
}
client.open('POST', item.payload.url, false);
client.onreadystatechange = function () {
if (client.readyState === 4) {
if (client.status > 199 && client.status < 299) {
localStorage.removeItem(item.key);
}
process();
}
};
client.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
client.send(item.payload.data);
}
// checks for the next item in localStorage
// deletes it if over max retries
// sends next item
function process() {
var item = nextItem();
if (!item) return;
if (item.payload.retried >= MAX_RETRIES) {
localStorage.removeItem(item.key);
return process();
}
send(item);
}
window.navigator.sendBeacon = function sendBeacon(url, data) {
if (type(data) !== 'string') {
throw TypeError('Data must be a string');
}
// TODO: allow Blob, FormData or ArrayBufferView
if (data.length > LIMIT) {
return false;
}
localStorage.setItem(LS_PFX + Date.now(), JSON.stringify({
data: escStr(data),
retried: 0,
url: url
}));
return true;
};
window.addEventListener('load', function () {
setTimeout(process, WAIT);
}, false);
}());
<!doctype html>
<script src="beaconfill.js"></script>
<script>
window.onunload = function () {
var data = { thing: true, time: Date.now(), x: 1000, rand: Math.random() };
navigator.sendBeacon('/endpoint', JSON.stringify(data))
};
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment