Refactor tracker.

This commit is contained in:
Mike Cao 2020-09-18 13:40:46 -07:00
parent 2e22deb58a
commit 023adafa39
8 changed files with 85 additions and 136 deletions

1
.gitignore vendored
View File

@ -16,7 +16,6 @@
# production
/build
/public/umami.js
/public/snippet.js
/lang-compiled
/lang-formatted

View File

@ -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 }) {
/>
</FormRow>
<FormButtons>
<CopyButton type="submit" variant="action" element={ref}/>
<CopyButton type="submit" variant="action" element={ref} />
<Button onClick={onClose}>
<FormattedMessage id="button.cancel" defaultMessage="Cancel"/>
<FormattedMessage id="button.cancel" defaultMessage="Cancel" />
</Button>
</FormButtons>
</FormLayout>

View File

@ -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 <mike@mikecao.com>",
"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",

View File

@ -11,6 +11,12 @@ export default function Test() {
return <h1>No id query specified.</h1>;
}
function handleClick() {
window.umami('Custom event');
window.umami.pageView('/fake', 'https://www.google.com');
window.umami.pageEvent('pageEvent', 'custom-type');
}
return (
<>
<Head>
@ -20,7 +26,7 @@ export default function Test() {
</Head>
<Layout>
<p>
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 <b>collect</b>. The links below should
trigger page views. Clicking on the button should trigger an event.
</p>
@ -40,6 +46,10 @@ export default function Test() {
>
Button
</button>
<h2>Manual trigger</h2>
<button id="manual-button" type="button" onClick={handleClick}>
Button
</button>
</Layout>
</>
);

View File

@ -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 } }),
],
};

View File

@ -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 } })],
};

View File

@ -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);

View File

@ -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.