Files

114 lines
4.1 KiB
JavaScript
Raw Permalink Normal View History

2026-07-15 18:01:56 +08:00
// 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,
};