Files
michaelschiemer/resources/js/modules/analytics/Analytics.js
Michael Schiemer 36ef2a1e2c
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
fix: Gitea Traefik routing and connection pool optimization
- 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
2025-11-09 14:46:15 +01:00

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');
}
}