mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-03-09 12:27:11 +00:00
Add centralized analytics tracking
This commit is contained in:
@@ -34,6 +34,10 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title></title>
|
||||
<script type="module">
|
||||
import { trackPageView } from "./js/analytics.js";
|
||||
trackPageView(window.location.pathname);
|
||||
</script>
|
||||
<script type="module" crossorigin>
|
||||
var pv = Object.defineProperty;
|
||||
var gv = (e, t, n) =>
|
||||
|
||||
170
js/analytics.js
Normal file
170
js/analytics.js
Normal file
@@ -0,0 +1,170 @@
|
||||
// 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();
|
||||
}
|
||||
18
js/analyticsConfig.js
Normal file
18
js/analyticsConfig.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// js/analyticsConfig.js
|
||||
// Central configuration for site-wide analytics tracking.
|
||||
|
||||
export const ANALYTICS_CONFIG = Object.freeze({
|
||||
/**
|
||||
* Hosted Umami script that powers analytics.
|
||||
* Update this value if the tracker is relocated.
|
||||
*/
|
||||
scriptSrc: "https://umami.malin.onl/script.js",
|
||||
/**
|
||||
* The Umami website identifier for this deployment.
|
||||
*/
|
||||
websiteId: "1f8eead2-79f0-4dba-8c3b-ed9b08b6e877",
|
||||
/**
|
||||
* Event name used when recording individual video views.
|
||||
*/
|
||||
videoViewEventName: "video_view",
|
||||
});
|
||||
20
js/app.js
20
js/app.js
@@ -9,6 +9,7 @@ import { safeDecodeMagnet } from "./magnetUtils.js";
|
||||
import { normalizeAndAugmentMagnet } from "./magnet.js";
|
||||
import { deriveTorrentPlaybackConfig } from "./playbackUtils.js";
|
||||
import { URL_FIRST_ENABLED } from "./constants.js";
|
||||
import { trackVideoView } from "./analytics.js";
|
||||
import {
|
||||
initialWhitelist,
|
||||
initialBlacklist,
|
||||
@@ -3111,6 +3112,14 @@ class bitvidApp {
|
||||
const magnetSupported = isValidMagnetUri(usableMagnetCandidate);
|
||||
const sanitizedMagnet = magnetSupported ? usableMagnetCandidate : "";
|
||||
|
||||
trackVideoView({
|
||||
videoId: video.id || eventId,
|
||||
title: video.title || "Untitled",
|
||||
source: "event",
|
||||
hasMagnet: !!sanitizedMagnet,
|
||||
hasUrl: !!trimmedUrl,
|
||||
});
|
||||
|
||||
this.currentVideo = {
|
||||
...video,
|
||||
url: trimmedUrl,
|
||||
@@ -3195,6 +3204,17 @@ class bitvidApp {
|
||||
const magnetSupported = isValidMagnetUri(usableMagnet);
|
||||
const sanitizedMagnet = magnetSupported ? usableMagnet : "";
|
||||
|
||||
trackVideoView({
|
||||
videoId:
|
||||
typeof title === "string" && title.trim().length > 0
|
||||
? `direct:${title.trim()}`
|
||||
: "direct-playback",
|
||||
title,
|
||||
source: "direct",
|
||||
hasMagnet: !!sanitizedMagnet,
|
||||
hasUrl: !!sanitizedUrl,
|
||||
});
|
||||
|
||||
if (!sanitizedUrl && !sanitizedMagnet) {
|
||||
const message = trimmedMagnet && !magnetSupported
|
||||
? UNSUPPORTED_BTITH_MESSAGE
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// js/index.js
|
||||
|
||||
import { trackPageView } from "./analytics.js";
|
||||
|
||||
// 1) Load modals (login, application, etc.)
|
||||
async function loadModal(url) {
|
||||
try {
|
||||
@@ -301,6 +303,11 @@ function handleQueryParams() {
|
||||
/**
|
||||
* Handle #view=... in the hash and load the correct partial view.
|
||||
*/
|
||||
function recordView(viewName) {
|
||||
const path = `${window.location.pathname}#view=${viewName}`;
|
||||
trackPageView(path);
|
||||
}
|
||||
|
||||
function handleHashChange() {
|
||||
console.log("handleHashChange called, current hash =", window.location.hash);
|
||||
|
||||
@@ -317,6 +324,7 @@ function handleHashChange() {
|
||||
if (typeof initFn === "function") {
|
||||
initFn();
|
||||
}
|
||||
recordView("most-recent-videos");
|
||||
});
|
||||
});
|
||||
return;
|
||||
@@ -332,6 +340,7 @@ function handleHashChange() {
|
||||
if (typeof initFn === "function") {
|
||||
initFn();
|
||||
}
|
||||
recordView(viewName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user