/** * AV (Atlas) device-discovery service. * * Thin shim over integrations/atlas: pulls the Atlas devices belonging to a * store and shapes each into a renderer-friendly object the storeDetail AV * report consumes. Mirrors the contract of services/webexPhone.js — on any * unrecoverable failure (missing ATLAS_AUTH_KEY, transport error) it returns * `{ devices: [], unavailable: true, reason }` rather than throwing, so the * bot can render a banner alongside whatever other AV data (MDM Apple TVs * etc.) is still available. * * Atlas field names vary subtly across device types; `shapeAtlasDevice` * picks defensively from the locations the collabFinder reference touched * (`state.IpAddress`, `state.MacAddress`, `connection_status`, `status`, * etc.). The renderer treats `online === null` as "unknown" so an unfamiliar * payload degrades gracefully. */ const { getAtlasDevicesForStore } = require('../integrations/atlas/atlasDevices'); const logger = require('../utils/logger'); const ONLINE_KEYWORDS = new Set(['online', 'connected', 'active', 'up']); const OFFLINE_KEYWORDS = new Set(['offline', 'disconnected', 'inactive', 'down']); function deriveOnline(dev) { // Try the most specific fields first; fall back to top-level `status`. const candidates = [ dev?.connection_status, dev?.state?.connection_status, dev?.state?.status, dev?.status, ]; for (const c of candidates) { if (typeof c === 'boolean') return c; if (typeof c !== 'string') continue; const lower = c.trim().toLowerCase(); if (!lower) continue; if (ONLINE_KEYWORDS.has(lower)) return true; if (OFFLINE_KEYWORDS.has(lower)) return false; } return null; } function pickFirst(...values) { for (const v of values) { if (v !== undefined && v !== null && v !== '') return v; } return null; } function shapeAtlasDevice(dev) { const state = dev?.state || {}; const model = pickFirst( typeof dev?.model === 'string' ? dev.model : null, dev?.model?.name, dev?.model_name, dev?.product ); const firmware = pickFirst(dev?.firmware?.version, dev?.firmware_version, state?.firmware); return { id: dev?.id || null, name: dev?.name || dev?.displayName || 'Unknown AV Device', model, firmware, mac: pickFirst(dev?.mac, state?.MacAddress, state?.macAddress, dev?.mac_address), ip: pickFirst(state?.IpAddress, state?.ipAddress, dev?.ip, dev?.ip_address), online: deriveOnline(dev), lastSeen: pickFirst( dev?.last_connection, dev?.last_seen, state?.lastSeen, state?.last_seen, dev?.updated_at ), }; } /** * Collect AV devices for the given store. Always resolves with the * unavailable banner contract — never throws — so the storeDetail layer can * compose this alongside MDM and Meraki output without try/catch noise. */ async function collectAvStatus(storeNumber) { logger.debug('collectAvStatus start', { storeNumber }); let result; try { result = await getAtlasDevicesForStore(storeNumber); } catch (err) { logger.error('Atlas lookup threw unexpectedly', { storeNumber, error: err.message }); return { devices: [], unavailable: true, reason: err.message }; } if (result.unavailable) { return { devices: [], unavailable: true, reason: result.reason }; } const devices = (result.devices || []).map(shapeAtlasDevice); logger.info('collectAvStatus done', { storeNumber, count: devices.length }); return { devices }; } module.exports = { collectAvStatus, // Exposed for tests shapeAtlasDevice, deriveOnline, };