import DBPLitElement from '@dbp-toolkit/common/dbp-lit-element'; import {LoginStatus} from "@dbp-toolkit/auth/src/util"; export class MatomoElement extends DBPLitElement { constructor() { super(); this.endpoint = ''; this.siteId = ''; this.isRunning = false; this.lastEvent = []; this.gitInfo = ''; this.auth = {}; this.analyticsEvent = {}; this.loginStatus = ''; } static get properties() { return { ...super.properties, endpoint: { type: String }, siteId: { type: String, attribute: 'site-id' }, gitInfo: { type: String, attribute: 'git-info' }, auth: { type: Object }, analyticsEvent: { type: Object, attribute: 'analytics-event' }, }; } update(changedProperties) { changedProperties.forEach((oldValue, propName) => { switch (propName) { case 'auth': { const loginStatus = this.auth['login-status']; if (this.loginStatus !== loginStatus) { this.setupMatomo(loginStatus === LoginStatus.LOGGED_IN); this.loginStatus = loginStatus; } } break; case 'analyticsEvent': { // ignore analyticsEvent without data if (this.analyticsEvent.category === undefined && this.analyticsEvent.message === undefined) { break; } console.log('MatomoElement(' + this.isRunning + ') analyticsEvent: ' + this.analyticsEvent.action + ', ' + this.analyticsEvent.message); const event = ['trackEvent', this.analyticsEvent.category, this.analyticsEvent.action, this.analyticsEvent.name, this.analyticsEvent.value]; if (this.isRunning) { this.pushEvent(event); } else { this.lastEvent = event; } } break; } }); super.update(changedProperties); } render() { return ``; } setupMatomo(loggedIn) { if (loggedIn && ! this.isRunning) { if (this.siteId === '') { console.log('site id missing, skipping matomo...'); return; } if (this.endpoint === '') { console.log('endpoint missing, skipping matomo...'); return; } console.log('add matomo...'); this.pushEvent(['setCustomVariable', 1, "GitCommit", this.gitInfo, "visit"]); this.pushEvent(['enableHeartBeatTimer']); this.pushEvent(['disableCookies']); this.pushEvent(['trackPageView']); this.pushEvent(['enableLinkTracking']); const that = this; (function (endpoint, siteId) { that.pushEvent(['setTrackerUrl', endpoint+'matomo.php']); that.pushEvent(['setSiteId', siteId]); var g = document.createElement('script'); var s = document.getElementsByTagName('script')[0]; g.type = 'text/javascript'; g.async = true; g.defer = true; g.src = endpoint + 'matomo.js'; s.parentNode.insertBefore(g, s); })(this.endpoint, this.siteId); // track changed locations window.addEventListener('locationchanged', function(e) { that.pushEvent(['setReferrerUrl', e.detail.referrerUrl]); that.pushEvent(['setCustomUrl', location.href]); // that.pushEvent(['setDocumentTitle', '']); that.pushEvent(['trackPageView']); // make Matomo aware of newly added content const content = document.getElementById('content'); that.pushEvent(['MediaAnalytics::scanForMedia', content]); that.pushEvent(['FormAnalytics::scanForForms', content]); that.pushEvent(['trackContentImpressionsWithinNode', content]); }); // track errors window.addEventListener('error', function(e) { that.pushEvent(['trackEvent', 'Error', e.error ? e.error.message + '\n' + e.error.stack : e.message]); }); window.addEventListener('unhandledrejection', function(e) { let name = e.reason; // TypeError objects have no toJSON() method, so we can't serialize them by themselves if (e.reason instanceof TypeError) { const error = e.reason; name = { 'message': error.message, 'name': error.name, 'fileName': error.fileName, 'lineNumber': error.lineNumber, 'columnNumber': error.columnNumber, 'stack': error.stack, }; } that.pushEvent(['trackEvent', 'UnhandledRejection', name]); }); this.isRunning = true; if (this.lastEvent.length > 0) { console.log('MatomoElement* (' + this.isRunning + '): ' + this.lastEvent[1] + ', ' + this.lastEvent[2]); that.pushEvent(this.lastEvent); this.lastEvent = []; } return; } if (! loggedIn && this.isRunning) { // TODO: remove those event listeners console.log('remove matomo...'); this.isRunning = false; } } /** * Pushes an event array to Matomo * See: https://matomo.org/docs/event-tracking/ * * event[0]: Event Category * event[1]: Event Action * event[2]: Event Name * event[3]: Event Value * * @param event */ pushEvent(event) { window._paq = window._paq || []; // add some special checks for "trackEvent" if (event[0] === 'trackEvent') { // make sure the event action is a non-empty string // prevents: "Error while logging event: Parameters `category` and `action` must not be empty or filled with whitespaces" if (event[1] === null || event[1] === '' || event[1] === undefined) { event[1] = 'empty'; } // make sure the event name is a non-empty string if (event[2] === null || event[2] === '' || event[2] === undefined) { event[2] = 'empty'; } else if (typeof event[2] === 'object') { event[2] = JSON.stringify(event[2]); } } window._paq.push(event); } }