RC2
This commit is contained in:
64
.gitignore
vendored
64
.gitignore
vendored
@ -1,25 +1,65 @@
|
|||||||
# Bytecode-Dateien
|
# Bytecode files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|
||||||
# Virtuelle Umgebungen
|
# Virtual environments
|
||||||
.venv/
|
.venv/
|
||||||
venv/
|
venv/
|
||||||
|
|
||||||
# Betriebssystem-Dateien
|
# System files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Logfiles und Dumps
|
# Logs and temporary files
|
||||||
*.log
|
*.log
|
||||||
*.bak
|
*.bak
|
||||||
*.swp
|
*.swp
|
||||||
*.tmp
|
*.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/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
*.sublime-workspace
|
||||||
|
*.sublime-project
|
||||||
|
|
||||||
|
# Local development
|
||||||
|
.cache/
|
||||||
|
.temp/
|
||||||
|
.tmp/
|
||||||
|
|
||||||
|
# Project specific
|
||||||
data/*
|
data/*
|
||||||
!data/.gitignore
|
!data/.gitignore
|
||||||
|
|
||||||
@ -28,3 +68,17 @@ log/*
|
|||||||
|
|
||||||
streams/*
|
streams/*
|
||||||
!streams/.gitignore
|
!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
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
{"uid":"oibchello","size":3371119,"mtime":1752994076}
|
|
||||||
{"uid":"orangeicebear","size":1734396,"mtime":1748767975}
|
|
179
run-navigation-test.js
Normal file
179
run-navigation-test.js
Normal 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);
|
||||||
|
});
|
@ -505,10 +505,15 @@ window.clearTimeout = (id) => {
|
|||||||
originalClearTimeout(id);
|
originalClearTimeout(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track auth check calls
|
// Track auth check calls and cache state
|
||||||
let lastAuthCheckTime = 0;
|
let lastAuthCheckTime = 0;
|
||||||
let authCheckCounter = 0;
|
let authCheckCounter = 0;
|
||||||
const AUTH_CHECK_DEBOUNCE = 1000; // 1 second
|
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
|
// Override console.log to capture all logs
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
@ -822,33 +827,46 @@ function updateAccountDeletionVisibility(isAuthenticated) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check authentication state and update UI
|
// Check authentication state and update UI with caching and debouncing
|
||||||
function checkAuthState() {
|
function checkAuthState(force = false) {
|
||||||
// Debounce rapid calls
|
|
||||||
const now = Date.now();
|
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;
|
return wasAuthenticated === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastAuthCheckTime = now;
|
lastAuthCheckTime = now;
|
||||||
authCheckCounter++;
|
authCheckCounter++;
|
||||||
|
|
||||||
// Check various authentication indicators
|
// Use a single check for authentication state
|
||||||
const hasAuthCookie = document.cookie.includes('isAuthenticated=true');
|
let isAuthenticated = false;
|
||||||
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
|
// Check the most likely indicators first for better performance
|
||||||
const isAuthenticated = hasAuthCookie || hasUidCookie || hasLocalStorageAuth || hasAuthToken;
|
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:', {
|
console.log('Auth State Check:', {
|
||||||
hasAuthCookie,
|
|
||||||
hasUidCookie,
|
|
||||||
hasLocalStorageAuth,
|
|
||||||
hasAuthToken,
|
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
wasAuthenticated
|
wasAuthenticated,
|
||||||
|
cacheHit: !force && now - authStateCache.timestamp < authStateCache.ttl,
|
||||||
|
cacheAge: now - authStateCache.timestamp
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -887,19 +905,44 @@ function checkAuthState() {
|
|||||||
return isAuthenticated;
|
return isAuthenticated;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Periodically check authentication state
|
// Periodically check authentication state with optimized polling
|
||||||
function setupAuthStatePolling() {
|
function setupAuthStatePolling() {
|
||||||
// Initial check
|
// Initial check with force to ensure we get the latest state
|
||||||
checkAuthState();
|
checkAuthState(true);
|
||||||
|
|
||||||
// Check every 30 seconds instead of 2 to reduce load
|
// Use a single interval for all checks
|
||||||
setInterval(checkAuthState, 30000);
|
const checkAndUpdate = () => {
|
||||||
|
// Only force check if the page is visible
|
||||||
|
checkAuthState(!document.hidden);
|
||||||
|
};
|
||||||
|
|
||||||
// Also check after certain events that might affect auth state
|
// Check every 30 seconds (reduced from previous implementation)
|
||||||
window.addEventListener('storage', checkAuthState);
|
const AUTH_CHECK_INTERVAL = 30000;
|
||||||
document.addEventListener('visibilitychange', () => {
|
setInterval(checkAndUpdate, AUTH_CHECK_INTERVAL);
|
||||||
if (!document.hidden) checkAuthState();
|
|
||||||
});
|
// 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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,50 +1,69 @@
|
|||||||
// Force hide guest navigation for authenticated users
|
// Debounce helper function
|
||||||
function fixMobileNavigation() {
|
function debounce(func, wait) {
|
||||||
console.log('[FIX-NAV] Running navigation fix...');
|
let timeout;
|
||||||
|
return function() {
|
||||||
|
const context = this;
|
||||||
|
const args = arguments;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func.apply(context, args), wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Check if user is authenticated
|
// Throttle helper function
|
||||||
const hasAuthCookie = document.cookie.includes('isAuthenticated=true');
|
function throttle(func, limit) {
|
||||||
const hasUidCookie = document.cookie.includes('uid=');
|
let inThrottle;
|
||||||
const hasLocalStorageAuth = localStorage.getItem('isAuthenticated') === 'true';
|
return function() {
|
||||||
const hasAuthToken = localStorage.getItem('authToken') !== null;
|
const args = arguments;
|
||||||
const isAuthenticated = hasAuthCookie || hasUidCookie || hasLocalStorageAuth || hasAuthToken;
|
const context = this;
|
||||||
|
if (!inThrottle) {
|
||||||
|
func.apply(context, args);
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => inThrottle = false, limit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[FIX-NAV] Authentication state:', {
|
// Check authentication state once and cache it
|
||||||
isAuthenticated,
|
function getAuthState() {
|
||||||
hasAuthCookie,
|
return (
|
||||||
hasUidCookie,
|
document.cookie.includes('isAuthenticated=') ||
|
||||||
hasLocalStorageAuth,
|
document.cookie.includes('uid=') ||
|
||||||
hasAuthToken
|
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) {
|
if (isAuthenticated) {
|
||||||
// Force hide guest navigation with !important styles
|
// Hide guest navigation for authenticated users
|
||||||
const guestNav = document.getElementById('guest-dashboard');
|
const guestNav = document.getElementById('guest-dashboard');
|
||||||
if (guestNav) {
|
if (guestNav) {
|
||||||
console.log('[FIX-NAV] Hiding guest navigation');
|
|
||||||
guestNav.style.cssText = `
|
guestNav.style.cssText = `
|
||||||
display: none !important;
|
display: none !important;
|
||||||
visibility: hidden !important;
|
visibility: hidden !important;
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
height: 0 !important;
|
height: 0 !important;
|
||||||
width: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
border: none !important;
|
|
||||||
position: absolute !important;
|
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
|
position: absolute !important;
|
||||||
clip: rect(0, 0, 0, 0) !important;
|
clip: rect(0, 0, 0, 0) !important;
|
||||||
pointer-events: none !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');
|
const userNav = document.getElementById('user-dashboard');
|
||||||
if (userNav) {
|
if (userNav) {
|
||||||
console.log('[FIX-NAV] Showing user navigation');
|
|
||||||
userNav.style.cssText = `
|
userNav.style.cssText = `
|
||||||
display: flex !important;
|
display: block !important;
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
@ -55,25 +74,9 @@ function fixMobileNavigation() {
|
|||||||
userNav.classList.add('force-visible');
|
userNav.classList.add('force-visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add authenticated class to body
|
// Update body classes
|
||||||
document.body.classList.add('authenticated');
|
document.body.classList.add('authenticated');
|
||||||
document.body.classList.remove('guest-mode');
|
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 {
|
} else {
|
||||||
// User is not authenticated - ensure guest nav is visible
|
// User is not authenticated - ensure guest nav is visible
|
||||||
const guestNav = document.getElementById('guest-dashboard');
|
const guestNav = document.getElementById('guest-dashboard');
|
||||||
@ -85,50 +88,53 @@ function fixMobileNavigation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run on page load
|
// Initialize the navigation state
|
||||||
document.addEventListener('DOMContentLoaded', fixMobileNavigation);
|
updateNavigation.lastState = null;
|
||||||
|
|
||||||
// Also run after a short delay to catch any dynamic content
|
// Handle navigation link clicks
|
||||||
setTimeout(fixMobileNavigation, 100);
|
function handleNavLinkClick(e) {
|
||||||
setTimeout(fixMobileNavigation, 300);
|
const link = e.target.closest('a[href^="#"]');
|
||||||
setTimeout(fixMobileNavigation, 1000);
|
if (!link) return;
|
||||||
|
|
||||||
// Listen for hash changes (navigation)
|
e.preventDefault();
|
||||||
window.addEventListener('hashchange', fixMobileNavigation);
|
const targetId = link.getAttribute('href');
|
||||||
|
if (targetId && targetId !== '#') {
|
||||||
// Listen for pushState/replaceState (SPA navigation)
|
// Update URL without triggering full page reload
|
||||||
const originalPushState = history.pushState;
|
history.pushState(null, '', targetId);
|
||||||
const originalReplaceState = history.replaceState;
|
// Dispatch a custom event for other scripts
|
||||||
|
window.dispatchEvent(new CustomEvent('hashchange'));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, {
|
// Initialize the navigation system
|
||||||
childList: true,
|
function initNavigation() {
|
||||||
subtree: true,
|
// Set up event delegation for navigation links
|
||||||
attributes: true,
|
document.body.addEventListener('click', handleNavLinkClick);
|
||||||
attributeFilter: ['class', 'style', 'id']
|
|
||||||
});
|
|
||||||
|
|
||||||
// Export for debugging
|
// Listen for hash changes (throttled)
|
||||||
window.fixMobileNavigation = fixMobileNavigation;
|
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
|
||||||
|
};
|
||||||
|
@ -8,5 +8,7 @@
|
|||||||
<a href="#" data-target="privacy-page">Privacy</a>
|
<a href="#" data-target="privacy-page">Privacy</a>
|
||||||
<span class="separator">•</span>
|
<span class="separator">•</span>
|
||||||
<a href="#" data-target="imprint-page">Imprint</a>
|
<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>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -258,9 +258,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const target = link.dataset.target;
|
const target = link.dataset.target;
|
||||||
if (target) {
|
if (target) {
|
||||||
// Show the target section without updating URL hash
|
// 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);
|
showSection(target);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
374
tests/profile-auth.js
Normal file
374
tests/profile-auth.js
Normal 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.');
|
||||||
|
})();
|
Reference in New Issue
Block a user