diff --git a/lib/db.js b/lib/db.js index b925b52c..8725f88c 100644 --- a/lib/db.js +++ b/lib/db.js @@ -68,3 +68,25 @@ export async function savePageView(website_id, session_id, url, referrer) { }), ); } + +export async function saveEvent(website_id, session_id, url, eventType, eventValue) { + return runQuery( + prisma.pageview.create({ + data: { + website: { + connect: { + website_uuid: website_id, + }, + }, + session: { + connect: { + session_uuid: session_id, + }, + }, + url, + eventType, + eventValue, + }, + }), + ); +} diff --git a/lib/utils.js b/lib/utils.js index ccf8c43d..c2730f70 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -91,6 +91,8 @@ export function parseCollectRequest(req) { const { url, referrer, + eventType, + eventValue, session: { website_id, session_id, time, hash: validationHash }, } = payload; @@ -107,6 +109,8 @@ export function parseCollectRequest(req) { session_id, url, referrer, + eventType, + eventValue, }; } } diff --git a/pages/api/collect.js b/pages/api/collect.js index 2f47cf9a..724191f8 100644 --- a/pages/api/collect.js +++ b/pages/api/collect.js @@ -1,5 +1,5 @@ import { parseCollectRequest } from 'lib/utils'; -import { savePageView } from 'lib/db'; +import { savePageView, saveEvent } from 'lib/db'; import { allowPost } from 'lib/middleware'; export default async (req, res) => { @@ -8,12 +8,16 @@ export default async (req, res) => { const values = parseCollectRequest(req); if (values.success) { - const { type, website_id, session_id, url, referrer } = values; + const { type, website_id, session_id, url, referrer, eventType, eventValue } = values; if (type === 'pageview') { await savePageView(website_id, session_id, url, referrer).catch(() => { values.success = 0; }); + } else if (type === 'event') { + await saveEvent(website_id, session_id, url, eventType, eventValue).catch(() => { + values.success = 0; + }); } } diff --git a/pages/index.js b/pages/index.js index e632bfd1..0bd3d0ac 100644 --- a/pages/index.js +++ b/pages/index.js @@ -14,6 +14,10 @@ export default function Home() { 123 +
+ ); } diff --git a/public/umami.js b/public/umami.js index 51691c4e..b4ab7334 100644 --- a/public/umami.js +++ b/public/umami.js @@ -1 +1 @@ -!function(){"use strict";function e(e){var n=this.constructor;return this.then((function(t){return n.resolve(e()).then((function(){return t}))}),(function(t){return n.resolve(e()).then((function(){return n.reject(t)}))}))}var n=setTimeout;function t(e){return Boolean(e&&void 0!==e.length)}function r(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],f(e,this)}function i(e,n){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,o._immediateFn((function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null!==t){var r;try{r=t(e._value)}catch(e){return void s(n.promise,e)}u(n.promise,r)}else(1===e._state?u:s)(n.promise,e._value)}))):e._deferreds.push(n)}function u(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var t=n.then;if(n instanceof o)return e._state=3,e._value=n,void c(e);if("function"==typeof t)return void f((r=t,i=n,function(){r.apply(i,arguments)}),e)}e._state=1,e._value=n,c(e)}catch(n){s(e,n)}var r,i}function s(e,n){e._state=2,e._value=n,c(e)}function c(e){2===e._state&&0===e._deferreds.length&&o._immediateFn((function(){e._handled||o._unhandledRejectionFn(e._value)}));for(var n=0,t=e._deferreds.length;n fetch(url, { method: 'post', @@ -56,6 +60,29 @@ if (script) { return success; }); + const trackEvent = (url, eventType, eventValue) => + post(`${hostUrl}/api/collect`, { + type: 'event', + payload: { url, eventType, eventValue, session: getSession() }, + }).then(({ success }) => { + if (!success) { + store.removeItem(sessionKey); + } + return success; + }); + + const elementToString = e => { + return JSON.stringify( + e.getAttributeNames().reduce( + (obj, val) => { + obj[val] = e.getAttribute(val); + return obj; + }, + { tag: e.tagName.toLowerCase() }, + ), + ); + }; + const execute = (url, referrer) => { const data = getSessionData(url); @@ -69,6 +96,8 @@ if (script) { } }; + /* Handle push state */ + const handlePush = (state, title, url) => { currenrRef = currentUrl; currentUrl = url; @@ -87,6 +116,27 @@ if (script) { history.pushState = hook('pushState', handlePush); history.replaceState = hook('replaceState', handlePush); + /* Handle events */ + + document.querySelectorAll("[class*='umami--']").forEach(e => { + e.className.split(' ').forEach(c => { + console.log('class', c); + if (/^umami--/.test(c)) { + const [, event] = c.split('--'); + console.log('event', event); + if (event) { + e.addEventListener(event, () => { + trackEvent(currentUrl, event, elementToString(e)); + console.log('exec event', event, elementToString(e)); + }); + } + } + }); + console.log('match', e); + }); + + /* Start */ + execute(currentUrl, currenrRef); } }