Package Exports
- @vigneshwaranbs/activity-tracker
- @vigneshwaranbs/activity-tracker/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@vigneshwaranbs/activity-tracker) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
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
- ๐ฑ๏ธ 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 (same origin)
- ๐ Cross-Domain Support - Server-based sync for different domains
- ๐ Page Visibility Tracking - Detects when users switch tabs
- ๐ฏ Window Focus Tracking - Monitors when users switch windows
- ๐ TypeScript Support - Full type definitions included
- โก Performance Optimized - Debounced events, passive listeners
- ๐ง Highly Configurable - 20+ configuration options
- ๐ Retry Logic - Automatic retry for failed heartbeats
- ๐ฑ Framework Agnostic - Works with React, Vue, Angular, vanilla JS
๐ฆ Installation
npm install @vigneshwaranbs/activity-trackeryarn add @vigneshwaranbs/activity-trackerpnpm add @vigneshwaranbs/activity-tracker๐ Quick Start
Basic 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>โ๏ธ 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 |
serverSync |
boolean |
true |
Enable server heartbeat sync |
trackPageVisibility |
boolean |
true |
Track page visibility changes |
trackWindowFocus |
boolean |
true |
Track window focus/blur |
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 |
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 |
๐ 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: bs.vigneshwaran@gmail.com
- GitHub Issues: https://github.com/vigneshwaranbs/activity-tracker/issues
Made with โค๏ธ by Vigneshwaran BS