mirror of
https://github.com/samber/awesome-prometheus-alerts.git
synced 2026-06-20 16:46:37 +08:00
feat: track sponsor clicks with blocking event before navigation
- Add recordCopy() for copy events (bumps session/lifetime counters) - Add recordAndWait() for blocking events (1500ms timeout, errors swallowed) - Extract shared sponsor click handler into site/src/scripts/sponsor.ts - Plain left-click blocks navigation until HTTP response; modifier/middle clicks track fire-and-forget and let the browser navigate natively - Distinguish header vs footer placement via data-sponsor-slot attribute
This commit is contained in:
parent
1c5f626046
commit
5a5976c9a3
5 changed files with 86 additions and 16 deletions
|
|
@ -76,7 +76,7 @@ const featuredGroups = data.groups.filter((g) => featuredGroupSlugs.includes(get
|
|||
<h3 class="text-sm font-semibold text-slate-900 dark:text-white mb-3">Sponsors</h3>
|
||||
<div class="space-y-4">
|
||||
{sponsors.map((s) => (
|
||||
<a href={s.url} target="_blank" rel="noopener noreferrer" class="block hover:opacity-80 transition-opacity">
|
||||
<a href={s.url} target="_blank" rel="noopener noreferrer" class="block hover:opacity-80 transition-opacity" data-sponsor-name={s.name} data-sponsor-slot="footer">
|
||||
<img src={`${base}${s.logo}`} alt={`${s.name} — ${s.description}`} class="h-6" />
|
||||
</a>
|
||||
))}
|
||||
|
|
@ -98,3 +98,8 @@ const featuredGroups = data.groups.filter((g) => featuredGroupSlugs.includes(get
|
|||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
import { initSponsorClickTracking } from '../scripts/sponsor';
|
||||
initSponsorClickTracking();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ const starsLabel = stars > 0 ? (stars >= 1000 ? `${(stars / 1000).toFixed(1)}k`
|
|||
<div class="max-w-7xl mx-auto flex items-center justify-center gap-3">
|
||||
<span class="text-xs font-medium tracking-wider uppercase text-slate-400 dark:text-slate-500">Sponsored by</span>
|
||||
{sponsors.map((s) => (
|
||||
<a href={s.url} target="_blank" rel="noopener noreferrer" class="hover:opacity-75 transition-opacity" title={s.name}>
|
||||
<a href={s.url} target="_blank" rel="noopener noreferrer" class="hover:opacity-75 transition-opacity" title={s.name} data-sponsor-name={s.name} data-sponsor-slot="header">
|
||||
<img src={`${base}${s.logo}`} alt={`${s.name} — ${s.description}`} class="h-10 w-auto" />
|
||||
</a>
|
||||
))}
|
||||
|
|
@ -149,6 +149,11 @@ const starsLabel = stars > 0 ? (stars >= 1000 ? `${(stars / 1000).toFixed(1)}k`
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<script>
|
||||
import { initSponsorClickTracking } from '../scripts/sponsor';
|
||||
initSponsorClickTracking();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const btn = document.getElementById('mobile-menu-btn');
|
||||
const menu = document.getElementById('mobile-menu');
|
||||
|
|
|
|||
|
|
@ -99,10 +99,10 @@ const canonical = canonicalUrl ?? `${SITE_ORIGIN}${base}${Astro.url.pathname.rep
|
|||
<Footer base={base} />
|
||||
<StarToast />
|
||||
<script>
|
||||
import { record } from '../scripts/pipe';
|
||||
import { recordCopy } from '../scripts/pipe';
|
||||
window.addEventListener('apa-copy', (e: Event) => {
|
||||
const { name, ...data } = (e as CustomEvent<Record<string, unknown>>).detail;
|
||||
record(name as string, data);
|
||||
recordCopy(name as string, data);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -68,15 +68,13 @@ function getPageContext() {
|
|||
getUserId();
|
||||
getSessionId();
|
||||
|
||||
export function record(name: string, data: Record<string, unknown>): void {
|
||||
const payload = {
|
||||
function buildPayload(name: string, data: Record<string, unknown>): 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<string, unknown>): 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<string, unknown>): 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<string, unknown>): void {
|
||||
record(name, {
|
||||
session_copy_count: bumpSessionCount(),
|
||||
lifetime_copy_count: bumpLifetimeCount(),
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
export async function recordAndWait(name: string, data: Record<string, unknown>): Promise<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
33
site/src/scripts/sponsor.ts
Normal file
33
site/src/scripts/sponsor.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { record, recordAndWait } from './pipe';
|
||||
|
||||
export function initSponsorClickTracking(): void {
|
||||
document.querySelectorAll<HTMLAnchorElement>('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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue