Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,297 @@
/**
* Advanced Link Prefetching System
* Supports multiple strategies: hover, visible, eager
*/
import { Logger } from './logger.js';
import { SimpleCache } from '../utils/cache.js';
export class LinkPrefetcher {
constructor(options = {}) {
this.options = {
maxCacheSize: options.maxCacheSize || 20,
cacheTTL: options.cacheTTL || 60000, // 60 seconds
hoverDelay: options.hoverDelay || 150, // ms before prefetch on hover
strategies: options.strategies || ['hover', 'visible'], // Available: hover, visible, eager
observerMargin: options.observerMargin || '50px',
priority: options.priority || 'low', // low, high
...options
};
this.cache = new SimpleCache(this.options.maxCacheSize, this.options.cacheTTL);
this.prefetching = new Set(); // Currently prefetching URLs
this.prefetched = new Set(); // Already prefetched URLs
this.observer = null;
this.hoverTimeout = null;
this.cleanupInterval = null;
this.init();
}
init() {
// Setup strategies
if (this.options.strategies.includes('visible')) {
this.setupIntersectionObserver();
}
// Setup cleanup interval
this.cleanupInterval = setInterval(() => {
this.cache.cleanup();
}, 120000); // Clean every 2 minutes
Logger.info('[LinkPrefetcher] initialized with strategies:', this.options.strategies);
}
setupIntersectionObserver() {
if (!('IntersectionObserver' in window)) {
Logger.warn('[LinkPrefetcher] IntersectionObserver not supported');
return;
}
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const link = entry.target;
const href = link.getAttribute('href');
// Lower priority for visible links
this.prefetch(href, { priority: 'low' });
// Stop observing once prefetched
this.observer.unobserve(link);
}
});
}, {
rootMargin: this.options.observerMargin
});
}
/**
* Check if a link should be prefetched
*/
shouldPrefetch(link) {
if (!link || !(link instanceof HTMLAnchorElement)) return false;
const href = link.getAttribute('href');
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:')) {
return false;
}
// Skip external links
try {
const url = new URL(href, window.location.origin);
if (url.origin !== window.location.origin) return false;
} catch {
return false;
}
// Skip if has data-no-prefetch attribute
if (link.hasAttribute('data-no-prefetch')) return false;
// Skip download links
if (link.hasAttribute('download')) return false;
// Skip target="_blank" links
if (link.target === '_blank') return false;
return true;
}
/**
* Prefetch a URL
*/
async prefetch(href, options = {}) {
if (!href || this.prefetching.has(href) || this.cache.has(href)) {
return;
}
// Mark as prefetching
this.prefetching.add(href);
try {
Logger.info(`[LinkPrefetcher] prefetching: ${href}`);
// Use different methods based on priority
if (options.priority === 'high' || this.options.priority === 'high') {
await this.fetchWithPriority(href, 'high');
} else {
// Use link prefetch hint for low priority
this.createPrefetchLink(href);
// Also fetch for cache
await this.fetchWithPriority(href, 'low');
}
} catch (error) {
Logger.warn(`[LinkPrefetcher] failed to prefetch ${href}:`, error);
} finally {
this.prefetching.delete(href);
this.prefetched.add(href);
}
}
/**
* Fetch with priority
*/
async fetchWithPriority(href, priority = 'low') {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
try {
const response = await fetch(href, {
signal: controller.signal,
priority: priority, // Fetch priority hint (Chrome 121+)
credentials: 'same-origin',
headers: {
'X-Prefetch': 'true'
}
});
clearTimeout(timeoutId);
if (response.ok) {
const html = await response.text();
this.cache.set(href, {
data: html,
timestamp: Date.now(),
headers: {
contentType: response.headers.get('content-type'),
lastModified: response.headers.get('last-modified')
}
});
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name !== 'AbortError') {
throw error;
}
}
}
/**
* Create a link element for browser prefetch hint
*/
createPrefetchLink(href) {
// Check if already exists
if (document.querySelector(`link[rel="prefetch"][href="${href}"]`)) {
return;
}
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = href;
link.as = 'document';
document.head.appendChild(link);
// Remove after some time to avoid cluttering
setTimeout(() => link.remove(), 30000);
}
/**
* Handle hover events
*/
handleHover(link) {
if (!this.shouldPrefetch(link)) return;
const href = link.getAttribute('href');
clearTimeout(this.hoverTimeout);
this.hoverTimeout = setTimeout(() => {
this.prefetch(href, { priority: 'high' });
}, this.options.hoverDelay);
}
/**
* Handle mouse leave
*/
handleMouseLeave() {
clearTimeout(this.hoverTimeout);
}
/**
* Observe a link for intersection
*/
observeLink(link) {
if (!this.observer || !this.shouldPrefetch(link)) return;
this.observer.observe(link);
}
/**
* Observe all links in a container
*/
observeLinks(container = document) {
if (!this.options.strategies.includes('visible')) return;
const links = container.querySelectorAll('a[href]');
links.forEach(link => this.observeLink(link));
}
/**
* Eagerly prefetch specific URLs
*/
prefetchEager(urls) {
if (!Array.isArray(urls)) urls = [urls];
urls.forEach(url => {
this.prefetch(url, { priority: 'high' });
});
}
/**
* Get cached content
*/
getCached(href) {
const cached = this.cache.get(href);
return cached ? cached.data : null;
}
/**
* Check if URL is cached
*/
isCached(href) {
return this.cache.has(href);
}
/**
* Clear cache
*/
clearCache() {
this.cache.clear();
this.prefetched.clear();
}
/**
* Get cache statistics
*/
getStats() {
return {
cacheSize: this.cache.size(),
prefetching: this.prefetching.size,
prefetched: this.prefetched.size,
strategies: this.options.strategies
};
}
/**
* Destroy the prefetcher
*/
destroy() {
clearTimeout(this.hoverTimeout);
clearInterval(this.cleanupInterval);
if (this.observer) {
this.observer.disconnect();
}
this.cache.clear();
this.prefetching.clear();
this.prefetched.clear();
// Remove all prefetch links
document.querySelectorAll('link[rel="prefetch"]').forEach(link => link.remove());
Logger.info('[LinkPrefetcher] destroyed');
}
}
// Export singleton instance
export const linkPrefetcher = new LinkPrefetcher();