Files
e-ticket/assets/modules/analytics.js
Serreau Jovann d57669b5b9 Enable analytics tracking in dev and prod
Remove dev environment check — tracking runs everywhere.
Data won't mix since each environment has its own database.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:06:12 +01:00

115 lines
3.2 KiB
JavaScript

const ENDPOINT = '/t'
const SK_UID = '_u'
const SK_HASH = '_h'
let encKey = null
async function importKey(b64) {
const raw = Uint8Array.from(atob(b64), c => c.charCodeAt(0))
return globalThis.crypto.subtle.importKey('raw', raw, 'AES-GCM', false, ['encrypt', 'decrypt'])
}
async function encrypt(data) {
if (!encKey) return null
const json = new TextEncoder().encode(JSON.stringify(data))
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12))
const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv, tagLength: 128 }, encKey, json)
const buf = new Uint8Array(encrypted)
const combined = new Uint8Array(12 + buf.length)
combined.set(iv)
combined.set(buf, 12)
return btoa(String.fromCharCode(...combined))
}
async function decrypt(b64) {
if (!encKey) return null
const raw = Uint8Array.from(atob(b64), c => c.charCodeAt(0))
const iv = raw.slice(0, 12)
const data = raw.slice(12)
try {
const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 128 }, encKey, data)
return JSON.parse(new TextDecoder().decode(decrypted))
} catch {
return null
}
}
async function send(data, expectResponse = false) {
const d = await encrypt(data)
if (!d) return null
try {
if (!expectResponse && navigator.sendBeacon) {
navigator.sendBeacon(ENDPOINT, JSON.stringify({ d }))
return null
}
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ d }),
keepalive: true,
})
if (!res.ok || res.status === 204) return null
const json = await res.json()
return json.d ? await decrypt(json.d) : null
} catch {
return null
}
}
async function getOrCreateVisitor() {
let uid = sessionStorage.getItem(SK_UID)
let hash = sessionStorage.getItem(SK_HASH)
if (uid && hash) return { uid, hash }
const resp = await send({
sw: screen.width,
sh: screen.height,
l: navigator.language || null,
}, true)
if (!resp || !resp.uid || !resp.h) return null
sessionStorage.setItem(SK_UID, resp.uid)
sessionStorage.setItem(SK_HASH, resp.h)
return { uid: resp.uid, hash: resp.h }
}
async function trackPageView(visitor) {
await send({
uid: visitor.uid,
h: visitor.hash,
u: location.pathname + location.search,
t: document.title,
r: document.referrer || null,
})
}
export async function initAnalytics() {
const keyB64 = document.body.dataset.k
if (!keyB64) return
try {
encKey = await importKey(keyB64)
} catch {
return
}
const visitor = await getOrCreateVisitor()
if (!visitor) return
await trackPageView(visitor)
const authUserId = document.body.dataset.uid
if (authUserId) {
await setAuth(parseInt(authUserId, 10))
}
}
export async function setAuth(userId) {
const uid = sessionStorage.getItem(SK_UID)
const hash = sessionStorage.getItem(SK_HASH)
if (!uid || !hash || !encKey) return
await send({ uid, h: hash, setUser: userId })
}