vue3-game-analytics
Version:
Comprehensive analytics tracking system for Vue 3 game applications
915 lines (914 loc) • 29.2 kB
JavaScript
import { reactive as R, computed as y, readonly as L, unref as P } from "vue";
var _ = /* @__PURE__ */ ((e) => (e.BUFFER_OVERFLOW = "BUFFER_OVERFLOW", e.API_SUBMISSION_FAILED = "API_SUBMISSION_FAILED", e.INVALID_EVENT = "INVALID_EVENT", e.CONFIGURATION_ERROR = "CONFIGURATION_ERROR", e.CONSENT_REQUIRED = "CONSENT_REQUIRED", e.DO_NOT_TRACK = "DO_NOT_TRACK", e))(_ || {});
function E(e, t = 100) {
let o, u, i = 0;
function c(...s) {
const r = Date.now(), a = t - (r - i);
return a <= 0 || a > t ? (o && (clearTimeout(o), o = void 0), i = r, u = e.apply(this, s)) : o || (o = setTimeout(() => {
i = Date.now(), o = void 0, u = e.apply(this, s);
}, a)), u;
}
return c;
}
function F(e = 1e3) {
let t = 0, o = performance.now(), u = 0;
function i() {
t++;
const c = performance.now(), s = c - o;
s >= e && (u = Math.round(t * 1e3 / s), t = 0, o = c), requestAnimationFrame(i);
}
return requestAnimationFrame(i), function() {
return u;
};
}
function $(e, t = 2e3) {
typeof window < "u" && "requestIdleCallback" in window ? window.requestIdleCallback(e, { timeout: t }) : setTimeout(e, 1);
}
function M() {
return typeof window > "u" || typeof navigator > "u" ? {} : {
deviceType: H(),
browser: z(),
os: G(),
screenResolution: X(),
orientation: Y(),
connectionType: N(),
language: navigator.language || "unknown",
userAgent: navigator.userAgent,
doNotTrack: Q(),
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
};
}
function H() {
const e = navigator.userAgent;
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(e) ? window.innerWidth > 767 || /iPad/i.test(e) ? "tablet" : "mobile" : "desktop";
}
function z() {
var u, i, c, s, r, a;
const e = navigator.userAgent;
let t = "unknown", o = "unknown";
return /Chrome/.test(e) && !/Chromium|Edge|Edg|OPR|Opera/.test(e) ? (t = "Chrome", o = ((u = e.match(/Chrome\/(\d+\.\d+)/)) == null ? void 0 : u[1]) || "") : /Firefox/.test(e) ? (t = "Firefox", o = ((i = e.match(/Firefox\/(\d+\.\d+)/)) == null ? void 0 : i[1]) || "") : /Safari/.test(e) && !/Chrome|Chromium|Edge|Edg|OPR|Opera/.test(e) ? (t = "Safari", o = ((c = e.match(/Version\/(\d+\.\d+)/)) == null ? void 0 : c[1]) || "") : /Edge|Edg/.test(e) ? (t = "Edge", o = ((s = e.match(/Edge\/(\d+\.\d+)|Edg\/(\d+\.\d+)/)) == null ? void 0 : s[1]) || "") : /OPR|Opera/.test(e) ? (t = "Opera", o = ((r = e.match(/OPR\/(\d+\.\d+)|Opera\/(\d+\.\d+)/)) == null ? void 0 : r[1]) || "") : /MSIE|Trident/.test(e) && (t = "Internet Explorer", o = ((a = e.match(/MSIE (\d+\.\d+)|rv:(\d+\.\d+)/)) == null ? void 0 : a[1]) || ""), `${t} ${o}`.trim();
}
function G() {
var o, u, i, c, s, r;
const e = navigator.userAgent;
let t = "unknown";
if (/Windows NT/.test(e)) {
t = "Windows";
const a = (o = e.match(/Windows NT (\d+\.\d+)/)) == null ? void 0 : o[1];
if (a)
switch (a) {
case "10.0":
t += " 10";
break;
case "6.3":
t += " 8.1";
break;
case "6.2":
t += " 8";
break;
case "6.1":
t += " 7";
break;
case "6.0":
t += " Vista";
break;
case "5.2":
t += " XP";
break;
case "5.1":
t += " XP";
break;
default:
t += ` ${a}`;
break;
}
} else if (/Mac OS X/.test(e)) {
t = "macOS";
const a = (i = (u = e.match(/Mac OS X (\d+[._]\d+)/)) == null ? void 0 : u[1]) == null ? void 0 : i.replace("_", ".");
a && (t += ` ${a}`);
} else if (/iPhone|iPad|iPod/.test(e)) {
t = "iOS";
const a = (s = (c = e.match(/OS (\d+[._]\d+)/)) == null ? void 0 : c[1]) == null ? void 0 : s.replace("_", ".");
a && (t += ` ${a}`);
} else if (/Android/.test(e)) {
t = "Android";
const a = (r = e.match(/Android (\d+\.\d+)/)) == null ? void 0 : r[1];
a && (t += ` ${a}`);
} else /Linux/.test(e) && (t = "Linux");
return t;
}
function X() {
if (typeof screen > "u")
return "unknown";
const e = screen.width || window.innerWidth, t = screen.height || window.innerHeight;
return `${e}x${t}`;
}
function Y() {
return typeof window > "u" || typeof screen > "u" ? "unknown" : screen.orientation ? screen.orientation.type.includes("portrait") ? "portrait" : "landscape" : window.innerHeight > window.innerWidth ? "portrait" : "landscape";
}
function N() {
const e = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
return e && (e.effectiveType || e.type) || "unknown";
}
function Q() {
return navigator.doNotTrack ? navigator.doNotTrack === "1" ? "yes" : "no" : navigator.msDoNotTrack ? navigator.msDoNotTrack === "1" ? "yes" : "no" : window.doNotTrack ? window.doNotTrack === "1" ? "yes" : "no" : "unspecified";
}
function U(e) {
const t = () => {
e({
isOnline: navigator.onLine,
connectionType: N()
});
};
window.addEventListener("online", t), window.addEventListener("offline", t);
const o = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
return o && o.addEventListener("change", t), t(), () => {
window.removeEventListener("online", t), window.removeEventListener("offline", t), o && o.removeEventListener("change", t);
};
}
function O() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(e) {
const t = Math.random() * 16 | 0;
return (e === "x" ? t : t & 3 | 8).toString(16);
});
}
function I(e, t) {
const o = {};
if (!e || !e.attributes)
return o;
for (let u = 0; u < e.attributes.length; u++) {
const i = e.attributes[u];
if (i.name.startsWith("data-")) {
const c = i.name.substring(5);
if (c.startsWith(t + "-")) {
const s = c.substring(t.length + 1);
o[s] = i.value;
}
}
}
return o;
}
function W(e) {
const t = /* @__PURE__ */ new WeakSet();
return JSON.stringify(e, (o, u) => {
if (typeof u == "function")
return "[Function]";
if (u instanceof Error)
return {
message: u.message,
stack: u.stack,
name: u.name
};
if (u instanceof HTMLElement)
return `[HTMLElement ${u.tagName.toLowerCase()}]`;
if (typeof u == "symbol")
return u.toString();
if (typeof u == "object" && u !== null) {
if (t.has(u))
return "[Circular]";
t.add(u);
}
return u;
});
}
function q(e, t) {
try {
const o = W(t);
return localStorage.setItem(e, o), !0;
} catch (o) {
return console.error("Failed to persist data to storage:", o), !1;
}
}
function B(e) {
try {
const t = localStorage.getItem(e);
return t === null ? null : JSON.parse(t);
} catch (t) {
return console.error("Failed to retrieve data from storage:", t), null;
}
}
function V(e) {
return e >= 1 ? !0 : e <= 0 ? !1 : Math.random() <= e;
}
const p = "vue3-game-analytics-events", K = "vue3-game-analytics-config", C = {
batchSize: 50,
flushInterval: 6e4,
// 1 minute
maxQueueSize: 1e3,
trackClicks: !0,
trackTouches: !0,
trackKeyboard: !0,
trackErrors: !0,
trackNetwork: !0,
trackPerformance: !1,
sampleRate: 1,
collectEnvironmentData: !0,
throttleHighFrequencyEvents: !0,
throttleInterval: 100,
debug: !1,
verboseLogging: !1,
visibleTracking: !1,
anonymizeIp: !0,
respectDoNotTrack: !0,
consentRequired: !0
}, j = {
capacity: 1e3
}, n = R({
events: [],
config: {
apiEndpoint: "",
gameId: "",
playId: "",
...C
},
isEnabled: !1,
isDebugMode: !1,
hasConsent: !1,
bufferInfo: {
capacity: j.capacity,
size: 0,
overflowCount: 0
},
networkStatus: {
isOnline: !0,
lastConnectionType: "unknown"
},
pendingFlush: !1,
failedSubmissions: 0,
lastFlushTime: null
}), x = {
/**
* Check if tracking is currently allowed based on settings and user preferences
*/
isTrackingAllowed: y(() => !(!n.isEnabled || n.config.consentRequired && !n.hasConsent || n.config.respectDoNotTrack && (navigator.doNotTrack === "1" || // @ts-ignore: Non-standard property
window.doNotTrack === "1" || // @ts-ignore: Non-standard property
navigator.msDoNotTrack === "1"))),
/**
* Get the number of events in the queue
*/
eventCount: y(() => n.events.length),
/**
* Check if we should be showing debug information
*/
shouldShowDebugInfo: y(() => n.isDebugMode || !!n.config.debug || !!n.config.visibleTracking)
}, g = {
/**
* Initialize the store with configuration
* @param options Configuration options
*/
initialize(e) {
n.config = {
...C,
...e
}, n.isDebugMode = !!n.config.debug;
const t = B(p);
t && Array.isArray(t) && (n.events = t, n.bufferInfo.size = t.length, g.logDebug(`Loaded ${t.length} events from storage`)), n.isEnabled = !0, n.config.flushInterval && n.config.flushInterval > 0 && setInterval(() => {
n.events.length > 0 && g.flushEvents();
}, n.config.flushInterval), n.networkStatus.isOnline = navigator.onLine, typeof window < "u" && window.addEventListener("beforeunload", () => {
g.saveEventsToStorage(), n.events.length > 0 && navigator.onLine && g.flushEvents();
}), g.logDebug("Analytics store initialized", n.config);
},
/**
* Set user consent for tracking
* @param hasConsent Whether user has given consent
*/
setConsent(e) {
n.hasConsent = e, g.logDebug(`User consent set to: ${e}`), e || (g.clearEvents(), localStorage.removeItem(p), localStorage.removeItem(K));
},
/**
* Enable or disable debug mode
* @param enabled Whether debug mode should be enabled
*/
setDebugMode(e) {
n.isDebugMode = e, g.logDebug(`Debug mode ${e ? "enabled" : "disabled"}`);
},
/**
* Update network status
* @param status Network status information
*/
updateNetworkStatus(e) {
const t = !n.networkStatus.isOnline;
n.networkStatus = {
isOnline: e.isOnline,
lastConnectionType: e.connectionType
}, t && e.isOnline && n.events.length > 0 && g.flushEvents(), g.logDebug("Network status updated", e);
},
/**
* Track a new analytics event
* @param event Event data to track
*/
trackEvent(e) {
if (!x.isTrackingAllowed.value) {
g.logDebug("Event not tracked: tracking not allowed", e);
return;
}
if (n.config.sampleRate !== void 0 && n.config.sampleRate < 1 && !V(n.config.sampleRate)) {
g.logDebug("Event sampled out", e);
return;
}
const t = {
id: O(),
timestamp: Date.now(),
type: e.type || "custom",
gameId: e.gameId || n.config.gameId,
playId: e.playId || n.config.playId,
...e
};
if (n.config.collectEnvironmentData && !t.environmentData && (t.environmentData = M()), n.config.eventTransformer)
try {
const o = n.config.eventTransformer(t);
g.addEventToQueue(o);
} catch (o) {
console.error("Error in event transformer:", o), g.addEventToQueue(t);
}
else
g.addEventToQueue(t);
},
/**
* Add event to the queue and handle queue management
* @param event Event to add
*/
addEventToQueue(e) {
n.events.push(e), n.bufferInfo.size = n.events.length, g.logDebug("Event tracked", e), n.config.maxQueueSize && n.events.length > n.config.maxQueueSize && (n.events = n.events.slice(-n.config.maxQueueSize), n.bufferInfo.size = n.events.length, n.bufferInfo.overflowCount++, g.logDebug(`Queue overflow: removed oldest events. Current size: ${n.events.length}`)), n.config.batchSize && n.events.length >= n.config.batchSize && g.flushEvents(), $(() => {
g.saveEventsToStorage();
});
},
/**
* Save events to localStorage for persistence
*/
saveEventsToStorage() {
n.events.length > 0 && (q(p, n.events), g.logDebug(`Saved ${n.events.length} events to storage`));
},
/**
* Send all events to the API endpoint
* @returns Promise that resolves when all events are sent
*/
async flushEvents() {
if (!(n.events.length === 0 || n.pendingFlush)) {
if (!n.networkStatus.isOnline) {
g.logDebug("Flush skipped: device is offline");
return;
}
n.pendingFlush = !0, g.logDebug(`Flushing ${n.events.length} events to ${n.config.apiEndpoint}`);
try {
const e = [...n.events], t = await fetch(n.config.apiEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Game-ID": n.config.gameId,
"X-Play-ID": n.config.playId
},
body: JSON.stringify({ events: e })
});
if (!t.ok)
throw new Error(`API responded with status ${t.status}`);
n.events = n.events.filter((o) => !e.some((u) => u.id === o.id)), n.bufferInfo.size = n.events.length, n.lastFlushTime = Date.now(), localStorage.removeItem(p), g.logDebug(`Successfully sent ${e.length} events`);
} catch (e) {
n.failedSubmissions++, console.error("Failed to send events:", e), n.config.trackErrors && g.trackEvent({
type: "error",
target: "analytics",
error: {
message: e instanceof Error ? e.message : String(e),
code: _.API_SUBMISSION_FAILED
}
});
} finally {
n.pendingFlush = !1;
}
}
},
/**
* Clear all events from the queue
*/
clearEvents() {
n.events = [], n.bufferInfo.size = 0, g.logDebug("Event queue cleared");
},
/**
* Filter events in the queue based on criteria
* @param filterFn Function to filter events
*/
filterEvents(e) {
const t = n.events.length;
n.events = n.events.filter(e), n.bufferInfo.size = n.events.length, g.logDebug(`Filtered events: removed ${t - n.events.length} events`);
},
/**
* Export all events as JSON
* @returns JSON string of all events
*/
exportEvents() {
return JSON.stringify(n.events);
},
/**
* Log debug messages if debug mode is enabled
* @param message Debug message
* @param data Optional data to log
*/
logDebug(e, t) {
(n.isDebugMode || n.config.debug) && (n.config.verboseLogging && t ? console.debug(`[GameAnalytics] ${e}`, t) : console.debug(`[GameAnalytics] ${e}`));
}
};
let T = null;
function h() {
return T || (T = {
// Expose readonly state
...L(n),
// Expose getters
...x,
// Expose actions
...g
}), T;
}
function J(e) {
const t = h(), o = [], u = e.throttleInterval || 100, i = e.throttleHighFrequencyEvents !== !1;
if (e.trackClicks !== !1) {
const c = (r) => {
if (!r.target) return;
const a = r.target, l = {
type: "click",
target: w(a),
coordinates: {
x: r.clientX,
y: r.clientY,
screenX: r.screenX,
screenY: r.screenY
},
elementData: {
type: a.tagName.toLowerCase(),
attributes: I(a, "game")
}
};
t.trackEvent(l), t.shouldShowDebugInfo && e.visibleTracking && D(r.clientX, r.clientY);
}, s = i ? E(c, u) : c;
document.addEventListener("click", s), o.push({
element: document,
type: "click",
handler: s
});
}
if (e.trackTouches !== !1) {
const c = (l) => {
if (!l.target || !l.touches.length) return;
const d = l.touches[0], f = l.target, m = {
type: "touch_start",
target: w(f),
coordinates: {
x: d.clientX,
y: d.clientY,
screenX: d.screenX,
screenY: d.screenY
},
elementData: {
type: f.tagName.toLowerCase(),
attributes: I(f, "game")
}
};
t.trackEvent(m), t.shouldShowDebugInfo && e.visibleTracking && D(d.clientX, d.clientY);
}, s = (l) => {
if (!l.target || !l.changedTouches.length) return;
const d = l.changedTouches[0], f = l.target, m = {
type: "touch_end",
target: w(f),
coordinates: {
x: d.clientX,
y: d.clientY,
screenX: d.screenX,
screenY: d.screenY
},
elementData: {
type: f.tagName.toLowerCase(),
attributes: I(f, "game")
}
};
t.trackEvent(m);
}, r = i ? E(c, u) : c, a = i ? E(s, u) : s;
document.addEventListener("touchstart", r), document.addEventListener("touchend", a), o.push({
element: document,
type: "touchstart",
handler: r
}), o.push({
element: document,
type: "touchend",
handler: a
});
}
if (e.trackKeyboard !== !1) {
const c = (r) => {
if (["Control", "Shift", "Alt", "Meta"].includes(r.key) || r.repeat)
return;
const a = r.target, l = {
type: "keydown",
target: w(a),
elementData: {
type: a.tagName.toLowerCase(),
key: r.key,
keyCode: r.keyCode
}
};
t.trackEvent(l);
}, s = i ? E(c, u) : c;
document.addEventListener("keydown", s), o.push({
element: document,
type: "keydown",
handler: s
});
}
if (e.trackErrors !== !1) {
const c = (r) => {
var l;
const a = {
type: "error",
error: {
message: r.message,
stack: (l = r.error) == null ? void 0 : l.stack
}
};
t.trackEvent(a);
};
window.addEventListener("error", c), o.push({
element: window,
type: "error",
handler: c
});
const s = (r) => {
var l, d;
const a = {
type: "error",
error: {
message: ((l = r.reason) == null ? void 0 : l.message) || "Unhandled promise rejection",
stack: (d = r.reason) == null ? void 0 : d.stack,
code: "UNHANDLED_REJECTION"
}
};
t.trackEvent(a);
};
window.addEventListener("unhandledrejection", s), o.push({
element: window,
type: "unhandledrejection",
handler: s
});
}
return () => {
o.forEach(({ element: c, type: s, handler: r }) => {
c.removeEventListener(s, r);
});
};
}
function w(e) {
if (e.id)
return `#${e.id}`;
const t = e.getAttribute("data-game-id");
if (t)
return `[data-game-id="${t}"]`;
const o = e.getAttribute("name");
return o ? `[name="${o}"]` : e.className && typeof e.className == "string" ? `.${e.className.trim().replace(/\s+/g, ".")}` : e.tagName.toLowerCase();
}
function D(e, t) {
const o = document.createElement("div");
o.style.position = "absolute", o.style.width = "20px", o.style.height = "20px", o.style.borderRadius = "50%", o.style.backgroundColor = "rgba(255, 0, 0, 0.5)", o.style.left = `${e - 10}px`, o.style.top = `${t - 10}px`, o.style.pointerEvents = "none", o.style.zIndex = "10000", o.style.transform = "scale(0)", o.style.transition = "transform 0.2s ease-out, opacity 0.5s ease-out", document.body.appendChild(o), setTimeout(() => {
o.style.transform = "scale(1)";
}, 10), setTimeout(() => {
o.style.opacity = "0", setTimeout(() => {
o.parentNode && o.parentNode.removeChild(o);
}, 500);
}, 1e3);
}
const Z = {
install(e, t) {
if (!t.apiEndpoint) {
console.error("[GameAnalytics] apiEndpoint is required");
return;
}
if (!t.gameId) {
console.error("[GameAnalytics] gameId is required");
return;
}
t.playId || (t.playId = O()), e.config.globalProperties._gameAnalyticsOptions = t, e.config.globalProperties._eventQueue = [], e.config.globalProperties._analyticsReady = !1;
let o, u, i;
const c = e.mount;
e.mount = function(r) {
const a = c.call(this, r);
try {
const l = h();
l.initialize(t), o = J(t), u = U((m) => {
l.updateNetworkStatus(m);
});
let d;
t.trackPerformance && (d = F(), i = setInterval(() => {
const m = d ? d() : 0;
l.trackEvent({
type: "performance",
metadata: {
fps: m,
// @ts-expect-error: Non-standard property
memoryUsage: window.performance && window.performance.memory ? (
// @ts-expect-error: Non-standard property
Math.round(window.performance.memory.usedJSHeapSize / (1024 * 1024))
) : void 0
}
});
}, 3e4)), e.config.globalProperties._consentSetting !== void 0 && (l.setConsent(e.config.globalProperties._consentSetting), delete e.config.globalProperties._consentSetting);
const f = e.config.globalProperties._eventQueue;
f && f.length > 0 && (f.forEach((m) => {
l.trackEvent(m);
}), e.config.globalProperties._eventQueue = []), e.config.globalProperties._analyticsReady = !0;
} catch (l) {
console.error("[GameAnalytics] Failed to initialize analytics store:", l), e.config.globalProperties._analyticsReady = !0;
}
return a;
}, e.config.globalProperties.$gameAnalytics = {
trackEvent: (r) => {
if (e.config.globalProperties._analyticsReady)
try {
h().trackEvent(r);
} catch (a) {
console.error("[GameAnalytics] Error tracking event:", a);
}
else
e.config.globalProperties._eventQueue.push(r);
},
flushEvents: () => {
if (e.config.globalProperties._analyticsReady)
try {
h().flushEvents();
} catch (r) {
console.error("[GameAnalytics] Error flushing events:", r);
}
},
enableDebug: () => {
if (e.config.globalProperties._analyticsReady)
try {
h().setDebugMode(!0);
} catch (r) {
console.error("[GameAnalytics] Error enabling debug mode:", r);
}
},
disableDebug: () => {
if (e.config.globalProperties._analyticsReady)
try {
h().setDebugMode(!1);
} catch (r) {
console.error("[GameAnalytics] Error disabling debug mode:", r);
}
},
clearEvents: () => {
if (e.config.globalProperties._analyticsReady)
try {
h().clearEvents();
} catch (r) {
console.error("[GameAnalytics] Error clearing events:", r);
}
e.config.globalProperties._eventQueue = [];
},
getEventCount: () => {
if (e.config.globalProperties._analyticsReady)
try {
return h().eventCount;
} catch (r) {
return console.error("[GameAnalytics] Error getting event count:", r), 0;
}
return e.config.globalProperties._eventQueue.length;
},
setConsent: (r) => {
if (e.config.globalProperties._analyticsReady)
try {
h().setConsent(r);
} catch (a) {
console.error("[GameAnalytics] Error setting consent:", a);
}
else
e.config.globalProperties._consentSetting = r;
}
};
const s = e.unmount;
e.unmount = function() {
try {
if (o && o(), u && u(), i && clearInterval(i), e.config.globalProperties._analyticsReady)
try {
h().flushEvents();
} catch (r) {
console.error("[GameAnalytics] Error flushing events during unmount:", r);
}
} catch (r) {
console.error("[GameAnalytics] Error during cleanup:", r);
}
s.call(this);
};
}
};
function ee() {
const e = h(), t = /* @__PURE__ */ new WeakMap(), o = (i, c, s) => {
const r = s.value || {}, a = r.event || s.arg || "click", l = r.target || i.getAttribute("data-track-id") || te(i), d = r.context || {};
let f = {
type: a,
target: l,
elementData: {
type: i.tagName.toLowerCase(),
attributes: I(i, "game")
},
metadata: d
};
if (c instanceof MouseEvent)
f.coordinates = {
x: c.clientX,
y: c.clientY,
screenX: c.screenX,
screenY: c.screenY
};
else if (c instanceof TouchEvent && c.touches.length > 0) {
const m = c.touches[0];
f.coordinates = {
x: m.clientX,
y: m.clientY,
screenX: m.screenX,
screenY: m.screenY
};
}
e.trackEvent(f);
}, u = (i, c, s, r) => {
var f;
const a = ((f = s.value) == null ? void 0 : f.throttle) || 300;
let l = t.get(i);
l || (l = {}, t.set(i, l));
const d = `${c}_${a}`;
return l[d] || (l[d] = E(r, a)), l[d];
};
return {
// When directive is mounted to an element
mounted(i, c) {
(c.arg ? [c.arg] : ["click"]).forEach((r) => {
const l = u(i, r, c, (f) => o(i, f, c));
i.addEventListener(r, l);
const d = i._trackHandlers || {};
d[r] = l, i._trackHandlers = d;
});
},
// When directive parameters change
updated(i, c) {
i._trackHandlers && Object.entries(i._trackHandlers).forEach(([a, l]) => {
i.removeEventListener(a, l);
});
const s = c.arg ? [c.arg] : ["click"], r = {};
s.forEach((a) => {
const d = u(i, a, c, (f) => o(i, f, c));
i.addEventListener(a, d), r[a] = d;
}), i._trackHandlers = r;
},
// When directive is removed
unmounted(i) {
i._trackHandlers && (Object.entries(i._trackHandlers).forEach(([c, s]) => {
i.removeEventListener(c, s);
}), delete i._trackHandlers), t.delete(i);
}
};
}
function te(e) {
if (e.id)
return `#${e.id}`;
const t = e.getAttribute("data-game-id");
if (t)
return `[data-game-id="${t}"]`;
if (e.className && typeof e.className == "string") {
const o = e.className.trim().split(/\s+/);
if (o.length > 0)
return `.${o.join(".")}`;
}
return e.tagName.toLowerCase();
}
function ne(e) {
e.directive("track", ee());
}
function oe() {
const e = h();
function t(s) {
e.trackEvent(s);
}
function o(s, r = {}) {
const {
type: a = "interaction",
throttleInterval: l = 100,
metadata: d = {}
} = r;
return E((m, A = {}) => {
const v = P(s);
if (!v) return;
const k = v.getBoundingClientRect();
e.trackEvent({
type: m || a,
target: v.id ? `#${v.id}` : v.tagName.toLowerCase(),
coordinates: {
x: k.left + k.width / 2,
y: k.top + k.height / 2
},
elementData: {
type: v.tagName.toLowerCase(),
state: v.getAttribute("data-state") || void 0,
attributes: Array.from(v.attributes).filter((b) => b.name.startsWith("data-game-")).reduce((b, S) => (b[S.name.replace("data-game-", "")] = S.value, b), {})
},
metadata: {
...d,
...A
}
});
}, l);
}
function u(s) {
const r = {};
return {
/**
* Start tracking a timed interaction
* @param metadata Additional metadata for the start event
*/
start: (a = {}) => {
r[s] = Date.now(), e.trackEvent({
type: "interaction_start",
target: s,
metadata: {
interactionId: s,
...a
}
});
},
/**
* End tracking a timed interaction and calculate duration
* @param metadata Additional metadata for the end event
* @returns Duration in milliseconds, or undefined if start wasn't called
*/
end: (a = {}) => {
const l = r[s];
if (!l) {
console.warn(`[GameAnalytics] No start time found for timed interaction "${s}"`);
return;
}
const d = Date.now() - l;
return delete r[s], e.trackEvent({
type: "interaction_end",
target: s,
duration: d,
metadata: {
interactionId: s,
...a
}
}), d;
},
/**
* Cancel tracking a timed interaction
* @param reason Optional reason for cancellation
*/
cancel: (a) => {
const l = r[s];
if (!l) return;
const d = Date.now() - l;
delete r[s], e.trackEvent({
type: "interaction_cancelled",
target: s,
duration: d,
metadata: {
interactionId: s,
reason: a
}
});
}
};
}
function i(s) {
e.trackEvent({
type: "game_state_change",
gameState: s
});
}
async function c() {
return e.flushEvents();
}
return {
// Core tracking methods
trackEvent: t,
trackElement: o,
trackTimedInteraction: u,
trackGameState: i,
// Utility methods
flushEvents: c,
clearEvents: () => e.clearEvents(),
// Debug methods
enableDebug: () => e.setDebugMode(!0),
disableDebug: () => e.setDebugMode(!1),
isDebugMode: y(() => e.isDebugMode),
// Privacy and consent
setConsent: (s) => e.setConsent(s),
hasConsent: y(() => e.hasConsent),
// Store information
eventCount: y(() => e.eventCount),
isEnabled: y(() => e.isEnabled),
isOnline: y(() => e.networkStatus.isOnline)
};
}
const ae = {
install(e, t) {
e.use(Z, t), ne(e);
}
};
export {
_ as AnalyticsErrorCode,
ae as default,
O as generateUUID,
oe as useGameAnalytics
};