Technical Implementation

Real-time Analytics and Dashboard Development for Popup Systems

Master real-time analytics and dashboard development for popup systems. Learn WebSocket implementation, dashboard UI frameworks, data visualization, and performance monitoring for live popup analytics.

A
Alex Chen
Real-time Systems Engineer at Nudgesmart
January 16, 2024
22 min read
⚙️

Technical Implementation Article

Important Notice: This content is for educational purposes only. Results may vary based on your specific business circumstances, industry, market conditions, and implementation. No specific outcomes are guaranteed. This is not legal advice - consult with technical professionals for specific guidance.

Real-time Analytics and Dashboard Development for Popup Systems

Building effective real-time analytics dashboards for popup systems requires a comprehensive understanding of data streaming, UI frameworks, visualization techniques, and performance optimization strategies. Modern popup analytics demand instant data processing, live metric calculations, and responsive dashboard interfaces that can handle high-volume data streams while maintaining optimal user experience.

This technical guide explores the complete architecture of real-time analytics systems for popup campaigns, covering WebSocket implementation for live data streaming, dashboard UI component design, data visualization libraries, real-time metrics aggregation, and performance monitoring systems. Whether you're building simple popup impression tracking or complex multi-dimensional analytics platforms, these principles will help you create scalable, efficient real-time analytics solutions.

Real-time Data Streaming Architecture

WebSocket Implementation for Live Data

WebSocket connections provide the foundation for real-time data streaming in popup analytics systems:

class PopupAnalyticsWebSocket {
    constructor(url, options = {}) {
        this.url = url;
        this.options = {
            reconnectInterval: 5000,
            maxReconnectAttempts: 10,
            heartbeatInterval: 30000,
            ...options
        };

        this.ws = null;
        this.reconnectAttempts = 0;
        this.heartbeatTimer = null;
        this.messageQueue = [];
        this.eventHandlers = new Map();

        this.connect();
    }

    connect() {
        try {
            this.ws = new WebSocket(this.url);
            this.setupEventHandlers();
        } catch (error) {
            console.error('WebSocket connection error:', error);
            this.scheduleReconnect();
        }
    }

    setupEventHandlers() {
        this.ws.onopen = () => {
            console.log('WebSocket connected');
            this.reconnectAttempts = 0;
            this.startHeartbeat();
            this.flushMessageQueue();
            this.emit('connected');
        };

        this.ws.onmessage = (event) => {
            try {
                const data = JSON.parse(event.data);
                this.handleMessage(data);
            } catch (error) {
                console.error('Message parsing error:', error);
            }
        };

        this.ws.onclose = () => {
            console.log('WebSocket disconnected');
            this.stopHeartbeat();
            this.emit('disconnected');
            this.scheduleReconnect();
        };

        this.ws.onerror = (error) => {
            console.error('WebSocket error:', error);
            this.emit('error', error);
        };
    }

    handleMessage(data) {
        switch (data.type) {
            case 'popup_impression':
                this.emit('impression', data.payload);
                break;
            case 'popup_interaction':
                this.emit('interaction', data.payload);
                break;
            case 'campaign_update':
                this.emit('campaignUpdate', data.payload);
                break;
            case 'metrics_update':
                this.emit('metricsUpdate', data.payload);
                break;
            default:
                console.warn('Unknown message type:', data.type);
        }
    }

    send(data) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(data));
        } else {
            this.messageQueue.push(data);
        }
    }

    flushMessageQueue() {
        while (this.messageQueue.length > 0) {
            const message = this.messageQueue.shift();
            this.send(message);
        }
    }

    startHeartbeat() {
        this.heartbeatTimer = setInterval(() => {
            this.send({ type: 'ping', timestamp: Date.now() });
        }, this.options.heartbeatInterval);
    }

    stopHeartbeat() {
        if (this.heartbeatTimer) {
            clearInterval(this.heartbeatTimer);
            this.heartbeatTimer = null;
        }
    }

    scheduleReconnect() {
        if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
            setTimeout(() => {
                this.reconnectAttempts++;
                this.connect();
            }, this.options.reconnectInterval);
        }
    }

    on(event, handler) {
        if (!this.eventHandlers.has(event)) {
            this.eventHandlers.set(event, []);
        }
        this.eventHandlers.get(event).push(handler);
    }

    emit(event, data) {
        if (this.eventHandlers.has(event)) {
            this.eventHandlers.get(event).forEach(handler => handler(data));
        }
    }
}

Data Stream Processing Pipeline

Implement efficient data processing for real-time analytics:

class AnalyticsStreamProcessor {
    constructor() {
        this.aggregationWindows = new Map();
        this.metrics = new Map();
        this.subscribers = new Map();
        this.processingQueue = [];
        this.isProcessing = false;
    }

    subscribe(eventType, callback, filters = {}) {
        const subscriptionId = this.generateId();
        const subscription = {
            id: subscriptionId,
            eventType,
            callback,
            filters,
            lastUpdate: 0
        };

        this.subscribers.set(subscriptionId, subscription);
        return subscriptionId;
    }

    unsubscribe(subscriptionId) {
        this.subscribers.delete(subscriptionId);
    }

    processEvent(event) {
        this.processingQueue.push({
            event,
            timestamp: Date.now()
        });

        if (!this.isProcessing) {
            this.processQueue();
        }
    }

    async processQueue() {
        this.isProcessing = true;

        while (this.processingQueue.length > 0) {
            const { event, timestamp } = this.processingQueue.shift();

            try {
                await this.processSingleEvent(event, timestamp);
                this.notifySubscribers(event, timestamp);
            } catch (error) {
                console.error('Event processing error:', error);
            }
        }

        this.isProcessing = false;
    }

    async processSingleEvent(event, timestamp) {
        // Update real-time metrics
        this.updateMetrics(event, timestamp);

        // Update aggregation windows
        this.updateAggregations(event, timestamp);

        // Update campaign-specific data
        this.updateCampaignData(event, timestamp);
    }

    updateMetrics(event, timestamp) {
        const metricKey = this.getMetricKey(event);

        if (!this.metrics.has(metricKey)) {
            this.metrics.set(metricKey, {
                count: 0,
                lastUpdate: timestamp,
                data: {}
            });
        }

        const metric = this.metrics.get(metricKey);
        metric.count++;
        metric.lastUpdate = timestamp;

        // Update specific metric data
        switch (event.type) {
            case 'popup_impression':
                metric.data.impressions = (metric.data.impressions || 0) + 1;
                metric.data.uniqueViews = this.calculateUniqueViews(event);
                break;
            case 'popup_interaction':
                metric.data.interactions = (metric.data.interactions || 0) + 1;
                metric.data.conversionRate = this.calculateConversionRate(metricKey);
                break;
        }
    }

