2025-08-31 19:43:58 +02:00
|
|
|
// Callsign enrichment and display module
|
2026-02-13 15:21:56 +01:00
|
|
|
import { escapeHtml } from './html-utils.js';
|
|
|
|
|
|
2025-08-31 19:43:58 +02:00
|
|
|
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<Object>} - 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<Object>} - 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 = '') {
|
2026-02-13 15:21:56 +01:00
|
|
|
const esc = escapeHtml;
|
2025-08-31 19:43:58 +02:00
|
|
|
if (!callsignInfo || !callsignInfo.is_valid) {
|
|
|
|
|
// Fallback for invalid or missing callsign data
|
|
|
|
|
if (originalCallsign) {
|
2026-02-13 15:21:56 +01:00
|
|
|
return `<span class="callsign-display simple">${esc(originalCallsign)}</span>`;
|
2025-08-31 19:43:58 +02:00
|
|
|
}
|
|
|
|
|
return '<span class="callsign-display no-data">N/A</span>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parts = [];
|
2026-02-13 15:21:56 +01:00
|
|
|
|
2025-08-31 19:43:58 +02:00
|
|
|
// Airline code
|
|
|
|
|
if (callsignInfo.airline_code) {
|
2026-02-13 15:21:56 +01:00
|
|
|
parts.push(`<span class="airline-code">${esc(callsignInfo.airline_code)}</span>`);
|
2025-08-31 19:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flight number
|
|
|
|
|
if (callsignInfo.flight_number) {
|
2026-02-13 15:21:56 +01:00
|
|
|
parts.push(`<span class="flight-number">${esc(callsignInfo.flight_number)}</span>`);
|
2025-08-31 19:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Airline name (if available)
|
|
|
|
|
let airlineInfo = '';
|
|
|
|
|
if (callsignInfo.airline_name) {
|
2026-02-13 15:21:56 +01:00
|
|
|
airlineInfo = `<span class="airline-name" title="${esc(callsignInfo.airline_name)}">
|
|
|
|
|
${esc(callsignInfo.airline_name)}
|
2025-08-31 19:43:58 +02:00
|
|
|
</span>`;
|
2026-02-13 15:21:56 +01:00
|
|
|
|
2025-08-31 19:43:58 +02:00
|
|
|
// Add country if available
|
|
|
|
|
if (callsignInfo.airline_country) {
|
2026-02-13 15:21:56 +01:00
|
|
|
airlineInfo += ` <span class="airline-country">(${esc(callsignInfo.airline_country)})</span>`;
|
2025-08-31 19:43:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
<span class="callsign-display enriched">
|
|
|
|
|
<span class="callsign-code">${parts.join(' ')}</span>
|
|
|
|
|
${airlineInfo ? `<span class="callsign-details">${airlineInfo}</span>` : ''}
|
|
|
|
|
</span>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 = '') {
|
2026-02-13 15:21:56 +01:00
|
|
|
const esc = escapeHtml;
|
2025-08-31 19:43:58 +02:00
|
|
|
if (!callsignInfo || !callsignInfo.is_valid) {
|
2026-02-13 15:21:56 +01:00
|
|
|
return esc(originalCallsign) || 'N/A';
|
2025-08-31 19:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For tables, use the display_name or format airline + flight
|
|
|
|
|
if (callsignInfo.display_name) {
|
2026-02-13 15:21:56 +01:00
|
|
|
return `<span class="callsign-compact" title="${esc(callsignInfo.airline_name || '')}">${esc(callsignInfo.display_name)}</span>`;
|
2025-08-31 19:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
2026-02-13 15:21:56 +01:00
|
|
|
return `<span class="callsign-compact">${esc(callsignInfo.airline_code)} ${esc(callsignInfo.flight_number)}</span>`;
|
2025-08-31 19:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|