// integrations/cisco-mpp-phone/downloadStatusJson.js // // Parser for /Download%20Status.json (provisioning + firmware history). import { coerceRows, fieldsFromRows, extractIndexed } from './jsonRows.js'; /** * @param {string|Array} raw * @returns {object} */ export function parseDownloadStatusJson(raw) { const rows = coerceRows(raw); const fields = fieldsFromRows(rows); const firmwareUpgrades = extractIndexed(fields, /^Firmware Upgrade Status \d+$/i); const provisioning = extractIndexed(fields, /^Provisioning Status \d+$/i); const transitionAuth = extractIndexed(fields, /^Transition Authorization Status \d+$/i); const micCertStatus = fields['MIC Cert Provisioning Status'] || null; const micCertInfo = fields['MIC Cert Info'] || null; return { fields, latestFirmwareUpgrade: parseHistoryEntry(firmwareUpgrades[0]), latestProvisioning: parseHistoryEntry(provisioning[0]), latestTransitionAuth: parseHistoryEntry(transitionAuth[0]), firmwareUpgrades: firmwareUpgrades.map(parseHistoryEntry).filter(Boolean), provisioningHistory: provisioning.map(parseHistoryEntry).filter(Boolean), micCert: { provisioningStatus: micCertStatus, info: micCertInfo, failed: !!(micCertStatus && /fail/i.test(micCertStatus)), }, }; } /** * Parse "[timestamp][url]Result." or "[seq][timestamp][url]Result." entries. * @param {string|null} raw */ export function parseHistoryEntry(raw) { if (!raw || typeof raw !== 'string') return null; const trimmed = raw.trim(); if (!trimmed) return null; const triple = trimmed.match(/^\[([^\]]*)\]\[([^\]]*)\]\[([^\]]*)\](.+)$/); if (triple && looksLikeUrl(triple[3])) { return buildEntry(trimmed, triple[2], triple[3], triple[4]); } const double = trimmed.match(/^\[([^\]]*)\]\[([^\]]*)\](.+)$/); if (double) { if (looksLikeUrl(double[2])) { return buildEntry(trimmed, double[1], double[2], double[3]); } return buildEntry(trimmed, null, double[1], double[2]); } return buildEntry(trimmed, null, null, trimmed); } function looksLikeUrl(s) { return typeof s === 'string' && (/^https?:\/\//i.test(s) || /\.[a-z]{2,}/i.test(s)); } function buildEntry(raw, timestamp, url, resultPart) { const result = (resultPart || '').trim(); return { raw, timestamp: timestamp || null, url: url || null, result: result || null, succeeded: /succeed/i.test(result), failed: /fail/i.test(result), }; } export function downloadStatusWarnings(download) { const warnings = []; if (!download) return warnings; if (download.micCert?.failed) { warnings.push('mic_cert_failed'); } if (download.latestProvisioning?.failed) { warnings.push('provisioning_failed'); } if (download.latestFirmwareUpgrade?.failed) { warnings.push('firmware_upgrade_failed'); } return warnings; } /** * Human-readable MIC cert issue for chat rendering (no raw URLs). * @param {{ provisioningStatus?: string|null, info?: string|null, failed?: boolean }|null} micCert * @returns {string|null} */ export function formatMicCertForDisplay(micCert) { if (!micCert?.failed && !micCert?.provisioningStatus) return null; const entry = parseHistoryEntry(micCert.provisioningStatus || micCert.info); const at = entry?.timestamp || null; const reason = shortenMicReason(entry?.result || micCert.provisioningStatus || 'download failed'); const atSuffix = at ? ` (${at})` : ''; return `MIC cert download failed — ${reason}${atSuffix}`; } function shortenMicReason(raw) { if (!raw || typeof raw !== 'string') return 'unknown error'; const reasonMatch = raw.match(/Reason:\s*(.+?)\.?$/i); if (reasonMatch) return reasonMatch[1].trim(); const failedMatch = raw.match(/MIC Cert Download Failed\.?\s*(.*)$/i); if (failedMatch && failedMatch[1].trim()) return failedMatch[1].replace(/^Reason:\s*/i, '').trim(); if (/fail/i.test(raw)) return 'download failed'; return raw.length > 80 ? `${raw.slice(0, 77)}…` : raw; }