summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-31 12:00:39 -0600
committerAdam <[email protected]>2025-12-31 13:12:30 -0600
commit65bc72098b737bee12f388215913808fa1629a4d (patch)
tree5e392743dd2e4a5f7874d05bb99c20fe6f690bb2
parentb5546dce802a6befcb7e8b03577543399cef795b (diff)
downloadopencode-65bc72098b737bee12f388215913808fa1629a4d.tar.gz
opencode-65bc72098b737bee12f388215913808fa1629a4d.zip
fix(desktop): more defensive access
-rw-r--r--packages/app/src/context/global-sync.tsx21
-rw-r--r--packages/app/src/context/sync.tsx13
-rw-r--r--packages/desktop/src/index.tsx89
3 files changed, 98 insertions, 25 deletions
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx
index a6851aecb..dd040d8d5 100644
--- a/packages/app/src/context/global-sync.tsx
+++ b/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(() =>
diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx
index 5291f1099..909af2447 100644
--- a/packages/app/src/context/sync.tsx
+++ b/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)
diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx
index 6c2106935..4d3c8b36d 100644
--- a/packages/desktop/src/index.tsx
+++ b/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()
},