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.
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.