// js/analytics.js import { ANALYTICS_CONFIG } from "./analyticsConfig.js"; const SCRIPT_ATTR = "data-bitvid-analytics"; const SCRIPT_IDENTIFIER = "umami"; const pendingCalls = []; let flushTimerId = null; let scriptLoadedOnce = false; function isBrowserEnvironment() { return typeof window !== "undefined" && typeof document !== "undefined"; } function flushPendingCalls() { if (!isBrowserEnvironment()) { return; } const umami = window.umami; if (!umami) { return; } while (pendingCalls.length > 0) { const { method, args } = pendingCalls.shift(); const fn = typeof umami[method] === "function" ? umami[method] : null; if (!fn) { continue; } try { fn.apply(umami, args); } catch (err) { console.warn("[analytics] Failed to call", method, err); } } } function scheduleFlush() { if (!isBrowserEnvironment()) { return; } if (flushTimerId !== null) { return; } flushTimerId = window.setInterval(() => { if (window.umami && typeof window.umami.trackEvent === "function") { window.clearInterval(flushTimerId); flushTimerId = null; flushPendingCalls(); } }, 500); } export function ensureAnalyticsLoaded(doc = typeof document !== "undefined" ? document : null) { if (!doc) { return null; } let script = doc.querySelector(`script[${SCRIPT_ATTR}]`); if (script) { if (!scriptLoadedOnce) { // If a server rendered script already exists, make sure we flush asap. scriptLoadedOnce = true; flushPendingCalls(); } return script; } script = doc.createElement("script"); script.defer = true; script.src = ANALYTICS_CONFIG.scriptSrc; script.setAttribute(SCRIPT_ATTR, SCRIPT_IDENTIFIER); script.dataset.websiteId = ANALYTICS_CONFIG.websiteId; script.addEventListener("load", () => { scriptLoadedOnce = true; flushPendingCalls(); }); doc.head.appendChild(script); scheduleFlush(); return script; } function queueCall(method, args) { pendingCalls.push({ method, args }); scheduleFlush(); } function invokeUmami(method, args) { if (!isBrowserEnvironment()) { return; } ensureAnalyticsLoaded(); const umami = window.umami; if (umami && typeof umami[method] === "function") { try { umami[method].apply(umami, args); } catch (err) { console.warn("[analytics] Failed to call", method, err); } return; } queueCall(method, args); } export function trackPageView(path, referrer) { if (!isBrowserEnvironment()) { return; } const resolvedPath = typeof path === "string" && path.length > 0 ? path : `${window.location.pathname}${window.location.hash || ""}`; const resolvedReferrer = typeof referrer === "string" ? referrer : document.referrer || ""; invokeUmami("trackView", [resolvedPath, resolvedReferrer]); } export function trackVideoView({ videoId, title, source, hasMagnet, hasUrl, } = {}) { if (!isBrowserEnvironment()) { return; } const payload = {}; if (videoId) { payload.videoId = String(videoId); } if (title) { payload.title = String(title); } if (source) { payload.source = String(source); } if (typeof hasMagnet === "boolean") { payload.hasMagnet = hasMagnet; } if (typeof hasUrl === "boolean") { payload.hasUrl = hasUrl; } invokeUmami("trackEvent", [ ANALYTICS_CONFIG.videoViewEventName, payload, ]); } // Immediately queue the analytics script so page views are captured early. if (isBrowserEnvironment()) { ensureAnalyticsLoaded(); }