feat(collector): ✨ Implement Beacon client for sending telemetry data via browser-to-server API
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
e1f469b6f1
commit
b507184df9
1 changed files with 123 additions and 0 deletions
123
services/collector/public/beacon.js
Normal file
123
services/collector/public/beacon.js
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*!
|
||||
* @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();
|
||||
});
|
||||
})();
|
||||
Loading…
Add table
Reference in a new issue