// Callsign enrichment and display module export class CallsignManager { constructor() { this.callsignCache = new Map(); this.pendingRequests = new Map(); // Rate limiting to avoid overwhelming the API this.lastRequestTime = 0; this.requestInterval = 100; // Minimum 100ms between requests } /** * Get enriched callsign information, using cache when available * @param {string} callsign - The raw callsign to lookup * @returns {Promise} - Enriched callsign data */ async getCallsignInfo(callsign) { if (!callsign || callsign.trim() === '') { return null; } const cleanCallsign = callsign.trim().toUpperCase(); // Check cache first if (this.callsignCache.has(cleanCallsign)) { return this.callsignCache.get(cleanCallsign); } // Check if we already have a pending request for this callsign if (this.pendingRequests.has(cleanCallsign)) { return this.pendingRequests.get(cleanCallsign); } // Rate limiting const now = Date.now(); if (now - this.lastRequestTime < this.requestInterval) { await new Promise(resolve => setTimeout(resolve, this.requestInterval)); } // Create the API request const requestPromise = this.fetchCallsignInfo(cleanCallsign); this.pendingRequests.set(cleanCallsign, requestPromise); try { const result = await requestPromise; // Cache the result for future use if (result && result.callsign) { this.callsignCache.set(cleanCallsign, result.callsign); } return result ? result.callsign : null; } catch (error) { console.warn(`Failed to lookup callsign ${cleanCallsign}:`, error); return null; } finally { // Clean up pending request this.pendingRequests.delete(cleanCallsign); this.lastRequestTime = Date.now(); } } /** * Fetch callsign information from the API * @param {string} callsign - The callsign to lookup * @returns {Promise} - API response */ async fetchCallsignInfo(callsign) { const response = await fetch(`/api/callsign/${encodeURIComponent(callsign)}`); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } /** * Generate rich HTML display for a callsign * @param {Object} callsignInfo - Enriched callsign data from API * @param {string} originalCallsign - Original callsign if API data is null * @returns {string} - HTML string for display */ generateCallsignDisplay(callsignInfo, originalCallsign = '') { if (!callsignInfo || !callsignInfo.is_valid) { // Fallback for invalid or missing callsign data if (originalCallsign) { return `${originalCallsign}`; } return 'N/A'; } const parts = []; // Airline code if (callsignInfo.airline_code) { parts.push(`${callsignInfo.airline_code}`); } // Flight number if (callsignInfo.flight_number) { parts.push(`${callsignInfo.flight_number}`); } // Airline name (if available) let airlineInfo = ''; if (callsignInfo.airline_name) { airlineInfo = ` ${callsignInfo.airline_name} `; // Add country if available if (callsignInfo.airline_country) { airlineInfo += ` (${callsignInfo.airline_country})`; } } return ` ${parts.join(' ')} ${airlineInfo ? `${airlineInfo}` : ''} `; } /** * Generate compact callsign display for table view * @param {Object} callsignInfo - Enriched callsign data * @param {string} originalCallsign - Original callsign fallback * @returns {string} - Compact HTML for table display */ generateCompactCallsignDisplay(callsignInfo, originalCallsign = '') { if (!callsignInfo || !callsignInfo.is_valid) { return originalCallsign || 'N/A'; } // For tables, use the display_name or format airline + flight if (callsignInfo.display_name) { return `${callsignInfo.display_name}`; } return `${callsignInfo.airline_code} ${callsignInfo.flight_number}`; } /** * Clear the callsign cache (useful for memory management) */ clearCache() { this.callsignCache.clear(); console.debug('Callsign cache cleared'); } /** * Get cache statistics for debugging * @returns {Object} - Cache size and pending requests */ getCacheStats() { return { cacheSize: this.callsignCache.size, pendingRequests: this.pendingRequests.size }; } }