diff --git a/.gitignore b/.gitignore index 8b349dbb..ca0f3c4f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ # production /build /public/umami.js -/public/snippet.js /lang-compiled /lang-formatted diff --git a/components/forms/TrackingCodeForm.js b/components/forms/TrackingCodeForm.js index 61df1275..1f44f835 100644 --- a/components/forms/TrackingCodeForm.js +++ b/components/forms/TrackingCodeForm.js @@ -2,7 +2,7 @@ import React, { useRef } from 'react'; import { FormattedMessage } from 'react-intl'; import Button from 'components/common/Button'; import FormLayout, { FormButtons, FormRow } from 'components/layout/FormLayout'; -import CopyButton from '../common/CopyButton'; +import CopyButton from 'components/common/CopyButton'; export default function TrackingCodeForm({ values, onClose }) { const ref = useRef(); @@ -27,9 +27,9 @@ export default function TrackingCodeForm({ values, onClose }) { /> - + diff --git a/package.json b/package.json index 93d23142..bf34bf44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.38.0", + "version": "0.39.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", @@ -15,7 +15,6 @@ "start": "next start", "build-app": "next build", "build-tracker": "rollup -c rollup.tracker.config.js", - "build-snippet": "rollup -c rollup.snippet.config.js", "copy-db-schema": "node scripts/copy-db-schema.js", "build-db-schema": "dotenv prisma introspect", "build-db-client": "dotenv prisma generate", diff --git a/pages/test.js b/pages/test.js index 30c00e70..820a3187 100644 --- a/pages/test.js +++ b/pages/test.js @@ -11,6 +11,12 @@ export default function Test() { return

No id query specified.

