humanbehavior-js
Version:
SDK for HumanBehavior session and event recording
839 lines (721 loc) âĸ 36.4 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HumanBehavior SDK - Simple SPA Demo</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: white;
}
.container {
max-width: 800px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.nav {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 40px;
}
.nav button {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 12px 24px;
border-radius: 25px;
cursor: pointer;
font-size: 16px;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.nav button:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.nav button.active {
background: rgba(255, 255, 255, 0.4);
border-color: rgba(255, 255, 255, 0.6);
}
.content {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 30px;
min-height: 400px;
backdrop-filter: blur(5px);
}
.button {
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
color: white;
border: none;
padding: 12px 24px;
border-radius: 25px;
cursor: pointer;
font-size: 16px;
margin: 10px;
transition: all 0.3s ease;
}
.button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.button.secondary {
background: linear-gradient(45deg, #4834d4, #686de0);
}
.button.success {
background: linear-gradient(45deg, #00b894, #00cec9);
}
.status {
background: rgba(0, 0, 0, 0.2);
border-radius: 10px;
padding: 15px;
margin: 20px 0;
font-family: monospace;
font-size: 14px;
}
.logs {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 15px;
margin: 20px 0;
max-height: 200px;
overflow-y: auto;
font-family: monospace;
font-size: 12px;
}
.fade-in {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<div class="container">
<h1>HumanBehavior SDK - Simple SPA Demo</h1>
<div class="nav">
<button class="nav-btn active" data-page="home">đ Home</button>
<button class="nav-btn" data-page="about">âšī¸ About</button>
<button class="nav-btn" data-page="demo">đ¯ Demo</button>
<button class="nav-btn" data-page="status">đ Status</button>
</div>
<div id="content" class="content fade-in">
<!-- Content will be loaded here -->
</div>
</div>
<!-- Load the HumanBehavior SDK -->
<script src="dist/index.min.js"></script>
<script>
// Global tracker instance
let tracker = null;
let currentPage = 'home';
// Page content definitions
const pages = {
home: {
title: 'đ Home',
content: `
<h2>Welcome to HumanBehavior SDK Demo</h2>
<p>This is a single-page application that demonstrates session continuity across page navigations.</p>
<div class="status">
<strong>Session Info:</strong><br>
Session ID: <span id="sessionId">Loading...</span><br>
End User ID: <span id="endUserId">Loading...</span><br>
User Type: <span id="userType">Loading...</span><br>
Current URL: <span id="currentUrl">Loading...</span>
</div>
<h3>Test Actions</h3>
<button class="button" onclick="trackCustomEvent('home_button_click', {page: 'home'})">
Track Custom Event
</button>
<button class="button secondary" onclick="testConsoleLog()">
Test Console Log
</button>
<button class="button success" onclick="testConsoleError()">
Test Console Error
</button>
<div class="logs" id="logs">
<div>Logs will appear here...</div>
</div>
`
},
about: {
title: 'âšī¸ About',
content: `
<h2>About This Demo</h2>
<p>This single-page application demonstrates how the HumanBehavior SDK maintains session continuity across page navigations.</p>
<h3>Key Features</h3>
<ul>
<li><strong>Session Continuity:</strong> The tracker never reinitializes when navigating between pages</li>
<li><strong>URL Tracking:</strong> All navigation events are captured and sent to the server</li>
<li><strong>Event Recording:</strong> User interactions, console logs, and custom events are all tracked</li>
<li><strong>Real-time Updates:</strong> Session information updates in real-time</li>
</ul>
<div class="status">
<strong>Current Session:</strong><br>
Session ID: <span id="sessionId-about">Loading...</span><br>
User Type: <span id="userType-about">Loading...</span><br>
Page Load Time: <span id="pageLoadTime">Loading...</span>
</div>
<button class="button" onclick="trackCustomEvent('about_page_viewed', {section: 'about'})">
Track About Page View
</button>
<button class="button secondary" onclick="showUserInfo()">
Show Detailed User Info
</button>
`
},
demo: {
title: 'đ¯ Interactive Demo',
content: `
<h2>Interactive Demo</h2>
<p>Test various tracking features and see them in action.</p>
<h3>Form Interaction</h3>
<form onsubmit="handleFormSubmit(event)">
<input type="text" id="demoInput" placeholder="Type something..." style="padding: 10px; border-radius: 5px; border: none; margin: 10px; width: 200px;">
<button type="submit" class="button">Submit Form</button>
</form>
<h3>Custom Events</h3>
<button class="button" onclick="trackCustomEvent('demo_button_1', {button: 'demo1'})">Demo Button 1</button>
<button class="button secondary" onclick="trackCustomEvent('demo_button_2', {button: 'demo2'})">Demo Button 2</button>
<button class="button success" onclick="trackCustomEvent('demo_button_3', {button: 'demo3'})">Demo Button 3</button>
<h3>User Authentication Test</h3>
<button class="button" onclick="testUserAuthentication()">
Test User Authentication
</button>
<p style="font-size: 12px; color: #666; margin-top: 5px;">
Tests the new behavior where endUserId stays the same but posthogName gets updated with email
</p>
<h3>Real User Authentication</h3>
<form onsubmit="handleAuthSubmit(event)" style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; margin: 15px 0;">
<div style="margin-bottom: 15px;">
<label for="authEmail" style="display: block; margin-bottom: 5px; font-weight: 600;">Email:</label>
<input type="email" id="authEmail" placeholder="your.email@example.com" required
style="width: 100%; padding: 10px; border-radius: 5px; border: none; background: rgba(255,255,255,0.9);">
</div>
<div style="margin-bottom: 15px;">
<label for="authName" style="display: block; margin-bottom: 5px; font-weight: 600;">Name:</label>
<input type="text" id="authName" placeholder="Your Name" required
style="width: 100%; padding: 10px; border-radius: 5px; border: none; background: rgba(255,255,255,0.9);">
</div>
<div style="margin-bottom: 15px;">
<label for="authPlan" style="display: block; margin-bottom: 5px; font-weight: 600;">Plan:</label>
<select id="authPlan" style="width: 100%; padding: 10px; border-radius: 5px; border: none; background: rgba(255,255,255,0.9);">
<option value="free">Free</option>
<option value="basic">Basic</option>
<option value="premium">Premium</option>
<option value="enterprise">Enterprise</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label for="authCompany" style="display: block; margin-bottom: 5px; font-weight: 600;">Company (Optional):</label>
<input type="text" id="authCompany" placeholder="Your Company"
style="width: 100%; padding: 10px; border-radius: 5px; border: none; background: rgba(255,255,255,0.9);">
</div>
<button type="submit" class="button success" style="width: 100%;">
đ Authenticate User
</button>
</form>
<div style="margin-top: 15px; display: flex; gap: 10px;">
<button class="button secondary" onclick="showAuthStatus()" style="flex: 1;">
đ Show Auth Status
</button>
</div>
<div id="authResult" style="margin-top: 15px; padding: 10px; border-radius: 5px; display: none;"></div>
<div class="logs" id="demoLogs">
<div>Demo events will appear here...</div>
</div>
`
},
status: {
title: 'đ Session Status',
content: `
<h2>Session Status & Debug Info</h2>
<div class="status">
<strong>Tracker Status:</strong><br>
Initialized: <span id="trackerInitialized">Loading...</span><br>
Session ID: <span id="sessionId-status">Loading...</span><br>
End User ID: <span id="endUserId-status">Loading...</span><br>
User Type: <span id="userType-status">Loading...</span><br>
Current URL: <span id="currentUrl-status">Loading...</span><br>
Connection Status: <span id="connectionStatus">Loading...</span>
</div>
<h3>Actions</h3>
<button class="button" onclick="viewLogs()">View Logs</button>
<button class="button secondary" onclick="testConnection()">Test Connection</button>
<button class="button success" onclick="clearLogs()">Clear Logs</button>
<div class="logs" id="statusLogs">
<div>Status logs will appear here...</div>
</div>
`
}
};
// Initialize the application
async function initApp() {
try {
console.log('Starting SPA initialization...');
// Check if SDK is loaded
if (typeof HumanBehaviorTracker === 'undefined') {
throw new Error('HumanBehaviorTracker SDK not loaded. Check if dist/index.js exists.');
}
// Initialize HumanBehavior tracker
console.log('Initializing tracker...');
tracker = HumanBehaviorTracker.init('13c3e029-ca45-4a3c-a33b-f5dcb297e31c', {
logLevel: 'debug',
redactFields: ['password', 'credit_card']
});
console.log('Tracker created, waiting for initialization...');
// Wait for tracker to initialize with timeout
if (tracker.initializationPromise) {
try {
await Promise.race([
tracker.initializationPromise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Initialization timeout')), 10000)
)
]);
console.log('Tracker initialized successfully');
} catch (initError) {
console.warn('Tracker initialization failed or timed out:', initError);
addLog('Tracker initialization warning: ' + initError.message);
// Continue anyway - tracker might still work
}
} else {
console.log('No initialization promise, continuing...');
}
// Set up navigation
setupNavigation();
// Load initial page
loadPage('home');
// Update status periodically
setInterval(updateStatus, 2000);
console.log('SPA App initialized successfully');
addLog('SPA App initialized successfully');
} catch (error) {
console.error('Failed to initialize SPA app:', error);
addLog('Error initializing app: ' + error.message);
// Show error in content
document.getElementById('content').innerHTML = `
<h2>Initialization Error</h2>
<p>Failed to initialize the HumanBehavior SDK:</p>
<div class="status">
<strong>Error:</strong> ${error.message}<br>
<strong>Stack:</strong> ${error.stack}
</div>
<button class="button" onclick="location.reload()">Reload Page</button>
`;
}
}
// Set up client-side navigation
function setupNavigation() {
const navButtons = document.querySelectorAll('.nav-btn');
navButtons.forEach(button => {
button.addEventListener('click', () => {
const page = button.dataset.page;
navigateToPage(page);
});
});
// Handle browser back/forward buttons
window.addEventListener('popstate', (event) => {
const page = event.state?.page || 'home';
loadPage(page);
updateActiveNavButton(page);
});
}
// Navigate to a page
function navigateToPage(page) {
if (page === currentPage) return;
console.log(`Navigating from ${currentPage} to ${page}`);
// Update URL without page reload
history.pushState({ page }, pages[page].title, `#${page}`);
// Load the page content
loadPage(page);
// Update active nav button
updateActiveNavButton(page);
// Track navigation event
if (tracker && tracker.trackNavigationEvent) {
try {
tracker.trackNavigationEvent('spa_navigation', currentPage, page);
} catch (error) {
console.error('Failed to track navigation:', error);
}
}
currentPage = page;
}
// Load page content
function loadPage(page) {
const contentDiv = document.getElementById('content');
const pageData = pages[page];
if (!pageData) {
contentDiv.innerHTML = '<h2>Page not found</h2>';
return;
}
// Add fade-in animation
contentDiv.classList.remove('fade-in');
void contentDiv.offsetWidth; // Trigger reflow
contentDiv.classList.add('fade-in');
contentDiv.innerHTML = pageData.content;
// Update page title
document.title = `HumanBehavior SDK - ${pageData.title}`;
// Track page view
if (tracker && tracker.trackPageView) {
try {
tracker.trackPageView();
} catch (error) {
console.error('Failed to track pageview:', error);
}
}
addLog(`Navigated to ${page} page`);
}
// Update active navigation button
function updateActiveNavButton(page) {
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.classList.remove('active');
});
const activeButton = document.querySelector(`[data-page="${page}"]`);
if (activeButton) {
activeButton.classList.add('active');
}
}
// Update status information
function updateStatus() {
if (!tracker) return;
try {
// Update session info
const sessionIdElements = document.querySelectorAll('#sessionId, #sessionId-about, #sessionId-status');
const sessionId = tracker.getSessionId ? tracker.getSessionId() : 'Not available';
sessionIdElements.forEach(el => {
if (el) el.textContent = sessionId;
});
const endUserIdElements = document.querySelectorAll('#endUserId, #endUserId-status');
const endUserId = tracker.endUserId || 'Not set';
endUserIdElements.forEach(el => {
if (el) el.textContent = endUserId;
});
// Update user type (preexisting vs new)
const userTypeElements = document.querySelectorAll('#userType, #userType-about, #userType-status');
let userType = 'Unknown';
if (tracker.isPreexistingUser) {
try {
const isPreexisting = tracker.isPreexistingUser();
userType = isPreexisting ? 'đ Preexisting User' : 'đ New User';
} catch (error) {
console.warn('Error checking preexisting user:', error);
userType = 'Error';
}
}
userTypeElements.forEach(el => {
if (el) el.textContent = userType;
});
const currentUrlElements = document.querySelectorAll('#currentUrl, #currentUrl-status');
const currentUrl = window.location.href;
currentUrlElements.forEach(el => {
if (el) el.textContent = currentUrl;
});
const trackerInitialized = document.getElementById('trackerInitialized');
if (trackerInitialized) {
trackerInitialized.textContent = tracker.initialized ? 'Yes' : 'No';
}
const connectionStatus = document.getElementById('connectionStatus');
if (connectionStatus && tracker.getConnectionStatus) {
try {
const status = tracker.getConnectionStatus();
connectionStatus.textContent = status.blocked ? 'Blocked' : 'Connected';
} catch (error) {
console.warn('Error getting connection status:', error);
connectionStatus.textContent = 'Error';
}
}
const pageLoadTime = document.getElementById('pageLoadTime');
if (pageLoadTime) {
pageLoadTime.textContent = new Date().toLocaleTimeString();
}
} catch (error) {
console.error('Error updating status:', error);
// Don't let status update errors crash the app
}
}
// Track custom events
async function trackCustomEvent(eventName, properties = {}) {
if (!tracker) {
addLog('Tracker not available');
return;
}
try {
if (tracker.customEvent) {
await tracker.customEvent(eventName, properties);
addLog(`Custom event tracked: ${eventName}`, properties);
} else {
addLog('customEvent method not available');
}
} catch (error) {
console.error('Error tracking custom event:', error);
addLog('Error tracking custom event: ' + error.message);
}
}
// Test user authentication (new behavior)
async function testUserAuthentication() {
if (!tracker) {
addLog('Tracker not available');
return;
}
try {
const originalEndUserId = tracker.getSessionId();
addLog(`Original endUserId: ${originalEndUserId}`);
// Simulate user sign-in
const userId = await tracker.addUserInfo({
userId: 'user123@example.com',
userProperties: {
email: 'user123@example.com',
name: 'John Doe',
plan: 'premium',
signupDate: new Date().toISOString()
}
});
const newEndUserId = tracker.getSessionId();
addLog(`New endUserId: ${newEndUserId}`);
addLog(`endUserId changed: ${originalEndUserId !== newEndUserId ? 'YES (BAD)' : 'NO (GOOD)'}`);
addLog(`User authenticated: ${userId}`);
// The endUserId should stay the same, but posthogName will be updated with email
addLog('â
endUserId maintained for session continuity');
addLog('â
posthogName will be updated with email for UI display');
} catch (error) {
console.error('Error testing user authentication:', error);
addLog('Error testing user authentication: ' + error.message);
}
}
// Test console logging
function testConsoleLog() {
console.log('This is a test console log message');
addLog('Console log test executed');
}
function testConsoleError() {
console.error('This is a test console error message');
addLog('Console error test executed');
}
// Handle form submission
function handleFormSubmit(event) {
event.preventDefault();
const input = document.getElementById('demoInput');
const value = input.value;
trackCustomEvent('form_submitted', { value: value });
addLog(`Form submitted with value: ${value}`);
input.value = '';
}
// Handle authentication form submission
async function handleAuthSubmit(event) {
event.preventDefault();
const email = document.getElementById('authEmail').value;
const name = document.getElementById('authName').value;
const plan = document.getElementById('authPlan').value;
const company = document.getElementById('authCompany').value;
if (!email || !name) {
alert('Email and Name are required for authentication.');
return;
}
try {
const userId = await tracker.addUserInfo({
userId: email, // Use email as userId
userProperties: {
email: email,
name: name,
plan: plan,
company: company,
signupDate: new Date().toISOString()
}
});
const newEndUserId = tracker.getSessionId();
addLog(`New endUserId after authentication: ${newEndUserId}`);
addLog(`endUserId changed: ${tracker.getSessionId() !== newEndUserId ? 'YES (BAD)' : 'NO (GOOD)'}`);
addLog(`User authenticated: ${userId}`);
const authResultDiv = document.getElementById('authResult');
authResultDiv.style.display = 'block';
authResultDiv.innerHTML = `
<strong>Authentication Successful!</strong><br>
New End User ID: ${newEndUserId}<br>
Original End User ID: ${tracker.getSessionId()}<br>
endUserId changed: ${tracker.getSessionId() !== newEndUserId ? 'YES (BAD)' : 'NO (GOOD)'}
`;
authResultDiv.style.color = 'green';
} catch (error) {
console.error('Error authenticating user:', error);
addLog('Error authenticating user: ' + error.message);
const authResultDiv = document.getElementById('authResult');
authResultDiv.style.display = 'block';
authResultDiv.innerHTML = `
<strong>Authentication Failed!</strong><br>
Error: ${error.message}
`;
authResultDiv.style.color = 'red';
}
}
// View logs
function viewLogs() {
if (tracker && tracker.viewLogs) {
tracker.viewLogs();
} else {
addLog('viewLogs method not available');
}
}
// Test connection
async function testConnection() {
if (!tracker) {
addLog('Tracker not available');
return;
}
try {
if (tracker.testConnection) {
const result = await tracker.testConnection();
addLog(`Connection test: ${result.success ? 'Success' : 'Failed'}`, result);
} else {
addLog('testConnection method not available');
}
} catch (error) {
addLog(`Connection test error: ${error.message}`);
}
}
// Clear logs
function clearLogs() {
const logElements = document.querySelectorAll('.logs');
logElements.forEach(el => {
el.innerHTML = '<div>Logs cleared...</div>';
});
}
// Show detailed user information
function showUserInfo() {
if (!tracker) {
addLog('Tracker not available');
return;
}
try {
const userInfo = tracker.getUserInfo ? tracker.getUserInfo() : null;
if (userInfo) {
const infoText = ` <strong>Detailed User Information:</strong><br>
End User ID: ${userInfo.endUserId || 'Not set'}<br>
Session ID: ${userInfo.sessionId}<br>
Is Preexisting User: ${userInfo.isPreexistingUser ? 'Yes' : 'No'}<br>
Initialized: ${userInfo.initialized ? 'Yes' : 'No'}<br>
<br>
<strong>Cookie Check:</strong><br>
End User Cookie: ${tracker.getCookie ? tracker.getCookie(`human_behavior_end_user_id_13c3e029-ca45-4a3c-a33b-f5dcb297e31c`) : 'Method not available'}<br>
Session Cookie: ${tracker.getCookie ? tracker.getCookie('human_behavior_session_id') : 'Method not available'}
`;
// Show in an alert or add to logs
addLog('Detailed user info displayed', userInfo);
alert(infoText);
} else {
addLog('getUserInfo method not available');
}
} catch (error) {
addLog(`Error getting user info: ${error.message}`);
}
}
// Show current authentication status
function showAuthStatus() {
if (!tracker) {
addLog('Tracker not available');
return;
}
try {
const userInfo = tracker.getUserInfo ? tracker.getUserInfo() : null;
const userAttributes = tracker.getUserAttributes ? tracker.getUserAttributes() : {};
if (userInfo) {
const statusText = `
<strong>Current Authentication Status:</strong><br>
End User ID: ${userInfo.endUserId || 'Not set'}<br>
Session ID: ${userInfo.sessionId}<br>
Is Preexisting User: ${userInfo.isPreexistingUser ? 'Yes' : 'No'}<br>
Initialized: ${userInfo.initialized ? 'Yes' : 'No'}<br>
<br>
<strong>User Attributes:</strong><br>
${Object.keys(userAttributes).length > 0 ?
Object.entries(userAttributes).map(([key, value]) => `${key}: ${value}`).join('<br>') :
'No user attributes set'
}
<br><br>
<strong>Cookie Check:</strong><br>
End User Cookie: ${tracker.getCookie ? tracker.getCookie(`human_behavior_end_user_id_13c3e029-ca45-4a3c-a33b-f5dcb297e31c`) : 'Method not available'}<br>
Session Cookie: ${tracker.getCookie ? tracker.getCookie('human_behavior_session_id') : 'Method not available'}
`;
addLog('Authentication status displayed', { userInfo, userAttributes });
alert(statusText);
} else {
addLog('getUserInfo method not available for status');
}
} catch (error) {
addLog(`Error getting authentication status: ${error.message}`);
}
}
// Add log message
function addLog(message, data = null) {
const logElements = document.querySelectorAll('.logs');
const timestamp = new Date().toLocaleTimeString();
const logEntry = `<div>[${timestamp}] ${message}${data ? ' - ' + JSON.stringify(data) : ''}</div>`;
logElements.forEach(el => {
el.innerHTML += logEntry;
el.scrollTop = el.scrollHeight;
});
// Also log to console
console.log(`[${timestamp}] ${message}`, data);
}
// Initialize the app when the page loads
document.addEventListener('DOMContentLoaded', initApp);
// Add global error handler to prevent page from going blank
window.addEventListener('error', function(event) {
console.error('Global error caught:', event.error);
addLog('Global error: ' + event.error.message);
// Prevent the page from going blank by showing error content
const contentDiv = document.getElementById('content');
if (contentDiv && contentDiv.innerHTML.trim() === '') {
contentDiv.innerHTML = `
<h2>Application Error</h2>
<p>An error occurred but the application is still running:</p>
<div class="status">
<strong>Error:</strong> ${event.error.message}<br>
<strong>File:</strong> ${event.filename}<br>
<strong>Line:</strong> ${event.lineno}
</div>
<button class="button" onclick="loadPage('home')">Go to Home</button>
<button class="button secondary" onclick="location.reload()">Reload Page</button>
`;
}
});
// Add unhandled promise rejection handler
window.addEventListener('unhandledrejection', function(event) {
console.error('Unhandled promise rejection:', event.reason);
addLog('Unhandled promise rejection: ' + event.reason);
event.preventDefault(); // Prevent the default browser behavior
});
// Add periodic health check
setInterval(() => {
try {
const contentDiv = document.getElementById('content');
if (contentDiv && contentDiv.innerHTML.trim() === '') {
console.warn('Content div is empty, attempting to restore...');
loadPage('home');
}
} catch (error) {
console.error('Health check error:', error);
}
}, 5000); // Check every 5 seconds
</script>
</body>
</html>