diff --git a/site/src/components/Footer.astro b/site/src/components/Footer.astro index 1da41ea..34c207c 100644 --- a/site/src/components/Footer.astro +++ b/site/src/components/Footer.astro @@ -76,7 +76,7 @@ const featuredGroups = data.groups.filter((g) => featuredGroupSlugs.includes(get

Sponsors

{sponsors.map((s) => ( - + {`${s.name} ))} @@ -98,3 +98,8 @@ const featuredGroups = data.groups.filter((g) => featuredGroupSlugs.includes(get
+ + diff --git a/site/src/components/Header.astro b/site/src/components/Header.astro index b9992f5..2f193a1 100644 --- a/site/src/components/Header.astro +++ b/site/src/components/Header.astro @@ -126,7 +126,7 @@ const starsLabel = stars > 0 ? (stars >= 1000 ? `${(stars / 1000).toFixed(1)}k`
Sponsored by {sponsors.map((s) => ( - + {`${s.name} ))} @@ -149,6 +149,11 @@ const starsLabel = stars > 0 ? (stars >= 1000 ? `${(stars / 1000).toFixed(1)}k`
+ + diff --git a/site/src/scripts/pipe.ts b/site/src/scripts/pipe.ts index 00f56bc..33fbbb5 100644 --- a/site/src/scripts/pipe.ts +++ b/site/src/scripts/pipe.ts @@ -68,15 +68,13 @@ function getPageContext() { getUserId(); getSessionId(); -export function record(name: string, data: Record): void { - const payload = { +function buildPayload(name: string, data: Record): object { + return { timestamp: new Date().toISOString(), transaction_id: uid(), - name: "awesome_prometheus_alerts_"+name, + name: "awesome_prometheus_alerts_" + name, user_id: getUserId(), session_id: getSessionId(), - session_copy_count: bumpSessionCount(), - lifetime_copy_count: bumpLifetimeCount(), ...data, ...getPageContext(), language: navigator.language, @@ -87,11 +85,40 @@ export function record(name: string, data: Record): void { user_agent: navigator.userAgent, is_bot: /bot|crawl|spider/i.test(navigator.userAgent), }; - - fetch(PIPE_URL+"awesome_prometheus_alerts_"+name, { - method: 'POST', - body: JSON.stringify(payload), - headers: { Authorization: `Bearer ${PIPE_KEY}` }, - keepalive: true, - }).catch(console.error); +} + +export function record(name: string, data: Record): void { + const payload = buildPayload(name, data); + fetch(PIPE_URL + "awesome_prometheus_alerts_" + name, { + method: 'POST', + body: JSON.stringify(payload), + headers: { Authorization: `Bearer ${PIPE_KEY}` }, + keepalive: true, + }).catch(console.error); +} + +export function recordCopy(name: string, data: Record): void { + record(name, { + session_copy_count: bumpSessionCount(), + lifetime_copy_count: bumpLifetimeCount(), + ...data, + }); +} + +export async function recordAndWait(name: string, data: Record): Promise { + const payload = buildPayload(name, data); + const ctrl = new AbortController(); + const timer = setTimeout(() => ctrl.abort(), 1500); + try { + await fetch(PIPE_URL + "awesome_prometheus_alerts_" + name, { + method: 'POST', + body: JSON.stringify(payload), + headers: { Authorization: `Bearer ${PIPE_KEY}` }, + signal: ctrl.signal, + }); + } catch { + // swallow — a failed event must never break sponsor navigation + } finally { + clearTimeout(timer); + } } diff --git a/site/src/scripts/sponsor.ts b/site/src/scripts/sponsor.ts new file mode 100644 index 0000000..297307e --- /dev/null +++ b/site/src/scripts/sponsor.ts @@ -0,0 +1,33 @@ +import { record, recordAndWait } from './pipe'; + +export function initSponsorClickTracking(): void { + document.querySelectorAll('a[data-sponsor-name]').forEach((a) => { + a.addEventListener('click', async (e) => { + const me = e as MouseEvent; + const href = a.href; + const sponsorName = a.dataset.sponsorName!; + const sponsorSlot = a.dataset.sponsorSlot!; + const eventData = { sponsor_name: sponsorName, sponsor_url: href, sponsor_slot: sponsorSlot }; + + // Modifier / non-primary clicks: track fire-and-forget, let browser handle navigation + if (me.button !== 0 || me.metaKey || me.ctrlKey || me.shiftKey) { + record('sponsor_click', eventData); + return; + } + + // Plain left-click: block navigation until event is recorded + e.preventDefault(); + // Open blank tab now (inside user gesture) to avoid popup-blocker after await + const w = a.target === '_blank' ? window.open('', '_blank') : null; + try { + await recordAndWait('sponsor_click', eventData); + } finally { + if (w) { + w.location.href = href; + } else { + window.open(href, '_blank') ?? (window.location.href = href); + } + } + }); + }); +}