fix: 状态镜像防冲掉——服务端按 key 合并,客户端读不到宿主就不写
This commit is contained in:
+28
-11
@@ -9160,7 +9160,7 @@ var CHANGELOG_V030 = `\u66F4\u65B0\u516C\u544A \xB7 v0.5.1
|
||||
\u30103.1 \u6587\u5B57\u4E0E\u7EC6\u8282\u6253\u78E8\u3011\u5168\u5C40\u5B57\u4F53\u4E0E\u5B57\u53F7\u7EDF\u4E00\u4F18\u5316\u3001\u4E0B\u62C9\u9762\u677F\u73BB\u7483\u5316\u4E0E\u5BF9\u9F50\u3001\u83DC\u5355\u70B9\u9009\u540E\u4FDD\u6301\u6253\u5F00\u4FBF\u4E8E\u8FDE\u7EED\u9884\u89C8\u3001\u6309\u94AE\u63CF\u8FB9\u4E0E\u60AC\u505C\u53CD\u9988\u7B49\u4EA4\u4E92\u7EC6\u8282\uFF1B\u540C\u65F6\u4FEE\u590D\u4E86\u591A\u9879\u4F53\u9A8C\u95EE\u9898\uFF08\u7167\u7247\u4E0A\u4F20\u6E05\u6670\u5EA6\u3001\u80CC\u666F\u7F51\u683C\u7EBF\u5728\u7167\u7247\u6A21\u5F0F\u4E0B\u4E0D\u751F\u6548\u3001\u6D45\u8272\u4E3B\u9898\u4E0B\u5DE5\u4F5C\u72B6\u6001\u5149\u6548\u4E0D\u53EF\u89C1\u7B49\uFF09\u3002`;
|
||||
|
||||
// src/client/updateCheck.ts
|
||||
var LOCAL_VERSION = false ? "dev" : "0.5.2-local.2";
|
||||
var LOCAL_VERSION = false ? "dev" : "0.5.2-local.3";
|
||||
var UPDATE_REPO = "Aisland-SJL/dsh-worktable";
|
||||
var K_UPDATE_CHECK = "dsh.worktable.updateCheck.v1";
|
||||
var K_LAST_CHECK = "dsh.worktable.lastUpdateCheck.v1";
|
||||
@@ -20735,15 +20735,22 @@ var WAVE_BG_B64 = "/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQUFBAYFBQUHBgYHCQ8KCQgIC
|
||||
|
||||
// src/client/hostState.ts
|
||||
var ENDPOINT = "/api/worktable/state";
|
||||
var RETRY_DELAYS = [0, 300, 700, 1200, 2e3, 3e3];
|
||||
async function loadHostState() {
|
||||
try {
|
||||
const r = await fetch(ENDPOINT, { cache: "no-store", headers: { accept: "application/json" } });
|
||||
if (!r.ok) return null;
|
||||
const j = await r.json();
|
||||
return j && typeof j === "object" && j.state && typeof j.state === "object" ? j.state : null;
|
||||
} catch {
|
||||
return null;
|
||||
for (const delay of RETRY_DELAYS) {
|
||||
if (delay) await new Promise((r) => setTimeout(r, delay));
|
||||
try {
|
||||
const r = await fetch(ENDPOINT, { cache: "no-store", headers: { accept: "application/json" } });
|
||||
if (!r.ok) continue;
|
||||
const j = await r.json();
|
||||
if (j && typeof j === "object" && j.state && typeof j.state === "object") {
|
||||
return { reachable: true, state: j.state };
|
||||
}
|
||||
return { reachable: true, state: null };
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return { reachable: false };
|
||||
}
|
||||
var parts = {};
|
||||
var timer = null;
|
||||
@@ -20785,9 +20792,13 @@ function queueHostSave(patch) {
|
||||
if (!hydrated) return;
|
||||
schedule();
|
||||
}
|
||||
function markHydrated() {
|
||||
function markHydrated(seed) {
|
||||
if (seed) parts = { ...seed, ...parts };
|
||||
hydrated = true;
|
||||
}
|
||||
function staysReadOnly() {
|
||||
hydrated = false;
|
||||
}
|
||||
function flushHostSave() {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
@@ -22097,8 +22108,14 @@ function WorktableSection(props) {
|
||||
(0, import_react3.useEffect)(() => {
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const host = await loadHostState();
|
||||
const res = await loadHostState();
|
||||
if (cancelled) return;
|
||||
if (!res.reachable) {
|
||||
staysReadOnly();
|
||||
console.warn("[dsh-worktable] \u5BBF\u4E3B\u72B6\u6001\u6682\u4E0D\u53EF\u8BFB\uFF0C\u672C\u6B21\u4F1A\u8BDD\u4E0D\u5199\u955C\u50CF");
|
||||
return;
|
||||
}
|
||||
const host = res.state;
|
||||
let localAt = 0;
|
||||
try {
|
||||
localAt = Number(localStorage.getItem(SAVED_AT_KEY) ?? "0") || 0;
|
||||
@@ -22109,7 +22126,7 @@ function WorktableSection(props) {
|
||||
if (host.projects) persistProjects(normalizeProjects(host.projects));
|
||||
if (host.view) persistView(normalizeView(host.view));
|
||||
}
|
||||
markHydrated();
|
||||
markHydrated(host ? { projects: host.projects, view: host.view } : {});
|
||||
if (hostAt <= localAt) flushHostSave();
|
||||
})();
|
||||
const flush2 = () => flushHostSave();
|
||||
|
||||
+10
-3
@@ -225,7 +225,7 @@ function resolveDshHomeSafe() {
|
||||
cachedDshHome = baseDshHome();
|
||||
return cachedDshHome;
|
||||
}
|
||||
var PLUGIN_VERSION = false ? "dev" : "0.5.2-local.2";
|
||||
var PLUGIN_VERSION = false ? "dev" : "0.5.2-local.3";
|
||||
var name = "dsh-worktable";
|
||||
var inject = ["webServer", "sessions"];
|
||||
var HEALTH_PATH = "/api/worktable/health";
|
||||
@@ -857,11 +857,18 @@ function apply(ctx) {
|
||||
}
|
||||
try {
|
||||
const body = await readJsonBody(req);
|
||||
const state = body && typeof body.state === "object" && body.state !== null ? body.state : null;
|
||||
if (!state) {
|
||||
const incoming = body && typeof body.state === "object" && body.state !== null ? body.state : null;
|
||||
if (!incoming) {
|
||||
json(res, 400, { error: "missing state" });
|
||||
return;
|
||||
}
|
||||
let previous = null;
|
||||
try {
|
||||
const raw = await readFile(file, "utf8");
|
||||
previous = JSON.parse(raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw);
|
||||
} catch {
|
||||
}
|
||||
const state = previous && typeof previous === "object" && !Array.isArray(previous) ? { ...previous, ...incoming } : incoming;
|
||||
const text = JSON.stringify(state);
|
||||
if (text.length > 20 * 1024 * 1024) {
|
||||
json(res, 413, { error: "state too large" });
|
||||
|
||||
Reference in New Issue
Block a user