    updateAggregations(event, timestamp) {
        const windowSizes = [60000, 300000, 900000, 3600000]; // 1min, 5min, 15min, 1hour

        windowSizes.forEach(windowSize => {
            const windowKey = Math.floor(timestamp / windowSize);
            const aggregationKey = `${event.type}_${windowSize}_${windowKey}`;

            if (!this.aggregationWindows.has(aggregationKey)) {
                this.aggregationWindows.set(aggregationKey, {
                    windowStart: windowKey * windowSize,
                    windowSize,
                    events: [],
                    metrics: {}
                });
            }

            const window = this.aggregationWindows.get(aggregationKey);
            window.events.push(event);

            // Update aggregated metrics
            this.updateWindowMetrics(window, event);

            // Clean old windows
            this.cleanupOldWindows(timestamp, windowSize);
        });
    }

    updateWindowMetrics(window, event) {
        const eventType = event.type;

        if (!window.metrics[eventType]) {
            window.metrics[eventType] = {
                count: 0,
                uniqueUsers: new Set(),
                conversions: 0
            };
        }

        const metric = window.metrics[eventType];
        metric.count++;

        if (event.userId) {
            metric.uniqueUsers.add(event.userId);
        }

        if (event.converted) {
            metric.conversions++;
        }
    }

    notifySubscribers(event, timestamp) {
        this.subscribers.forEach(subscription => {
            if (subscription.eventType === event.type) {
                if (this.matchesFilters(event, subscription.filters)) {
                    try {
                        subscription.callback({
                            event,
                            timestamp,
                            metrics: this.getMetrics(event.type)
                        });
                    } catch (error) {
                        console.error('Subscriber callback error:', error);
                    }
                }
            }
        });
    }

    matchesFilters(event, filters) {
        if (Object.keys(filters).length === 0) {
            return true;
        }

        return Object.entries(filters).every(([key, value]) => {
            if (key === 'campaignId') {
                return event.campaignId === value;
            }
            if (key === 'eventType') {
                return event.type === value;
            }
            if (key === 'timeRange') {
                const eventTime = event.timestamp;
                return eventTime >= value.start && eventTime <= value.end;
            }
            return true;
        });
    }

    getMetrics(eventType) {
        const now = Date.now();
        const recentMetrics = [];

        // Get metrics from different time windows
        [60000, 300000, 900000, 3600000].forEach(windowSize => {
            const windowKey = Math.floor(now / windowSize);
            const aggregationKey = `${eventType}_${windowSize}_${windowKey}`;

            if (this.aggregationWindows.has(aggregationKey)) {
                const window = this.aggregationWindows.get(aggregationKey);
                recentMetrics.push({
                    windowSize,
                    count: window.metrics[eventType]?.count || 0,
                    uniqueUsers: window.metrics[eventType]?.uniqueUsers?.size || 0,
                    conversions: window.metrics[eventType]?.conversions || 0
                });
            }
        });

        return recentMetrics;
    }

    generateId() {
        return Math.random().toString(36).substr(2, 9);
    }

    getMetricKey(event) {
        return `${event.type}_${event.campaignId || 'global'}`;
    }

    calculateUniqueViews(event) {
        // Implementation for calculating unique views
        return this.metrics.get(this.getMetricKey(event))?.data?.uniqueViews || 0;
    }

    calculateConversionRate(metricKey) {
        const metric = this.metrics.get(metricKey);
        if (!metric || !metric.data.impressions) return 0;

        return (metric.data.interactions / metric.data.impressions) * 100;
    }

    cleanupOldWindows(timestamp, maxAge) {
        const cutoffTime = timestamp - maxAge * 2; // Keep 2x window size

        this.aggregationWindows.forEach((window, key) => {
            if (window.windowStart < cutoffTime) {
                this.aggregationWindows.delete(key);
            }
        });
    }
}

Dashboard UI Framework and Component Architecture

Component-Based Dashboard Structure

Build modular dashboard components for real-time analytics:

class DashboardComponent {
    constructor(container, config = {}) {
        this.container = container;
        this.config = {
            autoUpdate: true,
            updateInterval: 5000,
            animations: true,
            ...config
        };

        this.data = null;
        this.isLoading = false;
        this.error = null;
        this.updateTimer = null;
        this.eventListeners = new Map();

        this.init();
    }

    init() {
        this.render();
        this.bindEvents();

        if (this.config.autoUpdate) {
            this.startAutoUpdate();
        }
    }

    render() {
        this.container.innerHTML = this.getTemplate();
        this.elements = this.getElements();
        this.applyStyles();
    }

