Files
bitvid/js/analytics.js
2025-09-25 11:48:31 -04:00

195 lines
4.4 KiB
JavaScript

// 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;
}
// 🔧 merged conflicting changes from codex/add-tracking-script-to-all-pages vs unstable
flushTimerId = window.setInterval(() => {
if (window.umami && typeof window.umami.track === "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;
// Allow Umami to handle automatic pageview tracking by default.
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 || "";
// 🔧 merged conflicting changes from codex/add-tracking-script-to-all-pages vs unstable
let absoluteUrl = resolvedPath;
try {
absoluteUrl = new URL(resolvedPath, window.location.origin).toString();
} catch (err) {
// Fall back to the provided path if it cannot be resolved.
}
let absoluteReferrer = resolvedReferrer;
if (resolvedReferrer) {
try {
absoluteReferrer = new URL(resolvedReferrer, window.location.origin).toString();
} catch (err) {
// Keep original referrer if it cannot be normalized.
}
}
invokeUmami("track", [
(basePayload = {}) => ({
...basePayload,
url: absoluteUrl || basePayload.url,
referrer: absoluteReferrer || basePayload.referrer,
}),
"pageview",
]);
}
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;
}
// 🔧 merged conflicting changes from codex/add-tracking-script-to-all-pages vs unstable
invokeUmami("track", [ANALYTICS_CONFIG.videoViewEventName, payload]);
}
// Immediately queue the analytics script so page views are captured early.
if (isBrowserEnvironment()) {
ensureAnalyticsLoaded();
}