Introduces a new diagnostic command that walks a registry of check
modules against a store user's Webex Calling configuration and
surfaces per-issue adaptive-card remediation for the fixable ones.
Checks (services/voiceDiag/checks/): dnd, callForwarding, callWaiting,
callIntercept, voicemail, hoteling, executiveAssistant,
outgoingPermission, phoneOnline. Remediations offered for DND,
forwarding, waiting, and intercept.
Uses the /v1/people/{id}/features/* admin surface (spark-admin:people_read
+ spark-admin:people_write scopes we already hold) — the earlier
telephony/config/people/*/callSettings/* path scheme returns 404 from
the Webex gateway and is not a live surface. Runner distinguishes
routing-404s ("URL moved") from "not applicable" 404s ("no calling
license") via the response body.
Arg parser accepts detail/detailed/--detail/--detailed and normalises
macOS smart-dashes so --detailed doesn't die when auto-correct
turns it into an em-dash.
Wires a VOICEDIAG_ACTIONS dispatcher in index.js mirroring the IGMP
branch, and registers /voicediag in commands/registry.js. 170 tests
pass (52 new: 39 check + 12 renderer + 5 arg-normalization).
Docs updated in .env.example, services/phoneService.js:467, and a new
services/voiceDiag/README.md that includes a "how to add a check"
recipe plus a note on the earlier wrong URL scheme.
Co-authored-by: Cursor <cursoragent@cursor.com>
75 lines
2.1 KiB
JavaScript
75 lines
2.1 KiB
JavaScript
// src/utils/pendingVoiceFixes.js
|
|
//
|
|
// In-memory store for pending /voicediag remediation cards. Identical
|
|
// shape to pendingIgmpFixes / pendingHostAssigns — every entry is
|
|
// stamped with a `timestamp` on insert, and a background sweep expires
|
|
// un-acted cards after TTL_MS so the map never leaks.
|
|
//
|
|
// Entries hold:
|
|
// {
|
|
// storeNum,
|
|
// personId, // Webex person id the remediation targets
|
|
// personLabel, // displayName or email for message/audit
|
|
// remediationId, // e.g. 'disable_dnd', 'clear_forwarding_always'
|
|
// remediationPayload, // free-form; whatever the check's remediation
|
|
// // handler expects (usually the pre-computed
|
|
// // PUT body plus contextual identifiers).
|
|
// requester, // from extractRequester(trigger)
|
|
// timestamp, // auto-set on .set()
|
|
// }
|
|
//
|
|
// TTL matches pendingIgmpFixes at 15 minutes — long enough for a
|
|
// normal "let me check with the store first" handoff without leaving
|
|
// a stale card interactive past the point where the underlying state
|
|
// may have already been changed by hand.
|
|
|
|
import { logger } from './logger.js';
|
|
|
|
const TTL_MS = 15 * 60 * 1000;
|
|
const SWEEP_INTERVAL_MS = 60 * 1000;
|
|
|
|
const _store = new Map();
|
|
|
|
export const pendingVoiceFixes = {
|
|
set(cardId, data) {
|
|
_store.set(cardId, { ...data, timestamp: Date.now() });
|
|
return this;
|
|
},
|
|
|
|
get(cardId) {
|
|
return _store.get(cardId);
|
|
},
|
|
|
|
has(cardId) {
|
|
return _store.has(cardId);
|
|
},
|
|
|
|
delete(cardId) {
|
|
return _store.delete(cardId);
|
|
},
|
|
|
|
get size() {
|
|
return _store.size;
|
|
},
|
|
|
|
entries() {
|
|
return _store.entries();
|
|
},
|
|
};
|
|
|
|
const sweepHandle = setInterval(() => {
|
|
const now = Date.now();
|
|
for (const [cardId, data] of _store.entries()) {
|
|
const stamped = typeof data?.timestamp === 'number' ? data.timestamp : 0;
|
|
if (now - stamped > TTL_MS) {
|
|
logger(
|
|
'voicediag:cleanup',
|
|
`Expired card ${cardId} for store ${data?.storeNum || 'unknown'} ` +
|
|
`remediation ${data?.remediationId || 'unknown'}`,
|
|
);
|
|
_store.delete(cardId);
|
|
}
|
|
}
|
|
}, SWEEP_INTERVAL_MS);
|
|
|
|
sweepHandle.unref?.();
|