Merge pull request #162 from freeyourmusic/bh/unregister

Ability to unregister umami
This commit is contained in:
Mike Cao 2020-09-18 09:05:04 -07:00 committed by GitHub
commit bddda928ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 40 deletions

1
.gitignore vendored
View File

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

View File

@ -15,6 +15,7 @@
"start": "next start", "start": "next start",
"build-app": "next build", "build-app": "next build",
"build-tracker": "rollup -c rollup.tracker.config.js", "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", "copy-db-schema": "node scripts/copy-db-schema.js",
"build-db-schema": "dotenv prisma introspect", "build-db-schema": "dotenv prisma introspect",
"build-db-client": "dotenv prisma generate", "build-db-client": "dotenv prisma generate",

19
rollup.snippet.config.js Normal file
View File

@ -0,0 +1,19 @@
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,6 @@
import 'promise-polyfill/src/polyfill'; import 'promise-polyfill/src/polyfill';
import 'unfetch/polyfill'; import 'unfetch/polyfill';
import { post, hook, doNotTrack } from '../lib/web'; import { doNotTrack, hook, post } from '../lib/web';
import { removeTrailingSlash } from '../lib/url'; import { removeTrailingSlash } from '../lib/url';
(window => { (window => {
@ -19,6 +19,7 @@ import { removeTrailingSlash } from '../lib/url';
const website = script.getAttribute('data-website-id'); const website = script.getAttribute('data-website-id');
const hostUrl = script.getAttribute('data-host-url'); const hostUrl = script.getAttribute('data-host-url');
const skipAuto = script.getAttribute('data-skip-auto');
const root = hostUrl const root = hostUrl
? removeTrailingSlash(hostUrl) ? removeTrailingSlash(hostUrl)
: new URL(script.src).href.split('/').slice(0, -1).join('/'); : new URL(script.src).href.split('/').slice(0, -1).join('/');
@ -28,13 +29,31 @@ import { removeTrailingSlash } from '../lib/url';
let currentUrl = `${pathname}${search}`; let currentUrl = `${pathname}${search}`;
let currentRef = document.referrer; let currentRef = document.referrer;
/* Collect metrics */ /* Handle events */
const collect = (type, params) => { 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 = { const payload = {
url: currentUrl, website: uuid,
referrer: currentRef,
website,
hostname, hostname,
screen, screen,
language, language,
@ -51,13 +70,15 @@ import { removeTrailingSlash } from '../lib/url';
payload, payload,
}); });
}; };
const pageView = (url = currentUrl, referrer = currentRef, uuid = website) => collect('pageview', {
url,
referrer,
}, uuid);
const pageView = () => collect('pageview').then(() => setTimeout(loadEvents, 300)); /* Collect metrics */
const pageViewWithAutoEvents = (url, referrer) => pageView(url, referrer).then(() => setTimeout(loadEvents, 300));
const pageEvent = (event_type, event_value) => collect('event', { event_type, event_value });
/* Handle history */ /* Handle history */
const handlePush = (state, title, url) => { const handlePush = (state, title, url) => {
removeEvents(); removeEvents();
currentRef = currentUrl; currentRef = currentUrl;
@ -70,40 +91,38 @@ import { removeTrailingSlash } from '../lib/url';
currentUrl = newUrl; currentUrl = newUrl;
} }
pageView(); pageViewWithAutoEvents(currentUrl, currentRef);
}; };
const collectEvent = (event_type, event_value, url = currentUrl, uuid = website) => collect('event', {
url,
event_type,
event_value,
}, uuid);
const registerAutoEvents = () => {
history.pushState = hook(history, 'pushState', handlePush); history.pushState = hook(history, 'pushState', handlePush);
history.replaceState = hook(history, 'replaceState', handlePush); history.replaceState = hook(history, 'replaceState', handlePush);
return pageViewWithAutoEvents(currentUrl, currentRef);
/* Handle events */
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]); const umamiFunctions = { collect, pageView, collectEvent, registerAutoEvents };
element.addEventListener(type, listener, true); 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);
});
} }
});
});
};
/* Start */ /* Start */
if (!skipAuto) {
pageView(); registerAutoEvents().catch(e => console.error(e));
if (!window.umami) {
window.umami = event_value => collect('event', { event_type: 'custom', event_value });
} }
})(window); })(window);

43
tracker/snippet.js Normal file
View File

@ -0,0 +1,43 @@
(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.