    getTemplate() {
        return `
            

${this.getTitle()}

${this.getControls()}
${this.getDataTemplate()}
`; } getElements() { return { container: this.container.querySelector('.dashboard-component'), title: this.container.querySelector('.component-title'), content: this.container.querySelector('.component-content'), dataContainer: this.container.querySelector('.component-data'), loadingIndicator: this.container.querySelector('.loading-indicator'), errorMessage: this.container.querySelector('.error-message'), lastUpdated: this.container.querySelector('.last-updated') }; } bindEvents() { // Bind component-specific events this.bindCustomEvents(); // Bind refresh events this.bind('refresh', () => this.refresh()); // Bind resize events this.bind('resize', () => this.handleResize()); } bindCustomEvents() { // Override in subclasses } bind(event, handler) { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } this.eventListeners.get(event).push(handler); } emit(event, data) { if (this.eventListeners.has(event)) { this.eventListeners.get(event).forEach(handler => handler(data)); } } async refresh() { if (this.isLoading) return; this.setLoading(true); this.setError(null); try { const data = await this.fetchData(); this.updateData(data); this.setLastUpdated(new Date()); } catch (error) { this.setError(error.message); console.error('Data refresh error:', error); } finally { this.setLoading(false); } } async fetchData() { // Override in subclasses return {}; } updateData(data) { this.data = data; this.renderData(); this.emit('dataUpdated', data); } renderData() { if (!this.data) return; // Override in subclasses to implement specific rendering this.elements.dataContainer.innerHTML = this.formatData(this.data); } formatData(data) { return JSON.stringify(data, null, 2); } setLoading(isLoading) { this.isLoading = isLoading; if (this.elements.loadingIndicator) { this.elements.loadingIndicator.style.display = isLoading ? 'flex' : 'none'; } if (this.elements.dataContainer) { this.elements.dataContainer.style.display = isLoading ? 'none' : 'block'; } } setError(error) { this.error = error; if (this.elements.errorMessage) { this.elements.errorMessage.style.display = error ? 'block' : 'none'; this.elements.errorMessage.textContent = error || ''; } if (this.elements.dataContainer) { this.elements.dataContainer.style.display = error ? 'none' : 'block'; } } setLastUpdated(date) { if (this.elements.lastUpdated) { this.elements.lastUpdated.textContent = `Last updated: ${date.toLocaleTimeString()}`; } } startAutoUpdate() { this.stopAutoUpdate(); this.updateTimer = setInterval(() => { this.refresh(); }, this.config.updateInterval); } stopAutoUpdate() { if (this.updateTimer) { clearInterval(this.updateTimer); this.updateTimer = null; } } handleResize() { // Handle component resize this.emit('resized'); } destroy() { this.stopAutoUpdate(); this.eventListeners.clear(); if (this.container) { this.container.innerHTML = ''; } } } // Real-time metrics component class MetricsComponent extends DashboardComponent { constructor(container, config = {}) { super(container, { metrics: ['impressions', 'interactions', 'conversions'], timeRange: 300000, // 5 minutes ...config }); } getTitle() { return 'Real-time Metrics'; } getControls() { return ` `; } bindCustomEvents() { const timeRangeSelector = this.container.querySelector('.time-range-selector'); const refreshBtn = this.container.querySelector('.refresh-btn'); if (timeRangeSelector) { timeRangeSelector.addEventListener('change', (e) => { this.config.timeRange = parseInt(e.target.value); this.refresh(); }); } if (refreshBtn) { refreshBtn.addEventListener('click', () => this.refresh()); } } async fetchData() { const response = await fetch(`/api/analytics/metrics?timeRange=${this.config.timeRange}`); return response.json(); } getDataTemplate() { return `
${this.config.metrics.map(metric => `
${this.formatMetricLabel(metric)}
-
-
`).join('')}
`; } renderData() { if (!this.data) return; this.config.metrics.forEach(metric => { const metricCard = this.container.querySelector(`[data-metric="${metric}"]`); if (metricCard) { const value = this.data[metric] || 0; const previousValue = this.data[metric + '_previous'] || 0; const change = this.calculateChange(value, previousValue); metricCard.querySelector('.metric-value').textContent = this.formatMetricValue(metric, value); metricCard.querySelector('.metric-change').textContent = this.formatChange(change); metricCard.querySelector('.metric-change').className = `metric-change ${change >= 0 ? 'positive' : 'negative'}`; } }); } formatMetricLabel(metric) { const labels = { impressions: 'Impressions', interactions: 'Interactions', conversions: 'Conversions', revenue: 'Revenue', ctr: 'Click-through Rate' }; return labels[metric] || metric; } formatMetricValue(metric, value) { if (metric === 'ctr') { return `${value.toFixed(2)}%`; } if (metric === 'revenue') { return `$${value.toFixed(2)}`; } return value.toLocaleString(); } calculateChange(current, previous) { if (previous === 0) return current > 0 ? 100 : 0; return ((current - previous) / previous) * 100; } formatChange(change) { const sign = change >= 0 ? '+' : ''; return `${sign}${change.toFixed(1)}%`; } }

Real-time Chart Implementation

Create dynamic charts that update in real-time:

class RealtimeChartComponent extends DashboardComponent {
    constructor(container, config = {}) {
        super(container, {
            chartType: 'line',
            maxDataPoints: 50,
            updateInterval: 1000,
            datasets: [],
            ...config
        });

        this.chart = null;
        this.buffer = new Map();
    }

    init() {
        super.init();
        this.initChart();
        this.startDataStream();
    }

    initChart() {
        const canvas = this.container.querySelector('canvas');
        if (!canvas) return;

        const ctx = canvas.getContext('2d');
        this.chart = new Chart(ctx, {
            type: this.config.chartType,
            data: {
                labels: [],
                datasets: this.config.datasets.map(dataset => ({
                    ...dataset,
                    data: [],
                    borderWidth: 2,
                    tension: 0.4,
                    pointRadius: 1,
                    pointHoverRadius: 4
                }))
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                animation: {
                    duration: 0 // Disable animations for real-time updates
                },
                scales: {
                    x: {
                        type: 'time',
                        time: {
                            unit: 'second',
                            displayFormats: {
                                second: 'HH:mm:ss'
                            }
                        },
                        ticks: {
                            maxRotation: 0
                        }
                    },
                    y: {
                        beginAtZero: true
                    }
                },
                plugins: {
                    legend: {
                        display: this.config.datasets.length > 1
                    },
                    tooltip: {
                        mode: 'index',
                        intersect: false
                    }
                }
            }
        });
    }

    startDataStream() {
        // Subscribe to real-time data updates
        this.dataSubscription = analyticsStream.subscribe('popup_impression', (data) => {
            this.addDataPoint(data);
        });

        // Also start periodic data fetching
        this.dataInterval = setInterval(() => {
            this.fetchHistoricalData();
        }, this.config.updateInterval);
    }

    addDataPoint(data) {
        if (!this.chart) return;

        const timestamp = new Date(data.timestamp);

        // Add new data point to buffer
        this.config.datasets.forEach((dataset, index) => {
            if (!this.buffer.has(index)) {
                this.buffer.set(index, []);
            }

            const value = this.extractValue(data, dataset.dataKey);
            this.buffer.get(index).push({
                x: timestamp,
                y: value
            });
        });

        // Update chart with buffered data
        this.updateChart();
    }

    updateChart() {
        if (!this.chart) return;

        // Process buffered data
        this.buffer.forEach((buffer, datasetIndex) => {
            if (buffer.length > 0) {
                // Add buffered data to chart
                this.chart.data.datasets[datasetIndex].data.push(...buffer);

                // Remove old data points
                const maxPoints = this.config.maxDataPoints;
                if (this.chart.data.datasets[datasetIndex].data.length > maxPoints) {
                    this.chart.data.datasets[datasetIndex].data =
                        this.chart.data.datasets[datasetIndex].data.slice(-maxPoints);
                }

                // Clear buffer
                buffer.length = 0;
            }
        });

        // Update labels
        const allTimestamps = this.chart.data.datasets.flatMap(dataset => dataset.data);
        const uniqueTimestamps = [...new Set(allTimestamps.map(point => point.x))].sort();
        this.chart.data.labels = uniqueTimestamps;

        // Update chart
        this.chart.update('none'); // Update without animation
    }

    extractValue(data, dataKey) {
        if (typeof dataKey === 'function') {
            return dataKey(data);
        }

        const keys = dataKey.split('.');
        let value = data;

        for (const key of keys) {
            value = value?.[key];
            if (value === undefined) break;
        }

        return value || 0;
    }

    async fetchHistoricalData() {
        try {
            const endTime = Date.now();
            const startTime = endTime - (this.config.maxDataPoints * this.config.updateInterval);

            const response = await fetch(
                `/api/analytics/timeseries?startTime=${startTime}&endTime=${endTime}`
            );

            const data = await response.json();
            this.loadHistoricalData(data);
        } catch (error) {
            console.error('Historical data fetch error:', error);
        }
    }

    loadHistoricalData(data) {
        if (!this.chart || !data.points) return;

        // Load historical data into chart
        this.config.datasets.forEach((dataset, index) => {
            const historicalData = data.points.map(point => ({
                x: new Date(point.timestamp),
                y: this.extractValue(point, dataset.dataKey)
            }));

            this.chart.data.datasets[index].data = historicalData;
        });

        this.chart.update();
    }

    getTitle() {
        return 'Real-time Analytics';
    }

    getDataTemplate() {
        return `
            
`; } destroy() { super.destroy(); if (this.chart) { this.chart.destroy(); } if (this.dataSubscription) { analyticsStream.unsubscribe(this.dataSubscription); } if (this.dataInterval) { clearInterval(this.dataInterval); } } }

Data Visualization and Performance Monitoring

Advanced Visualization Techniques

Implement sophisticated data visualizations for popup analytics:

class PopupAnalyticsVisualization {
    constructor(container) {
        this.container = container;
        this.charts = new Map();
        this.colorScheme = {
            primary: '#3b82f6',
            secondary: '#10b981',
            tertiary: '#f59e0b',
            danger: '#ef4444',
            warning: '#f59e0b',
            success: '#10b981',
            info: '#3b82f6'
        };
        this.init();
    }

    init() {
        this.createLayout();
        this.initializeCharts();
        this.bindEvents();
    }

    createLayout() {
        this.container.innerHTML = `
            

Popup Analytics Dashboard

Total Impressions

0
+0%

Interactions

0
+0%

Conversion Rate

0%
+0%

Active Campaigns

0
+0%

Impression Trends

Conversion Funnel

Campaign Performance

Device Distribution

Live Activity Feed

`; } initializeCharts() { // Initialize impressions line chart this.initializeImpressionsChart(); // Initialize conversion funnel chart this.initializeFunnelChart(); // Initialize campaign performance chart this.initializeCampaignChart(); // Initialize device distribution chart this.initializeDeviceChart(); } initializeImpressionsChart() { const ctx = this.container.querySelector('#impressionsChart').getContext('2d'); this.charts.set('impressions', new Chart(ctx, { type: 'line', data: { labels: [], datasets: [{ label: 'Impressions', data: [], borderColor: this.colorScheme.primary, backgroundColor: this.colorScheme.primary + '20', fill: true, tension: 0.4 }, { label: 'Interactions', data: [], borderColor: this.colorScheme.secondary, backgroundColor: this.colorScheme.secondary + '20', fill: true, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } }, plugins: { legend: { position: 'top' } } } })); } initializeFunnelChart() { const ctx = this.container.querySelector('#funnelChart').getContext('2d'); this.charts.set('funnel', new Chart(ctx, { type: 'bar', data: { labels: ['Viewed', 'Interacted', 'Email Submitted', 'Converted'], datasets: [{ label: 'Conversion Funnel', data: [100, 45, 25, 12], backgroundColor: [ this.colorScheme.primary, this.colorScheme.secondary, this.colorScheme.tertiary, this.colorScheme.success ] }] }, options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y', plugins: { legend: { display: false } } } })); } initializeCampaignChart() { const ctx = this.container.querySelector('#campaignChart').getContext('2d'); this.charts.set('campaigns', new Chart(ctx, { type: 'radar', data: { labels: ['Impressions', 'CTR', 'Conversions', 'Revenue', 'Engagement'], datasets: [{ label: 'Campaign A', data: [85, 75, 80, 70, 90], borderColor: this.colorScheme.primary, backgroundColor: this.colorScheme.primary + '40' }, { label: 'Campaign B', data: [70, 85, 65, 80, 75], borderColor: this.colorScheme.secondary, backgroundColor: this.colorScheme.secondary + '40' }] }, options: { responsive: true, maintainAspectRatio: false, scales: { r: { beginAtZero: true, max: 100 } } } })); } initializeDeviceChart() { const ctx = this.container.querySelector('#deviceChart').getContext('2d'); this.charts.set('devices', new Chart(ctx, { type: 'doughnut', data: { labels: ['Desktop', 'Mobile', 'Tablet'], datasets: [{ data: [45, 40, 15], backgroundColor: [ this.colorScheme.primary, this.colorScheme.secondary, this.colorScheme.tertiary ] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } })); } updateMetrics(data) { // Update metric cards this.updateMetricCard('totalImpressions', data.totalImpressions, data.impressionsChange); this.updateMetricCard('totalInteractions', data.totalInteractions, data.interactionsChange); this.updateMetricCard('conversionRate', data.conversionRate + '%', data.conversionChange); this.updateMetricCard('activeCampaigns', data.activeCampaigns, data.campaignsChange); // Update charts this.updateImpressionsChart(data.impressionsData); this.updateFunnelChart(data.funnelData); this.updateCampaignChart(data.campaignData); this.updateDeviceChart(data.deviceData); // Update activity feed this.updateActivityFeed(data.recentActivity); } updateMetricCard(elementId, value, change) { const element = this.container.getElementById(elementId); const changeElement = this.container.getElementById(elementId.replace('total', '').toLowerCase() + 'Change'); if (element) { element.textContent = value; } if (changeElement) { changeElement.textContent = change >= 0 ? `+${change}%` : `${change}%`; changeElement.className = `metric-change ${change >= 0 ? 'positive' : 'negative'}`; } } updateImpressionsChart(data) { const chart = this.charts.get('impressions'); if (!chart || !data) return; chart.data.labels = data.timestamps; chart.data.datasets[0].data = data.impressions; chart.data.datasets[1].data = data.interactions; chart.update(); } updateActivityFeed(activities) { const feedContainer = this.container.querySelector('#activityList'); if (!feedContainer || !activities) return; feedContainer.innerHTML = activities.slice(0, 10).map(activity => `
${activity.title}
${new Date(activity.timestamp).toLocaleTimeString()}
`).join(''); } bindEvents() { // Time range selector const timeRangeSelector = this.container.querySelector('#timeRange'); if (timeRangeSelector) { timeRangeSelector.addEventListener('change', (e) => { this.loadTimeRangeData(e.target.value); }); } // Export button const exportButton = this.container.querySelector('#exportData'); if (exportButton) { exportButton.addEventListener('click', () => { this.exportAnalyticsData(); }); } } async loadTimeRangeData(timeRange) { try { const response = await fetch(`/api/analytics/dashboard?timeRange=${timeRange}`); const data = await response.json(); this.updateMetrics(data); } catch (error) { console.error('Error loading time range data:', error); } } exportAnalyticsData() { const exportData = this.prepareExportData(); const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `popup-analytics-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); } prepareExportData() { return { timestamp: new Date().toISOString(), metrics: { impressions: this.container.querySelector('#totalImpressions').textContent, interactions: this.container.querySelector('#totalInteractions').textContent, conversionRate: this.container.querySelector('#conversionRate').textContent, activeCampaigns: this.container.querySelector('#activeCampaigns').textContent }, charts: { impressions: this.charts.get('impressions')?.data, funnel: this.charts.get('funnel')?.data, campaigns: this.charts.get('campaigns')?.data, devices: this.charts.get('devices')?.data } }; } }

