mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-05 16:02:48 +00:00
128 lines
4.7 KiB
JavaScript
128 lines
4.7 KiB
JavaScript
/* Warmbly website tracking. Records page views for the workspace whose site
|
|
* key the script tag carries, sends nothing before consent when the
|
|
* workspace requires it, and honours Global Privacy Control / Do Not Track.
|
|
* API: warmbly('consent','granted'|'denied'), warmbly('page'), warmbly('reset'). */
|
|
(function () {
|
|
if (window.__wbly) return;
|
|
window.__wbly = 1;
|
|
var w = window, d = document, n = navigator;
|
|
var sc = d.currentScript;
|
|
if (!sc) {
|
|
var all = d.getElementsByTagName('script');
|
|
for (var i = all.length - 1; i >= 0; i--) {
|
|
if (all[i].getAttribute('data-site')) { sc = all[i]; break; }
|
|
}
|
|
}
|
|
if (!sc) return;
|
|
var key = sc.getAttribute('data-site');
|
|
if (!key) return;
|
|
var mode = sc.getAttribute('data-consent') === 'implicit' ? 'implicit' : 'explicit';
|
|
var base = sc.getAttribute('data-endpoint') || String(sc.src).replace(/\/tracking\.js.*$/, '');
|
|
var endpoint = base + '/p';
|
|
var VID = 'wbly_vid', SID = 'wbly_sid', SAT = 'wbly_sat', CON = 'wbly_consent', TOK = 'wbly_t';
|
|
|
|
function rnd() {
|
|
var a = new Uint8Array(16);
|
|
if (w.crypto && w.crypto.getRandomValues) { w.crypto.getRandomValues(a); }
|
|
else { for (var i = 0; i < 16; i++) a[i] = (Math.random() * 256) | 0; }
|
|
var s = '';
|
|
for (var j = 0; j < 16; j++) s += ('0' + a[j].toString(16)).slice(-2);
|
|
return s;
|
|
}
|
|
function get(k) { try { return w.localStorage.getItem(k); } catch (e) { return null; } }
|
|
function set(k, v) { try { w.localStorage.setItem(k, v); } catch (e) {} }
|
|
function del(k) { try { w.localStorage.removeItem(k); } catch (e) {} }
|
|
function sget(k) { try { return w.sessionStorage.getItem(k); } catch (e) { return null; } }
|
|
function sset(k, v) { try { w.sessionStorage.setItem(k, v); } catch (e) {} }
|
|
function sdel(k) { try { w.sessionStorage.removeItem(k); } catch (e) {} }
|
|
|
|
function optedOut() {
|
|
return n.globalPrivacyControl === true || n.doNotTrack === '1' || w.doNotTrack === '1';
|
|
}
|
|
function consent() {
|
|
if (optedOut()) return null;
|
|
var c = get(CON);
|
|
if (c === 'granted') return 'granted';
|
|
if (c === 'denied') return null;
|
|
return mode === 'implicit' ? 'implicit' : null;
|
|
}
|
|
|
|
/* The click redirect appends the identification ticket; take it off the
|
|
* address bar right away so it is never bookmarked or shared. */
|
|
var token = '';
|
|
try {
|
|
var u = new URL(w.location.href);
|
|
var t = u.searchParams.get(TOK);
|
|
if (t) {
|
|
token = t;
|
|
u.searchParams.delete(TOK);
|
|
w.history.replaceState(w.history.state, '', u.toString());
|
|
}
|
|
} catch (e) {}
|
|
|
|
var landing = false, lastUrl = '', lastAt = 0;
|
|
function session() {
|
|
var now = Date.now();
|
|
var sid = sget(SID), at = +sget(SAT) || 0;
|
|
if (!sid || now - at > 30 * 60 * 1000) { sid = rnd(); landing = true; }
|
|
sset(SID, sid);
|
|
sset(SAT, String(now));
|
|
return sid;
|
|
}
|
|
function tz() {
|
|
try { return Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch (e) { return ''; }
|
|
}
|
|
|
|
function send() {
|
|
var c = consent();
|
|
if (!c) return;
|
|
var url = w.location.href, now = Date.now();
|
|
if (url === lastUrl && now - lastAt < 1000) return;
|
|
lastUrl = url; lastAt = now;
|
|
var vid = get(VID);
|
|
if (!vid) { vid = rnd(); set(VID, vid); }
|
|
var sid = session();
|
|
var body = {
|
|
k: key, v: vid, s: sid, c: c, t: token, u: url,
|
|
ti: d.title || '', r: d.referrer || '', l: n.language || '', tz: tz(),
|
|
sw: (w.screen && w.screen.width) || 0, sh: (w.screen && w.screen.height) || 0, ld: landing
|
|
};
|
|
landing = false; token = '';
|
|
try {
|
|
/* text/plain keeps this a simple request: no preflight on every view. */
|
|
fetch(endpoint, {
|
|
method: 'POST', keepalive: true, credentials: 'omit',
|
|
headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify(body)
|
|
}).then(function (r) { return r.status === 200 ? r.json() : null; })
|
|
.then(function (j) { if (j && j.vid) set(VID, j.vid); })
|
|
.catch(function () {});
|
|
} catch (e) {}
|
|
}
|
|
|
|
/* Single-page apps: a pushed history entry is a page view. */
|
|
var push = w.history.pushState;
|
|
if (push) {
|
|
w.history.pushState = function () {
|
|
push.apply(this, arguments);
|
|
setTimeout(send, 0);
|
|
};
|
|
}
|
|
w.addEventListener('popstate', function () { setTimeout(send, 0); });
|
|
|
|
var queued = (w.warmbly && w.warmbly.q) || [];
|
|
function api(cmd, arg) {
|
|
if (cmd === 'consent') {
|
|
if (arg === 'granted') { set(CON, 'granted'); send(); }
|
|
else if (arg === 'denied') { set(CON, 'denied'); del(VID); sdel(SID); sdel(SAT); }
|
|
} else if (cmd === 'reset') {
|
|
del(VID); sdel(SID); sdel(SAT);
|
|
} else if (cmd === 'page') {
|
|
send();
|
|
}
|
|
}
|
|
w.warmbly = function () { api.apply(null, arguments); };
|
|
w.warmbly.q = [];
|
|
for (var q = 0; q < queued.length; q++) api.apply(null, queued[q]);
|
|
send();
|
|
})();
|