mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
269 lines
10 KiB
JavaScript
269 lines
10 KiB
JavaScript
(function (root, factory) {
|
|
const exported = factory();
|
|
if (typeof module !== "undefined" && module.exports) {
|
|
module.exports = exported;
|
|
}
|
|
root.MeshchatLoadingStatusNotice = exported;
|
|
})(typeof globalThis !== "undefined" ? globalThis : window, function () {
|
|
function toLowerText(value) {
|
|
if (value == null) {
|
|
return "";
|
|
}
|
|
return String(value).toLowerCase();
|
|
}
|
|
|
|
function parseMemoryLogLine(text) {
|
|
if (!text || typeof text !== "string") {
|
|
return null;
|
|
}
|
|
for (const rawLine of text.split(/\r?\n/)) {
|
|
const line = rawLine.trim();
|
|
if (!line.startsWith("MESHCHAT_MEMORY:")) {
|
|
continue;
|
|
}
|
|
const parsed = {};
|
|
const payload = line.slice("MESHCHAT_MEMORY:".length).trim();
|
|
for (const token of payload.split(/\s+/)) {
|
|
const idx = token.indexOf("=");
|
|
if (idx <= 0) {
|
|
continue;
|
|
}
|
|
const key = token.slice(0, idx);
|
|
const value = token.slice(idx + 1);
|
|
if (key === "total_mb" || key === "available_mb" || key === "percent_used") {
|
|
const num = Number(value);
|
|
if (Number.isFinite(num)) {
|
|
parsed[key] = num;
|
|
}
|
|
} else {
|
|
parsed[key] = value;
|
|
}
|
|
}
|
|
return Object.keys(parsed).length > 0 ? parsed : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param {string} stderr
|
|
* @param {string} stdout
|
|
* @param {number|null|undefined} exitCode
|
|
* @returns {{ category: string, summary: string, hints: string[] }}
|
|
*/
|
|
function diagnoseBackendCrash(stderr, stdout, exitCode) {
|
|
const combined = `${stdout || ""}\n${stderr || ""}`.toLowerCase();
|
|
const rawCombined = `${stdout || ""}\n${stderr || ""}`;
|
|
const hints = [];
|
|
const memoryLine = parseMemoryLogLine(rawCombined);
|
|
|
|
if (memoryLine) {
|
|
const available = Number(memoryLine.available_mb);
|
|
const action = String(memoryLine.action || "");
|
|
if (action === "abort" || (Number.isFinite(available) && available < 200)) {
|
|
hints.push("Close other apps to free RAM, then relaunch.");
|
|
hints.push("Try Emergency Mode if the device has 4 GB RAM or less.");
|
|
return {
|
|
category: "memory",
|
|
summary: Number.isFinite(available)
|
|
? `The backend reported critically low memory (${available.toFixed(0)} MB available).`
|
|
: "The backend aborted startup due to critically low memory.",
|
|
hints,
|
|
};
|
|
}
|
|
if (action === "warn") {
|
|
hints.push("Try Emergency Mode if the device has 4 GB RAM or less.");
|
|
return {
|
|
category: "memory",
|
|
summary: Number.isFinite(available)
|
|
? `The backend detected limited memory (${available.toFixed(0)} MB available).`
|
|
: "The backend detected limited system memory during startup.",
|
|
hints,
|
|
};
|
|
}
|
|
}
|
|
|
|
if (
|
|
combined.includes("memoryerror") ||
|
|
combined.includes("out of memory") ||
|
|
combined.includes("cannot allocate memory")
|
|
) {
|
|
hints.push("Close other apps to free RAM, then relaunch.");
|
|
hints.push("Try Emergency Mode if the device has 4 GB RAM or less.");
|
|
return {
|
|
category: "memory",
|
|
summary: "The backend likely ran out of memory during startup.",
|
|
hints,
|
|
};
|
|
}
|
|
|
|
if (
|
|
combined.includes("address already in use") ||
|
|
combined.includes("eaddrinuse") ||
|
|
combined.includes("only one usage of each socket")
|
|
) {
|
|
hints.push("End any ReticulumMeshChatX.exe process in Task Manager, then relaunch.");
|
|
return {
|
|
category: "port-conflict",
|
|
summary: "Port 9337 is already in use.",
|
|
hints,
|
|
};
|
|
}
|
|
|
|
if (
|
|
combined.includes("eacces") ||
|
|
combined.includes("eperm") ||
|
|
combined.includes("permission denied") ||
|
|
combined.includes("access is denied")
|
|
) {
|
|
hints.push("Ensure the MeshChatX data folder is writable.");
|
|
return {
|
|
category: "permission",
|
|
summary: "The backend could not access a required file or folder.",
|
|
hints,
|
|
};
|
|
}
|
|
|
|
if (combined.includes("!!! application crash detected !!!")) {
|
|
hints.push("Open meshchatx.log for the full diagnostic trace from the crash recovery engine.");
|
|
return {
|
|
category: "python-crash",
|
|
summary: "The backend crash recovery system reported a fatal Python error.",
|
|
hints,
|
|
};
|
|
}
|
|
|
|
if (exitCode != null && exitCode !== 0) {
|
|
hints.push("Open the logs folder below and review meshchatx.log and last-backend-crash.json.");
|
|
return {
|
|
category: "exit-code",
|
|
summary: `The backend exited with code ${exitCode}.`,
|
|
hints,
|
|
};
|
|
}
|
|
|
|
return {
|
|
category: "unknown",
|
|
summary: "The backend stopped before the UI could connect.",
|
|
hints: ["Review the saved crash report and meshchatx.log for details."],
|
|
};
|
|
}
|
|
|
|
function formatLogPathHint(paths) {
|
|
if (!paths || typeof paths !== "object") {
|
|
return "";
|
|
}
|
|
if (paths.backendLogPath) {
|
|
return `Logs: ${paths.backendLogPath}`;
|
|
}
|
|
if (paths.logsDir) {
|
|
return `Logs folder: ${paths.logsDir}`;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* @param {unknown[]} failures
|
|
* @param {object|null} runtimeState
|
|
* @param {object} options
|
|
* @returns {object}
|
|
*/
|
|
function classifyConnectionIssue(failures, runtimeState, options) {
|
|
const entries = Array.isArray(failures) ? failures : [];
|
|
const state = runtimeState && typeof runtimeState === "object" ? runtimeState : null;
|
|
const opts = options && typeof options === "object" ? options : {};
|
|
const attemptCount = Number(opts.attemptCount) || 0;
|
|
const networkWarnAfterAttempts = Number(opts.networkWarnAfterAttempts) || 24;
|
|
const crash = opts.crash && typeof opts.crash === "object" ? opts.crash : null;
|
|
const paths = opts.paths && typeof opts.paths === "object" ? opts.paths : null;
|
|
|
|
if (state && state.running === false && state.lastExitCode != null) {
|
|
const diagnosis = diagnoseBackendCrash(crash?.stderr || "", crash?.stdout || "", state.lastExitCode);
|
|
const logHint = formatLogPathHint(paths);
|
|
const detailParts = [diagnosis.summary, ...diagnosis.hints];
|
|
if (logHint) {
|
|
detailParts.push(logHint);
|
|
}
|
|
return {
|
|
reason: "backend-exited",
|
|
headline: "The backend process stopped unexpectedly.",
|
|
detail: detailParts.join(" "),
|
|
category: diagnosis.category,
|
|
exitCode: state.lastExitCode,
|
|
hints: diagnosis.hints,
|
|
logHint,
|
|
crash,
|
|
paths,
|
|
};
|
|
}
|
|
|
|
const hasAddressUnreachable = entries.some((entry) => entry && entry.kind === "address-unreachable");
|
|
if (hasAddressUnreachable) {
|
|
return {
|
|
reason: "loopback-blocked",
|
|
headline: "Cannot reach local backend on 127.0.0.1:9337.",
|
|
detail: "A firewall, VPN, sandbox, or loopback policy may be blocking local connections.",
|
|
};
|
|
}
|
|
|
|
const hasNetworkError = entries.some((entry) => entry && entry.kind === "network-error");
|
|
if (hasNetworkError) {
|
|
if (attemptCount < networkWarnAfterAttempts) {
|
|
return {
|
|
reason: "starting",
|
|
headline: "Waiting for backend startup.",
|
|
detail: "MeshChatX is still initializing services.",
|
|
};
|
|
}
|
|
return {
|
|
reason: "network-blocked",
|
|
headline: "Still waiting for local backend connection.",
|
|
detail: "If startup stays stuck, firewall or network filtering software may be blocking localhost traffic.",
|
|
};
|
|
}
|
|
|
|
const hasServerError = entries.some(
|
|
(entry) => entry && entry.kind === "http-error" && Number(entry.status) >= 500
|
|
);
|
|
if (hasServerError) {
|
|
return {
|
|
reason: "backend-http-error",
|
|
headline: "Backend is running but reported an internal error.",
|
|
detail: "MeshChatX will keep retrying while the backend finishes startup.",
|
|
};
|
|
}
|
|
|
|
const hasInvalidPayload = entries.some((entry) => entry && entry.kind === "invalid-payload");
|
|
if (hasInvalidPayload) {
|
|
return {
|
|
reason: "backend-invalid-response",
|
|
headline: "Backend responded with invalid startup data.",
|
|
detail: "MeshChatX will continue retrying while the backend stabilizes.",
|
|
};
|
|
}
|
|
|
|
return {
|
|
reason: "starting",
|
|
headline: "Waiting for backend startup.",
|
|
detail: "MeshChatX is still initializing services.",
|
|
};
|
|
}
|
|
|
|
function classifyFetchError(error) {
|
|
const message = `${toLowerText(error && error.name)} ${toLowerText(error && error.message)}`.trim();
|
|
if (
|
|
message.includes("err_address_unreachable") ||
|
|
message.includes("address_unreachable") ||
|
|
message.includes("ehostunreach")
|
|
) {
|
|
return "address-unreachable";
|
|
}
|
|
return "network-error";
|
|
}
|
|
|
|
return {
|
|
classifyConnectionIssue,
|
|
classifyFetchError,
|
|
diagnoseBackendCrash,
|
|
formatLogPathHint,
|
|
};
|
|
});
|