; } + function handleClick() { + window.umami('Custom event'); + window.umami.pageView('/fake', 'https://www.google.com'); + window.umami.pageEvent('pageEvent', 'custom-type'); + } + return ( <> @@ -20,7 +26,7 @@ export default function Test() {

- Here you can test if your umami installation works. Open the network tab in your browser's + Here you can test if your umami installation works. Open the network tab in your browser developer console and watch for requests to the url collect. The links below should trigger page views. Clicking on the button should trigger an event.

@@ -40,6 +46,10 @@ export default function Test() { > Button +

Manual trigger

+
); diff --git a/rollup.snippet.config.js b/rollup.snippet.config.js deleted file mode 100644 index c5925988..00000000 --- a/rollup.snippet.config.js +++ /dev/null @@ -1,19 +0,0 @@ -import 'dotenv/config'; -import buble from '@rollup/plugin-buble'; -import replace from '@rollup/plugin-replace'; -import resolve from '@rollup/plugin-node-resolve'; -import { terser } from 'rollup-plugin-terser'; - -export default { - input: 'tracker/snippet.js', - output: { - file: 'public/snippet.js', - format: 'iife', - }, - plugins: [ - replace({ __DNT__: !!process.env.ENABLE_DNT }), - resolve(), - buble(), - terser({ compress: { evaluate: false } }), - ], -}; diff --git a/rollup.tracker.config.js b/rollup.tracker.config.js index ea5f709f..e836955f 100644 --- a/rollup.tracker.config.js +++ b/rollup.tracker.config.js @@ -1,6 +1,5 @@ import 'dotenv/config'; import buble from '@rollup/plugin-buble'; -import replace from '@rollup/plugin-replace'; import resolve from '@rollup/plugin-node-resolve'; import { terser } from 'rollup-plugin-terser'; @@ -10,10 +9,5 @@ export default { file: 'public/umami.js', format: 'iife', }, - plugins: [ - replace({ __DNT__: !!process.env.ENABLE_DNT }), - resolve(), - buble(), - terser({ compress: { evaluate: false } }), - ], + plugins: [resolve(), buble(), terser({ compress: { evaluate: false } })], }; diff --git a/tracker/index.js b/tracker/index.js index 28b8dfca..e3cb4fe4 100644 --- a/tracker/index.js +++ b/tracker/index.js @@ -13,44 +13,25 @@ import { removeTrailingSlash } from '../lib/url'; } = window; const script = document.querySelector('script[data-website-id]'); + const attr = key => script && script.getAttribute(key); - // eslint-disable-next-line no-undef - if (!script || (__DNT__ && doNotTrack())) return; + 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') === 'true'; + + if (!script || (dnt && doNotTrack())) return; - const website = script.getAttribute('data-website-id'); - const hostUrl = script.getAttribute('data-host-url'); - const skipAuto = script.getAttribute('data-skip-auto'); const root = hostUrl ? removeTrailingSlash(hostUrl) : new URL(script.src).href.split('/').slice(0, -1).join('/'); const screen = `${width}x${height}`; const listeners = []; - let currentUrl = `${pathname}${search}`; let currentRef = document.referrer; - /* Handle events */ + /* Collect metrics */ - const removeEvents = () => { - listeners.forEach(([element, type, listener]) => { - element && element.removeEventListener(type, listener, true); - }); - listeners.length = 0; - }; - - const loadEvents = () => { - document.querySelectorAll('[class*=\'umami--\']').forEach(element => { - element.className.split(' ').forEach(className => { - if (/^umami--([a-z]+)--([a-z0-9_]+[a-z0-9-_]+)$/.test(className)) { - const [, type, value] = className.split('--'); - const listener = () => collectEvent(type, value); - - listeners.push([element, type, listener]); - element.addEventListener(type, listener, true); - } - }); - }); - }; const collect = (type, params, uuid) => { const payload = { website: uuid, @@ -70,17 +51,56 @@ import { removeTrailingSlash } from '../lib/url'; payload, }); }; - const pageView = (url = currentUrl, referrer = currentRef, uuid = website) => collect('pageview', { - url, - referrer, - }, uuid); - /* Collect metrics */ - const pageViewWithAutoEvents = (url, referrer) => pageView(url, referrer).then(() => setTimeout(loadEvents, 300)); + const pageView = (url = currentUrl, referrer = currentRef, uuid = website) => + collect( + 'pageview', + { + url, + referrer, + }, + uuid, + ); + + const pageEvent = (event_value, event_type = 'custom', url = currentUrl, uuid = website) => + collect( + 'event', + { + event_type, + event_value, + url, + }, + uuid, + ); + + /* Handle events */ + + const loadEvents = () => { + document.querySelectorAll("[class*='umami--']").forEach(element => { + element.className.split(' ').forEach(className => { + if (/^umami--([a-z]+)--([a-z0-9_]+[a-z0-9-_]+)$/.test(className)) { + const [, type, value] = className.split('--'); + const listener = () => pageEvent(value, type); + + listeners.push([element, type, listener]); + element.addEventListener(type, listener, true); + } + }); + }); + }; + + const removeEvents = () => { + listeners.forEach(([element, type, listener]) => { + element && element.removeEventListener(type, listener, true); + }); + listeners.length = 0; + }; + + /* Handle history changes */ - /* Handle history */ const handlePush = (state, title, url) => { removeEvents(); + currentRef = currentUrl; const newUrl = url.toString(); @@ -91,38 +111,27 @@ import { removeTrailingSlash } from '../lib/url'; currentUrl = newUrl; } - pageViewWithAutoEvents(currentUrl, currentRef); + pageView(currentUrl, currentRef); + + setTimeout(loadEvents, 300); }; - const collectEvent = (event_type, event_value, url = currentUrl, uuid = website) => collect('event', { - url, - event_type, - event_value, - }, uuid); + /* Global */ - const registerAutoEvents = () => { - history.pushState = hook(history, 'pushState', handlePush); - history.replaceState = hook(history, 'replaceState', handlePush); - return pageViewWithAutoEvents(currentUrl, currentRef); - }; + if (!window.umami) { + const umami = event_value => pageEvent(event_value); + umami.pageView = pageView; + umami.pageEvent = pageEvent; - - const umamiFunctions = { collect, pageView, collectEvent, registerAutoEvents }; - const scheduledCalls = window.umami.calls; - - window.umami = event_value => collect('event', { event_type: 'custom', event_value }); - Object.keys(umamiFunctions).forEach((key) => { - window.umami[key] = umamiFunctions[key]; - }); - - if (scheduledCalls) { - scheduledCalls.forEach(([fnName, ...params]) => { - window.umami[fnName].apply(window.umami, params); - }); + window.umami = umami; } /* Start */ - if (!skipAuto) { - registerAutoEvents().catch(e => console.error(e)); + + if (autoTrack) { + history.pushState = hook(history, 'pushState', handlePush); + history.replaceState = hook(history, 'replaceState', handlePush); + + pageView(currentUrl, currentRef); } })(window); diff --git a/tracker/snippet.js b/tracker/snippet.js deleted file mode 100644 index df1a8eb5..00000000 --- a/tracker/snippet.js +++ /dev/null @@ -1,43 +0,0 @@ -(window => { - const umami = window.umami = window.umami || []; - if (!umami.registerAutoEvents) { - if (umami.invoked) { - window.console && console.error && console.error('Umami snippet included twice.'); - } else { - umami.invoked = true; - umami.calls = []; - umami.methods = ['registerAutoEvents', 'event', 'pageView']; - umami.factory = t => { - return function() { - const e = Array.prototype.slice.call(arguments); - e.unshift(t); - umami.calls.push(e); - return umami; - }; - }; - for (let t = 0; t < umami.methods.length; t++) { - let e = umami.methods[t]; - umami[e] = umami.factory(e); - } - umami.load = function(umamiScript, umamiUUID, skipAuto) { - const scriptElement = document.createElement('script'); - scriptElement.type = 'text/javascript'; - scriptElement.defer = true; - scriptElement.async = true; - scriptElement.setAttribute('data-website-id', umamiUUID); - if (skipAuto) { - scriptElement.setAttribute('data-skip-auto', 'true'); - } - scriptElement.src = umamiScript; - const otherScript = document.getElementsByTagName('script')[0]; - otherScript.parentNode.insertBefore(scriptElement, otherScript); - }; - - umami.load('[HOST]/umami.js', '[UMAMI_UUID]', false); - } - } -})(window); -// This snippet is for more advanced use case of Umami. If you want to track custom events, -// and not worry about having blocking script in the header, -// use this snippet (compiled version available in /public/snippet.js). -// Just remember to replace [HOST] and [UMAMI_UUID] when pasting it. \ No newline at end of file