Files
MatchLiveTv/backend/public/site-analytics.js
T
eminuxandCursor 356aa28fad Aggiunge la garanzia Soddisfatti o rimborsati di 30 giorni su prezzi, homepage e Termini.
Riduce il rischio percepito all'acquisto (Light e Full) e chiama l'archivio replay in modo orientato all'utente, senza cambiare prezzi o logica dei piani.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 19:27:28 +02:00

343 lines
10 KiB
JavaScript

/*! Match Live TV first-party site analytics (click + mouse move + scroll). Requires analytics cookie consent. */
(function () {
"use strict";
var ENDPOINT = "/analytics/events";
var SNAPSHOT_ENDPOINT = "/analytics/snapshot";
var STORAGE_KEY = "mltv_cookie_consent";
var COOKIE_NAME = "mltv_cookie_consent";
var FLUSH_MS = 6000;
var MAX_QUEUE = 60;
var MOVE_THROTTLE_MS = 180;
var MOVE_GRID = 40;
var SNAPSHOT_DELAY_MS = 4000;
var queue = [];
var moveBuckets = {};
var maxScroll = 0;
var flushTimer = null;
var started = false;
var lastMoveAt = 0;
function excludedPath(pathname) {
var p = pathname || "/";
if (p.indexOf("/admin") === 0) return true;
if (p.indexOf("/regia") === 0) return true;
if (p.indexOf("/analytics") === 0) return true;
if (/^\/live\/[^/]+/.test(p)) return true;
if (/^\/replay\/[^/]+/.test(p)) return true;
return false;
}
function isAnalyticsSuppressed() {
var meta = document.querySelector("meta[name='mltv-analytics-suppress']");
return meta && meta.getAttribute("content") === "1";
}
function hasAnalyticsConsent() {
try {
var raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
var parsed = JSON.parse(raw);
if (parsed && parsed.analytics) return true;
}
} catch (e) { /* ignore */ }
var match = document.cookie.match(
new RegExp("(?:^|; )" + COOKIE_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "=([^;]*)")
);
return !!(match && decodeURIComponent(match[1]) === "all");
}
function deviceBucket() {
var w = window.innerWidth || document.documentElement.clientWidth || 1024;
if (w < 768) return "mobile";
if (w < 1024) return "tablet";
return "desktop";
}
function pageSize() {
var doc = document.documentElement;
var body = document.body;
return {
width: Math.max(body.scrollWidth || 0, doc.scrollWidth || 0, window.innerWidth || 1),
height: Math.max(body.scrollHeight || 0, doc.scrollHeight || 0, window.innerHeight || 1)
};
}
function scrollPct() {
var doc = document.documentElement;
var body = document.body;
var scrollTop = window.pageYOffset || doc.scrollTop || body.scrollTop || 0;
var height = Math.max(
body.scrollHeight || 0,
doc.scrollHeight || 0,
body.offsetHeight || 0,
doc.offsetHeight || 0,
doc.clientHeight || 0
);
var view = window.innerHeight || doc.clientHeight || 0;
var pct = ((scrollTop + view) / Math.max(height, 1)) * 100;
if (scrollTop <= 0 && view >= height) return 100;
return Math.max(0, Math.min(100, Math.round(pct)));
}
function pushEvent(evt) {
if (!started) return;
queue.push(evt);
if (queue.length >= MAX_QUEUE) flush(false);
}
function flushMovesIntoQueue() {
var keys = Object.keys(moveBuckets);
if (!keys.length) return;
var device = deviceBucket();
var path = location.pathname;
var ts = Date.now();
keys.forEach(function (key) {
var parts = key.split(",");
var cx = parseInt(parts[0], 10);
var cy = parseInt(parts[1], 10);
var n = moveBuckets[key];
if (!n) return;
// cell center in %
var x = ((cx + 0.5) / MOVE_GRID) * 100;
var y = ((cy + 0.5) / MOVE_GRID) * 100;
pushEvent({ type: "move", path: path, device: device, x: x, y: y, n: n, ts: ts });
});
moveBuckets = {};
}
function flush(useBeacon) {
flushMovesIntoQueue();
if (!queue.length || !hasAnalyticsConsent() || isAnalyticsSuppressed()) {
queue = [];
return;
}
var batch = queue.splice(0, MAX_QUEUE);
var body = JSON.stringify({ events: batch });
try {
if (useBeacon && navigator.sendBeacon) {
var blob = new Blob([body], { type: "application/json" });
navigator.sendBeacon(ENDPOINT, blob);
return;
}
} catch (e) { /* fall through */ }
try {
fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: body,
keepalive: true,
credentials: "same-origin"
}).catch(function () { /* ignore */ });
} catch (err) { /* ignore */ }
}
function trackPageview() {
pushEvent({
type: "pageview",
path: location.pathname,
device: deviceBucket(),
ts: Date.now()
});
}
function onClick(event) {
var target = event.target;
if (!(target instanceof Element)) return;
if (target.closest("input, textarea, select, [contenteditable='true']")) return;
var size = pageSize();
var x = event.pageX;
var y = event.pageY;
if (typeof x !== "number" || typeof y !== "number") return;
pushEvent({
type: "click",
path: location.pathname,
device: deviceBucket(),
x: Math.max(0, Math.min(100, (x / size.width) * 100)),
y: Math.max(0, Math.min(100, (y / size.height) * 100)),
ts: Date.now()
});
}
function onMouseMove(event) {
var now = Date.now();
if (now - lastMoveAt < MOVE_THROTTLE_MS) return;
lastMoveAt = now;
var size = pageSize();
var x = event.pageX;
var y = event.pageY;
if (typeof x !== "number" || typeof y !== "number") return;
var xPct = Math.max(0, Math.min(100, (x / size.width) * 100));
var yPct = Math.max(0, Math.min(100, (y / size.height) * 100));
var cx = Math.min(MOVE_GRID - 1, Math.max(0, Math.floor((xPct / 100) * MOVE_GRID)));
var cy = Math.min(MOVE_GRID - 1, Math.max(0, Math.floor((yPct / 100) * MOVE_GRID)));
var key = cx + "," + cy;
moveBuckets[key] = (moveBuckets[key] || 0) + 1;
}
function onScroll() {
var pct = scrollPct();
if (pct > maxScroll) maxScroll = pct;
}
function flushScroll() {
if (maxScroll <= 0) return;
pushEvent({
type: "scroll",
path: location.pathname,
device: deviceBucket(),
scroll: maxScroll,
ts: Date.now()
});
}
function scheduleFlush() {
if (flushTimer) return;
flushTimer = setInterval(function () {
flushScroll();
flush(false);
}, FLUSH_MS);
}
function snapshotStorageKey() {
return "mltv_snap:" + location.pathname + ":" + deviceBucket();
}
function todayKey() {
var d = new Date();
return d.getUTCFullYear() + "-" + (d.getUTCMonth() + 1) + "-" + d.getUTCDate();
}
function alreadyCapturedToday() {
try {
return localStorage.getItem(snapshotStorageKey()) === todayKey();
} catch (e) {
return false;
}
}
function markCapturedToday() {
try {
localStorage.setItem(snapshotStorageKey(), todayKey());
} catch (e) { /* ignore */ }
}
function loadHtml2Canvas(cb) {
if (window.html2canvas) {
cb(window.html2canvas);
return;
}
var s = document.createElement("script");
s.src = "/vendor/html2canvas.min.js";
s.async = true;
s.onload = function () {
if (window.html2canvas) cb(window.html2canvas);
};
s.onerror = function () { /* ignore */ };
document.head.appendChild(s);
}
function captureSnapshot() {
if (!hasAnalyticsConsent() || isAnalyticsSuppressed() || alreadyCapturedToday()) return;
loadHtml2Canvas(function (html2canvas) {
var size = pageSize();
html2canvas(document.documentElement, {
scale: Math.min(1, 1400 / Math.max(size.width, 1)),
useCORS: true,
allowTaint: false,
logging: false,
windowWidth: size.width,
windowHeight: size.height,
scrollX: 0,
scrollY: 0
})
.then(function (canvas) {
if (!canvas || !canvas.toBlob) return;
canvas.toBlob(
function (blob) {
if (!blob || blob.size < 8000 || blob.size > 1800000) return;
var fd = new FormData();
fd.append("image", blob, "snapshot.jpg");
fd.append("path", location.pathname);
fd.append("device", deviceBucket());
fd.append("width", String(Math.round(size.width)));
fd.append("height", String(Math.round(size.height)));
fetch(SNAPSHOT_ENDPOINT, {
method: "POST",
body: fd,
credentials: "same-origin"
})
.then(function (res) {
if (res.ok) markCapturedToday();
})
.catch(function () { /* ignore */ });
},
"image/jpeg",
0.72
);
})
.catch(function () { /* ignore */ });
});
}
function trackNamedEvent(name) {
if (!name) return;
if (typeof window.gtag === "function") {
window.gtag("event", name, { event_category: "engagement" });
}
}
function onNamedEventClick(event) {
var target = event.target;
if (!(target instanceof Element)) return;
var el = target.closest("[data-mltv-event-click]");
if (!el) return;
trackNamedEvent(el.getAttribute("data-mltv-event-click"));
}
function onDetailsToggle(event) {
var el = event.target;
if (!(el instanceof HTMLDetailsElement) || !el.open) return;
trackNamedEvent(el.getAttribute("data-mltv-event"));
}
function start() {
if (started) return;
if (excludedPath(location.pathname)) return;
if (isAnalyticsSuppressed()) return;
if (!hasAnalyticsConsent()) return;
started = true;
maxScroll = scrollPct();
trackPageview();
document.addEventListener("click", onClick, true);
document.addEventListener("mousemove", onMouseMove, { passive: true });
window.addEventListener("scroll", onScroll, { passive: true });
document.addEventListener("visibilitychange", function () {
if (document.visibilityState === "hidden") {
flushScroll();
flush(true);
}
});
window.addEventListener("pagehide", function () {
flushScroll();
flush(true);
});
scheduleFlush();
setTimeout(captureSnapshot, SNAPSHOT_DELAY_MS);
}
window.mltvSiteAnalyticsStart = start;
document.addEventListener("click", onNamedEventClick, true);
document.addEventListener("toggle", onDetailsToggle, true);
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", start);
} else {
start();
}
})();