puppr-js
v0.1.6
Published
A library to detect multiple types of user engagement signals, including on-page and navigational patterns.
Maintainers
Readme
Puppr
A lightweight JavaScript library to detect user engagement signals and behavioral patterns that indicate confusion, frustration, or high engagement. Perfect for triggering support chat, collecting UX insights, and improving user experience.
Features
- 🔥 Rage Click Detection: Detects when users click rapidly in the same area, indicating frustration
- 🖱️ Erratic Mouse Movement: Detects erratic mouse movements that indicate user confusion
- 📜 Scroll Thrash Detection: Detects rapid scrolling up and down, indicating content confusion
- 🚀 Rapid Navigation Detection: Detects when users navigate between pages very quickly
- 🔄 Revisiting Detection: Detects when users repeatedly visit the same pages
- 💬 Custom Event Triggering: Programmatically trigger your own engagement events
- ⚙️ Cooldown System: Prevents event spam with configurable global and event-specific cooldowns
- 📊 Real-time Statistics: Track total events, blocked events, and engagements
- 🎯 Event Subscription System: Subscribe to engagement events and stat changes with simple callbacks
Installation
Via CDN
<script src="https://cdn.jsdelivr.net/npm/puppr-js@latest/dist/puppr.umd.js"></script>
<script>
// Puppr is available as a global variable
const puppr = new Puppr.Puppr({
rageClick: true,
erraticMouse: true,
scrollThrash: true,
rapidNavigation: true,
revisiting: true
});
puppr.start();
</script>Via npm
npm install puppr-jsimport { Puppr } from 'puppr-js';
const puppr = new Puppr({
rageClick: true,
erraticMouse: true,
scrollThrash: true,
rapidNavigation: true,
revisiting: true
});
puppr.start();Quick Start
Basic Setup
// Initialize Puppr with all detectors enabled
const puppr = new Puppr({
rageClick: true,
erraticMouse: true,
scrollThrash: true,
rapidNavigation: true,
revisiting: true
});
// Start detection
puppr.start();Listening for Events
Puppr provides two ways to listen for engagement events:
Method 1: Event Subscription (Recommended)
// Subscribe to engagement events
const unsubscribeEngagement = puppr.onEngagement((detail) => {
console.log('Engagement detected:', detail);
// detail.engagementType: 'CONFUSION' | 'FRUSTRATION' | 'ENGAGEMENT'
// detail.eventType: 'rage-click' | 'erratic-mouse-movement' | etc.
// detail.message: Human-readable description
// detail.timestamp: Unix timestamp
// detail.data: Additional event-specific data
});
// Subscribe to stat changes
const unsubscribeStats = puppr.onStatsChange((stats) => {
console.log('Stats updated:', stats);
// { totalEvents: 15, blockedEvents: 3, engagements: 12 }
});
// Clean up subscriptions when done
unsubscribeEngagement();
unsubscribeStats();Method 2: DOM Event Listener
// Listen for engagement events via DOM events
window.addEventListener('user_engagement_detected', (event) => {
const detail = event.detail;
console.log('Engagement detected:', detail.engagementType, detail.eventType, detail.message);
});Triggering Your Own Events
You can programmatically trigger custom engagement events for any scenario. This is perfect for triggering support chat when users encounter errors, validation failures, or other engagement opportunities.
// Trigger a custom engagement event
puppr.triggerEngagement(
'ENGAGEMENT', // engagementType: 'CONFUSION' | 'FRUSTRATION' | 'ENGAGEMENT'
'custom-engagement', // eventType: Your custom event identifier
'User needs assistance', // message: Human-readable description
{ // data: Optional additional data
userId: '123',
page: '/checkout',
error: 'Payment failed'
}
);Example: Trigger Support Chat on Form Validation Error
function validateForm() {
const value = parseFloat(document.getElementById('amount').value);
if (value < 1 || value > 1000) {
// Trigger engagement event to open support chat
puppr.triggerEngagement(
'ENGAGEMENT',
'form-validation-error',
'Invalid amount entered. Support chat triggered.',
{
value: value,
validRange: { min: 1, max: 1000 }
}
);
// Open your support chat (e.g., Crisp, Intercom, etc.)
if (window.$crisp) {
window.$crisp.push(['do', 'chat:open']);
}
}
}Example: Handle Different Engagement Types
puppr.onEngagement((detail) => {
switch(detail.engagementType) {
case 'FRUSTRATION':
// User is frustrated - show help immediately
showHelpTooltip(detail.message);
break;
case 'CONFUSION':
// User is confused - offer guided tour
offerGuidedTour();
break;
case 'ENGAGEMENT':
// High engagement - open support chat
openSupportChat();
break;
}
});Configuration
Individual Detector Options
Customize the sensitivity of each detector:
const puppr = new Puppr({
rageClick: {
clickCountThreshold: 5, // Number of clicks to trigger (default: 5)
timeThresholdMs: 1000, // Time window for clicks in ms (default: 1000)
distanceThresholdPx: 50 // Max distance between clicks in pixels (default: 50)
},
erraticMouse: {
reversalThreshold: 4, // Number of direction changes (default: 4)
timeThresholdMs: 500 // Time window for movements in ms (default: 500)
},
scrollThrash: {
reversalThreshold: 3, // Number of scroll reversals (default: 3)
timeThresholdMs: 2000 // Time window for thrashing in ms (default: 2000)
},
rapidNavigation: {
pageCountThreshold: 5, // Number of pages to visit (default: 5)
timeThresholdMs: 5000 // Time window for navigation in ms (default: 5000)
},
revisiting: {
visitCountThreshold: 3, // Number of visits to same page (default: 3)
historySize: 10 // Size of navigation history (default: 10)
}
});Cooldown Configuration
Puppr includes a cooldown system to prevent event spam. By default, there's a 3-second global cooldown between any engagement events.
const puppr = new Puppr();
// Set global cooldown (affects all engagement events)
puppr.setGlobalCooldown(5000); // 5 seconds
// Set event-specific cooldown (overrides global cooldown)
puppr.setEventTypeCooldown('scroll-thrash', 8000); // 8 seconds for scroll thrash
puppr.setEventTypeCooldown('rage-click', 2000); // 2 seconds for rage clicks
// Clear all cooldowns
puppr.clearAllCooldowns();
// Set deduplication window (default: 100ms)
puppr.setDedupWindow(200); // 200ms
// Clear deduplication cache
puppr.clearDedupCache();
// Get current deduplication window
const windowMs = puppr.getDedupWindow();Event Types
Puppr detects the following engagement signals:
| Event Type | Engagement Type | Description |
|------------|----------------|-------------|
| rage-click | FRUSTRATION | User clicked rapidly in the same area |
| erratic-mouse-movement | FRUSTRATION | User moved the mouse erratically |
| scroll-thrash | FRUSTRATION | User scrolled up and down rapidly |
| rapid-navigation | CONFUSION | User navigated between pages very quickly |
| revisiting-page | CONFUSION | User repeatedly visited the same page |
Event Detail Structure
{
engagementType: 'CONFUSION' | 'FRUSTRATION' | 'ENGAGEMENT',
eventType: string, // Event type (e.g., 'rage-click')
message: string, // Human-readable description
timestamp: number, // Unix timestamp
data: any // Additional event-specific data
}Examples
Complete Integration Example
// Initialize Puppr
const puppr = new Puppr({
rageClick: true,
erraticMouse: true,
scrollThrash: true,
rapidNavigation: true,
revisiting: true
});
// Subscribe to engagement events
puppr.onEngagement((detail) => {
console.log(`${detail.engagementType}: ${detail.eventType} - ${detail.message}`);
// Handle different engagement types
if (detail.engagementType === 'FRUSTRATION') {
// Show help tooltip or open support chat
showHelpMessage(detail.message);
} else if (detail.engagementType === 'CONFUSION') {
// Offer guided tour or help documentation
offerHelp(detail.message);
} else if (detail.engagementType === 'ENGAGEMENT') {
// High engagement - open support chat
openSupportChat();
}
});
// Subscribe to stat changes
puppr.onStatsChange((stats) => {
console.log('Stats:', stats);
// Update your analytics dashboard
updateAnalytics(stats);
});
// Start detection
puppr.start();Statistics and Monitoring
const puppr = new Puppr();
// Get current stats
const stats = puppr.getStats();
console.log('Current stats:', stats);
// {
// totalEvents: 15,
// blockedEvents: 3,
// engagements: 12
// }
// Subscribe to stat changes
puppr.onStatsChange((stats) => {
console.log('Stats updated:', stats);
// Update your dashboard or send to analytics
});Integration with Support Chat (Crisp, Intercom, etc.)
const puppr = new Puppr();
puppr.onEngagement((detail) => {
// Open support chat on engagement events
if (detail.engagementType === 'ENGAGEMENT') {
// Crisp
if (window.$crisp) {
window.$crisp.push(['do', 'chat:open']);
}
// Intercom
if (window.Intercom) {
window.Intercom('show');
}
// Custom chat widget
openChatWidget();
}
});
puppr.start();API Reference
Puppr Class
Constructor
new Puppr(options?: PupprOptions)Core Methods
start()
Start all configured detectors.
puppr.start();stop()
Stop all running detectors.
puppr.stop();isRunning()
Check if Puppr is currently running.
const isRunning = puppr.isRunning(); // returns booleanEvent Management
onEngagement(callback)
Subscribe to engagement events. Returns an unsubscribe function.
const unsubscribe = puppr.onEngagement((detail) => {
console.log('Engagement:', detail);
});
// Later, to unsubscribe:
unsubscribe();onStatsChange(callback)
Subscribe to stat changes. Returns an unsubscribe function.
const unsubscribe = puppr.onStatsChange((stats) => {
console.log('Stats:', stats);
});triggerEngagement(engagementType, eventType, message, data?)
Manually trigger an engagement event.
puppr.triggerEngagement(
'ENGAGEMENT',
'custom-event',
'User needs help',
{ userId: '123' }
);Configuration Methods
setGlobalCooldown(ms)
Set global cooldown for all engagement events (in milliseconds).
puppr.setGlobalCooldown(5000); // 5 secondssetEventTypeCooldown(eventType, ms)
Set cooldown for a specific event type (overrides global cooldown).
puppr.setEventTypeCooldown('scroll-thrash', 8000); // 8 secondsclearAllCooldowns()
Clear all cooldowns (global and event-specific).
puppr.clearAllCooldowns();setDedupWindow(ms)
Set deduplication window time (default: 100ms).
puppr.setDedupWindow(200); // 200msgetDedupWindow()
Get current deduplication window time.
const windowMs = puppr.getDedupWindow();clearDedupCache()
Clear the deduplication cache.
puppr.clearDedupCache();Statistics Methods
getStats()
Get current statistics.
const stats = puppr.getStats();
// Returns: { totalEvents: 15, blockedEvents: 3, engagements: 12 }Statistics
Puppr provides real-time statistics about detected events:
const stats = puppr.getStats();
// {
// totalEvents: 15, // Total number of events processed
// blockedEvents: 3, // Number of events blocked by cooldowns
// engagements: 12 // Number of successful engagement events dispatched
// }Browser Support
- Modern browsers with ES6+ support
- Chrome, Firefox, Safari, Edge (latest versions)
- IE11+ (with polyfills)
License
ISC
