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>
180 lines
5.8 KiB
JavaScript
180 lines
5.8 KiB
JavaScript
// services/phoneCollectorService.js
|
|
//
|
|
// Fan-out MPP phone probes over the DECT relay hub.
|
|
|
|
import { getDectRelayHub, RelayErrorCodes } from './dectRelayHub.js';
|
|
import { buildMppProbeView } from '../integrations/cisco-mpp-phone/aggregateProbe.js';
|
|
import { logger } from '../utils/logger.js';
|
|
|
|
const LOG_SCOPE = 'phone:collector';
|
|
|
|
const DEFAULT_TIMEOUT_MS = Number(process.env.PHONE_COLLECT_TIMEOUT_MS) || 15_000;
|
|
|
|
/**
|
|
* @param {object[]} phones from discoverDeskPhones()
|
|
* @param {object} [opts]
|
|
* @returns {Promise<object[]>}
|
|
*/
|
|
export async function probeAll(phones, opts = {}) {
|
|
const list = Array.isArray(phones) ? phones : [];
|
|
if (list.length === 0) return [];
|
|
const hub = opts.hub || getDectRelayHub();
|
|
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
|
|
logger(LOG_SCOPE, `Fanning out phone-probe() to ${list.length} phone(s)`, 'debug');
|
|
|
|
const results = await Promise.all(list.map((phone) => probeOne(hub, phone, timeoutMs)));
|
|
|
|
const okCount = results.filter((r) => r.ok).length;
|
|
logger(LOG_SCOPE, `Phone probe finished: ${okCount}/${list.length} succeeded`, 'debug');
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* @param {object[]} phones
|
|
* @param {object} [opts]
|
|
* @returns {Promise<object[]>}
|
|
*/
|
|
export async function probeRawAll(phones, opts = {}) {
|
|
const list = Array.isArray(phones) ? phones : [];
|
|
if (list.length === 0) return [];
|
|
const hub = opts.hub || getDectRelayHub();
|
|
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
|
|
logger(LOG_SCOPE, `Fanning out phone-probe-raw() to ${list.length} phone(s)`, 'debug');
|
|
|
|
const results = await Promise.all(list.map((phone) => probeRawOne(hub, phone, timeoutMs)));
|
|
|
|
const okCount = results.filter((r) => r.ok).length;
|
|
logger(LOG_SCOPE, `Phone probe-raw finished: ${okCount}/${list.length} succeeded`, 'debug');
|
|
return results;
|
|
}
|
|
|
|
export async function probeOne(hub, phone, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
if (!phone?.ip) {
|
|
return {
|
|
phone, ok: false, probes: null, statusJson: null, statusXml: null, parsed: null, verdict: null,
|
|
elapsedMs: null,
|
|
error: { code: 'NO_IP', message: 'phone has no IP address' },
|
|
};
|
|
}
|
|
const started = Date.now();
|
|
try {
|
|
const { result, elapsedMs } = await hub.phoneProbe(phone.ip, { timeoutMs });
|
|
const mpp = buildMppProbeView({
|
|
statusJson: result?.statusJson,
|
|
statusXml: result?.statusXml,
|
|
downloadStatusJson: result?.downloadStatusJson,
|
|
nsJson: result?.nsJson,
|
|
systemJson: result?.systemJson,
|
|
probes: result?.probes,
|
|
});
|
|
const summary = result?.summary || null;
|
|
return {
|
|
phone,
|
|
ok: !!(summary?.statusJsonFound || summary?.statusXmlFound || mpp.parsed),
|
|
probes: result?.probes || null,
|
|
statusJson: result?.statusJson || null,
|
|
statusXml: result?.statusXml || null,
|
|
downloadStatusJson: result?.downloadStatusJson || null,
|
|
nsJson: result?.nsJson || null,
|
|
systemJson: result?.systemJson || null,
|
|
cfgParsed: result?.cfgParsed || null,
|
|
parsed: mpp.parsed,
|
|
verdict: mpp.verdict,
|
|
mpp,
|
|
summary,
|
|
elapsedMs: elapsedMs ?? (Date.now() - started),
|
|
error: null,
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
phone,
|
|
ok: false,
|
|
probes: null,
|
|
statusJson: null,
|
|
statusXml: null,
|
|
parsed: null,
|
|
verdict: null,
|
|
elapsedMs: Date.now() - started,
|
|
error: {
|
|
code: err?.code || 'UNKNOWN',
|
|
message: err?.message || String(err),
|
|
hint: hintFor(err?.code),
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function probeRawOne(hub, phone, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
if (!phone?.ip) {
|
|
return {
|
|
phone, ok: false, probes: null, statusJson: null, statusXml: null, parsed: null, verdict: null,
|
|
byteLength: null, elapsedMs: null,
|
|
error: { code: 'NO_IP', message: 'phone has no IP address' },
|
|
};
|
|
}
|
|
const started = Date.now();
|
|
try {
|
|
const { result, elapsedMs } = await hub.phoneProbeRaw(phone.ip, { timeoutMs });
|
|
const mpp = buildMppProbeView({
|
|
statusJson: result?.statusJson,
|
|
statusXml: result?.statusXml,
|
|
downloadStatusJson: result?.downloadStatusJson,
|
|
nsJson: result?.nsJson,
|
|
systemJson: result?.systemJson,
|
|
probes: result?.probes,
|
|
});
|
|
const summary = result?.summary || null;
|
|
return {
|
|
phone,
|
|
ok: !!(summary?.statusJsonFound || summary?.statusXmlFound || result?.statusJson || result?.statusXml),
|
|
probes: result?.probes || null,
|
|
statusJson: result?.statusJson || null,
|
|
statusXml: result?.statusXml || null,
|
|
downloadStatusJson: result?.downloadStatusJson || null,
|
|
nsJson: result?.nsJson || null,
|
|
systemJson: result?.systemJson || null,
|
|
byteLength: result?.byteLength ?? null,
|
|
cfgParsed: result?.cfgParsed || null,
|
|
parsed: mpp.parsed,
|
|
verdict: mpp.verdict,
|
|
mpp,
|
|
summary,
|
|
elapsedMs: elapsedMs ?? (Date.now() - started),
|
|
error: null,
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
phone,
|
|
ok: false,
|
|
probes: null,
|
|
statusJson: null,
|
|
statusXml: null,
|
|
parsed: null,
|
|
verdict: null,
|
|
byteLength: null,
|
|
elapsedMs: Date.now() - started,
|
|
error: {
|
|
code: err?.code || 'UNKNOWN',
|
|
message: err?.message || String(err),
|
|
hint: hintFor(err?.code),
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
function hintFor(code) {
|
|
switch (code) {
|
|
case RelayErrorCodes.NOT_CONNECTED:
|
|
return 'DECT relay agent is not connected. Check that dect-relay-agent is running in the data center.';
|
|
case RelayErrorCodes.TIMEOUT:
|
|
return 'Relay accepted the request but the phone did not respond in time.';
|
|
case RelayErrorCodes.DISCONNECTED:
|
|
return 'Relay agent disconnected while this command was in flight. Try again.';
|
|
case 'AGENT_CONFIG':
|
|
return 'Relay agent configuration is incomplete — check dect-relay-agent/.env and restart.';
|
|
default:
|
|
return null;
|
|
}
|
|
}
|