mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-15 17:55:08 +01:00
5d8fb2f791
Do not send the x-umami-cache request header when the cache is not defined (the first request sent to the umami server when the visitor opens the page)
221 lines
5.3 KiB
JavaScript
221 lines
5.3 KiB
JavaScript
(window => {
|
|
const {
|
|
screen: { width, height },
|
|
navigator: { language },
|
|
location,
|
|
localStorage,
|
|
document,
|
|
history,
|
|
} = window;
|
|
const { hostname, pathname, search } = location;
|
|
const { currentScript } = document;
|
|
|
|
if (!currentScript) return;
|
|
|
|
const delayDuration = 300;
|
|
const _data = 'data-';
|
|
const _false = 'false';
|
|
const attr = currentScript.getAttribute.bind(currentScript);
|
|
const website = attr(_data + 'website-id');
|
|
const hostUrl = attr(_data + 'host-url');
|
|
const autoTrack = attr(_data + 'auto-track') !== _false;
|
|
const dnt = attr(_data + 'do-not-track');
|
|
const domain = attr(_data + 'domains') || '';
|
|
const domains = domain.split(',').map(n => n.trim());
|
|
const root = hostUrl
|
|
? hostUrl.replace(/\/$/, '')
|
|
: currentScript.src.split('/').slice(0, -1).join('/');
|
|
const endpoint = `${root}/api/send`;
|
|
const screen = `${width}x${height}`;
|
|
const eventRegex = /data-umami-event-([\w-_]+)/;
|
|
const eventNameAttribute = _data + 'umami-event';
|
|
|
|
/* Helper functions */
|
|
|
|
const hook = (_this, method, callback) => {
|
|
const orig = _this[method];
|
|
|
|
return (...args) => {
|
|
callback.apply(null, args);
|
|
|
|
return orig.apply(_this, args);
|
|
};
|
|
};
|
|
|
|
const getPath = url => {
|
|
if (url.substring(0, 4) === 'http') {
|
|
return '/' + url.split('/').splice(3).join('/');
|
|
}
|
|
return url;
|
|
};
|
|
|
|
const getPayload = () => ({
|
|
website,
|
|
hostname,
|
|
screen,
|
|
language,
|
|
title,
|
|
url: currentUrl,
|
|
referrer: currentRef,
|
|
});
|
|
|
|
/* Tracking functions */
|
|
|
|
const doNotTrack = () => {
|
|
const { doNotTrack, navigator, external } = window;
|
|
|
|
const msTrackProtection = 'msTrackingProtectionEnabled';
|
|
const msTracking = () => {
|
|
return external && msTrackProtection in external && external[msTrackProtection]();
|
|
};
|
|
|
|
const dnt = doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack || msTracking();
|
|
|
|
return dnt == '1' || dnt === 'yes';
|
|
};
|
|
|
|
const trackingDisabled = () =>
|
|
(localStorage && localStorage.getItem('umami.disabled')) ||
|
|
(dnt && doNotTrack()) ||
|
|
(domain && !domains.includes(hostname));
|
|
|
|
const handlePush = (state, title, url) => {
|
|
if (!url) return;
|
|
|
|
currentRef = currentUrl;
|
|
currentUrl = getPath(url.toString());
|
|
|
|
if (currentUrl !== currentRef) {
|
|
setTimeout(track, delayDuration);
|
|
}
|
|
};
|
|
|
|
const handleClick = () => {
|
|
const trackElement = el => {
|
|
const attr = el.getAttribute.bind(el);
|
|
const eventName = attr(eventNameAttribute);
|
|
|
|
if (eventName) {
|
|
const eventData = {};
|
|
|
|
el.getAttributeNames().forEach(name => {
|
|
const match = name.match(eventRegex);
|
|
|
|
if (match) {
|
|
eventData[match[1]] = attr(name);
|
|
}
|
|
});
|
|
|
|
return track(eventName, { data: eventData });
|
|
}
|
|
return Promise.resolve();
|
|
};
|
|
|
|
const callback = e => {
|
|
const el = e.target;
|
|
const anchor =
|
|
el.tagName === 'A'
|
|
? el
|
|
: el.parentElement && el.parentElement.tagName === 'A'
|
|
? el.parentElement
|
|
: null;
|
|
|
|
if (anchor) {
|
|
const { href, target } = anchor;
|
|
const external =
|
|
target === '_blank' ||
|
|
e.ctrlKey ||
|
|
e.shiftKey ||
|
|
e.metaKey ||
|
|
(e.button && e.button === 1);
|
|
const eventName = anchor.getAttribute(eventNameAttribute);
|
|
|
|
if (eventName && href) {
|
|
if (!external) {
|
|
e.preventDefault();
|
|
}
|
|
return trackElement(anchor).then(() => {
|
|
if (!external) location.href = href;
|
|
});
|
|
}
|
|
} else {
|
|
trackElement(el);
|
|
}
|
|
};
|
|
|
|
document.addEventListener('click', callback, true);
|
|
};
|
|
|
|
const observeTitle = () => {
|
|
const callback = ([entry]) => {
|
|
title = entry.target.text;
|
|
};
|
|
|
|
const observer = new MutationObserver(callback);
|
|
|
|
observer.observe(document.querySelector('head > title'), {
|
|
subtree: true,
|
|
characterData: true,
|
|
childList: true,
|
|
});
|
|
};
|
|
|
|
const send = payload => {
|
|
if (trackingDisabled()) return;
|
|
const headers = {
|
|
'Content-Type': 'application/json'
|
|
};
|
|
if (typeof cache !== 'undefined') {
|
|
headers['x-umami-cache'] = cache;
|
|
}
|
|
return fetch(endpoint, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ type: 'event', payload }),
|
|
headers: headers
|
|
})
|
|
.then(res => res.text())
|
|
.then(text => (cache = text));
|
|
};
|
|
|
|
const track = (name = {}, data = {}) => {
|
|
if (typeof name === 'string') {
|
|
return send({ ...getPayload(), ...data, name });
|
|
} else if (typeof name === 'object') {
|
|
return send({ ...getPayload(), ...name });
|
|
}
|
|
return Promise.reject();
|
|
};
|
|
|
|
/* Start */
|
|
|
|
if (!window.umami) {
|
|
window.umami = {
|
|
track,
|
|
};
|
|
}
|
|
|
|
let currentUrl = `${pathname}${search}`;
|
|
let currentRef = document.referrer;
|
|
let title = document.title;
|
|
let cache;
|
|
let initialized;
|
|
|
|
if (autoTrack && !trackingDisabled()) {
|
|
history.pushState = hook(history, 'pushState', handlePush);
|
|
history.replaceState = hook(history, 'replaceState', handlePush);
|
|
handleClick();
|
|
observeTitle();
|
|
|
|
const init = () => {
|
|
if (document.readyState === 'complete' && !initialized) {
|
|
track();
|
|
initialized = true;
|
|
}
|
|
};
|
|
|
|
document.addEventListener('readystatechange', init, true);
|
|
|
|
init();
|
|
}
|
|
})(window);
|