collabSupport/services/dectStatus/captureRawXml.js
jmcqueen 2b8c4e06aa Improve /dectstatus with handset RF context and cleaner base cards.
Surface handset registrations and RSSI, tighten reboot health to 7 days,
and consolidate Base / Handsets & RF / Network & RTP sections.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 09:02:55 -04:00

93 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// services/dectStatus/captureRawXml.js
//
// Fetch raw status.xml from store DECT bases via the on-prem relay.
// Used by GET /api/dect/raw-xml/:storeNumber and scripts/fetchDectStatusXml.js.
import { collectPhoneStatus } from '../phoneService.js';
import { discoverDectBases } from '../dectDiscovery.js';
import { collectRawAll } from '../dectCollectorService.js';
import { getDectRelayHub } from '../dectRelayHub.js';
import { logger } from '../../utils/logger.js';
const LOG_SCOPE = 'dect:capture';
/**
* @param {string} storeNum
* @param {object} [opts]
* @param {string} [opts.baseFilter] optional IP or MAC filter
* @returns {Promise<object>}
*/
export async function captureDectRawXml(storeNum, opts = {}) {
const store = String(storeNum || '').trim();
if (!store || !/^\d{2,4}$/.test(store)) {
const err = new Error('storeNum must be a 24 digit store number');
err.code = 'INVALID_STORE';
throw err;
}
if (!process.env.DECT_RELAY_AGENT_TOKEN) {
const err = new Error('DECT_RELAY_AGENT_TOKEN is not configured on this bot');
err.code = 'RELAY_NOT_CONFIGURED';
throw err;
}
let relay = null;
try {
relay = getDectRelayHub().status();
} catch (err) {
logger(LOG_SCOPE, `Relay status unavailable: ${err.message}`, 'warn');
relay = { connected: false };
}
const phoneData = await collectPhoneStatus(store);
const { bases, warnings: discoveryWarnings } = discoverDectBases(phoneData || {});
let targets = bases;
const baseFilter = (opts.baseFilter || '').trim();
if (baseFilter) {
targets = filterBases(bases, baseFilter);
if (targets.length === 0) {
const err = new Error(`No discovered base matched "${baseFilter}" for store ${store}`);
err.code = 'BASE_NOT_FOUND';
err.knownBases = bases.map((b) => ({ ip: b.ip, mac: b.mac, name: b.name }));
throw err;
}
}
const results = await collectRawAll(targets);
return {
storeNum: store,
relay,
discoveryWarnings,
bases: results.map((r) => ({
mac: r.base?.mac || null,
ip: r.base?.ip || null,
webexId: r.base?.webexId || null,
name: r.base?.name || null,
ok: r.ok,
byteLength: r.byteLength,
rawXml: r.rawXml,
sectionInventory: r.sectionInventory,
parsed: r.data,
verdict: r.verdict,
elapsedMs: r.elapsedMs,
error: r.error,
})),
};
}
function filterBases(bases, raw) {
const needle = String(raw).trim().toLowerCase();
const needleMac = needle.replace(/[^0-9a-f]/g, '');
return bases.filter((b) => {
if (b.ip && b.ip.toLowerCase() === needle) return true;
if (b.mac) {
const macHex = b.mac.replace(/[^0-9a-fA-F]/g, '').toLowerCase();
if (macHex === needleMac && needleMac.length === 12) return true;
if (b.mac.toLowerCase() === needle) return true;
}
if (b.name && b.name.toLowerCase().includes(needle)) return true;
return false;
});
}