UNPKG

humanbehavior-js

Version:

SDK for HumanBehavior session and event recording

839 lines (721 loc) â€ĸ 36.4 kB
<!DOCTYPE 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>