Add website analytics
A Tianji-style Websites section: a small tracking script, a public collection endpoint, and a page per website with visitors, page views, visits, bounce rate, a chart and top lists for pages, referrers, browsers, systems, devices, countries, languages and events. No cookies and no stored IP address or user agent. SANDBAR_TRUST_PROXY makes the client address come from the proxy. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ea4fe5289
commit
b4fd31a182
13 files changed
+843
-22
No files matched your search
@@ -0,0 +1,66 @@
|
||||
/* Sandbar website analytics. Add to a page:
|
||||
<script defer src="https://sandbar.example.com/script.js" data-website="WEBSITE_ID"></script>
|
||||
It sets no cookies and stores nothing in the browser. It sends the page path, the referrer, the
|
||||
language and the browser's own user agent, and it honors Do Not Track.
|
||||
Options on the tag: data-hash (count #hash changes as pages), data-auto="false" (send nothing
|
||||
until you call sandbar.view() yourself). Custom events: sandbar.track("signup", {plan: "pro"}). */
|
||||
(function () {
|
||||
var tag = document.currentScript;
|
||||
if (!tag) return;
|
||||
var site = tag.getAttribute('data-website');
|
||||
if (!site) return;
|
||||
var base = tag.src.replace(/\/script\.js(\?.*)?$/, '');
|
||||
var dnt = navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1';
|
||||
var withHash = tag.hasAttribute('data-hash');
|
||||
var last = '';
|
||||
var first = true;
|
||||
|
||||
function send(body) {
|
||||
if (dnt) return;
|
||||
body.website = site;
|
||||
body.hostname = location.hostname;
|
||||
body.language = navigator.language || '';
|
||||
var json = JSON.stringify(body);
|
||||
// text/plain keeps this a "simple" cross-origin request, so no preflight is needed
|
||||
try {
|
||||
if (navigator.sendBeacon && navigator.sendBeacon(base + '/api/send', new Blob([json], { type: 'text/plain' }))) return;
|
||||
} catch (e) { /* fall through to fetch */ }
|
||||
try {
|
||||
fetch(base + '/api/send', { method: 'POST', body: json, headers: { 'Content-Type': 'text/plain' }, keepalive: true, mode: 'cors', credentials: 'omit' });
|
||||
} catch (e) { /* analytics must never break the page */ }
|
||||
}
|
||||
|
||||
function view() {
|
||||
var path = location.pathname + (withHash ? location.hash : '');
|
||||
if (path === last) return;
|
||||
last = path;
|
||||
send({ path: path, referrer: first ? document.referrer : '' });
|
||||
first = false;
|
||||
}
|
||||
|
||||
function hook(name) {
|
||||
var orig = history[name];
|
||||
if (!orig) return;
|
||||
history[name] = function () {
|
||||
var r = orig.apply(this, arguments);
|
||||
setTimeout(view, 0);
|
||||
return r;
|
||||
};
|
||||
}
|
||||
hook('pushState');
|
||||
hook('replaceState');
|
||||
window.addEventListener('popstate', function () { setTimeout(view, 0); });
|
||||
if (withHash) window.addEventListener('hashchange', function () { setTimeout(view, 0); });
|
||||
|
||||
window.sandbar = {
|
||||
view: view,
|
||||
track: function (name, data) {
|
||||
if (!name) return;
|
||||
send({ path: location.pathname, event: String(name).slice(0, 80), data: data });
|
||||
}
|
||||
};
|
||||
|
||||
if (tag.getAttribute('data-auto') === 'false') return;
|
||||
if (document.readyState === 'complete') view();
|
||||
else window.addEventListener('load', view);
|
||||
})();
|
||||
Reference in new issue
Block a user