Performance Monitoring and Alerting

Implement comprehensive performance monitoring for popup systems:

class PopupPerformanceMonitor {
    constructor(config = {}) {
        this.config = {
            thresholds: {
                loadTime: { warning: 1000, critical: 2000 },
                interactionDelay: { warning: 100, critical: 300 },
                errorRate: { warning: 1, critical: 5 },
                conversionRate: { warning: 1, critical: 0.5 }
            },
            alertChannels: ['console', 'webhook'],
            monitoringInterval: 30000,
            ...config
        };

        this.metrics = new Map();
        this.alerts = [];
        this.isMonitoring = false;
        this.monitoringTimer = null;
        this.performanceObserver = null;

        this.init();
    }

    init() {
        this.setupPerformanceObserver();
        this.setupErrorTracking();
        this.startMonitoring();
    }

    setupPerformanceObserver() {
        if ('PerformanceObserver' in window) {
            this.performanceObserver = new PerformanceObserver((list) => {
                list.getEntries().forEach(entry => {
                    this.recordPerformanceMetric(entry);
                });
            });

            this.performanceObserver.observe({
                entryTypes: ['navigation', 'resource', 'measure', 'paint']
            });
        }
    }

    setupErrorTracking() {
        // Track JavaScript errors
        window.addEventListener('error', (event) => {
            this.recordError({
                type: 'javascript',
                message: event.message,
                filename: event.filename,
                lineno: event.lineno,
                colno: event.colno,
                timestamp: Date.now()
            });
        });

        // Track promise rejections
        window.addEventListener('unhandledrejection', (event) => {
            this.recordError({
                type: 'promise',
                message: event.reason?.message || 'Unhandled promise rejection',
                timestamp: Date.now()
            });
        });
    }

