Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
407 lines
11 KiB
JavaScript
407 lines
11 KiB
JavaScript
/**
|
|
* Analytics Module
|
|
*
|
|
* Provides unified analytics system for event tracking, page views, and user behavior.
|
|
* Features:
|
|
* - Event tracking
|
|
* - Page view tracking
|
|
* - User behavior tracking
|
|
* - Custom events
|
|
* - Integration with LiveComponents
|
|
* - GDPR compliance
|
|
*/
|
|
|
|
import { Logger } from '../../core/logger.js';
|
|
|
|
/**
|
|
* AnalyticsProvider - Base class for analytics providers
|
|
*/
|
|
export class AnalyticsProvider {
|
|
constructor(config = {}) {
|
|
this.config = config;
|
|
this.enabled = config.enabled ?? true;
|
|
}
|
|
|
|
/**
|
|
* Track event
|
|
*/
|
|
async track(eventName, properties = {}) {
|
|
if (!this.enabled) return;
|
|
// Override in subclasses
|
|
}
|
|
|
|
/**
|
|
* Track page view
|
|
*/
|
|
async pageView(path, properties = {}) {
|
|
if (!this.enabled) return;
|
|
// Override in subclasses
|
|
}
|
|
|
|
/**
|
|
* Identify user
|
|
*/
|
|
async identify(userId, traits = {}) {
|
|
if (!this.enabled) return;
|
|
// Override in subclasses
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GoogleAnalyticsProvider - Google Analytics integration
|
|
*/
|
|
export class GoogleAnalyticsProvider extends AnalyticsProvider {
|
|
constructor(config = {}) {
|
|
super(config);
|
|
this.measurementId = config.measurementId || config.gaId;
|
|
}
|
|
|
|
async track(eventName, properties = {}) {
|
|
if (typeof gtag !== 'undefined') {
|
|
gtag('event', eventName, properties);
|
|
}
|
|
}
|
|
|
|
async pageView(path, properties = {}) {
|
|
if (typeof gtag !== 'undefined') {
|
|
gtag('config', this.measurementId, {
|
|
page_path: path,
|
|
...properties
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* CustomProvider - Custom analytics endpoint
|
|
*/
|
|
export class CustomProvider extends AnalyticsProvider {
|
|
constructor(config = {}) {
|
|
super(config);
|
|
this.endpoint = config.endpoint || '/api/analytics';
|
|
}
|
|
|
|
async track(eventName, properties = {}) {
|
|
try {
|
|
await fetch(this.endpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: JSON.stringify({
|
|
type: 'event',
|
|
name: eventName,
|
|
properties,
|
|
timestamp: Date.now()
|
|
})
|
|
});
|
|
} catch (error) {
|
|
Logger.error('[Analytics] Failed to track event', error);
|
|
}
|
|
}
|
|
|
|
async pageView(path, properties = {}) {
|
|
try {
|
|
await fetch(this.endpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: JSON.stringify({
|
|
type: 'page_view',
|
|
path,
|
|
properties,
|
|
timestamp: Date.now()
|
|
})
|
|
});
|
|
} catch (error) {
|
|
Logger.error('[Analytics] Failed to track page view', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Analytics - Unified analytics system
|
|
*/
|
|
export class Analytics {
|
|
constructor(config = {}) {
|
|
this.config = {
|
|
enabled: config.enabled ?? true,
|
|
providers: config.providers || [],
|
|
gdprCompliant: config.gdprCompliant ?? true,
|
|
requireConsent: config.requireConsent ?? false,
|
|
consentGiven: config.consentGiven ?? false,
|
|
anonymizeIp: config.anonymizeIp ?? true,
|
|
...config
|
|
};
|
|
|
|
this.providers = [];
|
|
this.eventQueue = [];
|
|
this.userId = null;
|
|
this.userTraits = {};
|
|
|
|
// Initialize providers
|
|
this.initProviders();
|
|
|
|
// Track initial page view
|
|
if (this.config.enabled && this.hasConsent()) {
|
|
this.trackPageView();
|
|
}
|
|
|
|
Logger.info('[Analytics] Initialized', {
|
|
enabled: this.config.enabled,
|
|
providers: this.providers.length,
|
|
gdprCompliant: this.config.gdprCompliant
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a new Analytics instance
|
|
*/
|
|
static create(config = {}) {
|
|
return new Analytics(config);
|
|
}
|
|
|
|
/**
|
|
* Initialize providers
|
|
*/
|
|
initProviders() {
|
|
for (const providerConfig of this.config.providers) {
|
|
let provider;
|
|
|
|
if (typeof providerConfig === 'string') {
|
|
// Provider name
|
|
if (providerConfig === 'google-analytics' || providerConfig === 'ga') {
|
|
provider = new GoogleAnalyticsProvider({});
|
|
} else if (providerConfig === 'custom') {
|
|
provider = new CustomProvider({});
|
|
}
|
|
} else if (typeof providerConfig === 'object') {
|
|
// Provider config
|
|
if (providerConfig.type === 'google-analytics' || providerConfig.type === 'ga') {
|
|
provider = new GoogleAnalyticsProvider(providerConfig);
|
|
} else if (providerConfig.type === 'custom') {
|
|
provider = new CustomProvider(providerConfig);
|
|
} else if (providerConfig instanceof AnalyticsProvider) {
|
|
provider = providerConfig;
|
|
}
|
|
}
|
|
|
|
if (provider) {
|
|
this.providers.push(provider);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if consent is given (GDPR)
|
|
*/
|
|
hasConsent() {
|
|
if (!this.config.requireConsent) {
|
|
return true;
|
|
}
|
|
return this.config.consentGiven;
|
|
}
|
|
|
|
/**
|
|
* Give consent (GDPR)
|
|
*/
|
|
giveConsent() {
|
|
this.config.consentGiven = true;
|
|
|
|
// Process queued events
|
|
this.processEventQueue();
|
|
|
|
Logger.info('[Analytics] Consent given');
|
|
}
|
|
|
|
/**
|
|
* Revoke consent (GDPR)
|
|
*/
|
|
revokeConsent() {
|
|
this.config.consentGiven = false;
|
|
Logger.info('[Analytics] Consent revoked');
|
|
}
|
|
|
|
/**
|
|
* Track event
|
|
*/
|
|
async track(eventName, properties = {}) {
|
|
if (!this.config.enabled || !this.hasConsent()) {
|
|
// Queue event if consent required
|
|
if (this.config.requireConsent && !this.config.consentGiven) {
|
|
this.eventQueue.push({ type: 'event', name: eventName, properties });
|
|
}
|
|
return;
|
|
}
|
|
|
|
const eventData = {
|
|
name: eventName,
|
|
properties: this.anonymizeData(properties),
|
|
timestamp: Date.now(),
|
|
userId: this.userId
|
|
};
|
|
|
|
// Send to all providers
|
|
for (const provider of this.providers) {
|
|
try {
|
|
await provider.track(eventName, eventData.properties);
|
|
} catch (error) {
|
|
Logger.error('[Analytics] Provider error', error);
|
|
}
|
|
}
|
|
|
|
Logger.debug('[Analytics] Event tracked', eventData);
|
|
|
|
// Trigger event
|
|
this.triggerAnalyticsEvent('track', eventData);
|
|
}
|
|
|
|
/**
|
|
* Track page view
|
|
*/
|
|
async trackPageView(path = null, properties = {}) {
|
|
if (!this.config.enabled || !this.hasConsent()) {
|
|
return;
|
|
}
|
|
|
|
const pagePath = path || window.location.pathname;
|
|
const pageData = {
|
|
path: pagePath,
|
|
title: document.title,
|
|
properties: this.anonymizeData(properties),
|
|
timestamp: Date.now()
|
|
};
|
|
|
|
// Send to all providers
|
|
for (const provider of this.providers) {
|
|
try {
|
|
await provider.pageView(pagePath, pageData.properties);
|
|
} catch (error) {
|
|
Logger.error('[Analytics] Provider error', error);
|
|
}
|
|
}
|
|
|
|
Logger.debug('[Analytics] Page view tracked', pageData);
|
|
|
|
// Trigger event
|
|
this.triggerAnalyticsEvent('page_view', pageData);
|
|
}
|
|
|
|
/**
|
|
* Identify user
|
|
*/
|
|
async identify(userId, traits = {}) {
|
|
if (!this.config.enabled || !this.hasConsent()) {
|
|
return;
|
|
}
|
|
|
|
this.userId = userId;
|
|
this.userTraits = { ...this.userTraits, ...traits };
|
|
|
|
// Send to all providers
|
|
for (const provider of this.providers) {
|
|
try {
|
|
if (typeof provider.identify === 'function') {
|
|
await provider.identify(userId, this.userTraits);
|
|
}
|
|
} catch (error) {
|
|
Logger.error('[Analytics] Provider error', error);
|
|
}
|
|
}
|
|
|
|
Logger.debug('[Analytics] User identified', { userId, traits: this.userTraits });
|
|
}
|
|
|
|
/**
|
|
* Track user behavior
|
|
*/
|
|
async trackBehavior(action, target, properties = {}) {
|
|
await this.track('user_behavior', {
|
|
action,
|
|
target,
|
|
...properties
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Anonymize data for GDPR compliance
|
|
*/
|
|
anonymizeData(data) {
|
|
if (!this.config.gdprCompliant) {
|
|
return data;
|
|
}
|
|
|
|
const anonymized = { ...data };
|
|
|
|
// Anonymize IP if enabled
|
|
if (this.config.anonymizeIp && anonymized.ip) {
|
|
anonymized.ip = anonymized.ip.split('.').slice(0, 3).join('.') + '.0';
|
|
}
|
|
|
|
// Remove PII if present
|
|
const piiFields = ['email', 'phone', 'address', 'ssn', 'credit_card'];
|
|
piiFields.forEach(field => {
|
|
if (anonymized[field]) {
|
|
delete anonymized[field];
|
|
}
|
|
});
|
|
|
|
return anonymized;
|
|
}
|
|
|
|
/**
|
|
* Process queued events
|
|
*/
|
|
processEventQueue() {
|
|
while (this.eventQueue.length > 0) {
|
|
const event = this.eventQueue.shift();
|
|
if (event.type === 'event') {
|
|
this.track(event.name, event.properties);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trigger analytics event
|
|
*/
|
|
triggerAnalyticsEvent(type, data) {
|
|
const event = new CustomEvent(`analytics:${type}`, {
|
|
detail: data,
|
|
bubbles: true
|
|
});
|
|
window.dispatchEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Enable analytics
|
|
*/
|
|
enable() {
|
|
this.config.enabled = true;
|
|
Logger.info('[Analytics] Enabled');
|
|
}
|
|
|
|
/**
|
|
* Disable analytics
|
|
*/
|
|
disable() {
|
|
this.config.enabled = false;
|
|
Logger.info('[Analytics] Disabled');
|
|
}
|
|
|
|
/**
|
|
* Destroy analytics
|
|
*/
|
|
destroy() {
|
|
this.providers = [];
|
|
this.eventQueue = [];
|
|
this.userId = null;
|
|
this.userTraits = {};
|
|
|
|
Logger.info('[Analytics] Destroyed');
|
|
}
|
|
}
|
|
|