Universal user activity tracking library
npm install @vigneshwaranbs/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.
---
---
``bash`
npm install @vigneshwaranbs/activity-tracker
`bash`
yarn add @vigneshwaranbs/activity-tracker
`bash`
pnpm add @vigneshwaranbs/activity-tracker
Basic Activity Tracking:
`html
`
Detailed Behavior Tracking (Single Collection):
`html
`
Detailed Behavior Tracking (Separate Collections):
`html
data-api-endpoint="https://your-api.com/api/user/activity">
`
CDN Usage: Perfect for vanilla HTML, WordPress, legacy apps, or quick prototypes. No npm install needed!
---
This library offers three tracking options to suit different use cases:
1. Basic Activity Tracking - Monitor user active/inactive status with heartbeat sync
2. Detailed Behavior Tracking (Single Collection) - Track clicks, scrolls, navigation in same collection as sessions
3. Detailed Behavior Tracking (Separate Collections) - Store events in separate collection for better data organization
---
Perfect for session management, timeout detection, and presence monitoring.
NPM Installation:
`bash`
npm install @vigneshwaranbs/activity-tracker
Basic Usage:
`typescript
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
`typescript
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
Vue Example
`vue
`---
CDN Usage (No Build Tools):
`html
My App
My Application
data-api-endpoint="https://api.yourapp.com/api/user/activity">
`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):
`html
data-app-id="custom-app-name"
data-api-endpoint="https://api.yourapp.com/api/user/activity">
`Basic Tracking Configuration (CDN):
`html
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">
`Detailed Tracking Configuration (CDN):
`html
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">
`Or use global config object:
`html
`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)
---
---
$3
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:
`bash
npm install @vigneshwaranbs/activity-tracker
`Usage:
`typescript
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):
`html
My App
My Application
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">
`---
$3
#### 1. Click Events
`typescript
{
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
`typescript
{
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
`typescript
{
eventId: "evt-1703456791-ghi789",
eventType: "navigation",
timestamp: 1703456791000,
url: "/checkout",
fromUrl: "/products",
toUrl: "/checkout",
timeOnPage: 45000 // 45 seconds on previous page
}
`#### 4. Viewport Events
`typescript
{
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
}
]
}
`$3
#### Track Scroll Sections
`html
`When users scroll through these sections, they'll be recorded in
sectionsViewed array.#### Track Viewport Elements
`html
`Elements with
data-track-viewport attribute are monitored for visibility and time in view.#### Exclude Elements from Tracking
`html
`$3
`typescript
{
// 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;
}
`$3
#### React with Detailed Tracking
`typescript
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 (
Welcome
Features
{/ Feature cards /}
);
}
`#### Vue with Detailed Tracking
`vue
Welcome
Features
`$3
#### 1. E-commerce Analytics
Track which products users view, how long they spend on product pages, and which "Add to Cart" buttons they click:
`typescript
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:
`typescript
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:
`html
`$3
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-track attribute excludes elements
- โ
No Form Values - Only tracks field interactions, not values
- โ
GDPR Compliant - Respects user consent and data retention policiesExcluded by Default:
-
-
- Elements with data-no-track="true"$3
Detailed tracking uses two API endpoints:
Basic Tracking Endpoint (Heartbeat):
`
POST /api/user/activity
`Detailed 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):
`javascript
// 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):
`javascript
// 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):
`javascript
{
_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):
`javascript
{
_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")
}
`$3
#### Selective Feature Enabling
`typescript
// 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
`typescript
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
`typescript
// 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
`typescript
const batchSize = tracker.getBatchSize();
console.log(Current batch has ${batchSize} events);
`$3
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
eventBatchInterval to reduce API calls
- Use sampling (track 10% of users for detailed analytics)$3
| 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
$3
#### 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 |---
$3
#### 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 |---
$3
#### Minimal Configuration (NPM)
`typescript
const tracker = new ActivityTracker({
appId: 'my-app',
apiEndpoint: 'https://api.yourapp.com/api/user/activity',
userId: 'user-123'
});
tracker.start();
`#### Minimal Configuration (CDN)
`html
data-app-id="my-app"
data-api-endpoint="https://api.yourapp.com/api/user/activity">
`#### Full Configuration (NPM)
`typescript
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)
`html
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">
`---
๐ Advanced Examples
$3
`typescript
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();
`$3
`typescript
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();
`$3
`typescript
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();
`$3
`typescript
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();
`$3
`typescript
// 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(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 (
{!isActive && }
);
}
`---
๐ Backend API Integration
$3
Your backend should handle
POST requests to the configured apiEndpoint:Request:
`typescript
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:
`typescript
{
"success": true,
"message": "Activity tracked successfully",
"activeInOtherApp": false,
"lastActivityTime": 1703456789000,
"action": "continue" // or "logout", "warn"
}
`$3
`javascript
// 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'));
`$3
`javascript
// 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
$3
`typescript
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']
});
}
});
`$3
`typescript
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
`typescript
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
});
}
});
`$3
`typescript
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?
$3
- Mouse: mousedown, mousemove, click
- Keyboard: keypress, keydown
- Touch: touchstart (mobile devices)
- Scroll: scroll (page scrolling)$3
- Page Visibility: Tab active/hidden (Visibility API)
- Window Focus: Window focused/blurred
- Tab Visibility: User switched tabs$3
- 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
$3
- 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)$3
- 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:
`typescript
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
$3
`typescript
// 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
$3
`typescript
new ActivityTracker(config: ActivityTrackerConfig)
`$3
| 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 |$3
`typescript
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
``---
Contributions are welcome! Please feel free to submit a Pull Request.
---
MIT ยฉ Vigneshwaran BS
---
- 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
---
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