Просмотр исходного кода

fix(desktop): more defensive access

Adam 1 месяц назад
Родитель
Сommit
65bc72098b

+ 14 - 7
packages/app/src/context/global-sync.tsx

@@ -115,13 +115,14 @@ function createGlobalSync() {
       .then((x) => {
         const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
         const nonArchived = (x.data ?? [])
+          .filter((s) => !!s?.id)
+          .filter((s) => !s.time?.archived)
           .slice()
-          .filter((s) => !s.time.archived)
           .sort((a, b) => a.id.localeCompare(b.id))
         // Include up to the limit, plus any updated in the last 4 hours
         const sessions = nonArchived.filter((s, i) => {
           if (i < store.limit) return true
-          const updated = new Date(s.time.updated).getTime()
+          const updated = new Date(s.time?.updated ?? s.time?.created).getTime()
           return updated > fourHoursAgo
         })
         setStore("session", reconcile(sessions, { key: "id" }))
@@ -169,6 +170,7 @@ function createGlobalSync() {
         sdk.permission.list().then((x) => {
           const grouped: Record<string, Permission[]> = {}
           for (const perm of x.data ?? []) {
+            if (!perm?.id || !perm.sessionID) continue
             const existing = grouped[perm.sessionID]
             if (existing) {
               existing.push(perm)
@@ -187,7 +189,10 @@ function createGlobalSync() {
                 "permission",
                 sessionID,
                 reconcile(
-                  permissions.slice().sort((a, b) => a.id.localeCompare(b.id)),
+                  permissions
+                    .filter((p) => !!p?.id)
+                    .slice()
+                    .sort((a, b) => a.id.localeCompare(b.id)),
                   { key: "id" },
                 ),
               )
@@ -414,10 +419,12 @@ function createGlobalSync() {
       ),
       retry(() =>
         globalSDK.client.project.list().then(async (x) => {
-          setGlobalStore(
-            "project",
-            x.data!.filter((p) => !p.worktree.includes("opencode-test")).sort((a, b) => a.id.localeCompare(b.id)),
-          )
+          const projects = (x.data ?? [])
+            .filter((p) => !!p?.id)
+            .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
+            .slice()
+            .sort((a, b) => a.id.localeCompare(b.id))
+          setGlobalStore("project", projects)
         }),
       ),
       retry(() =>

+ 11 - 2
packages/app/src/context/sync.tsx

@@ -56,7 +56,10 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
                 const result = Binary.search(messages, input.messageID, (m) => m.id)
                 messages.splice(result.index, 0, message)
               }
-              draft.part[input.messageID] = input.parts.slice().sort((a, b) => a.id.localeCompare(b.id))
+              draft.part[input.messageID] = input.parts
+                .filter((p) => !!p?.id)
+                .slice()
+                .sort((a, b) => a.id.localeCompare(b.id))
             }),
           )
         },
@@ -88,6 +91,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
               reconcile(
                 (messages.data ?? [])
                   .map((x) => x.info)
+                  .filter((m) => !!m?.id)
                   .slice()
                   .sort((a, b) => a.id.localeCompare(b.id)),
                 { key: "id" },
@@ -95,11 +99,15 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
             )
 
             for (const message of messages.data ?? []) {
+              if (!message?.info?.id) continue
               setStore(
                 "part",
                 message.info.id,
                 reconcile(
-                  message.parts.slice().sort((a, b) => a.id.localeCompare(b.id)),
+                  message.parts
+                    .filter((p) => !!p?.id)
+                    .slice()
+                    .sort((a, b) => a.id.localeCompare(b.id)),
                   { key: "id" },
                 ),
               )
@@ -112,6 +120,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
           setStore("limit", (x) => x + count)
           await sdk.client.session.list().then((x) => {
             const sessions = (x.data ?? [])
+              .filter((s) => !!s?.id)
               .slice()
               .sort((a, b) => a.id.localeCompare(b.id))
               .slice(0, store.limit)

+ 73 - 16
packages/desktop/src/index.tsx

@@ -57,19 +57,71 @@ const platform: Platform = {
   },
 
   openLink(url: string) {
-    shellOpen(url)
+    void shellOpen(url).catch(() => undefined)
   },
 
   storage: (name = "default.dat") => {
-    const api: AsyncStorage = {
+    type StoreLike = {
+      get(key: string): Promise<string | null | undefined>
+      set(key: string, value: string): Promise<unknown>
+      delete(key: string): Promise<unknown>
+      clear(): Promise<unknown>
+      keys(): Promise<string[]>
+      length(): Promise<number>
+    }
+
+    const memory = () => {
+      const data = new Map<string, string>()
+      const store: StoreLike = {
+        get: async (key) => data.get(key),
+        set: async (key, value) => {
+          data.set(key, value)
+        },
+        delete: async (key) => {
+          data.delete(key)
+        },
+        clear: async () => {
+          data.clear()
+        },
+        keys: async () => Array.from(data.keys()),
+        length: async () => data.size,
+      }
+      return store
+    }
+
+    const api: AsyncStorage & { _store: Promise<StoreLike> | null; _getStore: () => Promise<StoreLike> } = {
       _store: null,
-      _getStore: async () => api._store || (api._store = Store.load(name)),
-      getItem: async (key: string) => (await (await api._getStore()).get(key)) ?? null,
-      setItem: async (key: string, value: string) => await (await api._getStore()).set(key, value),
-      removeItem: async (key: string) => await (await api._getStore()).delete(key),
-      clear: async () => await (await api._getStore()).clear(),
-      key: async (index: number) => (await (await api._getStore()).keys())[index],
-      getLength: async () => (await api._getStore()).length(),
+      _getStore: async () => {
+        if (api._store) return api._store
+        api._store = Store.load(name).catch(() => memory())
+        return api._store
+      },
+      getItem: async (key: string) => {
+        const store = await api._getStore()
+        const value = await store.get(key).catch(() => null)
+        if (value === undefined) return null
+        return value
+      },
+      setItem: async (key: string, value: string) => {
+        const store = await api._getStore()
+        await store.set(key, value).catch(() => undefined)
+      },
+      removeItem: async (key: string) => {
+        const store = await api._getStore()
+        await store.delete(key).catch(() => undefined)
+      },
+      clear: async () => {
+        const store = await api._getStore()
+        await store.clear().catch(() => undefined)
+      },
+      key: async (index: number) => {
+        const store = await api._getStore()
+        return (await store.keys().catch(() => []))[index]
+      },
+      getLength: async () => {
+        const store = await api._getStore()
+        return await store.length().catch(() => 0)
+      },
       get length() {
         return api.getLength()
       },
@@ -79,20 +131,25 @@ const platform: Platform = {
 
   checkUpdate: async () => {
     if (!UPDATER_ENABLED) return { updateAvailable: false }
-    update = await check()
-    if (!update) return { updateAvailable: false }
-    await update.download()
-    return { updateAvailable: true, version: update.version }
+    const next = await check().catch(() => null)
+    if (!next) return { updateAvailable: false }
+    const ok = await next
+      .download()
+      .then(() => true)
+      .catch(() => false)
+    if (!ok) return { updateAvailable: false }
+    update = next
+    return { updateAvailable: true, version: next.version }
   },
 
   update: async () => {
     if (!UPDATER_ENABLED || !update) return
-    if (ostype() === "windows") await invoke("kill_sidecar")
-    await update.install()
+    if (ostype() === "windows") await invoke("kill_sidecar").catch(() => undefined)
+    await update.install().catch(() => undefined)
   },
 
   restart: async () => {
-    await invoke("kill_sidecar")
+    await invoke("kill_sidecar").catch(() => undefined)
     await relaunch()
   },