    startMonitoring() {
        if (this.isMonitoring) return;

        this.isMonitoring = true;
        this.monitoringTimer = setInterval(() => {
            this.checkPerformanceMetrics();
        }, this.config.monitoringInterval);

        console.log('Performance monitoring started');
    }

    stopMonitoring() {
        if (!this.isMonitoring) return;

        this.isMonitoring = false;
        if (this.monitoringTimer) {
            clearInterval(this.monitoringTimer);
            this.monitoringTimer = null;
        }

        console.log('Performance monitoring stopped');
    }

    recordPerformanceMetric(entry) {
        const metric = {
            name: entry.name,
            type: entry.entryType,
            value: entry.duration || entry.startTime,
            timestamp: entry.startTime || Date.now()
        };

        this.updateMetric(metric);
    }

    recordError(error) {
        const errorMetric = {
            type: 'error',
            ...error,
            timestamp: error.timestamp
        };

        this.updateMetric(errorMetric);
        this.checkErrorThresholds(errorMetric);
    }

    recordPopupMetric(popupId, metricType, value, metadata = {}) {
        const metric = {
            popupId,
            type: 'popup',
            metricType,
            value,
            metadata,
            timestamp: Date.now()
        };

        this.updateMetric(metric);
        this.checkPopupThresholds(metric);
    }

    updateMetric(metric) {
        const key = this.getMetricKey(metric);

        if (!this.metrics.has(key)) {
            this.metrics.set(key, {
                count: 0,
                sum: 0,
                values: [],
                min: Infinity,
                max: -Infinity,
                lastUpdate: 0
            });
        }

        const data = this.metrics.get(key);
        data.count++;
        data.sum += metric.value;
        data.values.push({
            value: metric.value,
            timestamp: metric.timestamp
        });
        data.min = Math.min(data.min, metric.value);
        data.max = Math.max(data.max, metric.value);
        data.lastUpdate = metric.timestamp;

        // Keep only last 1000 values to prevent memory issues
        if (data.values.length > 1000) {
            data.values = data.values.slice(-1000);
        }
    }

    getMetricKey(metric) {
        if (metric.type === 'popup') {
            return `popup_${metric.popupId}_${metric.metricType}`;
        }
        return `${metric.type}_${metric.name}`;
    }

    checkPerformanceMetrics() {
        this.metrics.forEach((data, key) => {
            const average = data.sum / data.count;

            // Check load time metrics
            if (key.includes('loadTime')) {
                this.checkThreshold('loadTime', average, key);
            }

            // Check interaction delay metrics
            if (key.includes('interactionDelay')) {
                this.checkThreshold('interactionDelay', average, key);
            }

            // Check error rate metrics
            if (key.includes('error')) {
                this.checkErrorRate(data, key);
            }

            // Check conversion rate metrics
            if (key.includes('conversionRate')) {
                this.checkThreshold('conversionRate', average, key, true);
            }
        });
    }

    checkThreshold(thresholdType, value, metricKey, isPercentage = false) {
        const threshold = this.config.thresholds[thresholdType];
        if (!threshold) return;

        const normalizedValue = isPercentage ? value : value;

        if (normalizedValue >= threshold.critical) {
            this.createAlert('critical', thresholdType, metricKey, normalizedValue, threshold);
        } else if (normalizedValue >= threshold.warning) {
            this.createAlert('warning', thresholdType, metricKey, normalizedValue, threshold);
        }
    }

    checkErrorThresholds(error) {
        // Calculate recent error rate
        const recentErrors = this.getRecentErrors(300000); // Last 5 minutes
        const errorRate = recentErrors.length;

        this.checkThreshold('errorRate', errorRate, 'recent_errors');
    }

    checkErrorRate(data, key) {
        const now = Date.now();
        const recentErrors = data.values.filter(v => now - v.timestamp < 300000); // Last 5 minutes
        const errorRate = recentErrors.length;

        this.checkThreshold('errorRate', errorRate, key);
    }

    checkPopupThresholds(metric) {
        const popupThresholds = {
            interactionDelay: { warning: 200, critical: 500 },
            conversionRate: { warning: 1, critical: 0.5 },
            bounceRate: { warning: 70, critical: 85 }
        };

        const threshold = popupThresholds[metric.metricType];
        if (!threshold) return;

        this.checkThresholdCustom(metric.metricType, metric.value, threshold, metric.popupId);
    }

    checkThresholdCustom(thresholdType, value, threshold, context) {
        if (value >= threshold.critical) {
            this.createAlert('critical', thresholdType, context, value, threshold);
        } else if (value >= threshold.warning) {
            this.createAlert('warning', thresholdType, context, value, threshold);
        }
    }

