Files

129 lines
3.4 KiB
JavaScript

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(),
};
}