返回 DeepSeek-Reasonix
runtimeStateSync.ts
根目录 / desktop / frontend / src / lib / runtimeStateSync.ts
1 import { app } from "./bridge";
2 import { desktopHost } from "./desktopHost";
3 import { acceptRuntimeState } from "./runtimeStateReducer";
4 import { runtimeStateStore, type RuntimeProjection } from "./runtimeStateStore";
5 export interface RuntimeSyncPorts {
6 subscribe: (accept: (snapshot: RuntimeProjection) => void) => () => void;
7 read: () => Promise<RuntimeProjection>;
8 timer: (callback: () => void, delay: number) => unknown;
9 clearTimer: (timer: unknown) => void;
10 focus: (callback: () => void) => () => void;
11 recover?: (callback: () => void) => () => void;
12 diagnostic?: (data: { reason: string; revision?: number; stale: number; conflicts: number; failures: number }) => void;
13 }
14 export function startRuntimeStateSync(ports: RuntimeSyncPorts, store = runtimeStateStore) {
15 let disposed = false, inFlight = false, failures = 0;
16 let stale = 0, conflicts = 0;
17 let recoveryVersion = 0, recoveryQueued = false;
18 let timer: unknown;
19 const note = (reason: string, revision?: number) => ports.diagnostic?.({ reason, revision, stale, conflicts, failures });
20 const sync = async (reason = "initial") => {
21 if (disposed || inFlight) return;
22 inFlight = true;
23 const version = recoveryVersion;
24 ports.clearTimer(timer);
25 const before = store.getSnapshot();
26 try {
27 const snapshot = await ports.read();
28 if (disposed || version !== recoveryVersion) return;
29 const result = acceptRuntimeState(store, snapshot, before === store.getSnapshot());
30 if (result === "stale") { stale++; note("stale-read", snapshot.revision); }
31 if (result === "conflict") { conflicts++; throw new Error("Runtime snapshot version conflict"); }
32 failures = snapshot.sessions.some(session => session.remote && session.freshness !== "synced") ? failures + 1 : 0;
33 } catch {
34 if (!disposed && version === recoveryVersion) { failures++; store.fail(); note(reason); }
35 } finally {
36 inFlight = false;
37 if (!disposed && recoveryQueued) {
38 recoveryQueued = false;
39 void sync("event-recovery");
40 } else if (!disposed) timer = ports.timer(() => { void sync("periodic"); }, failures ? [5000, 10000, 20000, 30000][Math.min(failures - 1, 3)] : 30000);
41 }
42 };
43 const off = ports.subscribe(snapshot => {
44 if (disposed) return;
45 const result = acceptRuntimeState(store, snapshot);
46 if (result === "stale") { stale++; note("stale-event", snapshot.revision); }
47 if (result === "conflict") { conflicts++; note("conflicting-event", snapshot.revision); void sync("conflicting-event"); }
48 });
49 const offFocus = ports.focus(() => { void sync("focus-or-connection"); });
50 const offRecover = ports.recover?.(() => {
51 recoveryVersion++;
52 store.fail();
53 if (inFlight) recoveryQueued = true;
54 else void sync("event-recovery");
55 });
56 void sync();
57 return () => { disposed = true; off(); offFocus(); offRecover?.(); ports.clearTimer(timer); };
58 }
59
60 export function startAppRuntimeStateSync() {
61 return startRuntimeStateSync({
62 diagnostic: data => console.debug("runtime synchronization", { source: "desktop-runtime", ...data }),
63 subscribe: accept => desktopHost().events.on("runtime-state:changed", (snapshot: unknown) => accept(snapshot as RuntimeProjection)),
64 read: async () => {
65 if (!app.SyncRuntimeState) return app.GetRuntimeStateSnapshot!();
66 try { return await app.SyncRuntimeState(); }
67 catch { return app.GetRuntimeStateSnapshot!(); }
68 },
69 timer: (callback, delay) => window.setTimeout(callback, delay),
70 clearTimer: timer => { if (timer !== undefined) window.clearTimeout(timer as number); },
71 recover: callback => desktopHost().events.on("desktop:resync", callback),
72 focus: callback => {
73 const connections = new Map<string, string>();
74 const off = desktopHost().events.on("remote-tab:updated", (payload: unknown) => {
75 const tab = payload as { id?: string; remoteState?: string };
76 if (tab.id && connections.get(tab.id) !== tab.remoteState) { connections.set(tab.id, tab.remoteState ?? ""); callback(); }
77 });
78 window.addEventListener("focus", callback);
79 window.addEventListener("online", callback);
80 return () => { off?.(); window.removeEventListener("focus", callback); window.removeEventListener("online", callback); };
81 },
82 });
83 }
84
84 lines TYPESCRIPT