123 lines
4.1 KiB
JavaScript
123 lines
4.1 KiB
JavaScript
/*!
|
|
* @lilith/user-data-beacon — cookie-free, fingerprint-free pageview beacon.
|
|
*
|
|
* Static-HTML sites (Adult Therapy Tour, Sansonnet, SEO bait) drop in:
|
|
* <script defer src="https://quinn.data/beacon.js" data-site="att"></script>
|
|
*
|
|
* The collector at quinn.data derives visitor_id_daily server-side from
|
|
* (daily-rotating salt || IP || UA || Accept-Language). No client identity is
|
|
* sent. No cookies, no localStorage, no canvas fingerprinting.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
// nginx on data.transquinnftw.com routes /analytics/track/* → collector :4001
|
|
// and injects X-Write-Key server-side.
|
|
var COLLECTOR = 'https://data.transquinnftw.com/analytics/track';
|
|
|
|
function tag() {
|
|
var s = document.currentScript;
|
|
if (s && s.getAttribute) return s.getAttribute('data-site') || null;
|
|
var all = document.getElementsByTagName('script');
|
|
for (var i = 0; i < all.length; i++) {
|
|
if (/\/beacon\.js(\?|$)/.test(all[i].src || '')) {
|
|
return all[i].getAttribute('data-site');
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function clientDevice() {
|
|
var s = window.screen || {};
|
|
return {
|
|
screenWidth: s.width,
|
|
screenHeight: s.height,
|
|
viewportWidth: window.innerWidth,
|
|
viewportHeight: window.innerHeight,
|
|
pixelRatio: window.devicePixelRatio,
|
|
colorDepth: s.colorDepth,
|
|
language: navigator.language,
|
|
languages: navigator.languages ? Array.prototype.slice.call(navigator.languages) : undefined,
|
|
timezone: (Intl && Intl.DateTimeFormat && Intl.DateTimeFormat().resolvedOptions().timeZone) || undefined,
|
|
timezoneOffset: new Date().getTimezoneOffset(),
|
|
deviceMemory: navigator.deviceMemory,
|
|
hardwareConcurrency: navigator.hardwareConcurrency,
|
|
touchPoints: navigator.maxTouchPoints,
|
|
cookiesEnabled: navigator.cookieEnabled,
|
|
doNotTrack: navigator.doNotTrack === '1' || navigator.doNotTrack === 'yes'
|
|
};
|
|
}
|
|
|
|
function attribution() {
|
|
var p = new URLSearchParams(window.location.search);
|
|
return {
|
|
utmSource: p.get('utm_source') || undefined,
|
|
utmMedium: p.get('utm_medium') || undefined,
|
|
utmCampaign: p.get('utm_campaign') || undefined,
|
|
utmContent: p.get('utm_content') || undefined,
|
|
utmTerm: p.get('utm_term') || undefined,
|
|
referrer: document.referrer || undefined,
|
|
via: p.get('via') || undefined
|
|
};
|
|
}
|
|
|
|
// Session id: ephemeral, per-tab, in memory only. Visitor stitching
|
|
// happens server-side via the daily-rotating hash — this id is only used
|
|
// for tab-scoped funnel chaining.
|
|
var sessionId = Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
|
|
var site = tag();
|
|
var landedAt = Date.now();
|
|
|
|
function post(path, body, useBeacon) {
|
|
var url = COLLECTOR + path;
|
|
try {
|
|
var payload = JSON.stringify(body);
|
|
if (useBeacon && navigator.sendBeacon) {
|
|
navigator.sendBeacon(url, new Blob([payload], { type: 'application/json' }));
|
|
return;
|
|
}
|
|
fetch(url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: payload,
|
|
keepalive: true,
|
|
credentials: 'omit'
|
|
}).catch(function () { /* silent */ });
|
|
} catch (e) { /* silent */ }
|
|
}
|
|
|
|
function trackView() {
|
|
post('/view', {
|
|
pageUrl: window.location.href,
|
|
referrer: document.referrer || undefined,
|
|
sessionId: sessionId,
|
|
clientDevice: clientDevice(),
|
|
attribution: attribution(),
|
|
app: site || undefined,
|
|
metadata: { dataSite: site }
|
|
}, false);
|
|
}
|
|
|
|
function trackViewEnd() {
|
|
post('/event', {
|
|
eventType: 'view_end',
|
|
sessionId: sessionId,
|
|
pageUrl: window.location.href,
|
|
metadata: {
|
|
durationMs: Date.now() - landedAt,
|
|
dataSite: site
|
|
}
|
|
}, true);
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', trackView, { once: true });
|
|
} else {
|
|
trackView();
|
|
}
|
|
|
|
window.addEventListener('pagehide', trackViewEnd, { once: true });
|
|
document.addEventListener('visibilitychange', function () {
|
|
if (document.visibilityState === 'hidden') trackViewEnd();
|
|
});
|
|
})();
|