    createAlert(severity, thresholdType, context, value, threshold) {
        const alert = {
            id: this.generateAlertId(),
            severity,
            thresholdType,
            context,
            value,
            threshold: severity === 'critical' ? threshold.critical : threshold.warning,
            timestamp: Date.now(),
            message: this.formatAlertMessage(severity, thresholdType, context, value, threshold)
        };

        this.alerts.push(alert);
        this.sendAlert(alert);

        // Keep only last 100 alerts
        if (this.alerts.length > 100) {
            this.alerts = this.alerts.slice(-100);
        }
    }

    formatAlertMessage(severity, thresholdType, context, value, threshold) {
        const severityText = severity.toUpperCase();
        const unit = this.getThresholdUnit(thresholdType);

        return `${severityText}: ${thresholdType} for ${context} is ${value}${unit} (threshold: ${threshold}${unit})`;
    }

    getThresholdUnit(thresholdType) {
        const units = {
            loadTime: 'ms',
            interactionDelay: 'ms',
            errorRate: ' errors/min',
            conversionRate: '%',
            bounceRate: '%'
        };

        return units[thresholdType] || '';
    }

    sendAlert(alert) {
        this.config.alertChannels.forEach(channel => {
            switch (channel) {
                case 'console':
                    console.warn(`[POPUP ALERT] ${alert.message}`);
                    break;
                case 'webhook':
                    this.sendWebhookAlert(alert);
                    break;
                case 'email':
                    this.sendEmailAlert(alert);
                    break;
                case 'slack':
                    this.sendSlackAlert(alert);
                    break;
            }
        });
    }

