From 2ea91aaabfec51de37a1289e040de2792654b72b Mon Sep 17 00:00:00 2001
From: Bartosz Hernas
Date: Mon, 14 Sep 2020 18:13:59 +0200
Subject: [PATCH 01/10] Ability to unregister umami
---
lib/web.js | 8 +++++---
tracker/index.js | 12 ++++++++++--
2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/lib/web.js b/lib/web.js
index 4a8578f1dd..784a9e1ecb 100644
--- a/lib/web.js
+++ b/lib/web.js
@@ -39,12 +39,14 @@ export const put = (url, params) => apiRequest('put', url, JSON.stringify(params
export const hook = (_this, method, callback) => {
const orig = _this[method];
-
- return (...args) => {
+ _this[method] = (...args) => {
callback.apply(null, args);
-
return orig.apply(_this, args);
};
+
+ return () => {
+ _this[method] = orig;
+ };
};
export const doNotTrack = () => {
diff --git a/tracker/index.js b/tracker/index.js
index f7f0092773..0b91d4e713 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -73,8 +73,8 @@ import { removeTrailingSlash } from '../lib/url';
pageView();
};
- history.pushState = hook(history, 'pushState', handlePush);
- history.replaceState = hook(history, 'replaceState', handlePush);
+ const pushStateUnhook = hook(history, 'pushState', handlePush);
+ const replaceStateUnhook = hook(history, 'replaceState', handlePush);
/* Handle events */
@@ -106,4 +106,12 @@ import { removeTrailingSlash } from '../lib/url';
if (!window.umami) {
window.umami = event_value => collect('event', { event_type: 'custom', event_value });
}
+
+ if (!window.umamiUnregister) {
+ window.umamiUnregister = () => {
+ pushStateUnhook();
+ replaceStateUnhook();
+ removeEvents();
+ };
+ }
})(window);
From d19bcbabe053e6b6eb80138818acaa0bed70ea7a Mon Sep 17 00:00:00 2001
From: Bartosz Hernas
Date: Tue, 15 Sep 2020 00:09:09 +0200
Subject: [PATCH 02/10] Different method + fixed lint for tracker + added frame
mode for shared URLs
---
lib/web.js | 8 ++---
pages/share/[...id].js | 4 +--
tracker/index.js | 69 ++++++++++++++++++++++--------------------
3 files changed, 41 insertions(+), 40 deletions(-)
diff --git a/lib/web.js b/lib/web.js
index 784a9e1ecb..4a8578f1dd 100644
--- a/lib/web.js
+++ b/lib/web.js
@@ -39,13 +39,11 @@ export const put = (url, params) => apiRequest('put', url, JSON.stringify(params
export const hook = (_this, method, callback) => {
const orig = _this[method];
- _this[method] = (...args) => {
+
+ return (...args) => {
callback.apply(null, args);
- return orig.apply(_this, args);
- };
- return () => {
- _this[method] = orig;
+ return orig.apply(_this, args);
};
};
diff --git a/pages/share/[...id].js b/pages/share/[...id].js
index 1e0897a9dc..50467bf6cf 100644
--- a/pages/share/[...id].js
+++ b/pages/share/[...id].js
@@ -6,7 +6,7 @@ import useFetch from 'hooks/useFetch';
export default function SharePage() {
const router = useRouter();
- const { id } = router.query;
+ const { id, frame } = router.query;
const shareId = id?.[0];
const { data } = useFetch(shareId ? `/api/share/${shareId}` : null);
@@ -15,7 +15,7 @@ export default function SharePage() {
}
return (
-
+
);
diff --git a/tracker/index.js b/tracker/index.js
index 0b91d4e713..7bb1b7b3a3 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -1,6 +1,6 @@
import 'promise-polyfill/src/polyfill';
import 'unfetch/polyfill';
-import { post, hook, doNotTrack } from '../lib/web';
+import { doNotTrack, hook, post } from '../lib/web';
import { removeTrailingSlash } from '../lib/url';
(window => {
@@ -31,25 +31,7 @@ import { removeTrailingSlash } from '../lib/url';
/* Collect metrics */
const collect = (type, params) => {
- const payload = {
- url: currentUrl,
- referrer: currentRef,
- website,
- hostname,
- screen,
- language,
- };
-
- if (params) {
- Object.keys(params).forEach(key => {
- payload[key] = params[key];
- });
- }
-
- return post(`${root}/api/collect`, {
- type,
- payload,
- });
+ return window.umamiTrack(website, type, params);
};
const pageView = () => collect('pageview').then(() => setTimeout(loadEvents, 300));
@@ -73,8 +55,8 @@ import { removeTrailingSlash } from '../lib/url';
pageView();
};
- const pushStateUnhook = hook(history, 'pushState', handlePush);
- const replaceStateUnhook = hook(history, 'replaceState', handlePush);
+ history.pushState = hook(history, 'pushState', handlePush);
+ history.replaceState = hook(history, 'replaceState', handlePush);
/* Handle events */
@@ -86,7 +68,7 @@ import { removeTrailingSlash } from '../lib/url';
};
const loadEvents = () => {
- document.querySelectorAll("[class*='umami--']").forEach(element => {
+ 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('--');
@@ -99,19 +81,40 @@ import { removeTrailingSlash } from '../lib/url';
});
};
- /* Start */
+ if (!window.umamiTrack) {
+ window.umamiTrack = (type, params, id) => {
+ if (!id) {
+ id = website
+ }
+ const payload = {
+ url: currentUrl,
+ referrer: currentRef,
+ website: id,
+ hostname,
+ screen,
+ language,
+ };
+
+ if (params) {
+ Object.keys(params).forEach(key => {
+ payload[key] = params[key];
+ });
+ }
+
+ return post(`${root}/api/collect`, {
+ type,
+ payload,
+ });
+ };
+ }
- pageView();
+ /* Start */
+ const skipAuto = new URL(script.src).search.includes('auto=false');
+ if (!skipAuto) {
+ pageView();
+ }
if (!window.umami) {
window.umami = event_value => collect('event', { event_type: 'custom', event_value });
}
-
- if (!window.umamiUnregister) {
- window.umamiUnregister = () => {
- pushStateUnhook();
- replaceStateUnhook();
- removeEvents();
- };
- }
})(window);
From d6d1c68e6dbacdda3aa8b2fac70322a1c873f49e Mon Sep 17 00:00:00 2001
From: Bartosz Hernas
Date: Tue, 15 Sep 2020 00:12:09 +0200
Subject: [PATCH 03/10] Fix tracker
---
tracker/index.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/tracker/index.js b/tracker/index.js
index 7bb1b7b3a3..9c00a47b59 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -55,9 +55,6 @@ import { removeTrailingSlash } from '../lib/url';
pageView();
};
- history.pushState = hook(history, 'pushState', handlePush);
- history.replaceState = hook(history, 'replaceState', handlePush);
-
/* Handle events */
const removeEvents = () => {
@@ -84,7 +81,7 @@ import { removeTrailingSlash } from '../lib/url';
if (!window.umamiTrack) {
window.umamiTrack = (type, params, id) => {
if (!id) {
- id = website
+ id = website;
}
const payload = {
url: currentUrl,
@@ -111,6 +108,8 @@ import { removeTrailingSlash } from '../lib/url';
/* Start */
const skipAuto = new URL(script.src).search.includes('auto=false');
if (!skipAuto) {
+ history.pushState = hook(history, 'pushState', handlePush);
+ history.replaceState = hook(history, 'replaceState', handlePush);
pageView();
}
From d5e49141532455b409310b920e98cdf19b0ed3bb Mon Sep 17 00:00:00 2001
From: Bartosz Hernas
Date: Tue, 15 Sep 2020 12:03:34 +0200
Subject: [PATCH 04/10] Comments fixed
---
pages/share/[...id].js | 4 ++--
tracker/index.js | 44 +++++++++++++++++++++---------------------
2 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/pages/share/[...id].js b/pages/share/[...id].js
index 50467bf6cf..1e0897a9dc 100644
--- a/pages/share/[...id].js
+++ b/pages/share/[...id].js
@@ -6,7 +6,7 @@ import useFetch from 'hooks/useFetch';
export default function SharePage() {
const router = useRouter();
- const { id, frame } = router.query;
+ const { id } = router.query;
const shareId = id?.[0];
const { data } = useFetch(shareId ? `/api/share/${shareId}` : null);
@@ -15,7 +15,7 @@ export default function SharePage() {
}
return (
-
+
);
diff --git a/tracker/index.js b/tracker/index.js
index 9c00a47b59..ff7f099522 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -19,6 +19,7 @@ import { removeTrailingSlash } from '../lib/url';
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('/');
@@ -29,14 +30,7 @@ import { removeTrailingSlash } from '../lib/url';
let currentRef = document.referrer;
/* Collect metrics */
-
- const collect = (type, params) => {
- return window.umamiTrack(website, type, params);
- };
-
- const pageView = () => collect('pageview').then(() => setTimeout(loadEvents, 300));
-
- const pageEvent = (event_type, event_value) => collect('event', { event_type, event_value });
+ const pageViewWithAutoEvents = (url, referrer) => window.umami.pageView(url, referrer).then(() => setTimeout(loadEvents, 300));
/* Handle history */
@@ -52,7 +46,7 @@ import { removeTrailingSlash } from '../lib/url';
currentUrl = newUrl;
}
- pageView();
+ pageViewWithAutoEvents(currentUrl, currentRef);
};
/* Handle events */
@@ -69,7 +63,7 @@ import { removeTrailingSlash } from '../lib/url';
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);
+ const listener = () => window.umami.event(type, value);
listeners.push([element, type, listener]);
element.addEventListener(type, listener, true);
@@ -78,14 +72,13 @@ import { removeTrailingSlash } from '../lib/url';
});
};
- if (!window.umamiTrack) {
- window.umamiTrack = (type, params, id) => {
+ if (!window.umami) {
+ window.umami = event_value => window.umami.event('custom', event_value, currentUrl);
+ window.umami.collect = (type, params, id) => {
if (!id) {
id = website;
}
const payload = {
- url: currentUrl,
- referrer: currentRef,
website: id,
hostname,
screen,
@@ -103,17 +96,24 @@ import { removeTrailingSlash } from '../lib/url';
payload,
});
};
+ window.umami.pageView = (url = currentUrl, referrer = currentRef) => window.umami.collect('pageview', {
+ url,
+ referrer,
+ });
+ window.umami.event = (event_type, event_value, url = currentUrl) => window.umami.collect('event', {
+ url,
+ event_type,
+ event_value,
+ });
+ window.umami.registerAutoEvents = () => {
+ history.pushState = hook(history, 'pushState', handlePush);
+ history.replaceState = hook(history, 'replaceState', handlePush);
+ return pageViewWithAutoEvents(currentUrl, currentRef);
+ };
}
/* Start */
- const skipAuto = new URL(script.src).search.includes('auto=false');
if (!skipAuto) {
- history.pushState = hook(history, 'pushState', handlePush);
- history.replaceState = hook(history, 'replaceState', handlePush);
- pageView();
- }
-
- if (!window.umami) {
- window.umami = event_value => collect('event', { event_type: 'custom', event_value });
+ window.umami.registerAutoEvents().catch(e => console.error(e));
}
})(window);
From 7a71bb1c68256ee611cc6751f9c4295a3714b004 Mon Sep 17 00:00:00 2001
From: Bartosz Hernas
Date: Tue, 15 Sep 2020 13:54:35 +0200
Subject: [PATCH 05/10] Better snippet
---
.gitignore | 1 +
components/forms/TrackingCodeForm.js | 5 +-
package.json | 1 +
rollup.snippet.config.js | 19 ++++++
tracker/index.js | 91 +++++++++++++++++-----------
tracker/snippet.js | 42 +++++++++++++
6 files changed, 121 insertions(+), 38 deletions(-)
create mode 100644 rollup.snippet.config.js
create mode 100644 tracker/snippet.js
diff --git a/.gitignore b/.gitignore
index ca0f3c4f7c..8b349dbb41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@
# 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 a98b471db5..36dda30e43 100644
--- a/components/forms/TrackingCodeForm.js
+++ b/components/forms/TrackingCodeForm.js
@@ -17,12 +17,15 @@ export default function TrackingCodeForm({ values, onClose }) {
/>
+ {/* Run `npm run build-snippet, and copy paste here the content of public/snippet.js */}
+ {/* TODO: use webpack importing function to import the content of the file here ? */}
diff --git a/package.json b/package.json
index e6dc21ec22..e600611d16 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"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/rollup.snippet.config.js b/rollup.snippet.config.js
new file mode 100644
index 0000000000..c592598889
--- /dev/null
+++ b/rollup.snippet.config.js
@@ -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 } }),
+ ],
+};
diff --git a/tracker/index.js b/tracker/index.js
index ff7f099522..6d6886fbde 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -3,6 +3,13 @@ import 'unfetch/polyfill';
import { doNotTrack, hook, post } from '../lib/web';
import { removeTrailingSlash } from '../lib/url';
+function log(isDebug, messageFn) {
+ if (isDebug) {
+ return;
+ }
+ console.log(messageFn());
+}
+
(window => {
const {
screen: { width, height },
@@ -20,6 +27,7 @@ import { removeTrailingSlash } from '../lib/url';
const website = script.getAttribute('data-website-id');
const hostUrl = script.getAttribute('data-host-url');
const skipAuto = script.getAttribute('data-skip-auto');
+ const isDebug = script.getAttribute('data-debug');
const root = hostUrl
? removeTrailingSlash(hostUrl)
: new URL(script.src).href.split('/').slice(0, -1).join('/');
@@ -72,45 +80,54 @@ import { removeTrailingSlash } from '../lib/url';
});
};
- if (!window.umami) {
- window.umami = event_value => window.umami.event('custom', event_value, currentUrl);
- window.umami.collect = (type, params, id) => {
- if (!id) {
- id = website;
- }
- const payload = {
- website: id,
- hostname,
- screen,
- language,
- };
-
- if (params) {
- Object.keys(params).forEach(key => {
- payload[key] = params[key];
- });
- }
-
- return post(`${root}/api/collect`, {
- type,
- payload,
- });
+
+ const scheduledCalls = window.umami.calls;
+ window.umami = {};
+ window.umami.collect = (type, params, id) => {
+ if (!id) {
+ id = website;
+ }
+ const payload = {
+ website: id,
+ hostname,
+ screen,
+ language,
};
- window.umami.pageView = (url = currentUrl, referrer = currentRef) => window.umami.collect('pageview', {
- url,
- referrer,
- });
- window.umami.event = (event_type, event_value, url = currentUrl) => window.umami.collect('event', {
- url,
- event_type,
- event_value,
+
+ if (params) {
+ Object.keys(params).forEach(key => {
+ payload[key] = params[key];
+ });
+ }
+
+ log(isDebug, () => `Umami, collect ${type} with payload: ${JSON.stringify(payload, null, 2)}`);
+ return post(`${root}/api/collect`, {
+ type,
+ payload,
});
- window.umami.registerAutoEvents = () => {
- history.pushState = hook(history, 'pushState', handlePush);
- history.replaceState = hook(history, 'replaceState', handlePush);
- return pageViewWithAutoEvents(currentUrl, currentRef);
- };
- }
+ };
+ window.umami.pageView = (url = currentUrl, referrer = currentRef) => window.umami.collect('pageview', {
+ url,
+ referrer,
+ });
+ window.umami.event = (event_type, event_value, url = currentUrl) => window.umami.collect('event', {
+ url,
+ event_type,
+ event_value,
+ });
+ window.umami.registerAutoEvents = () => {
+ history.pushState = hook(history, 'pushState', handlePush);
+ history.replaceState = hook(history, 'replaceState', handlePush);
+ return pageViewWithAutoEvents(currentUrl, currentRef);
+ };
+
+ log(isDebug, () => 'Umami, calling scheduled invocations');
+ log(isDebug, () => scheduledCalls);
+
+ scheduledCalls.forEach(([fnName, ...params]) => {
+ log(isDebug, () => `Umami, calling ${fnName} fn with params: ${JSON.stringify(params)}`);
+ window.umami[fnName].apply(window.umami, params);
+ });
/* Start */
if (!skipAuto) {
diff --git a/tracker/snippet.js b/tracker/snippet.js
new file mode 100644
index 0000000000..ffb894230d
--- /dev/null
+++ b/tracker/snippet.js
@@ -0,0 +1,42 @@
+(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, isDebug) {
+ 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');
+ }
+ if (isDebug) {
+ scriptElement.setAttribute('data-debug', 'true');
+ }
+ scriptElement.src = umamiScript;
+ const otherScript = document.getElementsByTagName('script')[0];
+ otherScript.parentNode.insertBefore(scriptElement, otherScript);
+ };
+
+ umami.load('${document.location.origin}/umami.js', '${values.website_uuid}', false, false);
+ }
+ }
+})(window);
\ No newline at end of file
From 241905c5fed9a250f3be4dbb90817794537ff113 Mon Sep 17 00:00:00 2001
From: Bartosz Hernas
Date: Tue, 15 Sep 2020 14:10:01 +0200
Subject: [PATCH 06/10] Fix uuid passing
---
tracker/index.js | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/tracker/index.js b/tracker/index.js
index 6d6886fbde..8287b145b4 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -83,12 +83,9 @@ function log(isDebug, messageFn) {
const scheduledCalls = window.umami.calls;
window.umami = {};
- window.umami.collect = (type, params, id) => {
- if (!id) {
- id = website;
- }
+ window.umami.collect = (type, params, uuid) => {
const payload = {
- website: id,
+ website: uuid,
hostname,
screen,
language,
@@ -106,15 +103,15 @@ function log(isDebug, messageFn) {
payload,
});
};
- window.umami.pageView = (url = currentUrl, referrer = currentRef) => window.umami.collect('pageview', {
+ window.umami.pageView = (url = currentUrl, referrer = currentRef, uuid = website) => window.umami.collect('pageview', {
url,
referrer,
- });
- window.umami.event = (event_type, event_value, url = currentUrl) => window.umami.collect('event', {
+ }, uuid);
+ window.umami.event = (event_type, event_value, url = currentUrl, uuid = website) => window.umami.collect('event', {
url,
event_type,
event_value,
- });
+ }, uuid);
window.umami.registerAutoEvents = () => {
history.pushState = hook(history, 'pushState', handlePush);
history.replaceState = hook(history, 'replaceState', handlePush);
@@ -122,8 +119,6 @@ function log(isDebug, messageFn) {
};
log(isDebug, () => 'Umami, calling scheduled invocations');
- log(isDebug, () => scheduledCalls);
-
scheduledCalls.forEach(([fnName, ...params]) => {
log(isDebug, () => `Umami, calling ${fnName} fn with params: ${JSON.stringify(params)}`);
window.umami[fnName].apply(window.umami, params);
From 5318accc6f6eea5fff8f47f96bde5ea56f4d156b Mon Sep 17 00:00:00 2001
From: Bartosz Hernas
Date: Wed, 16 Sep 2020 12:07:22 +0200
Subject: [PATCH 07/10] Fixed comments
---
components/forms/TrackingCodeForm.js | 3 +-
tracker/index.js | 76 +++++++++++++---------------
tracker/snippet.js | 7 +--
3 files changed, 38 insertions(+), 48 deletions(-)
diff --git a/components/forms/TrackingCodeForm.js b/components/forms/TrackingCodeForm.js
index 36dda30e43..9e8e81e21d 100644
--- a/components/forms/TrackingCodeForm.js
+++ b/components/forms/TrackingCodeForm.js
@@ -24,8 +24,7 @@ export default function TrackingCodeForm({ values, onClose }) {
rows={3}
cols={60}
spellCheck={false}
- defaultValue={`!function(){"use strict";!function(e){var t=e.umami=e.umami||[];if(!t.registerAutoEvents)if(t.invoked)e.console&&console.error&&console.error("Umami snippet included twice.");else{t.invoked=!0,t.calls=[],t.methods=["registerAutoEvents","event","pageView"],t.factory=function(e){return function(){var r=Array.prototype.slice.call(arguments);return r.unshift(e),t.calls.push(r),t}};for(var r=0;r
diff --git a/tracker/index.js b/tracker/index.js
index 8287b145b4..2cbc3ce251 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -3,13 +3,6 @@ import 'unfetch/polyfill';
import { doNotTrack, hook, post } from '../lib/web';
import { removeTrailingSlash } from '../lib/url';
-function log(isDebug, messageFn) {
- if (isDebug) {
- return;
- }
- console.log(messageFn());
-}
-
(window => {
const {
screen: { width, height },
@@ -27,7 +20,6 @@ function log(isDebug, messageFn) {
const website = script.getAttribute('data-website-id');
const hostUrl = script.getAttribute('data-host-url');
const skipAuto = script.getAttribute('data-skip-auto');
- const isDebug = script.getAttribute('data-debug');
const root = hostUrl
? removeTrailingSlash(hostUrl)
: new URL(script.src).href.split('/').slice(0, -1).join('/');
@@ -37,26 +29,6 @@ function log(isDebug, messageFn) {
let currentUrl = `${pathname}${search}`;
let currentRef = document.referrer;
- /* Collect metrics */
- const pageViewWithAutoEvents = (url, referrer) => window.umami.pageView(url, referrer).then(() => setTimeout(loadEvents, 300));
-
- /* Handle history */
-
- const handlePush = (state, title, url) => {
- removeEvents();
- currentRef = currentUrl;
- const newUrl = url.toString();
-
- if (newUrl.substring(0, 4) === 'http') {
- const { pathname, search } = new URL(newUrl);
- currentUrl = `${pathname}${search}`;
- } else {
- currentUrl = newUrl;
- }
-
- pageViewWithAutoEvents(currentUrl, currentRef);
- };
-
/* Handle events */
const removeEvents = () => {
@@ -71,7 +43,7 @@ function log(isDebug, messageFn) {
element.className.split(' ').forEach(className => {
if (/^umami--([a-z]+)--([a-z0-9_]+[a-z0-9-_]+)$/.test(className)) {
const [, type, value] = className.split('--');
- const listener = () => window.umami.event(type, value);
+ const listener = () => event(type, value);
listeners.push([element, type, listener]);
element.addEventListener(type, listener, true);
@@ -79,11 +51,7 @@ function log(isDebug, messageFn) {
});
});
};
-
-
- const scheduledCalls = window.umami.calls;
- window.umami = {};
- window.umami.collect = (type, params, uuid) => {
+ const collect = (type, params, uuid) => {
const payload = {
website: uuid,
hostname,
@@ -97,35 +65,61 @@ function log(isDebug, messageFn) {
});
}
- log(isDebug, () => `Umami, collect ${type} with payload: ${JSON.stringify(payload, null, 2)}`);
return post(`${root}/api/collect`, {
type,
payload,
});
};
- window.umami.pageView = (url = currentUrl, referrer = currentRef, uuid = website) => window.umami.collect('pageview', {
+ const pageView = (url = currentUrl, referrer = currentRef, uuid = website) => collect('pageview', {
url,
referrer,
}, uuid);
- window.umami.event = (event_type, event_value, url = currentUrl, uuid = website) => window.umami.collect('event', {
+
+ /* Collect metrics */
+ const pageViewWithAutoEvents = (url, referrer) => pageView(url, referrer).then(() => setTimeout(loadEvents, 300));
+
+ /* Handle history */
+ const handlePush = (state, title, url) => {
+ removeEvents();
+ currentRef = currentUrl;
+ const newUrl = url.toString();
+
+ if (newUrl.substring(0, 4) === 'http') {
+ const { pathname, search } = new URL(newUrl);
+ currentUrl = `${pathname}${search}`;
+ } else {
+ currentUrl = newUrl;
+ }
+
+ pageViewWithAutoEvents(currentUrl, currentRef);
+ };
+
+ const event = (event_type, event_value, url = currentUrl, uuid = website) => collect('event', {
url,
event_type,
event_value,
}, uuid);
- window.umami.registerAutoEvents = () => {
+
+ const registerAutoEvents = () => {
history.pushState = hook(history, 'pushState', handlePush);
history.replaceState = hook(history, 'replaceState', handlePush);
return pageViewWithAutoEvents(currentUrl, currentRef);
};
- log(isDebug, () => 'Umami, calling scheduled invocations');
+
+ const scheduledCalls = window.umami.calls;
+ window.umami = {
+ collect,
+ pageView,
+ event,
+ registerAutoEvents
+ };
scheduledCalls.forEach(([fnName, ...params]) => {
- log(isDebug, () => `Umami, calling ${fnName} fn with params: ${JSON.stringify(params)}`);
window.umami[fnName].apply(window.umami, params);
});
/* Start */
if (!skipAuto) {
- window.umami.registerAutoEvents().catch(e => console.error(e));
+ registerAutoEvents().catch(e => console.error(e));
}
})(window);
diff --git a/tracker/snippet.js b/tracker/snippet.js
index ffb894230d..89a3a865b8 100644
--- a/tracker/snippet.js
+++ b/tracker/snippet.js
@@ -19,7 +19,7 @@
let e = umami.methods[t];
umami[e] = umami.factory(e);
}
- umami.load = function(umamiScript, umamiUUID, skipAuto, isDebug) {
+ umami.load = function(umamiScript, umamiUUID, skipAuto) {
const scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
scriptElement.defer = true;
@@ -28,15 +28,12 @@
if (skipAuto) {
scriptElement.setAttribute('data-skip-auto', 'true');
}
- if (isDebug) {
- scriptElement.setAttribute('data-debug', 'true');
- }
scriptElement.src = umamiScript;
const otherScript = document.getElementsByTagName('script')[0];
otherScript.parentNode.insertBefore(scriptElement, otherScript);
};
- umami.load('${document.location.origin}/umami.js', '${values.website_uuid}', false, false);
+ umami.load('${document.location.origin}/umami.js', '${values.website_uuid}', false);
}
}
})(window);
\ No newline at end of file
From 09235ddacf737c08357b89c610f37de034e4d4a9 Mon Sep 17 00:00:00 2001
From: Bartosz Hernas
Date: Fri, 18 Sep 2020 11:33:17 +0200
Subject: [PATCH 08/10] Reverted tracker to old behaviour while still keeping
more advanced use case
---
components/forms/TrackingCodeForm.js | 8 +++-----
tracker/index.js | 23 +++++++++++++----------
tracker/snippet.js | 8 ++++++--
3 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/components/forms/TrackingCodeForm.js b/components/forms/TrackingCodeForm.js
index 9e8e81e21d..61df12757a 100644
--- a/components/forms/TrackingCodeForm.js
+++ b/components/forms/TrackingCodeForm.js
@@ -17,21 +17,19 @@ export default function TrackingCodeForm({ values, onClose }) {
/>
- {/* Run `npm run build-snippet, and copy paste here the content of public/snippet.js */}
- {/* TODO: use webpack importing function to import the content of the file here ? */}
-
+
-
+
diff --git a/tracker/index.js b/tracker/index.js
index 2cbc3ce251..28b8dfcada 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -43,7 +43,7 @@ import { removeTrailingSlash } from '../lib/url';
element.className.split(' ').forEach(className => {
if (/^umami--([a-z]+)--([a-z0-9_]+[a-z0-9-_]+)$/.test(className)) {
const [, type, value] = className.split('--');
- const listener = () => event(type, value);
+ const listener = () => collectEvent(type, value);
listeners.push([element, type, listener]);
element.addEventListener(type, listener, true);
@@ -94,7 +94,7 @@ import { removeTrailingSlash } from '../lib/url';
pageViewWithAutoEvents(currentUrl, currentRef);
};
- const event = (event_type, event_value, url = currentUrl, uuid = website) => collect('event', {
+ const collectEvent = (event_type, event_value, url = currentUrl, uuid = website) => collect('event', {
url,
event_type,
event_value,
@@ -107,17 +107,20 @@ import { removeTrailingSlash } from '../lib/url';
};
+ const umamiFunctions = { collect, pageView, collectEvent, registerAutoEvents };
const scheduledCalls = window.umami.calls;
- window.umami = {
- collect,
- pageView,
- event,
- registerAutoEvents
- };
- scheduledCalls.forEach(([fnName, ...params]) => {
- window.umami[fnName].apply(window.umami, params);
+
+ 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 */
if (!skipAuto) {
registerAutoEvents().catch(e => console.error(e));
diff --git a/tracker/snippet.js b/tracker/snippet.js
index 89a3a865b8..df1a8eb53e 100644
--- a/tracker/snippet.js
+++ b/tracker/snippet.js
@@ -33,7 +33,11 @@
otherScript.parentNode.insertBefore(scriptElement, otherScript);
};
- umami.load('${document.location.origin}/umami.js', '${values.website_uuid}', false);
+ umami.load('[HOST]/umami.js', '[UMAMI_UUID]', false);
}
}
-})(window);
\ No newline at end of file
+})(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
From 023adafa3938115aaa06803f0194f964c8003359 Mon Sep 17 00:00:00 2001
From: Mike Cao
Date: Fri, 18 Sep 2020 13:40:46 -0700
Subject: [PATCH 09/10] Refactor tracker.
---
.gitignore | 1 -
components/forms/TrackingCodeForm.js | 6 +-
package.json | 3 +-
pages/test.js | 12 ++-
rollup.snippet.config.js | 19 ----
rollup.tracker.config.js | 8 +-
tracker/index.js | 127 ++++++++++++++-------------
tracker/snippet.js | 43 ---------
8 files changed, 84 insertions(+), 135 deletions(-)
delete mode 100644 rollup.snippet.config.js
delete mode 100644 tracker/snippet.js
diff --git a/.gitignore b/.gitignore
index 8b349dbb41..ca0f3c4f7c 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 61df12757a..1f44f835ef 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 93d2314294..bf34bf4488 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 30c00e7045..820a31879c 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
+
+ Button
+
>
);
diff --git a/rollup.snippet.config.js b/rollup.snippet.config.js
deleted file mode 100644
index c592598889..0000000000
--- 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 ea5f709f74..e836955fdf 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 28b8dfcada..e3cb4fe4ec 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 */
-
- 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);
+ /* Collect metrics */
- 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);
- };
-
- const collectEvent = (event_type, event_value, url = currentUrl, uuid = website) => collect('event', {
- url,
- event_type,
- event_value,
- }, uuid);
+ pageView(currentUrl, currentRef);
- const registerAutoEvents = () => {
- history.pushState = hook(history, 'pushState', handlePush);
- history.replaceState = hook(history, 'replaceState', handlePush);
- return pageViewWithAutoEvents(currentUrl, currentRef);
+ setTimeout(loadEvents, 300);
};
+ /* Global */
- 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 (!window.umami) {
+ const umami = event_value => pageEvent(event_value);
+ umami.pageView = pageView;
+ umami.pageEvent = pageEvent;
- 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 df1a8eb53e..0000000000
--- 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
From b19889638aa62ee088c9fe1cf5958d2da9d53d45 Mon Sep 17 00:00:00 2001
From: Mike Cao
Date: Fri, 18 Sep 2020 14:16:57 -0700
Subject: [PATCH 10/10] Removed polyfills from tracker. Switched to
XMLHttpRequest.
---
lib/middleware.js | 1 +
tracker/index.js | 20 +++++++++++++++++---
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/lib/middleware.js b/lib/middleware.js
index 014b225dd3..c41e75d017 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/tracker/index.js b/tracker/index.js
index e3cb4fe4ec..38aadeb3d7 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -1,6 +1,4 @@
-import 'promise-polyfill/src/polyfill';
-import 'unfetch/polyfill';
-import { doNotTrack, hook, post } from '../lib/web';
+import { doNotTrack, hook } from '../lib/web';
import { removeTrailingSlash } from '../lib/url';
(window => {
@@ -32,6 +30,20 @@ import { removeTrailingSlash } from '../lib/url';
/* Collect metrics */
+ 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 = {
website: uuid,
@@ -133,5 +145,7 @@ import { removeTrailingSlash } from '../lib/url';
history.replaceState = hook(history, 'replaceState', handlePush);
pageView(currentUrl, currentRef);
+
+ loadEvents();
}
})(window);