@vigneshwaranbs/activity-tracker
v2.1.6
Published
Universal user activity tracking library
Maintainers
Readme
🎯 Activity Tracker
Universal user activity tracking library for web applications
Track user activity across your web applications with automatic inactivity detection, cross-tab synchronization, and real-time heartbeat monitoring.
✨ Features
Core Features
- 🖱️ Activity Detection - Tracks mouse, keyboard, scroll, and touch events
- ⏱️ Inactivity Monitoring - Automatic detection after configurable timeout (default: 15 minutes)
- 💓 Heartbeat System - Regular server sync (default: 60 seconds)
- 🔄 Cross-Tab Sync - Real-time activity sync across browser tabs via BroadcastChannel
- 🌐 Cross-Domain Support - Server-based sync for different domains
- 📊 Page Visibility Tracking - Detects when users switch tabs (Page Visibility API)
- 🎯 Window Focus Tracking - Monitors when users switch to other apps/windows
- 🔐 JWT Token Support - Automatic JWT decoding to extract userId
- 🔁 Retry Logic - Automatic retry for failed heartbeats with exponential backoff
Detailed Behavior Tracking
- 🖱️ Click Tracking - Captures element ID, class, text, xpath, and coordinates
- 📜 Scroll Tracking - Records scroll depth %, position, and sections viewed
- 🔗 Navigation Tracking - Monitors page views, time on page, and navigation path
- 👁️ Viewport Tracking - Tracks visible elements and time in view
- 📦 Event Batching - Automatically batches events (default: 30 seconds, max 100 events)
- 🔒 Privacy-First - No PII, truncated text, respects data-no-track attribute
Developer Experience
- 🔒 TypeScript Support - Full type definitions included
- ⚡ Performance Optimized - Debounced events, passive listeners, minimal overhead
- 🔧 Highly Configurable - 30+ configuration options
- 📱 Framework Agnostic - Works with React, Vue, Angular, Svelte, vanilla JS
- 🌐 CDN Support - Drop-in script for no-build-tool projects
- 📝 Comprehensive Callbacks - 7 lifecycle callbacks for custom logic
- 🎨 HTML Attributes - Mark elements with data-section, data-track-viewport, data-no-track
📦 Installation
Option 1: NPM Package (For Build Tools)
npm install @vigneshwaranbs/activity-trackeryarn add @vigneshwaranbs/activity-trackerpnpm add @vigneshwaranbs/activity-trackerOption 2: CDN (Drop-in Script - No Build Tools)
Basic Activity Tracking:
<!-- Latest version -->
<script src="https://unpkg.com/@vigneshwaranbs/activity-tracker@latest/dist/activity-tracker-universal.js"></script>
<!-- Access globally as window.activityTracker -->
<script>
// Tracker auto-starts when page loads
console.log('Tracker:', window.activityTracker);
</script>Detailed Behavior Tracking (Single Collection):
<!-- Stores session + events in one collection (user_sessions) -->
<script src="https://unpkg.com/@vigneshwaranbs/activity-tracker@latest/dist/activity-tracker-universal-phase2.js"></script>
<!-- Access globally as window.activityTracker -->
<script>
console.log('Detailed Tracker:', window.activityTracker);
</script>Detailed Behavior Tracking (Separate Collections):
<!-- Stores sessions in user_sessions, events in user_activity_events -->
<script src="https://unpkg.com/@vigneshwaranbs/activity-tracker@latest/dist/activity-tracker-universal-userevent.js"
data-api-endpoint="https://your-api.com/api/user/activity">
</script>
<!-- Access globally as window.activityTracker -->
<script>
console.log('Detailed Tracker:', window.activityTracker);
</script>CDN Usage: Perfect for vanilla HTML, WordPress, legacy apps, or quick prototypes. No npm install needed!
🚀 Quick Start
Three Tracking Options Available
This library offers three tracking options to suit different use cases:
- Basic Activity Tracking - Monitor user active/inactive status with heartbeat sync
- Detailed Behavior Tracking (Single Collection) - Track clicks, scrolls, navigation in same collection as sessions
- Detailed Behavior Tracking (Separate Collections) - Store events in separate collection for better data organization
Method 1: Basic Activity Tracking
Perfect for session management, timeout detection, and presence monitoring.
NPM Installation:
npm install @vigneshwaranbs/activity-trackerBasic Usage:
import { ActivityTracker } from '@vigneshwaranbs/activity-tracker';
// Initialize tracker
const tracker = new ActivityTracker({
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || ''
});
// Start tracking
tracker.start();
// Stop tracking (cleanup)
tracker.stop();React Example
import { useEffect } from 'react';
import { ActivityTracker } from '@vigneshwaranbs/activity-tracker';
function App() {
useEffect(() => {
const tracker = new ActivityTracker({
appId: 'my-react-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
// Callbacks
onActive: (data) => {
console.log('✅ User is active', data);
},
onInactive: (data) => {
console.warn('⚠️ User inactive for', data.inactivityDuration / 60000, 'minutes');
}
});
tracker.start();
return () => tracker.stop(); // Cleanup on unmount
}, []);
return <div>Your App</div>;
}Vue Example
<script setup>
import { onMounted, onUnmounted } from 'vue';
import { ActivityTracker } from '@vigneshwaranbs/activity-tracker';
let tracker;
onMounted(() => {
tracker = new ActivityTracker({
appId: 'my-vue-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || ''
});
tracker.start();
});
onUnmounted(() => {
tracker?.stop();
});
</script>CDN Usage (No Build Tools):
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<h1>My Application</h1>
<!-- Step 1: Set JWT token in localStorage (your login code) -->
<script>
// After successful login, store JWT token
localStorage.setItem('authToken', 'eyJhbGci...');
</script>
<!-- Step 2: Include universal script - appId auto-generated from domain -->
<script src="https://unpkg.com/@vigneshwaranbs/[email protected]/dist/activity-tracker-universal.js"
data-api-endpoint="https://api.yourapp.com/api/user/activity">
</script>
<!-- That's it! Basic tracking starts automatically -->
<!-- appId is automatically generated from domain (e.g., www.app.xxxx.com → app.xxxx) -->
</body>
</html>Auto-Generated AppId:
The tracker automatically generates appId from your domain name:
| Domain | Auto-Generated AppId |
|--------|---------------------|
| www.app.xxxx.com | app.xxxx |
| app.xxxx.com | app.xxxx |
| xxxx.com | xxxx |
| localhost | localhost |
Override AppId (Optional):
<!-- Manually specify appId if you want a custom name -->
<script src="https://unpkg.com/@vigneshwaranbs/[email protected]/dist/activity-tracker-universal.js"
data-app-id="custom-app-name"
data-api-endpoint="https://api.yourapp.com/api/user/activity">
</script>Basic Tracking Configuration (CDN):
<!-- All configuration via data attributes -->
<script src="https://unpkg.com/@vigneshwaranbs/[email protected]/dist/activity-tracker-universal.js"
data-app-id="my-app"
data-api-endpoint="https://api.yourapp.com/api/user/activity"
data-auth-token-key="authToken"
data-inactivity-timeout="900000"
data-heartbeat-interval="60000"
data-log-level="info">
</script>Detailed Tracking Configuration (CDN):
<!-- Configuration for detailed tracking -->
<script src="https://unpkg.com/@vigneshwaranbs/[email protected]/dist/activity-tracker-universal-phase2.js"
data-app-id="my-app"
data-api-endpoint="https://api.yourapp.com/api/user/activity"
data-events-api-endpoint="https://api.yourapp.com/api/user/activity/events"
data-auth-token-key="authToken"
data-track-clicks="true"
data-track-scrolls="true"
data-track-navigation="true"
data-track-viewport="true"
data-event-batch-interval="30000"
data-max-events-per-batch="100">
</script>Or use global config object:
<script>
window.ACTIVITY_TRACKER_CONFIG = {
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
authTokenKey: 'authToken',
inactivityTimeout: 900000,
heartbeatInterval: 60000,
// For detailed tracking (phase2 script only)
eventsApiEndpoint: 'https://api.yourapp.com/api/user/activity/events',
trackClicks: true,
trackScrolls: true,
trackNavigation: true,
trackViewport: true,
// Callbacks
onActive: function(data) {
console.log('User is active!', data);
},
onInactive: function(data) {
console.warn('User is inactive!', data);
}
};
</script>
<!-- Choose appropriate script -->
<script src="https://unpkg.com/@vigneshwaranbs/[email protected]/dist/activity-tracker-universal-phase2.js"></script>CDN is perfect for:
- ✅ Vanilla HTML/CSS/JS projects
- ✅ WordPress, Shopify, Wix sites
- ✅ Legacy applications without build tools
- ✅ Quick prototypes and demos
- ✅ No npm install or build step needed
- ✅ Auto-generated appId from domain (no manual configuration needed)
Method 2: Detailed Behavior Tracking
Perfect for analytics, UX optimization, heatmaps, and understanding user behavior.
Features:
- 🖱️ Click Tracking - Captures element ID, class, text, xpath, and coordinates
- 📜 Scroll Tracking - Records scroll depth %, position, and sections viewed
- 🔗 Navigation Tracking - Monitors page views, time on page, and navigation path
- 👁️ Viewport Tracking - Tracks visible elements and time in view
- 📦 Event Batching - Automatically batches events (sends every 30 seconds)
- 🔒 Privacy-First - No PII, truncated text, respects data-no-track attribute
- ⚡ Performance Optimized - Debounced events, passive listeners, minimal overhead
NPM Installation:
npm install @vigneshwaranbs/activity-trackerUsage:
import { DetailedActivityTracker } from '@vigneshwaranbs/activity-tracker';
const tracker = new DetailedActivityTracker({
// Phase 1 config (required)
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
authToken: localStorage.getItem('authToken'),
// Phase 2 config (new)
trackClicks: true,
trackScrolls: true,
trackNavigation: true,
trackViewport: true,
eventsApiEndpoint: 'https://api.yourapp.com/api/user/activity/events',
eventBatchInterval: 30000, // 30 seconds
maxEventsPerBatch: 100,
// Phase 2 callbacks
onEventTracked: (event) => {
console.log('Event tracked:', event);
},
onEventBatchSent: (response) => {
console.log('Batch sent:', response.eventsReceived, 'events');
}
});
tracker.start();CDN Usage (No Build Tools):
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<h1>My Application</h1>
<!-- Step 1: Set JWT token in localStorage -->
<script>
localStorage.setItem('authToken', 'eyJhbGci...');
</script>
<!-- Step 2: Include universal script - detailed tracking with auto-generated appId -->
<script src="https://unpkg.com/@vigneshwaranbs/[email protected]/dist/activity-tracker-universal-phase2.js"
data-api-endpoint="https://api.yourapp.com/api/user/activity"
data-events-api-endpoint="https://api.yourapp.com/api/user/activity/events"
data-track-clicks="true"
data-track-scrolls="true"
data-track-navigation="true"
data-track-viewport="true">
</script>
<!-- Detailed tracking starts automatically -->
<!-- appId is auto-generated from domain (e.g., www.app.xxxx.com → app.xxxx) -->
<!-- To override: add data-app-id="custom-name" -->
</body>
</html>📊 Event Types
1. Click Events
{
eventId: "evt-1703456789-abc123",
eventType: "click",
timestamp: 1703456789000,
url: "/products",
element: "button",
elementId: "buy-now-btn",
elementClass: "btn btn-primary",
text: "Buy Now",
xpath: "/html/body/div[1]/button",
x: 350,
y: 250
}2. Scroll Events
{
eventId: "evt-1703456790-def456",
eventType: "scroll",
timestamp: 1703456790000,
url: "/products",
scrollDepth: 75, // 75% down page
scrollPosition: 1200,
pageHeight: 1600,
viewportHeight: 800,
sectionsViewed: ["hero", "features", "pricing"]
}3. Navigation Events
{
eventId: "evt-1703456791-ghi789",
eventType: "navigation",
timestamp: 1703456791000,
url: "/checkout",
fromUrl: "/products",
toUrl: "/checkout",
timeOnPage: 45000 // 45 seconds on previous page
}4. Viewport Events
{
eventId: "evt-1703456792-jkl012",
eventType: "viewport",
timestamp: 1703456792000,
url: "/products",
elementsInView: [
{
id: "product-card-1",
class: "product-card",
tag: "div",
text: "Premium Product",
timeInView: 5000 // 5 seconds
}
]
}🎨 HTML Attributes for Tracking
Track Scroll Sections
<div data-section="hero">
<!-- Hero content -->
</div>
<div data-section="features">
<!-- Features content -->
</div>
<div data-section="pricing">
<!-- Pricing content -->
</div>When users scroll through these sections, they'll be recorded in sectionsViewed array.
Track Viewport Elements
<div data-track-viewport id="important-content">
<!-- This element's visibility will be tracked -->
</div>
<div data-track-viewport class="product-card">
<!-- Track how long users view this product -->
</div>Elements with data-track-viewport attribute are monitored for visibility and time in view.
Exclude Elements from Tracking
<button data-no-track="true">
<!-- This button won't be tracked -->
</button>
<div data-no-track="true">
<!-- Clicks inside this div won't be tracked -->
</div>🔧 Detailed Tracking Configuration Options
{
// Feature Flags
trackClicks?: boolean; // Enable click tracking (default: true)
trackScrolls?: boolean; // Enable scroll tracking (default: true)
trackNavigation?: boolean; // Enable navigation tracking (default: true)
trackViewport?: boolean; // Enable viewport tracking (default: true)
// API Endpoints
eventsApiEndpoint?: string; // Separate endpoint for events
// Default: apiEndpoint + '/events'
// Batching Settings
eventBatchInterval?: number; // How often to send events (ms)
// Default: 30000 (30 seconds)
maxEventsPerBatch?: number; // Max events per batch
// Default: 100
// Callbacks
onEventTracked?: (event: ActivityEvent) => void;
onEventBatchSent?: (response: EventBatchResponse) => void;
onEventBatchError?: (error: Error) => void;
}📱 Framework Examples - Detailed Tracking
React with Detailed Tracking
import { useEffect } from 'react';
import { DetailedActivityTracker } from '@vigneshwaranbs/activity-tracker';
function App() {
useEffect(() => {
const tracker = new DetailedActivityTracker({
appId: 'my-react-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
eventsApiEndpoint: 'https://api.yourapp.com/api/user/activity/events',
authToken: localStorage.getItem('authToken'),
// Enable all Phase 2 features
trackClicks: true,
trackScrolls: true,
trackNavigation: true,
trackViewport: true,
// Callbacks
onEventTracked: (event) => {
// Send to analytics
if (event.eventType === 'click') {
analytics.track('Button Clicked', {
elementId: event.elementId,
text: event.text
});
}
}
});
tracker.start();
return () => tracker.stop();
}, []);
return (
<div>
<div data-section="hero">
<h1>Welcome</h1>
<button id="cta-button">Get Started</button>
</div>
<div data-section="features" data-track-viewport>
<h2>Features</h2>
{/* Feature cards */}
</div>
</div>
);
}Vue with Detailed Tracking
<template>
<div>
<div data-section="hero">
<h1>Welcome</h1>
<button id="cta-button">Get Started</button>
</div>
<div data-section="features" data-track-viewport>
<h2>Features</h2>
</div>
</div>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue';
import { DetailedActivityTracker } from '@vigneshwaranbs/activity-tracker';
let tracker;
onMounted(() => {
tracker = new DetailedActivityTracker({
appId: 'my-vue-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
eventsApiEndpoint: 'https://api.yourapp.com/api/user/activity/events',
authToken: localStorage.getItem('authToken'),
trackClicks: true,
trackScrolls: true,
trackNavigation: true,
trackViewport: true
});
tracker.start();
});
onUnmounted(() => {
tracker?.stop();
});
</script>🔍 Use Cases - Detailed Tracking
1. E-commerce Analytics
Track which products users view, how long they spend on product pages, and which "Add to Cart" buttons they click:
const tracker = new DetailedActivityTracker({
appId: 'ecommerce-store',
trackClicks: true, // Track "Add to Cart", "Buy Now" clicks
trackScrolls: true, // See how far users scroll on product pages
trackViewport: true, // Track which products are viewed
onEventTracked: (event) => {
if (event.eventType === 'click' && event.elementId?.includes('add-to-cart')) {
// User clicked "Add to Cart"
sendToAnalytics('add_to_cart', { productId: extractProductId(event.url) });
}
if (event.eventType === 'viewport' && event.elementsInView.length > 0) {
// Track product impressions
event.elementsInView.forEach(el => {
if (el.class?.includes('product-card')) {
sendToAnalytics('product_view', { productId: el.id });
}
});
}
}
});2. Content Engagement
Measure how users engage with blog posts or documentation:
const tracker = new DetailedActivityTracker({
appId: 'blog',
trackScrolls: true, // Measure read depth
trackNavigation: true, // Time spent on each article
onEventTracked: (event) => {
if (event.eventType === 'scroll') {
// User scrolled to 75% - likely read the article
if (event.scrollDepth >= 75) {
sendToAnalytics('article_read', {
url: event.url,
scrollDepth: event.scrollDepth
});
}
}
if (event.eventType === 'navigation') {
// Track time spent on article
if (event.timeOnPage > 60000) { // More than 1 minute
sendToAnalytics('article_engagement', {
url: event.fromUrl,
timeSpent: event.timeOnPage
});
}
}
}
});3. Form Optimization
Track which form fields users interact with:
<form>
<input type="text" id="email" placeholder="Email">
<input type="text" id="phone" placeholder="Phone">
<button id="submit-form">Submit</button>
</form>
<script>
const tracker = new DetailedActivityTracker({
appId: 'signup-form',
trackClicks: true,
onEventTracked: (event) => {
if (event.eventType === 'click') {
if (event.elementId === 'submit-form') {
// Track form submission
sendToAnalytics('form_submit');
}
}
}
});
</script>🔒 Privacy & Security
Detailed tracking respects user privacy:
- ✅ No PII Captured - Never tracks password inputs, credit card fields
- ✅ Text Truncation - Element text limited to 100 characters
- ✅ Opt-out Support -
data-no-trackattribute excludes elements - ✅ No Form Values - Only tracks field interactions, not values
- ✅ GDPR Compliant - Respects user consent and data retention policies
Excluded by Default:
<input type="password"><input type="hidden">- Elements with
data-no-track="true"
📊 Backend Integration - Detailed Tracking
Detailed tracking uses two API endpoints:
Basic Tracking Endpoint (Heartbeat):
POST /api/user/activityDetailed Tracking Endpoints (Events):
POST /api/user/activity/events # Stores events in same collection (user_sessions)
POST /api/user/userevent # Stores events in separate collection (user_activity_events)Example Backend (Express + MongoDB):
Option 1: Events in Same Collection (/api/user/activity/events):
// Event batching endpoint - stores in user_sessions collection
app.post('/api/user/activity/events', async (req, res) => {
const { userId, appId, events } = req.body;
// Validate
if (!userId || !appId || !events || events.length === 0) {
return res.status(400).json({ success: false, message: 'Invalid request' });
}
if (events.length > 100) {
return res.status(400).json({ success: false, message: 'Max 100 events per batch' });
}
// Append events to user document
await db.collection('user_sessions').updateOne(
{ userId: new ObjectId(userId), appId },
{
$push: {
events: {
$each: events,
$slice: -1000 // Keep only last 1000 events
}
},
$set: { updatedAt: new Date() }
},
{ upsert: true }
);
res.json({
success: true,
eventsReceived: events.length
});
});Option 2: Events in Separate Collection (/api/user/userevent):
// Event batching endpoint - stores in user_activity_events collection
app.post('/api/user/userevent', async (req, res) => {
const { userId, appId, events, timestamp } = req.body;
// Validate
if (!userId || !appId || !events || events.length === 0) {
return res.status(400).json({ success: false, message: 'Invalid request' });
}
if (events.length > 100) {
return res.status(400).json({ success: false, message: 'Max 100 events per batch' });
}
const now = new Date();
// Get last event timestamp
const lastEventTimestamp = events.length > 0
? new Date(Math.max(...events.map(e => e.timestamp)))
: now;
// Add receivedAt timestamp to each event
const eventsWithTimestamps = events.map(event => ({
...event,
timestamp: new Date(event.timestamp),
receivedAt: now
}));
// Store in separate collection
await db.collection('user_activity_events').updateOne(
{ userId: new ObjectId(userId), appId },
{
$push: {
events: {
$each: eventsWithTimestamps,
$slice: -1000
}
},
$set: {
lastEventActivityTime: lastEventTimestamp,
updatedAt: now
},
$setOnInsert: { createdAt: now }
},
{ upsert: true }
);
res.json({
success: true,
eventsReceived: events.length,
message: 'User events tracked successfully'
});
});MongoDB Document Structures:
Collection: user_sessions (Basic tracking + optional events):
{
_id: ObjectId("..."),
userId: ObjectId("..."),
appId: "my-app",
// Basic tracking fields
isActive: true,
lastActivityTime: ISODate("2024-12-24T10:30:00Z"),
lastHeartbeat: ISODate("2024-12-24T10:30:00Z"),
tabVisible: true,
windowFocused: true,
// Events array (optional - only if using activity-tracker-universal-phase2.js)
events: [
{
eventType: "click",
element: "button",
timestamp: ISODate("2024-12-24T10:30:00Z"),
receivedAt: ISODate("2024-12-24T10:30:30Z"),
...
},
{
eventType: "scroll",
scrollDepth: 75,
timestamp: ISODate("2024-12-24T10:31:00Z"),
receivedAt: ISODate("2024-12-24T10:31:30Z"),
...
}
],
createdAt: ISODate("2024-12-24T09:00:00Z"),
updatedAt: ISODate("2024-12-24T10:30:00Z")
}Collection: user_activity_events (Separate events collection - only if using activity-tracker-universal-userevent.js):
{
_id: ObjectId("..."),
userId: ObjectId("..."),
appId: "my-app",
// Events array
events: [
{
eventId: "evt-1234567890-abc",
eventType: "click",
element: "button",
elementId: "submit-btn",
timestamp: ISODate("2024-12-24T10:30:00Z"),
receivedAt: ISODate("2024-12-24T10:30:30Z"),
x: 100,
y: 200
},
{
eventId: "evt-1234567891-def",
eventType: "scroll",
scrollDepth: 75,
scrollY: 500,
timestamp: ISODate("2024-12-24T10:31:00Z"),
receivedAt: ISODate("2024-12-24T10:31:30Z")
},
{
eventId: "evt-1234567892-ghi",
eventType: "navigation",
fromUrl: "/home",
toUrl: "/about",
timeOnPage: 45000,
timestamp: ISODate("2024-12-24T10:32:00Z"),
receivedAt: ISODate("2024-12-24T10:32:30Z")
}
],
lastEventActivityTime: ISODate("2024-12-24T10:32:00Z"),
createdAt: ISODate("2024-12-24T09:00:00Z"),
updatedAt: ISODate("2024-12-24T10:32:30Z")
}🎛️ Advanced Usage - Detailed Tracking
Selective Feature Enabling
// Only track clicks and scrolls
const tracker = new DetailedActivityTracker({
appId: 'my-app',
trackClicks: true,
trackScrolls: true,
trackNavigation: false, // Disabled
trackViewport: false // Disabled
});Custom Event Handling
const tracker = new DetailedActivityTracker({
appId: 'my-app',
trackClicks: true,
onEventTracked: (event) => {
// Send to multiple analytics platforms
if (event.eventType === 'click') {
googleAnalytics.event('click', { elementId: event.elementId });
mixpanel.track('Button Click', { button: event.text });
amplitude.logEvent('click', { element: event.element });
}
},
onEventBatchSent: (response) => {
console.log(`✅ Sent ${response.eventsReceived} events to backend`);
},
onEventBatchError: (error) => {
console.error('❌ Failed to send events:', error);
// Retry logic or alert user
}
});Force Send Batch
// Manually trigger batch send (useful before page unload)
tracker.forceSendBatch();
// Example: Send batch before user leaves
window.addEventListener('beforeunload', () => {
tracker.forceSendBatch();
});Get Current Batch Size
const batchSize = tracker.getBatchSize();
console.log(`Current batch has ${batchSize} events`);📈 Performance Considerations
Detailed tracking is optimized for production:
- ✅ Debounced Events - Scroll events debounced by 1 second
- ✅ Passive Listeners - Non-blocking event listeners
- ✅ Event Batching - Reduces API calls by 50%
- ✅ Smart Filtering - Only tracks significant scroll changes (5%+)
- ✅ Memory Management - Automatic queue cleanup
- ✅ Minimal Overhead - <1% CPU usage, <50MB memory
Data Volume Estimate (1000 users):
- Events per user per day: ~5,000
- Storage per user per day: ~25 MB
- With 1000-event limit: ~5 MB per user
- Total: ~5 GB per day (1000 users)
Optimization Tips:
- Use 90-day data retention (auto-delete old events)
- Enable only needed features (disable viewport if not needed)
- Increase
eventBatchIntervalto reduce API calls - Use sampling (track 10% of users for detailed analytics)
🆚 Comparison: Basic vs Detailed Tracking
| Feature | Basic Tracking | Detailed Tracking |
|---------|----------------|-------------------|
| Active/Inactive Detection | ✅ | ✅ |
| Heartbeat to Backend | ✅ | ✅ |
| Tab Visibility | ✅ | ✅ |
| Window Focus | ✅ | ✅ |
| Click Tracking | ❌ | ✅ |
| Scroll Tracking | ❌ | ✅ |
| Navigation Tracking | ❌ | ✅ |
| Viewport Tracking | ❌ | ✅ |
| Event Batching | ❌ | ✅ |
| Class | ActivityTracker | DetailedActivityTracker |
| CDN Script | activity-tracker-universal.js | activity-tracker-universal-phase2.js |
| API Endpoints | 1 endpoint | 2 endpoints |
| Use Case | Session management, timeouts | Analytics, UX optimization |
Choose Basic Tracking for:
- ✅ Session timeout management
- ✅ User presence monitoring
- ✅ Active/inactive status
- ✅ Lightweight tracking
- ✅ Simple analytics
Choose Detailed Tracking for:
- ✅ E-commerce analytics
- ✅ Content engagement metrics
- ✅ UX optimization
- ✅ A/B testing insights
- ✅ Heatmap data collection
- ✅ User behavior analysis
⚙️ Configuration Reference
Basic Tracking Configuration
Required Options
| Option | Type | Description |
|--------|------|-------------|
| appId | string | Unique identifier for your application |
| apiEndpoint | string | API endpoint URL for heartbeat requests |
| userId or authToken or getAuthToken | string \| function | User identification |
Optional Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| inactivityTimeout | number | 900000 (15 min) | Time before marking user inactive (ms) |
| heartbeatInterval | number | 60000 (1 min) | Interval between heartbeat requests (ms) |
| checkInterval | number | 10000 (10 sec) | Interval for checking inactivity (ms) |
| crossTabSync | boolean | true | Enable cross-tab activity sync via BroadcastChannel |
| serverSync | boolean | true | Enable server heartbeat sync |
| trackPageVisibility | boolean | true | Track tab visibility changes (Page Visibility API) |
| trackWindowFocus | boolean | true | Track window focus/blur events |
| debounceDelay | number | 1000 (1 sec) | Debounce delay for activity events (ms) |
| retryAttempts | number | 3 | Number of retry attempts for failed heartbeats |
| logLevel | string | 'warn' | Log level: 'debug', 'info', 'warn', 'error', 'none' |
| customHeaders | object | {} | Custom HTTP headers for API requests |
| metadata | object | {} | Custom metadata to include in heartbeat |
CDN Data Attributes (Basic Tracking)
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| data-app-id | string | required | Application identifier |
| data-api-endpoint | string | required | Heartbeat API endpoint |
| data-auth-token-key | string | 'authToken' | localStorage key for auth token |
| data-user-id-key | string | 'userId' | localStorage key for user ID |
| data-inactivity-timeout | number | 900000 | Inactivity timeout in milliseconds |
| data-heartbeat-interval | number | 60000 | Heartbeat interval in milliseconds |
| data-check-interval | number | 10000 | Activity check interval in milliseconds |
| data-cross-tab-sync | boolean | true | Enable cross-tab sync |
| data-server-sync | boolean | true | Enable server sync |
| data-track-page-visibility | boolean | true | Track page visibility |
| data-track-window-focus | boolean | true | Track window focus |
| data-debounce-delay | number | 1000 | Debounce delay in milliseconds |
| data-retry-attempts | number | 3 | Retry attempts for failed requests |
| data-log-level | string | 'warn' | Log level |
Callbacks
| Callback | Parameters | Description |
|----------|------------|-------------|
| onActive | (data: ActivityData) => void | Called when user becomes active |
| onInactive | (data: InactivityData) => void | Called when user becomes inactive |
| onHeartbeatSuccess | (response: HeartbeatResponse) => void | Called on successful heartbeat |
| onHeartbeatError | (error: Error) => void | Called on heartbeat failure |
Detailed Tracking Configuration
Additional Options (extends Basic Tracking)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| eventsApiEndpoint | string | apiEndpoint + '/events' | Separate endpoint for event batching |
| trackClicks | boolean | true | Enable click event tracking |
| trackScrolls | boolean | true | Enable scroll event tracking |
| trackNavigation | boolean | true | Enable navigation event tracking |
| trackViewport | boolean | true | Enable viewport element tracking |
| eventBatchInterval | number | 30000 (30 sec) | How often to send event batches (ms) |
| maxEventsPerBatch | number | 100 | Maximum events per batch |
CDN Data Attributes (Detailed Tracking)
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| data-events-api-endpoint | string | apiEndpoint + '/events' | Events API endpoint |
| data-track-clicks | boolean | true | Enable click tracking |
| data-track-scrolls | boolean | true | Enable scroll tracking |
| data-track-navigation | boolean | true | Enable navigation tracking |
| data-track-viewport | boolean | true | Enable viewport tracking |
| data-event-batch-interval | number | 30000 | Event batch interval in milliseconds |
| data-max-events-per-batch | number | 100 | Max events per batch |
Additional Callbacks
| Callback | Parameters | Description |
|----------|------------|-------------|
| onEventTracked | (event: ActivityEvent) => void | Called when an event is tracked |
| onEventBatchSent | (response: EventBatchResponse) => void | Called when batch is sent successfully |
| onEventBatchError | (error: Error) => void | Called when batch send fails |
Configuration Examples
Minimal Configuration (NPM)
const tracker = new ActivityTracker({
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
userId: 'user-123'
});
tracker.start();Minimal Configuration (CDN)
<script src="https://unpkg.com/@vigneshwaranbs/[email protected]/dist/activity-tracker-universal.js"
data-app-id="my-app"
data-api-endpoint="https://api.yourapp.com/api/user/activity">
</script>Full Configuration (NPM)
const tracker = new DetailedActivityTracker({
// Basic tracking
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('authToken') || '',
inactivityTimeout: 600000, // 10 minutes
heartbeatInterval: 30000, // 30 seconds
checkInterval: 5000, // 5 seconds
crossTabSync: true,
serverSync: true,
trackPageVisibility: true,
trackWindowFocused: true,
debounceDelay: 2000, // 2 seconds
retryAttempts: 5,
logLevel: 'info',
// Detailed tracking
eventsApiEndpoint: 'https://api.yourapp.com/api/user/activity/events',
trackClicks: true,
trackScrolls: true,
trackNavigation: true,
trackViewport: true,
eventBatchInterval: 20000, // 20 seconds
maxEventsPerBatch: 50,
// Callbacks
onActive: (data) => console.log('Active:', data),
onInactive: (data) => console.warn('Inactive:', data),
onHeartbeatSuccess: (res) => console.log('Heartbeat:', res),
onHeartbeatError: (err) => console.error('Error:', err),
onEventTracked: (event) => console.log('Event:', event),
onEventBatchSent: (res) => console.log('Batch sent:', res),
onEventBatchError: (err) => console.error('Batch error:', err)
});
tracker.start();Full Configuration (CDN)
<script src="https://unpkg.com/@vigneshwaranbs/[email protected]/dist/activity-tracker-universal-phase2.js"
data-app-id="my-app"
data-api-endpoint="https://api.yourapp.com/api/user/activity"
data-events-api-endpoint="https://api.yourapp.com/api/user/activity/events"
data-auth-token-key="authToken"
data-inactivity-timeout="600000"
data-heartbeat-interval="30000"
data-check-interval="5000"
data-cross-tab-sync="true"
data-server-sync="true"
data-track-page-visibility="true"
data-track-window-focus="true"
data-debounce-delay="2000"
data-retry-attempts="5"
data-log-level="info"
data-track-clicks="true"
data-track-scrolls="true"
data-track-navigation="true"
data-track-viewport="true"
data-event-batch-interval="20000"
data-max-events-per-batch="50">
</script>📖 Advanced Examples
With Custom Metadata
const tracker = new ActivityTracker({
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
// Custom metadata
metadata: {
userAgent: navigator.userAgent,
screenResolution: `${window.screen.width}x${window.screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
language: navigator.language,
referrer: document.referrer
}
});
tracker.start();With Custom Headers (Authentication)
const tracker = new ActivityTracker({
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
userId: 'user-123',
// Custom headers for API authentication
customHeaders: {
'Authorization': `Bearer ${localStorage.getItem('authToken')}`,
'X-API-Key': 'your-api-key',
'X-App-Version': '1.0.0'
}
});
tracker.start();With All Callbacks
const tracker = new ActivityTracker({
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
// Activity callbacks
onActive: (data) => {
console.log('✅ User is active');
console.log('Last activity:', new Date(data.lastActivityTime));
// Send to analytics
analytics.track('user_active', data);
},
onInactive: (data) => {
console.warn('⚠️ User inactive');
console.warn('Duration:', data.inactivityDuration / 60000, 'minutes');
// Show warning modal
showInactivityWarning();
},
onHeartbeatSuccess: (response) => {
console.log('💓 Heartbeat sent successfully');
// Handle server response
if (response.action === 'logout') {
logout();
}
},
onHeartbeatError: (error) => {
console.error('❌ Heartbeat failed:', error.message);
// Send to error tracking (e.g., Sentry)
Sentry.captureException(error);
}
});
tracker.start();Development vs Production Config
const isDevelopment = process.env.NODE_ENV === 'development';
const tracker = new ActivityTracker({
appId: isDevelopment ? 'my-app-dev' : 'my-app',
apiEndpoint: isDevelopment
? 'http://localhost:4000/api/user/activity'
: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
// Development: shorter intervals for testing
inactivityTimeout: isDevelopment ? 2 * 60 * 1000 : 15 * 60 * 1000,
heartbeatInterval: isDevelopment ? 10 * 1000 : 60 * 1000,
// Development: verbose logging
logLevel: isDevelopment ? 'debug' : 'warn'
});
tracker.start();React Custom Hook
// hooks/useActivityTracker.ts
import { useEffect, useState } from 'react';
import { ActivityTracker } from '@vigneshwaranbs/activity-tracker';
export function useActivityTracker(appId: string) {
const [isActive, setIsActive] = useState(true);
const [tracker, setTracker] = useState<ActivityTracker | null>(null);
useEffect(() => {
const activityTracker = new ActivityTracker({
appId,
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
onActive: () => setIsActive(true),
onInactive: () => setIsActive(false)
});
activityTracker.start();
setTracker(activityTracker);
return () => activityTracker.stop();
}, [appId]);
return { isActive, tracker };
}
// Usage
function MyComponent() {
const { isActive } = useActivityTracker('my-app');
return (
<div>
<StatusBadge active={isActive} />
{!isActive && <InactivityWarning />}
</div>
);
}🔌 Backend API Integration
Expected API Endpoint
Your backend should handle POST requests to the configured apiEndpoint:
Request:
POST /api/user/activity
Content-Type: application/json
{
"userId": "676966c6c0b4b40f8cc2db9a",
"appId": "my-app",
"isActive": true,
"lastActivityTime": 1703456789000,
"tabVisible": true,
"windowFocused": true,
"metadata": {
"userAgent": "Mozilla/5.0...",
"platform": "MacIntel",
"language": "en-US",
"screenResolution": "1920x1080",
"viewport": "1440x900",
"timezone": "America/New_York",
"url": "https://app.example.com/dashboard"
}
}Response:
{
"success": true,
"message": "Activity tracked successfully",
"activeInOtherApp": false,
"lastActivityTime": 1703456789000,
"action": "continue" // or "logout", "warn"
}Express.js Backend Example
// server.js
import express from 'express';
import { MongoClient, ObjectId } from 'mongodb';
const app = express();
app.use(express.json());
const MONGODB_URI = process.env.MONGODB_URI;
const client = await MongoClient.connect(MONGODB_URI);
const db = client.db('your-database');
app.post('/api/user/activity', async (req, res) => {
try {
const { userId, appId, isActive, lastActivityTime, tabVisible, windowFocused, metadata } = req.body;
const now = new Date();
const sessionData = {
userId: new ObjectId(userId),
appId,
isActive,
lastActivityTime: new Date(lastActivityTime),
lastHeartbeat: now,
tabVisible,
windowFocused,
metadata,
updatedAt: now
};
// Upsert: one document per userId + appId
await db.collection('user_sessions').updateOne(
{ userId: new ObjectId(userId), appId },
{ $set: sessionData, $setOnInsert: { createdAt: now } },
{ upsert: true }
);
res.json({ success: true, message: 'Activity tracked successfully' });
} catch (error) {
console.error('Activity tracking error:', error);
res.status(500).json({ success: false, error: error.message });
}
});
app.listen(4000, () => console.log('Server running on port 4000'));MongoDB Schema
// Collection: user_sessions
{
"_id": ObjectId("..."),
"userId": ObjectId("676966c6c0b4b40f8cc2db9a"),
"appId": "my-app",
"isActive": true,
"lastActivityTime": ISODate("2024-12-24T10:30:00.000Z"),
"lastHeartbeat": ISODate("2024-12-24T10:30:00.000Z"),
"tabVisible": true,
"windowFocused": true,
"metadata": {
"userAgent": "Mozilla/5.0...",
"platform": "MacIntel",
"language": "en-US",
"screenResolution": "1920x1080",
"viewport": "1440x900",
"timezone": "America/New_York",
"url": "https://app.example.com/dashboard"
},
"createdAt": ISODate("2024-12-24T09:00:00.000Z"),
"updatedAt": ISODate("2024-12-24T10:30:00.000Z")
}
// Recommended indexes
db.user_sessions.createIndex({ userId: 1, appId: 1 }, { unique: true });
db.user_sessions.createIndex({ lastActivityTime: -1 });
db.user_sessions.createIndex({ isActive: 1 });
db.user_sessions.createIndex({ updatedAt: -1 });🎯 Use Cases
1. Session Timeout Management
const tracker = new ActivityTracker({
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
inactivityTimeout: 15 * 60 * 1000, // 15 minutes
onInactive: (data) => {
// Show warning modal
showModal({
title: 'Session Timeout Warning',
message: `You've been inactive for ${data.inactivityDuration / 60000} minutes. Your session will expire soon.`,
actions: ['Stay Logged In', 'Logout']
});
}
});2. Multi-Tab Activity Sync
const tracker = new ActivityTracker({
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
crossTabSync: true, // Enable cross-tab sync
onActive: () => {
// Activity detected in any tab syncs to all tabs
console.log('User active in this or another tab');
}
});3. Analytics Integration
const tracker = new ActivityTracker({
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
onActive: (data) => {
// Track active sessions in analytics
analytics.track('session_active', {
userId: data.userId,
appId: data.appId,
timestamp: data.lastActivityTime
});
},
onInactive: (data) => {
// Track session end in analytics
analytics.track('session_inactive', {
userId: data.userId,
duration: data.inactivityDuration
});
}
});4. Real-Time User Presence
const tracker = new ActivityTracker({
appId: 'collaboration-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
heartbeatInterval: 30 * 1000, // 30 seconds for real-time presence
onHeartbeatSuccess: (response) => {
// Update UI with active users from server response
updateActiveUsersList(response.activeUsers);
}
});🔍 What Gets Tracked?
Activity Events
- Mouse:
mousedown,mousemove,click - Keyboard:
keypress,keydown - Touch:
touchstart(mobile devices) - Scroll:
scroll(page scrolling)
Browser State
- Page Visibility: Tab active/hidden (Visibility API)
- Window Focus: Window focused/blurred
- Tab Visibility: User switched tabs
Metadata (Optional)
- User agent
- Platform (OS)
- Language
- Screen resolution
- Viewport size
- Timezone
- Current URL
- Referrer
- Custom metadata
🏗️ Architecture
┌─────────────────────────────────────────────────────────┐
│ Browser Tab 1 │
│ ActivityTracker Instance │
│ ├─ DOM Event Listeners (mouse, keyboard, scroll) │
│ ├─ BroadcastChannel (cross-tab sync) │
│ └─ Heartbeat Timer (60s) │
└─────────────────────────────────────────────────────────┘
│
│ POST /api/user/activity
▼
┌─────────────────────────────────────────────────────────┐
│ Backend API │
│ Express / Next.js / Fastify │
│ └─ POST /api/user/activity │
└─────────────────────────────────────────────────────────┘
│
│ Upsert (one doc per user)
▼
┌─────────────────────────────────────────────────────────┐
│ MongoDB Database │
│ Collection: user_sessions │
│ └─ { userId, appId, isActive, lastActivityTime } │
└─────────────────────────────────────────────────────────┘⚡ Performance
Optimizations
- Debouncing: Activity events debounced by 1 second (configurable)
- Passive Listeners: Non-blocking event listeners for scroll/touch
- Efficient Intervals: Minimal timer overhead
- Retry Queue: Failed heartbeats queued and retried
- Cross-Tab Sync: BroadcastChannel for instant sync (no polling)
Benchmarks
- Memory: ~50KB (minified + gzipped)
- CPU: < 0.1% average
- Network: 1 request per 60 seconds (configurable)
- Event Overhead: < 1ms per debounced event
🛠️ TypeScript Support
Full TypeScript definitions included:
import {
ActivityTracker,
ActivityTrackerConfig,
ActivityData,
InactivityData,
HeartbeatPayload,
HeartbeatResponse
} from '@vigneshwaranbs/activity-tracker';
const config: ActivityTrackerConfig = {
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
getAuthToken: () => localStorage.getItem('userId') || '',
inactivityTimeout: 15 * 60 * 1000,
onActive: (data: ActivityData) => console.log(data),
onInactive: (data: InactivityData) => console.warn(data)
};
const tracker = new ActivityTracker(config);
tracker.start();🧪 Testing
Manual Testing
// Create tracker with short timeouts for testing
const tracker = new ActivityTracker({
appId: 'test-app',
apiEndpoint: 'http://localhost:4000/api/user/activity',
userId: 'test-user',
// Short intervals for testing
inactivityTimeout: 30 * 1000, // 30 seconds
heartbeatInterval: 10 * 1000, // 10 seconds
checkInterval: 5 * 1000, // 5 seconds
// Verbose logging
logLevel: 'debug',
// Test callbacks
onActive: () => console.log('✅ ACTIVE'),
onInactive: () => console.log('⚠️ INACTIVE'),
onHeartbeatSuccess: (res) => console.log('💓 HEARTBEAT', res),
onHeartbeatError: (err) => console.error('❌ ERROR', err)
});
tracker.start();
// Test inactivity: Stop moving mouse for 30 seconds
// Test heartbeat: Check network tab for POST requests every 10 seconds
// Test cross-tab: Open multiple tabs and check sync📚 API Reference
Constructor
new ActivityTracker(config: ActivityTrackerConfig)Methods
| Method | Description |
|--------|-------------|
| start() | Start tracking user activity |
| stop() | Stop tracking and cleanup |
| isUserActive() | Check if user is currently active |
| getLastActivityTime() | Get timestamp of last activity |
| forceHeartbeat() | Manually trigger a heartbeat |
Example
const tracker = new ActivityTracker({ /* config */ });
tracker.start(); // Start tracking
const active = tracker.isUserActive(); // Check status
const lastTime = tracker.getLastActivityTime(); // Get timestamp
tracker.forceHeartbeat(); // Force sync
tracker.stop(); // Stop and cleanup🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
MIT © Vigneshwaran BS
🔗 Links
- NPM Package: https://www.npmjs.com/package/@vigneshwaranbs/activity-tracker
- GitHub Repository: https://github.com/vigneshwaranbs/activity-tracker
- Issues: https://github.com/vigneshwaranbs/activity-tracker/issues
- Author: Vigneshwaran BS
💡 Support
For questions, issues, or feature requests:
- Email: [email protected]
- GitHub Issues: https://github.com/vigneshwaranbs/activity-tracker/issues
Made with ❤️ by Vigneshwaran BS