    async sendWebhookAlert(alert) {
        try {
            await fetch('/api/alerts/webhook', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(alert)
            });
        } catch (error) {
            console.error('Webhook alert failed:', error);
        }
    }

    getRecentErrors(timeWindow = 300000) {
        const now = Date.now();
        const recentErrors = [];

        this.metrics.forEach((data, key) => {
            if (key.includes('error')) {
                data.values.forEach(error => {
                    if (now - error.timestamp < timeWindow) {
                        recentErrors.push(error);
                    }
                });
            }
        });

        return recentErrors;
    }

    generateAlertId() {
        return `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    }

    getMetrics() {
        const result = {};

        this.metrics.forEach((data, key) => {
            result[key] = {
                count: data.count,
                average: data.sum / data.count,
                min: data.min,
                max: data.max,
                lastUpdate: data.lastUpdate
            };
        });

        return result;
    }

    getAlerts(severity = null) {
        if (!severity) {
            return this.alerts;
        }

        return this.alerts.filter(alert => alert.severity === severity);
    }

    clearAlerts() {
        this.alerts = [];
    }

    exportMetrics() {
        return {
            timestamp: new Date().toISOString(),
            metrics: this.getMetrics(),
            alerts: this.alerts,
            config: this.config
        };
    }
}

Cross-Device Analytics and Data Synchronization

Multi-Device Tracking Implementation

Implement comprehensive cross-device analytics for popup systems:

class CrossDeviceAnalytics {
    constructor(config = {}) {
        this.config = {
            userIdCookieName: 'popup_user_id',
            sessionIdCookieName: 'popup_session_id',
            trackingServer: '/api/analytics',
            syncInterval: 60000,
            maxRetries: 3,
            ...config
        };

        this.userId = null;
        this.sessionId = null;
        this.deviceId = this.generateDeviceId();
        this.eventQueue = [];
        this.syncTimer = null;
        this.isOnline = navigator.onLine;

        this.init();
    }

    init() {
        this.identifyUser();
        this.startSession();
        this.setupEventListeners();
        this.startSyncTimer();
        this.trackInitialEvents();
    }

    generateDeviceId() {
        // Generate a unique device identifier
        const fingerprint = this.generateDeviceFingerprint();
        return btoa(fingerprint).replace(/[^a-zA-Z0-9]/g, '').substr(0, 16);
    }

    generateDeviceFingerprint() {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        ctx.textBaseline = 'top';
        ctx.font = '14px Arial';
        ctx.fillText('Device fingerprint', 2, 2);

        const fingerprint = [
            navigator.userAgent,
            navigator.language,
            screen.width + 'x' + screen.height,
            new Date().getTimezoneOffset(),
            canvas.toDataURL(),
            navigator.hardwareConcurrency || 'unknown',
            navigator.deviceMemory || 'unknown'
        ].join('|');

        return fingerprint;
    }

    identifyUser() {
        // Try to get existing user ID from cookie
        this.userId = this.getCookie(this.config.userIdCookieName);

        if (!this.userId) {
            // Generate new user ID
            this.userId = this.generateUserId();
            this.setCookie(this.config.userIdCookieName, this.userId, 365);
        }
    }

    generateUserId() {
        return 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
    }

    startSession() {
        this.sessionId = this.generateSessionId();
        this.setCookie(this.config.sessionIdCookieName, this.sessionId, 0.25); // 6 hours

        // Track session start
        this.track('session_start', {
            sessionId: this.sessionId,
            userId: this.userId,
            deviceId: this.deviceId
        });
    }

    generateSessionId() {
        return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
    }

    setupEventListeners() {
        // Track page visibility changes
        document.addEventListener('visibilitychange', () => {
            if (document.hidden) {
                this.track('page_hidden');
            } else {
                this.track('page_visible');
            }
        });

        // Track online/offline status
        window.addEventListener('online', () => {
            this.isOnline = true;
            this.track('connection_restored');
            this.syncEvents();
        });

        window.addEventListener('offline', () => {
            this.isOnline = false;
            this.track('connection_lost');
        });

        // Track page unload
        window.addEventListener('beforeunload', () => {
            this.track('page_unload');
            this.syncEvents(true); // Sync on page unload
        });

        // Track popup events
        this.setupPopupTracking();
    }

    setupPopupTracking() {
        // Listen for popup events
        document.addEventListener('popup:shown', (event) => {
            this.track('popup_shown', {
                popupId: event.detail.popupId,
                campaignId: event.detail.campaignId,
                triggerType: event.detail.triggerType,
                position: event.detail.position
            });
        });

        document.addEventListener('popup:hidden', (event) => {
            this.track('popup_hidden', {
                popupId: event.detail.popupId,
                campaignId: event.detail.campaignId,
                displayDuration: event.detail.displayDuration,
                userAction: event.detail.userAction
            });
        });

        document.addEventListener('popup:interaction', (event) => {
            this.track('popup_interaction', {
                popupId: event.detail.popupId,
                campaignId: event.detail.campaignId,
                interactionType: event.detail.interactionType,
                targetElement: event.detail.targetElement
            });
        });

        document.addEventListener('popup:conversion', (event) => {
            this.track('popup_conversion', {
                popupId: event.detail.popupId,
                campaignId: event.detail.campaignId,
                conversionType: event.detail.conversionType,
                conversionValue: event.detail.conversionValue
            });
        });
    }

    track(eventType, properties = {}) {
        const event = {
            eventType,
            properties: {
                ...properties,
                userId: this.userId,
                sessionId: this.sessionId,
                deviceId: this.deviceId,
                timestamp: Date.now(),
                url: window.location.href,
                userAgent: navigator.userAgent,
                referrer: document.referrer,
                screenResolution: screen.width + 'x' + screen.height,
                viewportSize: window.innerWidth + 'x' + window.innerHeight
            }
        };

        this.eventQueue.push(event);

        // Limit queue size to prevent memory issues
        if (this.eventQueue.length > 1000) {
            this.eventQueue = this.eventQueue.slice(-500);
        }

        // Sync immediately for important events
        if (this.isImportantEvent(eventType)) {
            this.syncEvents();
        }
    }

    isImportantEvent(eventType) {
        const importantEvents = ['popup_conversion', 'session_start', 'page_unload'];
        return importantEvents.includes(eventType);
    }

    trackInitialEvents() {
        this.track('page_view', {
            title: document.title,
            path: window.location.pathname
        });

        this.track('device_info', {
            deviceType: this.getDeviceType(),
            browser: this.getBrowserInfo(),
            os: this.getOSInfo(),
            connectionType: this.getConnectionType()
        });
    }

    getDeviceType() {
        const width = window.innerWidth;
        if (width < 768) return 'mobile';
        if (width < 1024) return 'tablet';
        return 'desktop';
    }

    getBrowserInfo() {
        const ua = navigator.userAgent;
        if (ua.includes('Chrome')) return 'Chrome';
        if (ua.includes('Firefox')) return 'Firefox';
        if (ua.includes('Safari')) return 'Safari';
        if (ua.includes('Edge')) return 'Edge';
        return 'Unknown';
    }

    getOSInfo() {
        const ua = navigator.userAgent;
        if (ua.includes('Windows')) return 'Windows';
        if (ua.includes('Mac')) return 'macOS';
        if (ua.includes('Linux')) return 'Linux';
        if (ua.includes('Android')) return 'Android';
        if (ua.includes('iOS')) return 'iOS';
        return 'Unknown';
    }

    getConnectionType() {
        const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
        return connection ? connection.effectiveType : 'unknown';
    }

    startSyncTimer() {
        this.syncTimer = setInterval(() => {
            if (this.isOnline && this.eventQueue.length > 0) {
                this.syncEvents();
            }
        }, this.config.syncInterval);
    }

    async syncEvents(isSyncingOnUnload = false) {
        if (!this.isOnline || this.eventQueue.length === 0) {
            return;
        }

        const eventsToSync = this.eventQueue.splice(0, 100); // Sync max 100 events at once
        const eventsBackup = [...eventsToSync]; // Keep backup in case sync fails

        try {
            const response = await fetch(this.config.trackingServer, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-User-ID': this.userId,
                    'X-Session-ID': this.sessionId,
                    'X-Device-ID': this.deviceId
                },
                body: JSON.stringify({
                    events: eventsToSync,
                    deviceInfo: {
                        deviceType: this.getDeviceType(),
                        browser: this.getBrowserInfo(),
                        os: this.getOSInfo()
                    }
                }),
                // Use keepalive for sync on page unload
                keepalive: isSyncingOnUnload
            });

            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }

            const result = await response.json();

            // Handle server response
            if (result.userId && result.userId !== this.userId) {
                this.userId = result.userId;
                this.setCookie(this.config.userIdCookieName, this.userId, 365);
            }

            console.log(`Synced ${eventsToSync.length} events successfully`);

        } catch (error) {
            console.error('Event sync failed:', error);

            // Put events back in queue for retry
            this.eventQueue.unshift(...eventsBackup);

            // Limit queue size
            if (this.eventQueue.length > 1000) {
                this.eventQueue = this.eventQueue.slice(-1000);
            }
        }
    }

    identify(newUserId, traits = {}) {
        this.track('user_identify', {
            newUserId,
            traits,
            previousUserId: this.userId
        });

        this.userId = newUserId;
        this.setCookie(this.config.userIdCookieName, this.userId, 365);

        // Sync immediately
        this.syncEvents();
    }

    alias(aliasId) {
        this.track('user_alias', {
            aliasId,
            originalUserId: this.userId
        });
    }

    getCookie(name) {
        const value = ` ; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) {
            return parts.pop().split(';').shift();
        }
        return null;
    }

    setCookie(name, value, days) {
        const expires = new Date();
        expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
        document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax`;
    }

    getSessionData() {
        return {
            userId: this.userId,
            sessionId: this.sessionId,
            deviceId: this.deviceId,
            eventQueueLength: this.eventQueue.length,
            isOnline: this.isOnline
        };
    }
}

Dashboard Security and Access Control

Authentication and Authorization

Implement robust security measures for analytics dashboards:

class DashboardSecurity {
    constructor(config = {}) {
        this.config = {
            authEndpoint: '/api/auth',
            refreshTokenEndpoint: '/api/auth/refresh',
            permissionsEndpoint: '/api/permissions',
            sessionTimeout: 3600000, // 1 hour
            maxFailedAttempts: 5,
            lockoutDuration: 900000, // 15 minutes
            ...config
        };

        this.currentUser = null;
        this.permissions = new Set();
        this.sessionTimer = null;
        this.failedAttempts = 0;
        this.isLocked = false;
        this.refreshTokenTimer = null;

        this.init();
    }

    init() {
        this.checkExistingSession();
        this.setupActivityMonitoring();
        this.setupTokenRefresh();
    }

    async login(credentials) {
        if (this.isLocked) {
            throw new Error('Account temporarily locked due to multiple failed attempts');
        }

        try {
            const response = await fetch(this.config.authEndpoint, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(credentials)
            });

            if (!response.ok) {
                if (response.status === 401) {
                    this.handleFailedLogin();
                }
                throw new Error('Authentication failed');
            }

            const data = await response.json();
            this.handleLoginSuccess(data);

            return data;

        } catch (error) {
            this.handleFailedLogin();
            throw error;
        }
    }

    handleLoginSuccess(authData) {
        this.currentUser = authData.user;
        this.permissions = new Set(authData.permissions);
        this.failedAttempts = 0;
        this.isLocked = false;

        // Store tokens
        localStorage.setItem('auth_token', authData.accessToken);
        localStorage.setItem('refresh_token', authData.refreshToken);

        // Start session timer
        this.startSessionTimer();

        // Setup token refresh
        this.setupTokenRefresh();

        // Emit login success event
        this.emit('loginSuccess', this.currentUser);
    }

    handleFailedLogin() {
        this.failedAttempts++;

        if (this.failedAttempts >= this.config.maxFailedAttempts) {
            this.lockAccount();
        }

        this.emit('loginFailed', {
            attempts: this.failedAttempts,
            remainingAttempts: this.config.maxFailedAttempts - this.failedAttempts
        });
    }

    lockAccount() {
        this.isLocked = true;
        this.emit('accountLocked', {
            lockoutDuration: this.config.lockoutDuration
        });

        setTimeout(() => {
            this.isLocked = false;
            this.failedAttempts = 0;
            this.emit('accountUnlocked');
        }, this.config.lockoutDuration);
    }

    async logout() {
        try {
            // Call logout endpoint to invalidate tokens
            await fetch(this.config.authEndpoint, {
                method: 'DELETE',
                headers: {
                    'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
                }
            });
        } catch (error) {
            console.error('Logout API call failed:', error);
        }

        // Clear local session data
        this.clearSession();

        this.emit('logout');
    }

    clearSession() {
        this.currentUser = null;
        this.permissions.clear();

        // Clear timers
        if (this.sessionTimer) {
            clearTimeout(this.sessionTimer);
            this.sessionTimer = null;
        }

        if (this.refreshTokenTimer) {
            clearTimeout(this.refreshTokenTimer);
            this.refreshTokenTimer = null;
        }

        // Clear storage
        localStorage.removeItem('auth_token');
        localStorage.removeItem('refresh_token');
        localStorage.removeItem('user_data');
    }

    async checkExistingSession() {
        const token = localStorage.getItem('auth_token');
        const userData = localStorage.getItem('user_data');

        if (!token || !userData) {
            return false;
        }

        try {
            const user = JSON.parse(userData);

            // Validate token with server
            const response = await fetch(`${this.config.authEndpoint}/validate`, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${token}`
                }
            });

            if (response.ok) {
                const data = await response.json();
                this.handleLoginSuccess({
                    user: data.user,
                    permissions: data.permissions,
                    accessToken: token,
                    refreshToken: localStorage.getItem('refresh_token')
                });

                return true;
            } else {
                this.clearSession();
                return false;
            }
        } catch (error) {
            console.error('Session validation failed:', error);
            this.clearSession();
            return false;
        }
    }

    hasPermission(permission) {
        if (!this.currentUser) {
            return false;
        }

        return this.permissions.has(permission) || this.permissions.has('admin');
    }

    hasRole(role) {
        if (!this.currentUser) {
            return false;
        }

        return this.currentUser.roles?.includes(role) || this.hasPermission('admin');
    }

    requireAuth(permission = null) {
        return (target, propertyKey, descriptor) => {
            const originalMethod = descriptor.value;

            descriptor.value = function(...args) {
                if (!this.security?.currentUser) {
                    throw new Error('Authentication required');
                }

                if (permission && !this.security.hasPermission(permission)) {
                    throw new Error(`Permission required: ${permission}`);
                }

                return originalMethod.apply(this, args);
            };

            return descriptor;
        };
    }

    setupActivityMonitoring() {
        let activityTimer;

        const resetActivityTimer = () => {
            clearTimeout(activityTimer);

            activityTimer = setTimeout(() => {
                this.emit('sessionWarning');

                // Give user 5 minutes to respond
                setTimeout(() => {
                    if (this.currentUser) {
                        this.logout();
                    }
                }, 300000);
            }, this.config.sessionTimeout - 300000);
        };

        // Track user activity
        ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'].forEach(event => {
            document.addEventListener(event, resetActivityTimer, true);
        });

        // Initial timer setup
        resetActivityTimer();
    }

    setupTokenRefresh() {
        const token = localStorage.getItem('auth_token');
        if (!token) return;

        try {
            // Parse JWT token to get expiration time
            const payload = JSON.parse(atob(token.split('.')[1]));
            const expirationTime = payload.exp * 1000;
            const refreshTime = expirationTime - (5 * 60 * 1000); // Refresh 5 minutes before expiration

            const timeUntilRefresh = refreshTime - Date.now();

            if (timeUntilRefresh > 0) {
                this.refreshTokenTimer = setTimeout(() => {
                    this.refreshAccessToken();
                }, timeUntilRefresh);
            } else {
                // Token is about to expire, refresh immediately
                this.refreshAccessToken();
            }
        } catch (error) {
            console.error('Token parsing error:', error);
        }
    }

    async refreshAccessToken() {
        const refreshToken = localStorage.getItem('refresh_token');
        if (!refreshToken) {
            this.logout();
            return;
        }

        try {
            const response = await fetch(this.config.refreshTokenEndpoint, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ refreshToken })
            });

            if (!response.ok) {
                throw new Error('Token refresh failed');
            }

            const data = await response.json();

            // Update tokens
            localStorage.setItem('auth_token', data.accessToken);
            if (data.refreshToken) {
                localStorage.setItem('refresh_token', data.refreshToken);
            }

            // Setup next refresh
            this.setupTokenRefresh();

            this.emit('tokenRefreshed');

        } catch (error) {
            console.error('Token refresh failed:', error);
            this.logout();
        }
    }

    startSessionTimer() {
        if (this.sessionTimer) {
            clearTimeout(this.sessionTimer);
        }

        this.sessionTimer = setTimeout(() => {
            this.emit('sessionTimeout');
            this.logout();
        }, this.config.sessionTimeout);
    }

    emit(event, data) {
        const customEvent = new CustomEvent(`dashboard:${event}`, {
            detail: data
        });
        document.dispatchEvent(customEvent);
    }

    on(event, handler) {
        document.addEventListener(`dashboard:${event}`, handler);
    }

    off(event, handler) {
        document.removeEventListener(`dashboard:${event}`, handler);
    }
}

