// services/callTest/locationResolver.js // Resolve Webex Calling location from a dialed E.164 (store main / AA DID). import { logger } from '../../utils/logger.js'; import { normalizeE164 } from './config.js'; const LOG_SCOPE = 'calltest:location'; const CACHE_TTL_MS = 60 * 60 * 1000; let _phoneIndex = null; let _phoneIndexAt = 0; async function getWebex() { const mod = await import('../../integrations/webex/WebexClient.js'); return mod.default; } function parseLinkNext(linkHeader) { if (!linkHeader || typeof linkHeader !== 'string') return null; for (const part of linkHeader.split(',')) { const m = part.match(/<([^>]+)>\s*;\s*rel\s*=\s*"?next"?/i); if (m) return m[1]; } return null; } function phoneDigits(value) { const d = String(value || '').replace(/\D/g, ''); if (d.length === 10) return `1${d}`; if (d.length === 11 && d.startsWith('1')) return d; return d; } async function fetchAllNumbers() { const webex = await getWebex(); const all = []; let nextUrl = null; let data; let headers; ({ data, headers } = await webex.requestRaw('GET', 'telephony/config/numbers', null, { max: 1000 })); const batch = data?.phoneNumbers || []; all.push(...batch); nextUrl = parseLinkNext(headers?.link || headers?.Link); while (nextUrl) { ({ data, headers } = await webex.requestRaw('GET', nextUrl)); all.push(...(data?.phoneNumbers || [])); nextUrl = parseLinkNext(headers?.link || headers?.Link); } return all; } async function buildPhoneIndex() { const now = Date.now(); if (_phoneIndex && now - _phoneIndexAt < CACHE_TTL_MS) { return _phoneIndex; } logger(LOG_SCOPE, 'Building location phone index from telephony/config/numbers', 'debug'); const numbers = await fetchAllNumbers(); const index = new Map(); for (const n of numbers) { const raw = n.phoneNumber || n.number || n.value; const loc = n.location; if (!raw || !loc?.id) continue; const entry = { locationId: loc.id, locationName: loc.name || null, phoneNumber: raw, }; const keys = new Set([phoneDigits(raw), phoneDigits(normalizeE164(raw) || raw)]); for (const k of keys) { if (k) index.set(k, entry); } } _phoneIndex = index; _phoneIndexAt = now; logger(LOG_SCOPE, `Phone index built: ${index.size} keys from ${numbers.length} numbers`, 'debug'); return index; } /** * Find Webex location for a dialed E.164 (e.g. store main on /calltest dial). * @returns {Promise<{locationId: string, locationName: string, phoneNumber: string}|null>} */ export async function resolveLocationForDialNumber(dialNumber) { const e164 = normalizeE164(dialNumber); if (!e164) return null; try { const index = await buildPhoneIndex(); const key = phoneDigits(e164); const hit = index.get(key); if (!hit?.locationName) return null; return { locationId: hit.locationId, locationName: hit.locationName, phoneNumber: hit.phoneNumber, }; } catch (err) { logger(LOG_SCOPE, `resolveLocationForDialNumber failed: ${err.message}`, 'warn'); return null; } } export function _clearLocationCacheForTests() { _phoneIndex = null; _phoneIndexAt = 0; }