Real-time Analytics - Phase 5
Live analytics dashboard with Server-Sent Events (SSE) for real-time visitor tracking.
π― What's Implementedβ
β Real-time Featuresβ
- Live Visitor Counter - See current visitors in real-time
- Live Activity Feed - Watch pageviews as they happen
- Active Pages - Which pages are being viewed right now
- Connection Status - Visual indicator of live connection
- Event Highlighting - New events flash briefly
ποΈ Architectureβ
Technology Choice: SSE vs WebSocketsβ
We chose Server-Sent Events (SSE) over WebSockets because:
β Simpler - One-way server-to-client streaming β Auto-reconnect - Built-in reconnection handling β HTTP-based - Works through proxies and firewalls β Efficient - Lower overhead for our use case β Browser native - No external libraries needed
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β Browser β β Backend β β Admin UI β
β (Tracking) β β (MedusaJS) β β (Live View)β
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β β β
β POST /track β β
βββββββββββββββββββββββββ>β β
β β β
β β Emit Event β
β βββββββββ>β β
β β β β
β β Subscriber β
β β Catches β
β β β β
β β β SSE Stream β
β β βββββββββββββββ>β
β β β β
β β β Update UI β
β β β β
π File Structureβ
src/
βββ workflows/analytics/
β βββ track-analytics-event.ts β
Emits events
βββ subscribers/
β βββ analytics-realtime.ts β
Catches events, broadcasts
βββ api/admin/analytics/live/
β βββ route.ts β
SSE endpoint
βββ admin/
βββ routes/websites/[id]/live/
β βββ page.tsx β
Live page route
βββ components/websites/
βββ live-analytics-panel.tsx β
Live dashboard
βββ website-general-section.tsx β
Added "Live" button
π§ How It Worksβ
1. Event Emission (Workflow)β
When a tracking event is created, it emits an event:
// src/workflows/analytics/track-analytics-event.ts
const eventBus = container.resolve(Modules.EVENT_BUS);
await eventBus.emit({
name: "analytics_event.created",
data: event,
});
2. Event Subscription (Subscriber)β
The subscriber catches the event and broadcasts to connected clients:
// src/subscribers/analytics-realtime.ts
export default async function analyticsRealtimeSubscriber({ event }: any) {
const { data } = event;
// Get all SSE connections for this website
const connections = analyticsConnections.get(data.website_id);
// Broadcast to all connected clients
connections.forEach((res) => {
res.write(`data: ${JSON.stringify({ type: 'new_event', data })}\n\n`);
});
}
export const config: SubscriberConfig = {
event: "analytics_event.created",
};
3. SSE Endpoint (API)β
Clients connect to the SSE endpoint:
// src/api/admin/analytics/live/route.ts
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const { website_id } = req.query;
// Set SSE headers
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// Add to connection pool
analyticsConnections.get(website_id).add(res);
// Send initial stats
res.write(`data: ${JSON.stringify({ type: 'connected', data: stats })}\n\n`);
// Heartbeat every 30 seconds
const heartbeat = setInterval(() => {
res.write(`: heartbeat\n\n`);
}, 30000);
// Cleanup on disconnect
req.on("close", () => {
clearInterval(heartbeat);
connections.delete(res);
});
};
4. React Component (Admin UI)β
The React component connects and displays live data:
// src/admin/components/websites/live-analytics-panel.tsx
useEffect(() => {
const eventSource = new EventSource(
`http://localhost:9000/admin/analytics/live?website_id=${websiteId}`
);
eventSource.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === "new_event") {
setLiveData(prev => ({
...prev,
recentEvents: [message.data, ...prev.recentEvents].slice(0, 20)
}));
}
};
return () => eventSource.close();
}, [websiteId]);
π Usageβ
Access Live Analyticsβ
- Navigate to website in admin panel
- Click "Live Analytics" in action menu (β‘ icon)
- Watch real-time data stream in
What You'll Seeβ
Live Visitor Counterβ
βββββββββββββββββββββββββββββββ
β β Live β
β β
β Current Visitors β
β 5 β
β 3 unique β
βββββββββββββββββββββββββββββββ
Active Pagesβ
βββββββββββββββββββββββββββββββ
β Active Pages β
βββββββββββββββββββββββββββββββ€
β /products 3 viewing β
β / 2 viewing β
β /about 1 viewing β
βββββββββββββββββββββββββββββββ
Live Activity Feedβ
βββββββββββββββββββββββββββββββ
β Live Activity β
βββββββββββββββββββββββββββββββ€
β [pageview] /products β
β from google β’ desktop β
β 10:45:23 AM β
βββββββββββββββββββββββββββββββ€
β [pageview] / β
β direct β’ mobile β
β 10:45:20 AM β
βββββββββββββββββββββββββββββββ
π¨ UI Featuresβ
Connection Status Indicatorβ
<span className={`w-3 h-3 rounded-full ${
isConnected ? "bg-green-500 animate-pulse" : "bg-red-500"
}`} />
- π’ Green pulsing = Connected
- π΄ Red = Disconnected
Event Highlightingβ
New events flash blue for 2 seconds:
const [isNew, setIsNew] = useState(true);
useEffect(() => {
const timer = setTimeout(() => setIsNew(false), 2000);
return () => clearTimeout(timer);
}, []);
<div className={isNew ? "bg-blue-50" : ""}>
{/* Event content */}
</div>
Auto-scrollβ
Activity feed auto-scrolls to show latest events:
<div className="max-h-96 overflow-y-auto">
{recentEvents.map(event => <LiveEventRow event={event} />)}
</div>
π Data Flowβ
Initial Connectionβ
1. User opens Live Analytics page
2. EventSource connects to /admin/analytics/live?website_id=...
3. Server sends initial stats:
{
type: "connected",
data: {
currentVisitors: 5,
uniqueVisitors: 3,
recentEvents: [...],
activePages: [...]
}
}
4. UI displays initial data
Real-time Updatesβ
1. Visitor views page on website
2. POST /web/analytics/track (includes visitor_id, session_id)
3. Workflow creates event
4. Workflow emits "analytics_event.created"
5. Subscriber catches event
6. Subscriber broadcasts to SSE connections
7. Admin UI receives event
8. UI recalculates unique visitors:
- Extracts visitor_id from all recent events
- Counts unique visitor_ids (not event count!)
- Updates active pages from latest event per visitor
9. UI displays accurate count
Visitor Tracking Logicβ
// Backend: Count unique visitors from recent events
const recentEvents = getEventsFromLast5Minutes();
const uniqueVisitors = new Set(recentEvents.map(e => e.visitor_id));
const currentVisitors = uniqueVisitors.size; // β
Correct count
// Frontend: Same logic on each new event
const uniqueVisitors = new Set(
recentEvents.map(e => e.visitor_id).filter(Boolean)
);
Active Page Trackingβ
// Get the most recent page for each visitor
const visitorCurrentPages = new Map<string, string>();
for (const event of sortedEventsByTime) {
if (!visitorCurrentPages.has(event.visitor_id)) {
visitorCurrentPages.set(event.visitor_id, event.pathname);
}
}
// Now count visitors per page
// If visitor navigates: /home β /products
// They only count once on /products (latest page)
Heartbeatβ
Every 30 seconds:
Server sends: ": heartbeat\n\n"
Keeps connection alive
Prevents timeout
π Securityβ
Authenticationβ
The SSE endpoint is under /admin/analytics/live, which requires:
- β Admin authentication
- β Valid session
- β Proper permissions
Data Privacyβ
Only sends necessary data:
- β No PII (names, emails)
- β No IP addresses
- β Anonymous visitor IDs
- β Page paths only
- β Aggregated stats
β‘ Performanceβ
Connection Managementβ
// In-memory connection pool
const analyticsConnections = new Map<string, Set<any>>();
// Automatic cleanup on disconnect
req.on("close", () => {
connections.delete(res);
if (connections.size === 0) {
analyticsConnections.delete(website_id);
}
});
Memory Usageβ
Per connection: ~1-2 KB
100 connections: ~100-200 KB
1000 connections: ~1-2 MB
Very lightweight! β
Network Usageβ
Initial connection: ~5 KB
Per event: ~500 bytes
Heartbeat: ~10 bytes
Minimal bandwidth! β
π Troubleshootingβ
Connection Failsβ
Symptom: Red dot, "Disconnected"
Solutions:
# 1. Check backend is running
curl http://localhost:9000/admin/analytics/live?website_id=xxx
# 2. Check CORS settings
# Add to .env:
WEB_CORS=http://localhost:7001
# 3. Check browser console
# Look for EventSource errors
No Events Showingβ
Symptom: Connected but no activity
Solutions:
# 1. Verify tracking is working
# Check browser console on website
# Should see: [Analytics] Initialized
# 2. Check event emission
# Add logging to workflow:
console.log("[Analytics] Event created:", event);
# 3. Check subscriber
# Add logging to subscriber:
console.log("[Analytics] Broadcasting to", connections.size, "clients");
Events Delayedβ
Symptom: Events show up late
Solutions:
// 1. Check subscriber is registered
// File must be in src/subscribers/
// 2. Check event name matches
// Workflow: "analytics_event.created"
// Subscriber: "analytics_event.created"
// 3. Reduce heartbeat interval
const heartbeat = setInterval(() => {
res.write(`: heartbeat\n\n`);
}, 10000); // 10 seconds instead of 30
π― Next Stepsβ
Enhancementsβ
-
Visitor Map
// Show visitors on world map
// Requires GeoIP lookup -
Real-time Charts
// Live updating line charts
// Show traffic trends -
Alerts
// Alert on traffic spikes
// Alert on errors -
Session Replay
// Record and replay user sessions
// See exactly what users do
π Monitoringβ
Key Metricsβ
-
Active Connections
console.log("Active connections:", analyticsConnections.size); -
Events Broadcasted
let eventCount = 0;
// Increment on each broadcast -
Connection Duration
const connectionStart = Date.now();
req.on("close", () => {
const duration = Date.now() - connectionStart;
console.log("Connection lasted:", duration, "ms");
});
β Testingβ
Manual Testβ
-
Open Live Analytics
http://localhost:9000/app/websites/01JM1PEW9H0ES7GGMD173GM2T9/live -
Open Website in Another Tab
http://localhost:3000 -
Navigate Around
- Click links
- View different pages
- Watch Live Analytics update!
Automated Testβ
// Test SSE connection
const eventSource = new EventSource(
"http://localhost:9000/admin/analytics/live?website_id=xxx"
);
eventSource.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log("Received:", message);
};
// Trigger tracking event
await fetch("http://localhost:9000/web/analytics/track", {
method: "POST",
body: JSON.stringify({
website_id: "xxx",
event_type: "pageview",
pathname: "/test",
// ...
})
});
// Should see event in SSE stream!
π Summaryβ
You now have real-time analytics!
What Works:β
β Live visitor counter β Real-time activity feed β Active pages tracking β Auto-reconnection β Event highlighting β Connection status β Minimal overhead
How to Use:β
- Click "Live Analytics" button
- Watch visitors in real-time
- See events as they happen
- Monitor active pages
Your analytics system is now truly live! ππβ‘