diff --git a/src/tracker/index.js b/src/tracker/index.js index 5c9b23a7..d155c2fd 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -8,19 +8,19 @@ top, doNotTrack, } = window; - const { hostname, href, origin } = location; const { currentScript, referrer } = document; - const localStorage = href.startsWith('data:') ? undefined : window.localStorage; - if (!currentScript) return; + const { hostname, href, origin } = location; + const localStorage = href.startsWith('data:') ? undefined : window.localStorage; + const _data = 'data-'; const _false = 'false'; const _true = 'true'; const attr = currentScript.getAttribute.bind(currentScript); const website = attr(_data + 'website-id'); const hostUrl = attr(_data + 'host-url'); - const tag = attr(_data + 'tag'); + const tag = attr(_data + 'tag') || undefined; const autoTrack = attr(_data + 'auto-track') !== _false; const dnt = attr(_data + 'do-not-track') === _true; const excludeSearch = attr(_data + 'exclude-search') === _true; @@ -41,11 +41,11 @@ website, screen, language, - title, + title: document.title, hostname, url: currentUrl, referrer: currentRef, - tag: tag ? tag : undefined, + tag, id: identity ? identity : undefined, }); @@ -56,20 +56,14 @@ /* Event handlers */ - const handlePush = (state, title, url) => { + const handlePush = (_state, _title, url) => { if (!url) return; currentRef = currentUrl; currentUrl = new URL(url, location.href); - if (excludeSearch) { - currentUrl.search = ''; - } - - if (excludeHash) { - currentUrl.hash = ''; - } - + if (excludeSearch) currentUrl.search = ''; + if (excludeHash) currentUrl.hash = ''; currentUrl = currentUrl.toString(); if (currentUrl !== currentRef) { @@ -80,10 +74,8 @@ const handlePathChanges = () => { const hook = (_this, method, callback) => { const orig = _this[method]; - return (...args) => { callback.apply(null, args); - return orig.apply(_this, args); }; }; @@ -92,96 +84,47 @@ history.replaceState = hook(history, 'replaceState', handlePush); }; - const handleTitleChanges = () => { - const observer = new MutationObserver(([entry]) => { - title = entry && entry.target ? entry.target.text : undefined; - }); - - const node = document.querySelector('head > title'); - - if (node) { - observer.observe(node, { - subtree: true, - characterData: true, - childList: true, - }); - } - }; - const handleClicks = () => { - document.addEventListener( - 'click', - async e => { - const isSpecialTag = tagName => ['BUTTON', 'A'].includes(tagName); + const trackElement = async el => { + const eventName = el.getAttribute(eventNameAttribute); + if (eventName) { + const eventData = {}; - const trackElement = async el => { - const attr = el.getAttribute.bind(el); - const eventName = attr(eventNameAttribute); + el.getAttributeNames().forEach(name => { + const match = name.match(eventRegex); + if (match) eventData[match[1]] = el.getAttribute(name); + }); - if (eventName) { - const eventData = {}; + return track(eventName, eventData); + } + }; + const onClick = async e => { + const el = e.target; + const parentElement = el.closest('a,button'); + if (!parentElement) return trackElement(el); - el.getAttributeNames().forEach(name => { - const match = name.match(eventRegex); + const { href, target } = parentElement; + if (!parentElement.getAttribute(eventNameAttribute)) return; - if (match) { - eventData[match[1]] = attr(name); - } - }); - - return track(eventName, eventData); + if (parentElement.tagName === 'BUTTON') { + return trackElement(parentElement); + } + if (parentElement.tagName === 'A' && href) { + const external = + target === '_blank' || + e.ctrlKey || + e.shiftKey || + e.metaKey || + (e.button && e.button === 1); + if (!external) e.preventDefault(); + return trackElement(parentElement).then(() => { + if (!external) { + (target === '_top' ? top.location : location).href = href; } - }; - - const findParentTag = (rootElem, maxSearchDepth) => { - let currentElement = rootElem; - for (let i = 0; i < maxSearchDepth; i++) { - if (isSpecialTag(currentElement.tagName)) { - return currentElement; - } - currentElement = currentElement.parentElement; - if (!currentElement) { - return null; - } - } - }; - - const el = e.target; - const parentElement = isSpecialTag(el.tagName) ? el : findParentTag(el, 10); - - if (parentElement) { - const { href, target } = parentElement; - const eventName = parentElement.getAttribute(eventNameAttribute); - - if (eventName) { - if (parentElement.tagName === 'A') { - const external = - target === '_blank' || - e.ctrlKey || - e.shiftKey || - e.metaKey || - (e.button && e.button === 1); - - if (eventName && href) { - if (!external) { - e.preventDefault(); - } - return trackElement(parentElement).then(() => { - if (!external) { - (target === '_top' ? top.location : location).href = href; - } - }); - } - } else if (parentElement.tagName === 'BUTTON') { - return trackElement(parentElement); - } - } - } else { - return trackElement(el); - } - }, - true, - ); + }); + } + }; + document.addEventListener('click', onClick, true); }; /* Tracking functions */ @@ -195,56 +138,40 @@ const send = async (payload, type = 'event') => { if (trackingDisabled()) return; - - const headers = { - 'Content-Type': 'application/json', - }; - - if (typeof cache !== 'undefined') { - headers['x-umami-cache'] = cache; - } - try { const res = await fetch(endpoint, { method: 'POST', body: JSON.stringify({ type, payload }), - headers, + headers: { + 'Content-Type': 'application/json', + ...(typeof cache !== 'undefined' && { 'x-umami-cache': cache }), + }, credentials: 'omit', }); const data = await res.json(); - if (data) { disabled = !!data.disabled; cache = data.cache; } } catch (e) { - /* empty */ + /* no-op */ } }; const init = () => { if (!initialized) { + initialized = true; track(); handlePathChanges(); - handleTitleChanges(); handleClicks(); - initialized = true; } }; - const track = (name, data) => { - if (typeof name === 'string') { - return send({ - ...getPayload(), - name, - data, - }); - } else if (typeof name === 'object') { - return send({ ...name }); - } else if (typeof name === 'function') { - return send(name(getPayload())); - } + const track = (obj, data) => { + if (typeof obj === 'string') return send({ ...getPayload(), name: obj, data }); + if (typeof obj === 'object') return send(obj); + if (typeof obj === 'function') return send(obj(getPayload())); return send(getPayload()); }; @@ -274,10 +201,9 @@ let currentUrl = href; let currentRef = referrer.startsWith(origin) ? '' : referrer; - let title = document.title; - let cache; - let initialized; + let initialized = false; let disabled = false; + let cache; let identity; if (autoTrack && !trackingDisabled()) {