This commit is contained in:
oib
2025-07-21 17:39:09 +02:00
parent ab9d93d913
commit f6c501030e
8 changed files with 789 additions and 126 deletions

64
.gitignore vendored
View File

@ -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

View File

@ -1,2 +0,0 @@
{"uid":"oibchello","size":3371119,"mtime":1752994076}
{"uid":"orangeicebear","size":1734396,"mtime":1748767975}

179
run-navigation-test.js Normal file
View File

@ -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);
});

View File

@ -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');
// Use a single check for authentication state
let isAuthenticated = false;
// User is considered authenticated if any of these are true
const isAuthenticated = hasAuthCookie || hasUidCookie || hasLocalStorageAuth || hasAuthToken;
// 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');
if (DEBUG_AUTH_STATE || isAuthenticated !== wasAuthenticated) {
// Update cache
authStateCache = {
timestamp: now,
value: isAuthenticated,
ttl: isAuthenticated ? 30000 : 5000 // Longer TTL for authenticated users
};
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);
};
}

View File

@ -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);
};
}
// 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;
// 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);
}
};
}
console.log('[FIX-NAV] Authentication state:', {
isAuthenticated,
hasAuthCookie,
hasUidCookie,
hasLocalStorageAuth,
hasAuthToken
});
// 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();
// 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);
// Handle navigation link clicks
function handleNavLinkClick(e) {
const link = e.target.closest('a[href^="#"]');
if (!link) return;
// 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);
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);
// Export for debugging
window.fixMobileNavigation = fixMobileNavigation;
// 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();
}
// 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
};

View File

@ -8,5 +8,7 @@
<a href="#" data-target="privacy-page">Privacy</a>
<span class="separator"></span>
<a href="#" data-target="imprint-page">Imprint</a>
<span class="separator auth-only" style="display: none;"></span>
<a href="#" data-target="your-stream" class="auth-only" style="display: none;">Your Stream</a>
</div>
</footer>

View File

@ -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);
}
}
});
});

374
tests/profile-auth.js Normal file
View File

@ -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.');
})();