返回 DeepSeek-Reasonix
remoteSessionCache.ts
根目录 / desktop / frontend / src / lib / remoteSessionCache.ts
1 import type { RemoteSessionView } from "./types";
2
3 const REMOTE_SESSIONS_CACHE_PREFIX = "projectTree:remoteSessions:";
4 const REMOTE_SESSION_CACHE_VERSION = 1;
5 export const REMOTE_SESSION_CACHE_TTL_MS = 5 * 60 * 1000;
6
7 interface RemoteSessionCacheEnvelope {
8 version: typeof REMOTE_SESSION_CACHE_VERSION;
9 savedAt: number;
10 rows: RemoteSessionView[];
11 }
12
13 function cacheKey(groupKey: string): string {
14 return REMOTE_SESSIONS_CACHE_PREFIX + groupKey;
15 }
16
17 function parseRows(value: unknown): RemoteSessionView[] | undefined {
18 if (!Array.isArray(value)) return undefined;
19 const rows: RemoteSessionView[] = [];
20 for (const row of value) {
21 if (!row || typeof row !== "object") return undefined;
22 const candidate = row as Partial<RemoteSessionView>;
23 if (typeof candidate.name !== "string") return undefined;
24 const valid = (candidate.title === undefined || typeof candidate.title === "string")
25 && (candidate.turns === undefined || typeof candidate.turns === "number" && Number.isFinite(candidate.turns))
26 && (candidate.path === undefined || typeof candidate.path === "string")
27 && (candidate.current === undefined || typeof candidate.current === "boolean")
28 && (candidate.running === undefined || typeof candidate.running === "boolean")
29 && (candidate.lastActivityAt === undefined
30 || typeof candidate.lastActivityAt === "number" && Number.isFinite(candidate.lastActivityAt))
31 && (candidate.pinned === undefined || typeof candidate.pinned === "boolean");
32 if (!valid) return undefined;
33 rows.push({
34 ...candidate,
35 name: candidate.name,
36 title: candidate.title ?? "",
37 turns: candidate.turns ?? 0,
38 });
39 }
40 return rows;
41 }
42
43 export function removeRemoteSessionCache(groupKey: string): void {
44 try {
45 if (typeof localStorage !== "undefined") localStorage.removeItem(cacheKey(groupKey));
46 } catch {
47 // localStorage is an optional optimistic-paint cache.
48 }
49 }
50
51 export function saveRemoteSessionCache(groupKey: string, rows: RemoteSessionView[], now = Date.now()): void {
52 try {
53 if (typeof localStorage === "undefined") return;
54 const durableRows = rows.filter((row) => row.name.trim() !== "");
55 const envelope: RemoteSessionCacheEnvelope = {
56 version: REMOTE_SESSION_CACHE_VERSION,
57 savedAt: now,
58 rows: durableRows,
59 };
60 localStorage.setItem(cacheKey(groupKey), JSON.stringify(envelope));
61 } catch {
62 // localStorage can be unavailable or full; live state remains authoritative.
63 }
64 }
65
66 export function loadRemoteSessionCache(groupKey: string, now = Date.now()): RemoteSessionView[] {
67 try {
68 if (typeof localStorage === "undefined") return [];
69 const raw = localStorage.getItem(cacheKey(groupKey));
70 if (!raw) return [];
71 const parsed = JSON.parse(raw) as Partial<RemoteSessionCacheEnvelope> | null;
72 const rows = parseRows(parsed?.rows);
73 const fresh = parsed?.version === REMOTE_SESSION_CACHE_VERSION
74 && typeof parsed.savedAt === "number"
75 && parsed.savedAt <= now
76 && now - parsed.savedAt <= REMOTE_SESSION_CACHE_TTL_MS
77 && rows !== undefined;
78 if (!fresh) {
79 removeRemoteSessionCache(groupKey);
80 return [];
81 }
82 return rows;
83 } catch {
84 removeRemoteSessionCache(groupKey);
85 return [];
86 }
87 }
88
88 lines TYPESCRIPT