feat: add desktop app hot update channel
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
// 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,
|
||||
};
|
||||
Reference in New Issue
Block a user