diff --git a/CHANGELOG.md b/CHANGELOG.md index 6edade6..36f9ab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added - **AI traffic source identification.** Pulse now automatically recognizes visitors coming from AI tools — ChatGPT, Perplexity, Claude, Gemini, Copilot, DeepSeek, Grok, Meta AI, You.com, and Phind. These sources appear in your Top Referrers with proper brand icons and display names instead of raw domain URLs. If someone clicks a link in an AI chat to visit your site, you'll see exactly which AI tool sent them. +- **Automatic outbound link tracking.** Pulse now tracks when visitors click links that take them to other websites. These show up as "outbound link" events in your Goals & Events panel — no setup needed. You can turn this off in your tracking snippet settings if you prefer. +- **Automatic file download tracking.** When a visitor clicks a link to a downloadable file — PDF, ZIP, Excel, Word, MP3, and 20+ other formats — Pulse records it as a "file download" event. Like outbound links, this works automatically with no setup required. ### Improved diff --git a/public/script.js b/public/script.js index d360ce7..990e0db 100644 --- a/public/script.js +++ b/public/script.js @@ -346,4 +346,39 @@ window.pulse = window.pulse || {}; window.pulse.track = trackCustomEvent; + // * Auto-track outbound link clicks and file downloads (on by default) + // * Opt-out: add data-no-outbound or data-no-downloads to the script tag + var trackOutbound = !script.hasAttribute('data-no-outbound'); + var trackDownloads = !script.hasAttribute('data-no-downloads'); + + if (trackOutbound || trackDownloads) { + var FILE_EXT_REGEX = /\.(pdf|zip|gz|tar|xlsx|xls|csv|docx|doc|pptx|ppt|mp4|mp3|wav|avi|mov|exe|dmg|pkg|deb|rpm|iso|7z|rar)($|\?|#)/i; + + document.addEventListener('click', function(e) { + var el = e.target; + // * Walk up from clicked element to find nearest tag + while (el && el.tagName !== 'A') el = el.parentElement; + if (!el || !el.href) return; + + try { + var url = new URL(el.href, location.href); + // * Skip non-http links (mailto:, tel:, javascript:, etc.) + if (url.protocol !== 'http:' && url.protocol !== 'https:') return; + + // * Check file download first (download attribute or known file extension) + if (trackDownloads && (el.hasAttribute('download') || FILE_EXT_REGEX.test(url.pathname))) { + trackCustomEvent('file_download'); + return; + } + + // * Check outbound link (different hostname) + if (trackOutbound && url.hostname && url.hostname !== location.hostname) { + trackCustomEvent('outbound_link'); + } + } catch (err) { + // * Invalid URL - skip silently + } + }, true); // * Capture phase: fires before default navigation + } + })();