diff --git a/components/forms/TrackingCodeForm.js b/components/forms/TrackingCodeForm.js index a98b471d..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(); diff --git a/lib/middleware.js b/lib/middleware.js index 014b225d..c41e75d0 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -23,6 +23,7 @@ export const useSession = use(async (req, res, next) => { try { session = await getSession(req); } catch (e) { + console.error(e); return serverError(res, e.message); } diff --git a/package.json b/package.json index 4cb3b858..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", 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.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 f7f00927..38aadeb3 100644 --- a/tracker/index.js +++ b/tracker/index.js @@ -1,6 +1,4 @@ -import 'promise-polyfill/src/polyfill'; -import 'unfetch/polyfill'; -import { post, hook, doNotTrack } from '../lib/web'; +import { doNotTrack, hook } from '../lib/web'; import { removeTrailingSlash } from '../lib/url'; (window => { @@ -13,28 +11,42 @@ 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 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; /* Collect metrics */ - const collect = (type, params) => { + const post = (url, data, callback) => { + const req = new XMLHttpRequest(); + req.open('POST', url, true); + req.setRequestHeader('Content-Type', 'application/json'); + + req.onreadystatechange = () => { + if (req.readyState === 4) { + callback && callback(); + } + }; + + req.send(JSON.stringify(data)); + }; + + const collect = (type, params, uuid) => { const payload = { - url: currentUrl, - referrer: currentRef, - website, + website: uuid, hostname, screen, language, @@ -52,14 +64,55 @@ import { removeTrailingSlash } from '../lib/url'; }); }; - const pageView = () => collect('pageview').then(() => setTimeout(loadEvents, 300)); + const pageView = (url = currentUrl, referrer = currentRef, uuid = website) => + collect( + 'pageview', + { + url, + referrer, + }, + uuid, + ); - const pageEvent = (event_type, event_value) => collect('event', { event_type, event_value }); + const pageEvent = (event_value, event_type = 'custom', url = currentUrl, uuid = website) => + collect( + 'event', + { + event_type, + event_value, + url, + }, + uuid, + ); - /* Handle history */ + /* 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 */ const handlePush = (state, title, url) => { removeEvents(); + currentRef = currentUrl; const newUrl = url.toString(); @@ -70,40 +123,29 @@ import { removeTrailingSlash } from '../lib/url'; currentUrl = newUrl; } - pageView(); + pageView(currentUrl, currentRef); + + setTimeout(loadEvents, 300); }; - history.pushState = hook(history, 'pushState', handlePush); - history.replaceState = hook(history, 'replaceState', handlePush); + /* Global */ - /* Handle events */ + if (!window.umami) { + const umami = event_value => pageEvent(event_value); + umami.pageView = pageView; + umami.pageEvent = pageEvent; - 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 = () => pageEvent(type, value); - - listeners.push([element, type, listener]); - element.addEventListener(type, listener, true); - } - }); - }); - }; + window.umami = umami; + } /* Start */ - pageView(); + if (autoTrack) { + history.pushState = hook(history, 'pushState', handlePush); + history.replaceState = hook(history, 'replaceState', handlePush); - if (!window.umami) { - window.umami = event_value => collect('event', { event_type: 'custom', event_value }); + pageView(currentUrl, currentRef); + + loadEvents(); } })(window);