Conclusion and Best Practices

Building real-time analytics dashboards for popup systems requires careful attention to data streaming architecture, UI performance, and security considerations. The implementation covered in this guide demonstrates how to create comprehensive analytics systems that can handle high-volume data streams while providing meaningful insights through interactive visualizations.

Key success factors include implementing efficient WebSocket connections for real-time data streaming, designing modular dashboard components that can update dynamically, choosing appropriate data visualization libraries for different metric types, implementing robust performance monitoring systems, and ensuring proper security measures for sensitive analytics data.

Remember that real-time analytics systems should be designed with scalability in mind. As your popup system grows and data volumes increase, the architecture should support horizontal scaling, efficient data aggregation, and optimal query performance. Regular monitoring and optimization of system performance will help maintain a responsive and reliable analytics experience for your users.

Compliance Notice: This technical guide is for educational purposes only and does not guarantee specific results. Real-time analytics implementations should be tailored to your specific business requirements, compliance obligations, and technical constraints. Always consult with analytics professionals and legal experts when implementing data collection and visualization systems that handle user information.

TAGS

real-time analyticsdashboard developmentwebsocketdata visualizationperformance monitoringcross-device trackinganalytics securitydashboard uimetrics calculationpopup systems
A

Alex Chen

Real-time Systems Engineer at Nudgesmart

Never Miss an Update

Get the latest conversion optimization tips and strategies delivered straight to your inbox.

Join 5,000+ subscribers. Unsubscribe anytime.