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]); }); // https://developer.mozilla.org/en-US/docs/Web/API/Element/securitypolicyviolation_event window.addEventListener('securitypolicyviolation', (e) => { let attrs = [ 'blockedURI', 'columnNumber', 'disposition', 'documentURI', 'effectiveDirective', 'lineNumber', 'originalPolicy', 'referrer', 'sample', 'sourceFile', 'statusCode', 'violatedDirective', ]; let payload = {}; for (let attr of attrs) { payload[attr] = e[attr]; } this.pushEvent(['trackEvent', 'SecurityPolicyViolation', JSON.stringify(payload)]); }); 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); } }