export const name = "Microsoft 365"; export const url = "https://status.cloud.microsoft/m365/referrer=serviceStatusRedirect"; const GRAPH_HEALTH_URL = "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews"; // Token cache let cachedToken = null; // Graph serviceStatus → dashboard status const STATUS_MAP = { serviceOperational: "operational", serviceRestored: "operational", postIncidentReviewPublished: "operational", verifyingService: "operational", falsePositive: "operational", serviceDegradation: "degraded", investigating: "degraded", restoringService: "degraded", extendedRecovery: "degraded", investigationSuspended: "degraded", serviceInterruption: "outage", }; // Graph serviceStatus → human-readable label for the message const STATUS_LABEL = { serviceDegradation: "service degradation", investigating: "investigating", restoringService: "restoring service", extendedRecovery: "extended recovery", investigationSuspended: "investigation suspended", serviceInterruption: "service interruption", }; const SEVERITY_ORDER = ["operational", "degraded", "outage", "unknown"]; function mapStatus(graphStatus) { return STATUS_MAP[graphStatus] || "unknown"; } function worstStatus(a, b) { return SEVERITY_ORDER.indexOf(a) >= SEVERITY_ORDER.indexOf(b) ? a : b; } async function getToken() { const { AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET } = process.env; if (!AZURE_TENANT_ID || !AZURE_CLIENT_ID || !AZURE_CLIENT_SECRET) { throw new Error( "Missing Azure credentials (AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET)" ); } // Return cached token if still valid (with 5-minute buffer) if (cachedToken && Date.now() < cachedToken.expiresAt - 5 * 60 * 1000) { return cachedToken.accessToken; } const tokenUrl = `https://login.microsoftonline.com/${AZURE_TENANT_ID}/oauth2/v2.0/token`; const body = new URLSearchParams({ grant_type: "client_credentials", client_id: AZURE_CLIENT_ID, client_secret: AZURE_CLIENT_SECRET, scope: "https://graph.microsoft.com/.default", }); const res = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body, }); if (!res.ok) { const text = await res.text(); throw new Error(`Token request failed (${res.status}): ${text}`); } const data = await res.json(); cachedToken = { accessToken: data.access_token, expiresAt: Date.now() + data.expires_in * 1000, }; return cachedToken.accessToken; } export async function checkStatus() { const token = await getToken(); const res = await fetch(GRAPH_HEALTH_URL, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) { const text = await res.text(); throw new Error(`Graph API request failed (${res.status}): ${text}`); } const data = await res.json(); const services = data.value || []; let overall = "operational"; const issues = []; for (const svc of services) { const mapped = mapStatus(svc.status); overall = worstStatus(overall, mapped); if (mapped !== "operational") { const label = STATUS_LABEL[svc.status] ?? svc.status; issues.push(`${svc.service}: ${label}`); } } const message = issues.length > 0 ? issues.join(", ") : "All services running normally."; return { name, status: overall, message, lastUpdated: new Date().toISOString(), }; }