/* === MeshCore Analyzer — channels.js === */ 'use strict'; (function () { let channels = []; let selectedHash = null; let messages = []; let wsHandler = null; let autoScroll = true; let nodeCache = {}; let selectedNode = null; var _nodeCacheTTL = 5 * 60 * 1000; // 5 minutes async function lookupNode(name) { var cached = nodeCache[name]; if (cached !== undefined) { if (cached && cached.fetchedAt && (Date.now() - cached.fetchedAt < _nodeCacheTTL)) return cached.data; if (cached && !cached.fetchedAt) return cached; // legacy null entries } try { const data = await api('/nodes/search?q=' + encodeURIComponent(name), { ttl: CLIENT_TTL.channelMessages }); // Try exact match first, then case-insensitive, then contains const nodes = data.nodes || []; const match = nodes.find(n => n.name === name) || nodes.find(n => n.name && n.name.toLowerCase() === name.toLowerCase()) || nodes.find(n => n.name && n.name.toLowerCase().includes(name.toLowerCase())) || nodes[0] || null; nodeCache[name] = { data: match, fetchedAt: Date.now() }; return match; } catch { nodeCache[name] = null; return null; } } async function showNodeTooltip(e, name) { const node = await lookupNode(name); let existing = document.getElementById('chNodeTooltip'); if (existing) existing.remove(); if (!node) return; const tip = document.createElement('div'); tip.id = 'chNodeTooltip'; tip.className = 'ch-node-tooltip'; tip.setAttribute('role', 'tooltip'); const roleKey = node.role || (node.is_repeater ? 'repeater' : node.is_room ? 'room' : node.is_sensor ? 'sensor' : 'companion'); const role = (ROLE_EMOJI[roleKey] || '●') + ' ' + (ROLE_LABELS[roleKey] || roleKey); const lastSeen = node.last_seen ? timeAgo(node.last_seen) : 'unknown'; tip.innerHTML = `