Files
Cognitive_Agent/apps/web-console/network.js

139 lines
4.6 KiB
JavaScript

(function initializeCognitiveAgentNetwork() {
const nativeFetch = window.fetch.bind(window);
const listeners = new Set();
const state = {
status: navigator.onLine === false ? "offline" : "connecting",
lastSuccessAt: null,
lastError: null,
};
let probeTimer = null;
function snapshot() {
return { ...state };
}
function renderBadge() {
const badge = document.querySelector("#connectionBadge");
if (!badge) return;
const presentation = {
connecting: ["neutral", "loader-circle", "连接中"],
online: ["", "wifi", "服务已连接"],
degraded: ["warning", "wifi-low", "连接不稳定"],
offline: ["danger", "wifi-off", "服务已断开"],
}[state.status];
badge.className = `status-badge connection-badge ${presentation[0]}`.trim();
badge.innerHTML = `<i data-lucide="${presentation[1]}"></i><span>${presentation[2]}</span>`;
badge.title = state.lastError || presentation[2];
window.lucide?.createIcons?.({
attrs: { "aria-hidden": "true" },
nodes: [badge],
});
}
function publish(status, error = null) {
const changed = state.status !== status || state.lastError !== error;
state.status = status;
state.lastError = error;
if (status === "online") state.lastSuccessAt = new Date().toISOString();
renderBadge();
if (!changed) return;
const detail = snapshot();
listeners.forEach((listener) => listener(detail));
window.dispatchEvent(new CustomEvent("cognitive-agent:connection", { detail }));
}
function sleep(milliseconds) {
return new Promise((resolve) => window.setTimeout(resolve, milliseconds));
}
async function request(input, options = {}) {
const {
timeoutMs = 10000,
retries: configuredRetries,
signal: callerSignal,
...fetchOptions
} = options;
const method = String(fetchOptions.method || "GET").toUpperCase();
const retries = configuredRetries ?? (method === "GET" ? 1 : 0);
let lastError;
for (let attempt = 0; attempt <= retries; attempt += 1) {
const controller = new AbortController();
const abortFromCaller = () => controller.abort(callerSignal?.reason);
callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
const timeout = window.setTimeout(() => controller.abort("timeout"), timeoutMs);
try {
const response = await nativeFetch(input, {
...fetchOptions,
signal: controller.signal,
});
publish(response.status >= 500 ? "degraded" : "online",
response.status >= 500 ? `服务返回 HTTP ${response.status}` : null);
return response;
} catch (error) {
lastError = error;
const message = controller.signal.aborted && !callerSignal?.aborted
? `请求超时(${Math.round(timeoutMs / 1000)} 秒)`
: (error?.message || "网络请求失败");
publish(attempt < retries ? "degraded" : "offline", message);
if (callerSignal?.aborted || attempt >= retries || method !== "GET") {
throw new Error(message, { cause: error });
}
await sleep(300 * (attempt + 1));
} finally {
window.clearTimeout(timeout);
callerSignal?.removeEventListener("abort", abortFromCaller);
}
}
throw lastError;
}
async function probe() {
if (document.hidden) return;
if (navigator.onLine === false) {
publish("offline", "设备当前处于离线状态");
return;
}
try {
const response = await request("/api/health", {
cache: "no-store",
timeoutMs: 4000,
retries: 0,
});
if (!response.ok && response.status !== 401) {
publish("degraded", `健康检查返回 HTTP ${response.status}`);
}
} catch (_error) {
// request() already published the detailed state.
}
}
function startMonitoring() {
if (probeTimer !== null) return;
renderBadge();
probe();
probeTimer = window.setInterval(probe, 10000);
window.addEventListener("online", () => {
publish("connecting");
probe();
});
window.addEventListener("offline", () => publish("offline", "设备当前处于离线状态"));
document.addEventListener("visibilitychange", () => {
if (!document.hidden) probe();
});
}
window.CognitiveAgentNetwork = {
request,
probe,
subscribe(listener) {
listeners.add(listener);
listener(snapshot());
return () => listeners.delete(listener);
},
getState: snapshot,
nativeFetch,
};
document.addEventListener("DOMContentLoaded", startMonitoring, { once: true });
})();