diff --git a/.gitignore b/.gitignore
index 2d1218b..0f64611 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,25 +1,65 @@
-# Bytecode-Dateien
+# Bytecode files
__pycache__/
*.py[cod]
-# Virtuelle Umgebungen
+# Virtual environments
.venv/
venv/
-# Betriebssystem-Dateien
+# System files
.DS_Store
Thumbs.db
-# Logfiles und Dumps
+# Logs and temporary files
*.log
*.bak
*.swp
*.tmp
-# IDEs und Editoren
+# Node.js dependencies
+node_modules/
+package.json
+package-lock.json
+yarn.lock
+
+# Development documentation
+PERFORMANCE-TESTING.md
+
+# Build and distribution
+dist/
+build/
+*.min.js
+*.min.css
+*.map
+
+# Testing
+coverage/
+*.test.js
+*.spec.js
+.nyc_output/
+
+# Environment variables
+.env
+.env.*
+!.env.example
+
+# Debug logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# IDEs and editors
.vscode/
.idea/
+*.sublime-workspace
+*.sublime-project
+# Local development
+.cache/
+.temp/
+.tmp/
+
+# Project specific
data/*
!data/.gitignore
@@ -28,3 +68,17 @@ log/*
streams/*
!streams/.gitignore
+
+# Test files
+tests/**/*.js
+!tests/*.test.js
+!tests/*.spec.js
+!tests/README.md
+!tests/profile-auth.js
+
+# Performance test results
+performance-results/*
+!performance-results/.gitkeep
+
+# Legacy files
+public_streams.txt
diff --git a/public_streams.txt b/public_streams.txt
deleted file mode 100644
index 3eb027b..0000000
--- a/public_streams.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-{"uid":"oibchello","size":3371119,"mtime":1752994076}
-{"uid":"orangeicebear","size":1734396,"mtime":1748767975}
diff --git a/run-navigation-test.js b/run-navigation-test.js
new file mode 100644
index 0000000..ef2d9fa
--- /dev/null
+++ b/run-navigation-test.js
@@ -0,0 +1,179 @@
+const puppeteer = require('puppeteer');
+const fs = require('fs');
+const path = require('path');
+
+// Configuration
+const BASE_URL = 'http://localhost:8000'; // Update this if your app runs on a different URL
+const TEST_ITERATIONS = 5;
+const OUTPUT_DIR = path.join(__dirname, 'performance-results');
+const TIMESTAMP = new Date().toISOString().replace(/[:.]/g, '-');
+
+// Ensure output directory exists
+if (!fs.existsSync(OUTPUT_DIR)) {
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
+}
+
+// Helper function to save results
+function saveResults(data, filename) {
+ const filepath = path.join(OUTPUT_DIR, `${filename}-${TIMESTAMP}.json`);
+ fs.writeFileSync(filepath, JSON.stringify(data, null, 2));
+ console.log(`Results saved to ${filepath}`);
+ return filepath;
+}
+
+// Test runner
+async function runNavigationTest() {
+ const browser = await puppeteer.launch({
+ headless: false, // Set to true for CI/CD
+ devtools: true,
+ args: [
+ '--no-sandbox',
+ '--disable-setuid-sandbox',
+ '--disable-dev-shm-usage',
+ '--disable-accelerated-2d-canvas',
+ '--no-first-run',
+ '--no-zygote',
+ '--single-process',
+ '--disable-gpu',
+ '--js-flags="--max-old-space-size=1024"'
+ ]
+ });
+
+ try {
+ const page = await browser.newPage();
+
+ // Enable performance metrics
+ await page.setViewport({ width: 1280, height: 800 });
+ await page.setDefaultNavigationTimeout(60000);
+
+ // Set up console logging
+ page.on('console', msg => console.log('PAGE LOG:', msg.text()));
+
+ // Load the performance test script
+ const testScript = fs.readFileSync(path.join(__dirname, 'static', 'router-perf-test.js'), 'utf8');
+
+ // Test guest mode
+ console.log('Testing guest mode...');
+ await page.goto(`${BASE_URL}`, { waitUntil: 'networkidle0' });
+
+ // Inject and run the test
+ const guestResults = await page.evaluate(async (script) => {
+ // Inject the test script
+ const scriptEl = document.createElement('script');
+ scriptEl.textContent = script;
+ document.head.appendChild(scriptEl);
+
+ // Run the test
+ const test = new RouterPerfTest();
+ return await test.runTest('guest');
+ }, testScript);
+
+ saveResults(guestResults, 'guest-results');
+
+ // Test logged-in mode (if credentials are provided)
+ if (process.env.LOGIN_EMAIL && process.env.LOGIN_PASSWORD) {
+ console.log('Testing logged-in mode...');
+
+ // Navigate to the test page with authentication token
+ console.log('Authenticating with provided token...');
+ await page.goto('https://dicta2stream.net/?token=d96561d7-6c95-4e10-80f7-62d5d3a5bd04', {
+ waitUntil: 'networkidle0',
+ timeout: 60000
+ });
+
+ // Wait for authentication to complete and verify
+ try {
+ await page.waitForSelector('body.authenticated', {
+ timeout: 30000,
+ visible: true
+ });
+ console.log('✅ Successfully authenticated');
+
+ // Verify user is actually logged in
+ const isAuthenticated = await page.evaluate(() => {
+ return document.body.classList.contains('authenticated');
+ });
+
+ if (!isAuthenticated) {
+ throw new Error('Authentication failed - not in authenticated state');
+ }
+
+ // Force a navigation to ensure the state is stable
+ await page.goto('https://dicta2stream.net/#welcome-page', {
+ waitUntil: 'networkidle0',
+ timeout: 30000
+ });
+
+ } catch (error) {
+ console.error('❌ Authentication failed:', error.message);
+ // Take a screenshot for debugging
+ await page.screenshot({ path: 'auth-failure.png' });
+ console.log('Screenshot saved as auth-failure.png');
+ throw error;
+ }
+
+ // Wait for the page to fully load after login
+ await page.waitForTimeout(2000);
+
+ // Run the test in logged-in mode
+ const loggedInResults = await page.evaluate(async (script) => {
+ const test = new RouterPerfTest();
+ return await test.runTest('loggedIn');
+ }, testScript);
+
+ saveResults(loggedInResults, 'loggedin-results');
+
+ // Generate comparison report
+ const comparison = {
+ timestamp: new Date().toISOString(),
+ guest: {
+ avg: guestResults.overall.avg,
+ min: guestResults.overall.min,
+ max: guestResults.overall.max
+ },
+ loggedIn: {
+ avg: loggedInResults.overall.avg,
+ min: loggedInResults.overall.min,
+ max: loggedInResults.overall.max
+ },
+ difference: {
+ ms: loggedInResults.overall.avg - guestResults.overall.avg,
+ percent: ((loggedInResults.overall.avg - guestResults.overall.avg) / guestResults.overall.avg) * 100
+ }
+ };
+
+ const reportPath = saveResults(comparison, 'performance-comparison');
+ console.log(`\nPerformance comparison report generated at: ${reportPath}`);
+
+ // Take a screenshot of the results
+ await page.screenshot({ path: path.join(OUTPUT_DIR, `results-${TIMESTAMP}.png`), fullPage: true });
+
+ return comparison;
+ }
+
+ return guestResults;
+
+ } catch (error) {
+ console.error('Test failed:', error);
+ // Take a screenshot on error
+ if (page) {
+ await page.screenshot({ path: path.join(OUTPUT_DIR, `error-${TIMESTAMP}.png`), fullPage: true });
+ }
+ throw error;
+
+ } finally {
+ await browser.close();
+ }
+}
+
+// Run the test
+runNavigationTest()
+ .then(results => {
+ console.log('Test completed successfully');
+ console.log('Results:', JSON.stringify(results, null, 2));
+ process.exit(0);
+ })
+ .catch(error => {
+ console.error('Test failed:', error);
+ process.exit(1);
+ });
diff --git a/static/app.js b/static/app.js
index 0025e3a..afc81bb 100644
--- a/static/app.js
+++ b/static/app.js
@@ -505,10 +505,15 @@ window.clearTimeout = (id) => {
originalClearTimeout(id);
};
-// Track auth check calls
+// Track auth check calls and cache state
let lastAuthCheckTime = 0;
let authCheckCounter = 0;
const AUTH_CHECK_DEBOUNCE = 1000; // 1 second
+let authStateCache = {
+ timestamp: 0,
+ value: null,
+ ttl: 5000 // Cache TTL in milliseconds
+};
// Override console.log to capture all logs
const originalConsoleLog = console.log;
@@ -822,33 +827,46 @@ function updateAccountDeletionVisibility(isAuthenticated) {
});
}
-// Check authentication state and update UI
-function checkAuthState() {
- // Debounce rapid calls
+// Check authentication state and update UI with caching and debouncing
+function checkAuthState(force = false) {
const now = Date.now();
- if (now - lastAuthCheckTime < AUTH_CHECK_DEBOUNCE) {
+
+ // Return cached value if still valid and not forcing a refresh
+ if (!force && now - authStateCache.timestamp < authStateCache.ttl && authStateCache.value !== null) {
+ return authStateCache.value;
+ }
+
+ // Debounce rapid calls
+ if (now - lastAuthCheckTime < AUTH_CHECK_DEBOUNCE && !force) {
return wasAuthenticated === true;
}
+
lastAuthCheckTime = now;
authCheckCounter++;
-
- // Check various authentication indicators
- const hasAuthCookie = document.cookie.includes('isAuthenticated=true');
- const hasUidCookie = document.cookie.includes('uid=');
- const hasLocalStorageAuth = localStorage.getItem('isAuthenticated') === 'true';
- const hasAuthToken = !!localStorage.getItem('authToken');
- // User is considered authenticated if any of these are true
- const isAuthenticated = hasAuthCookie || hasUidCookie || hasLocalStorageAuth || hasAuthToken;
+ // Use a single check for authentication state
+ let isAuthenticated = false;
+
+ // Check the most likely indicators first for better performance
+ isAuthenticated =
+ document.cookie.includes('isAuthenticated=') ||
+ document.cookie.includes('uid=') ||
+ localStorage.getItem('isAuthenticated') === 'true' ||
+ !!localStorage.getItem('authToken');
+
+ // Update cache
+ authStateCache = {
+ timestamp: now,
+ value: isAuthenticated,
+ ttl: isAuthenticated ? 30000 : 5000 // Longer TTL for authenticated users
+ };
- if (DEBUG_AUTH_STATE || isAuthenticated !== wasAuthenticated) {
+ if (DEBUG_AUTH_STATE && isAuthenticated !== wasAuthenticated) {
console.log('Auth State Check:', {
- hasAuthCookie,
- hasUidCookie,
- hasLocalStorageAuth,
- hasAuthToken,
isAuthenticated,
- wasAuthenticated
+ wasAuthenticated,
+ cacheHit: !force && now - authStateCache.timestamp < authStateCache.ttl,
+ cacheAge: now - authStateCache.timestamp
});
}
@@ -887,19 +905,44 @@ function checkAuthState() {
return isAuthenticated;
}
-// Periodically check authentication state
+// Periodically check authentication state with optimized polling
function setupAuthStatePolling() {
- // Initial check
- checkAuthState();
+ // Initial check with force to ensure we get the latest state
+ checkAuthState(true);
- // Check every 30 seconds instead of 2 to reduce load
- setInterval(checkAuthState, 30000);
+ // Use a single interval for all checks
+ const checkAndUpdate = () => {
+ // Only force check if the page is visible
+ checkAuthState(!document.hidden);
+ };
- // Also check after certain events that might affect auth state
- window.addEventListener('storage', checkAuthState);
- document.addEventListener('visibilitychange', () => {
- if (!document.hidden) checkAuthState();
- });
+ // Check every 30 seconds (reduced from previous implementation)
+ const AUTH_CHECK_INTERVAL = 30000;
+ setInterval(checkAndUpdate, AUTH_CHECK_INTERVAL);
+
+ // Listen for storage events (like login/logout from other tabs)
+ const handleStorageEvent = (e) => {
+ if (['isAuthenticated', 'authToken', 'uid'].includes(e.key)) {
+ checkAuthState(true); // Force check on relevant storage changes
+ }
+ };
+
+ window.addEventListener('storage', handleStorageEvent);
+
+ // Check auth state when page becomes visible
+ const handleVisibilityChange = () => {
+ if (!document.hidden) {
+ checkAuthState(true);
+ }
+ };
+
+ document.addEventListener('visibilitychange', handleVisibilityChange);
+
+ // Cleanup function
+ return () => {
+ window.removeEventListener('storage', handleStorageEvent);
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
+ };
}
diff --git a/static/fix-nav.js b/static/fix-nav.js
index 3ce5bc4..8c34805 100644
--- a/static/fix-nav.js
+++ b/static/fix-nav.js
@@ -1,50 +1,69 @@
-// Force hide guest navigation for authenticated users
-function fixMobileNavigation() {
- console.log('[FIX-NAV] Running navigation fix...');
+// Debounce helper function
+function debounce(func, wait) {
+ let timeout;
+ return function() {
+ const context = this;
+ const args = arguments;
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(context, args), wait);
+ };
+}
+
+// Throttle helper function
+function throttle(func, limit) {
+ let inThrottle;
+ return function() {
+ const args = arguments;
+ const context = this;
+ if (!inThrottle) {
+ func.apply(context, args);
+ inThrottle = true;
+ setTimeout(() => inThrottle = false, limit);
+ }
+ };
+}
+
+// Check authentication state once and cache it
+function getAuthState() {
+ return (
+ document.cookie.includes('isAuthenticated=') ||
+ document.cookie.includes('uid=') ||
+ localStorage.getItem('isAuthenticated') === 'true' ||
+ !!localStorage.getItem('authToken')
+ );
+}
+
+// Update navigation based on authentication state
+function updateNavigation() {
+ const isAuthenticated = getAuthState();
- // Check if user is authenticated
- const hasAuthCookie = document.cookie.includes('isAuthenticated=true');
- const hasUidCookie = document.cookie.includes('uid=');
- const hasLocalStorageAuth = localStorage.getItem('isAuthenticated') === 'true';
- const hasAuthToken = localStorage.getItem('authToken') !== null;
- const isAuthenticated = hasAuthCookie || hasUidCookie || hasLocalStorageAuth || hasAuthToken;
-
- console.log('[FIX-NAV] Authentication state:', {
- isAuthenticated,
- hasAuthCookie,
- hasUidCookie,
- hasLocalStorageAuth,
- hasAuthToken
- });
+ // Only proceed if the authentication state has changed
+ if (isAuthenticated === updateNavigation.lastState) {
+ return;
+ }
+ updateNavigation.lastState = isAuthenticated;
if (isAuthenticated) {
- // Force hide guest navigation with !important styles
+ // Hide guest navigation for authenticated users
const guestNav = document.getElementById('guest-dashboard');
if (guestNav) {
- console.log('[FIX-NAV] Hiding guest navigation');
guestNav.style.cssText = `
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
height: 0 !important;
- width: 0 !important;
- padding: 0 !important;
- margin: 0 !important;
- border: none !important;
- position: absolute !important;
overflow: hidden !important;
+ position: absolute !important;
clip: rect(0, 0, 0, 0) !important;
pointer-events: none !important;
`;
- guestNav.classList.add('force-hidden');
}
- // Ensure user navigation is visible with !important styles
+ // Show user navigation if it exists
const userNav = document.getElementById('user-dashboard');
if (userNav) {
- console.log('[FIX-NAV] Showing user navigation');
userNav.style.cssText = `
- display: flex !important;
+ display: block !important;
visibility: visible !important;
opacity: 1 !important;
height: auto !important;
@@ -55,25 +74,9 @@ function fixMobileNavigation() {
userNav.classList.add('force-visible');
}
- // Add authenticated class to body
+ // Update body classes
document.body.classList.add('authenticated');
document.body.classList.remove('guest-mode');
-
- // Prevent default behavior of nav links that might cause page reloads
- document.querySelectorAll('a[href^="#"]').forEach(link => {
- link.addEventListener('click', (e) => {
- e.preventDefault();
- const targetId = link.getAttribute('href');
- if (targetId && targetId !== '#') {
- // Use history API to update URL without full page reload
- history.pushState(null, '', targetId);
- // Dispatch a custom event that other scripts can listen for
- window.dispatchEvent(new CustomEvent('hashchange'));
- // Force re-apply our navigation fix
- setTimeout(fixMobileNavigation, 0);
- }
- });
- });
} else {
// User is not authenticated - ensure guest nav is visible
const guestNav = document.getElementById('guest-dashboard');
@@ -85,50 +88,53 @@ function fixMobileNavigation() {
}
}
-// Run on page load
-document.addEventListener('DOMContentLoaded', fixMobileNavigation);
+// Initialize the navigation state
+updateNavigation.lastState = null;
-// Also run after a short delay to catch any dynamic content
-setTimeout(fixMobileNavigation, 100);
-setTimeout(fixMobileNavigation, 300);
-setTimeout(fixMobileNavigation, 1000);
-
-// Listen for hash changes (navigation)
-window.addEventListener('hashchange', fixMobileNavigation);
-
-// Listen for pushState/replaceState (SPA navigation)
-const originalPushState = history.pushState;
-const originalReplaceState = history.replaceState;
-
-history.pushState = function() {
- originalPushState.apply(this, arguments);
- setTimeout(fixMobileNavigation, 0);
-};
-
-history.replaceState = function() {
- originalReplaceState.apply(this, arguments);
- setTimeout(fixMobileNavigation, 0);
-};
-
-// Run on any DOM mutations (for dynamically loaded content)
-const observer = new MutationObserver((mutations) => {
- let shouldFix = false;
- mutations.forEach((mutation) => {
- if (mutation.addedNodes.length || mutation.removedNodes.length) {
- shouldFix = true;
- }
- });
- if (shouldFix) {
- setTimeout(fixMobileNavigation, 0);
+// Handle navigation link clicks
+function handleNavLinkClick(e) {
+ const link = e.target.closest('a[href^="#"]');
+ if (!link) return;
+
+ e.preventDefault();
+ const targetId = link.getAttribute('href');
+ if (targetId && targetId !== '#') {
+ // Update URL without triggering full page reload
+ history.pushState(null, '', targetId);
+ // Dispatch a custom event for other scripts
+ window.dispatchEvent(new CustomEvent('hashchange'));
}
-});
+}
-observer.observe(document.body, {
- childList: true,
- subtree: true,
- attributes: true,
- attributeFilter: ['class', 'style', 'id']
-});
+// Initialize the navigation system
+function initNavigation() {
+ // Set up event delegation for navigation links
+ document.body.addEventListener('click', handleNavLinkClick);
+
+ // Listen for hash changes (throttled)
+ window.addEventListener('hashchange', throttle(updateNavigation, 100));
+
+ // Listen for storage changes (like login/logout from other tabs)
+ window.addEventListener('storage', debounce(updateNavigation, 100));
+
+ // Check for authentication changes periodically (every 30 seconds)
+ setInterval(updateNavigation, 30000);
+
+ // Initial update
+ updateNavigation();
+}
-// Export for debugging
-window.fixMobileNavigation = fixMobileNavigation;
+// Run initialization when DOM is ready
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initNavigation);
+} else {
+ // DOMContentLoaded has already fired
+ initNavigation();
+}
+
+// Export for testing if needed
+window.navigationUtils = {
+ updateNavigation,
+ getAuthState,
+ initNavigation
+};
diff --git a/static/footer.html b/static/footer.html
index 3a8684b..bb32a29 100644
--- a/static/footer.html
+++ b/static/footer.html
@@ -8,5 +8,7 @@
Privacy
•
Imprint
+ •
+ Your Stream
diff --git a/static/nav.js b/static/nav.js
index 61d2684..ea54db3 100644
--- a/static/nav.js
+++ b/static/nav.js
@@ -258,8 +258,15 @@ document.addEventListener("DOMContentLoaded", () => {
e.preventDefault();
const target = link.dataset.target;
if (target) {
- // Show the target section without updating URL hash
- showSection(target);
+ // Update URL hash to maintain proper history state
+ window.location.hash = target;
+ // Use the router to handle the navigation
+ if (router && typeof router.showOnly === 'function') {
+ router.showOnly(target);
+ } else {
+ // Fallback to showSection if router is not available
+ showSection(target);
+ }
}
});
});
diff --git a/tests/profile-auth.js b/tests/profile-auth.js
new file mode 100644
index 0000000..e19d536
--- /dev/null
+++ b/tests/profile-auth.js
@@ -0,0 +1,374 @@
+/**
+ * Authentication Profiling Tool for dicta2stream
+ *
+ * This script profiles the authentication-related operations during navigation
+ * to identify performance bottlenecks in the logged-in experience.
+ *
+ * Usage:
+ * 1. Open browser console (F12 → Console)
+ * 2. Copy and paste this entire script
+ * 3. Run: profileAuthNavigation()
+ */
+
+(function() {
+ 'use strict';
+
+ // Store original methods we want to profile
+ const originalFetch = window.fetch;
+ const originalXHROpen = XMLHttpRequest.prototype.open;
+ const originalXHRSend = XMLHttpRequest.prototype.send;
+
+ // Track authentication-related operations with detailed metrics
+ const authProfile = {
+ // Core metrics
+ startTime: null,
+ operations: [],
+ navigationEvents: [],
+
+ // Counters
+ fetchCount: 0,
+ xhrCount: 0,
+ domUpdates: 0,
+ authChecks: 0,
+
+ // Performance metrics
+ totalTime: 0,
+ maxFrameTime: 0,
+ longTasks: [],
+
+ // Memory tracking
+ memorySamples: [],
+ maxMemory: 0,
+
+ // Navigation tracking
+ currentNavigation: null,
+ navigationStart: null
+ };
+
+ // Track long tasks and performance metrics
+ if (window.PerformanceObserver) {
+ // Check which entry types are supported
+ const supportedEntryTypes = PerformanceObserver.supportedEntryTypes || [];
+
+ // Only set up observers for supported types
+ const perfObserver = new PerformanceObserver((list) => {
+ const entries = list.getEntries();
+ for (const entry of entries) {
+ // Track any task longer than 50ms as a long task
+ if (entry.duration > 50) {
+ authProfile.longTasks.push({
+ startTime: entry.startTime,
+ duration: entry.duration,
+ name: entry.name || 'unknown',
+ type: entry.entryType
+ });
+ }
+ }
+ });
+
+ // Try to observe supported entry types
+ try {
+ // Check for longtask support (not available in all browsers)
+ if (supportedEntryTypes.includes('longtask')) {
+ perfObserver.observe({ entryTypes: ['longtask'] });
+ }
+
+ // Always try to observe paint timing
+ try {
+ if (supportedEntryTypes.includes('paint')) {
+ perfObserver.observe({ entryTypes: ['paint'] });
+ } else {
+ // Fallback to buffered paint observation
+ perfObserver.observe({ type: 'paint', buffered: true });
+ }
+ } catch (e) {
+ console.debug('Paint timing not supported:', e.message);
+ }
+
+ } catch (e) {
+ console.debug('Performance observation not supported:', e.message);
+ }
+ }
+
+ // Instrument fetch API
+ window.fetch = async function(...args) {
+ const url = typeof args[0] === 'string' ? args[0] : args[0].url;
+ const isAuthRelated = isAuthOperation(url);
+
+ if (isAuthRelated) {
+ const start = performance.now();
+ authProfile.fetchCount++;
+
+ try {
+ const response = await originalFetch.apply(this, args);
+ const duration = performance.now() - start;
+
+ authProfile.operations.push({
+ type: 'fetch',
+ url,
+ duration,
+ timestamp: new Date().toISOString(),
+ status: response.status,
+ size: response.headers.get('content-length') || 'unknown'
+ });
+
+ return response;
+ } catch (error) {
+ const duration = performance.now() - start;
+ authProfile.operations.push({
+ type: 'fetch',
+ url,
+ duration,
+ timestamp: new Date().toISOString(),
+ error: error.message,
+ status: 'error'
+ });
+ throw error;
+ }
+ }
+
+ return originalFetch.apply(this, args);
+ };
+
+ // Instrument XHR
+ XMLHttpRequest.prototype.open = function(method, url) {
+ this._authProfile = isAuthOperation(url);
+ if (this._authProfile) {
+ this._startTime = performance.now();
+ this._url = url;
+ authProfile.xhrCount++;
+ }
+ return originalXHROpen.apply(this, arguments);
+ };
+
+ XMLHttpRequest.prototype.send = function(body) {
+ if (this._authProfile) {
+ this.addEventListener('load', () => {
+ const duration = performance.now() - this._startTime;
+ authProfile.operations.push({
+ type: 'xhr',
+ url: this._url,
+ duration,
+ timestamp: new Date().toISOString(),
+ status: this.status,
+ size: this.getResponseHeader('content-length') || 'unknown'
+ });
+ });
+
+ this.addEventListener('error', (error) => {
+ const duration = performance.now() - this._startTime;
+ authProfile.operations.push({
+ type: 'xhr',
+ url: this._url,
+ duration,
+ timestamp: new Date().toISOString(),
+ error: error.message,
+ status: 'error'
+ });
+ });
+ }
+
+ return originalXHRSend.apply(this, arguments);
+ };
+
+ // Track DOM updates after navigation with more details
+ const observer = new MutationObserver((mutations) => {
+ if (document.body.classList.contains('authenticated')) {
+ const now = performance.now();
+ const updateInfo = {
+ timestamp: now,
+ mutations: mutations.length,
+ addedNodes: 0,
+ removedNodes: 0,
+ attributeChanges: 0,
+ characterDataChanges: 0
+ };
+
+ mutations.forEach(mutation => {
+ updateInfo.addedNodes += mutation.addedNodes.length || 0;
+ updateInfo.removedNodes += mutation.removedNodes.length || 0;
+ updateInfo.attributeChanges += mutation.type === 'attributes' ? 1 : 0;
+ updateInfo.characterDataChanges += mutation.type === 'characterData' ? 1 : 0;
+ });
+
+ authProfile.domUpdates += mutations.length;
+
+ // Track memory usage if supported
+ if (window.performance && window.performance.memory) {
+ updateInfo.memoryUsed = performance.memory.usedJSHeapSize;
+ updateInfo.memoryTotal = performance.memory.totalJSHeapSize;
+ updateInfo.memoryLimit = performance.memory.jsHeapSizeLimit;
+
+ authProfile.memorySamples.push({
+ timestamp: now,
+ memory: performance.memory.usedJSHeapSize
+ });
+
+ authProfile.maxMemory = Math.max(
+ authProfile.maxMemory,
+ performance.memory.usedJSHeapSize
+ );
+ }
+
+ // Track frame time
+ requestAnimationFrame(() => {
+ const frameTime = performance.now() - now;
+ authProfile.maxFrameTime = Math.max(authProfile.maxFrameTime, frameTime);
+ });
+ }
+ });
+
+ // Track authentication state changes
+ const originalAddClass = DOMTokenList.prototype.add;
+ const originalRemoveClass = DOMTokenList.prototype.remove;
+
+ DOMTokenList.prototype.add = function(...args) {
+ if (this === document.body.classList && args[0] === 'authenticated') {
+ authProfile.authChecks++;
+ if (!authProfile.startTime) {
+ authProfile.startTime = performance.now();
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ attributes: true,
+ characterData: true
+ });
+ }
+ }
+ return originalAddClass.apply(this, args);
+ };
+
+ DOMTokenList.prototype.remove = function(...args) {
+ if (this === document.body.classList && args[0] === 'authenticated') {
+ authProfile.totalTime = performance.now() - (authProfile.startTime || performance.now());
+ observer.disconnect();
+ }
+ return originalRemoveClass.apply(this, args);
+ };
+
+ // Helper to identify auth-related operations
+ function isAuthOperation(url) {
+ if (!url) return false;
+ const authKeywords = [
+ 'auth', 'login', 'session', 'token', 'user', 'profile',
+ 'me', 'account', 'verify', 'validate', 'check'
+ ];
+ return authKeywords.some(keyword =>
+ url.toLowerCase().includes(keyword)
+ );
+ }
+
+ // Main function to run the profile
+ window.profileAuthNavigation = async function() {
+ // Reset profile with all metrics
+ Object.assign(authProfile, {
+ startTime: null,
+ operations: [],
+ navigationEvents: [],
+ fetchCount: 0,
+ xhrCount: 0,
+ domUpdates: 0,
+ authChecks: 0,
+ totalTime: 0,
+ maxFrameTime: 0,
+ longTasks: [],
+ memorySamples: [],
+ maxMemory: 0,
+ currentNavigation: null,
+ navigationStart: null
+ });
+
+ // Track navigation events
+ if (window.performance) {
+ const navObserver = new PerformanceObserver((list) => {
+ list.getEntries().forEach(entry => {
+ if (entry.entryType === 'navigation') {
+ authProfile.navigationEvents.push({
+ type: 'navigation',
+ name: entry.name,
+ startTime: entry.startTime,
+ duration: entry.duration,
+ domComplete: entry.domComplete,
+ domContentLoaded: entry.domContentLoadedEventEnd,
+ load: entry.loadEventEnd
+ });
+ }
+ });
+ });
+
+ try {
+ navObserver.observe({ entryTypes: ['navigation'] });
+ } catch (e) {
+ console.warn('Navigation timing not supported:', e);
+ }
+ }
+
+ console.log('Starting authentication navigation profile...');
+ console.log('1. Navigate to a few pages while logged in');
+ console.log('2. Run getAuthProfile() to see the results');
+ console.log('3. Run resetAuthProfile() to start over');
+
+ // Add global accessor
+ window.getAuthProfile = function() {
+ const now = performance.now();
+ const duration = authProfile.startTime ? (now - authProfile.startTime) / 1000 : 0;
+
+ console.log('%c\n=== AUTHENTICATION PROFILE RESULTS ===', 'font-weight:bold;font-size:1.2em');
+
+ // Summary
+ console.log('\n%c--- PERFORMANCE SUMMARY ---', 'font-weight:bold');
+ console.log(`Total Monitoring Time: ${duration.toFixed(2)}s`);
+ console.log(`Authentication Checks: ${authProfile.authChecks}`);
+ console.log(`Fetch API Calls: ${authProfile.fetchCount}`);
+ console.log(`XHR Requests: ${authProfile.xhrCount}`);
+ console.log(`DOM Updates: ${authProfile.domUpdates}`);
+ console.log(`Max Frame Time: ${authProfile.maxFrameTime.toFixed(2)}ms`);
+
+ // Memory usage
+ if (authProfile.memorySamples.length > 0) {
+ const lastMem = authProfile.memorySamples[authProfile.memorySamples.length - 1];
+ console.log(`\n%c--- MEMORY USAGE ---`, 'font-weight:bold');
+ console.log(`Max Memory Used: ${(authProfile.maxMemory / 1024 / 1024).toFixed(2)} MB`);
+ console.log(`Current Memory: ${(lastMem.memory / 1024 / 1024).toFixed(2)} MB`);
+ }
+
+ // Long tasks
+ if (authProfile.longTasks.length > 0) {
+ console.log(`\n%c--- LONG TASKS (${authProfile.longTasks.length} > 50ms) ---`, 'font-weight:bold');
+ authProfile.longTasks
+ .sort((a, b) => b.duration - a.duration)
+ .slice(0, 5)
+ .forEach((task, i) => {
+ console.log(`#${i + 1} ${task.name}: ${task.duration.toFixed(2)}ms`);
+ });
+ }
+
+ // Slow operations
+ if (authProfile.operations.length > 0) {
+ console.log('\n%c--- SLOW OPERATIONS ---', 'font-weight:bold');
+ const sortedOps = [...authProfile.operations].sort((a, b) => b.duration - a.duration);
+ sortedOps.slice(0, 10).forEach((op, i) => {
+ const memUsage = op.memory ? ` | +${(op.memory / 1024).toFixed(2)}KB` : '';
+ console.log(`#${i + 1} [${op.type.toUpperCase()}] ${op.url || 'unknown'}: ${op.duration.toFixed(2)}ms${memUsage}`);
+ });
+ }
+
+ return authProfile;
+ };
+
+ window.resetAuthProfile = function() {
+ Object.assign(authProfile, {
+ startTime: null,
+ operations: [],
+ fetchCount: 0,
+ xhrCount: 0,
+ domUpdates: 0,
+ authChecks: 0,
+ totalTime: 0
+ });
+ console.log('Authentication profile has been reset');
+ };
+ };
+
+ console.log('Authentication profiler loaded. Run profileAuthNavigation() to start.');
+})();