// hotstate.js —— 热更新版本状态机(纯 fs,bootstrap 与 main 共用)。 // 维护 userData/hot/current.json:{ target, confirmed, blacklist[], attempts{} } // - target :期望运行的热更版本 // - confirmed :最近一次证明健康(渲染层成功加载)的版本 = 安全回退点 // - blacklist :损坏版本,永不再选 // - attempts :未确认版本的启动尝试计数(先落盘再加载,崩溃后下次可判砖) "use strict"; const fs = require("fs"); const path = require("path"); const MAX_TRY = 1; // 未确认版本最多尝试次数;超过即判砖回退 function statePath(hotBase) { return path.join(hotBase, "current.json"); } function readState(hotBase) { try { const s = JSON.parse(fs.readFileSync(statePath(hotBase), "utf8")); return { target: s.target || null, confirmed: s.confirmed || null, blacklist: Array.isArray(s.blacklist) ? s.blacklist : [], attempts: s.attempts && typeof s.attempts === "object" ? s.attempts : {}, }; } catch { return { target: null, confirmed: null, blacklist: [], attempts: {} }; } } function writeState(hotBase, state) { try { fs.mkdirSync(hotBase, { recursive: true }); fs.writeFileSync(statePath(hotBase), JSON.stringify(state, null, 2)); return true; } catch { return false; // 无写权限等 → 由调用方降级 } } // 版本目录是否为可加载的完整结构 function versionUsable(hotBase, ver) { if (!ver) return false; try { return fs.existsSync(path.join(hotBase, ver, "electron", "main.js")); } catch { return false; } } // bootstrap 调用:决定本次启动加载哪个版本。返回版本名(字符串)或 null(=用包内)。 // 有副作用:对 pending 版本自增 attempts 并【先落盘】,超限则拉黑回退。 function chooseVersion(hotBase) { const state = readState(hotBase); let cand = state.target; if (!cand) return null; // 被拉黑或目录不可用 → 回退 confirmed if (state.blacklist.includes(cand) || !versionUsable(hotBase, cand)) { cand = state.confirmed; } if (!versionUsable(hotBase, cand)) return null; // confirmed 也没了 → 包内 if (cand === state.confirmed) return cand; // 已确认健康,直接用 // cand 是刚应用、尚未确认的新版本(pending) state.attempts[cand] = (state.attempts[cand] || 0) + 1; const over = state.attempts[cand] > MAX_TRY; if (over) { // 之前给过机会仍未确认 → 判砖,拉黑并回退 if (!state.blacklist.includes(cand)) state.blacklist.push(cand); delete state.attempts[cand]; state.target = state.confirmed; writeState(hotBase, state); return versionUsable(hotBase, state.confirmed) ? state.confirmed : null; } writeState(hotBase, state); // 关键:先落盘尝试计数,再返回让 bootstrap 加载 return cand; } // 加载崩溃时拉黑某版本 function blacklistVersion(hotBase, ver, reason) { if (!ver) return; const state = readState(hotBase); if (!state.blacklist.includes(ver)) state.blacklist.push(ver); delete state.attempts[ver]; if (state.target === ver) state.target = state.confirmed; state.lastError = { ver, reason: String(reason || "").slice(0, 500) }; writeState(hotBase, state); } // 渲染层成功加载后确认健康 function confirmHealthy(hotBase, ver) { if (!ver) return; const state = readState(hotBase); state.confirmed = ver; state.target = ver; delete state.attempts[ver]; state.blacklist = state.blacklist.filter((v) => v !== ver); writeState(hotBase, state); } // 应用新版本后设为 target(等重启生效) function setTarget(hotBase, ver) { const state = readState(hotBase); state.target = ver; writeState(hotBase, state); } // 当前“有效内容版本”:优先已确认,其次包内(由调用方给 fallback) function effectiveVersion(hotBase) { const state = readState(hotBase); return state.confirmed || null; } module.exports = { MAX_TRY, readState, writeState, versionUsable, chooseVersion, blacklistVersion, confirmHealthy, setTarget, effectiveVersion, };