collabSupport/services/phoneStatus/capturePhoneProbe.js
jmcqueen f7953b8eb5 Add MPP desk phone diagnostics follow-up to /phonestatus via relay.
Wire CP-78xx probe discovery, relay phone-probe commands, and a chat follow-up message so store desk phones get registration, switch, and provisioning detail alongside DECT and WAN diagnostics.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 18:01:01 -04:00

137 lines
4 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/phoneStatus/capturePhoneProbe.js
//
// Fetch MPP phone probe data from store desk phones via the on-prem relay.
import { collectPhoneStatus } from '../phoneService.js';
import { discoverDeskPhones, manualPhoneTarget } from '../phoneDiscovery.js';
import { probeRawAll, probeRawOne } from '../phoneCollectorService.js';
import { getDectRelayHub } from '../dectRelayHub.js';
import { logger } from '../../utils/logger.js';
const LOG_SCOPE = 'phone:capture';
function relayStatus() {
try {
return getDectRelayHub().status();
} catch (err) {
logger(LOG_SCOPE, `Relay status unavailable: ${err.message}`, 'warn');
return { connected: false };
}
}
function mapProbeResult(r) {
return {
mac: r.phone?.mac || null,
ip: r.phone?.ip || null,
webexId: r.phone?.webexId || null,
name: r.phone?.name || null,
product: r.phone?.product || null,
ok: r.ok,
byteLength: r.byteLength,
statusJson: r.statusJson || null,
statusXml: r.statusXml || null,
downloadStatusJson: r.downloadStatusJson || null,
nsJson: r.nsJson || null,
systemJson: r.systemJson || null,
cfgParsed: r.cfgParsed || null,
probes: r.probes,
parsed: r.parsed,
verdict: r.verdict,
mpp: r.mpp || null,
summary: r.summary,
elapsedMs: r.elapsedMs,
error: r.error,
};
}
/**
* Probe a single phone IP via the connected relay (bot process only).
* @param {string} targetIp
*/
export async function capturePhoneProbeDirect(targetIp) {
const ip = String(targetIp || '').trim();
if (!ip) {
const err = new Error('targetIp is required');
err.code = 'INVALID_IP';
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;
}
const relay = relayStatus();
const phone = manualPhoneTarget(ip);
const result = await probeRawOne(getDectRelayHub(), phone);
return {
relay,
phones: [mapProbeResult(result)],
};
}
/**
* @param {string} storeNum
* @param {object} [opts]
* @param {string} [opts.phoneFilter]
* @returns {Promise<object>}
*/
export async function capturePhoneProbe(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;
}
const relay = relayStatus();
const phoneData = await collectPhoneStatus(store);
const { phones, warnings: discoveryWarnings, inventory } = discoverDeskPhones(phoneData || {});
let targets = phones;
const phoneFilter = (opts.phoneFilter || '').trim();
if (phoneFilter) {
targets = filterPhones(phones, phoneFilter);
if (targets.length === 0) {
const err = new Error(`No discovered phone matched "${phoneFilter}" for store ${store}`);
err.code = 'PHONE_NOT_FOUND';
err.knownPhones = phones.map((p) => ({ ip: p.ip, mac: p.mac, name: p.name, product: p.product }));
throw err;
}
}
const results = await probeRawAll(targets);
return {
storeNum: store,
relay,
discoveryWarnings,
inventory,
phones: results.map(mapProbeResult),
};
}
function filterPhones(phones, raw) {
const needle = String(raw).trim().toLowerCase();
const needleMac = needle.replace(/[^0-9a-f]/g, '');
return phones.filter((p) => {
if (p.ip && p.ip.toLowerCase() === needle) return true;
if (p.mac) {
const macHex = p.mac.replace(/[^0-9a-fA-F]/g, '').toLowerCase();
if (macHex === needleMac && needleMac.length === 12) return true;
if (p.mac.toLowerCase() === needle) return true;
}
if (p.name && p.name.toLowerCase().includes(needle)) return true;
if (p.product && p.product.toLowerCase().includes(needle)) return true;
return false;
});
}