diff options
| author | Adam <[email protected]> | 2026-02-10 07:47:05 -0600 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-10 07:47:05 -0600 |
| commit | 1e03a55acdb1e80b747d0604d698f4cbef97ace1 (patch) | |
| tree | fe82b7eae2e9f043b182d81b787d4a0b3be3afed /packages/app/src | |
| parent | 65c966928393a2a7b03af267e8d3279d3370440c (diff) | |
| download | opencode-1e03a55acdb1e80b747d0604d698f4cbef97ace1.tar.gz opencode-1e03a55acdb1e80b747d0604d698f4cbef97ace1.zip | |
fix(app): persist defensiveness (#12973)
Diffstat (limited to 'packages/app/src')
| -rw-r--r-- | packages/app/src/utils/persist.test.ts | 5 | ||||
| -rw-r--r-- | packages/app/src/utils/persist.ts | 61 |
2 files changed, 36 insertions, 30 deletions
diff --git a/packages/app/src/utils/persist.test.ts b/packages/app/src/utils/persist.test.ts index 5be68f3b0..2a2c349b7 100644 --- a/packages/app/src/utils/persist.test.ts +++ b/packages/app/src/utils/persist.test.ts @@ -99,4 +99,9 @@ describe("persist localStorage resilience", () => { expect(storage.getItem("direct-value")).toBe('{"value":5}') }) + + test("normalizer rejects malformed JSON payloads", () => { + const result = persistTesting.normalize({ value: "ok" }, '{"value":"\\x"}') + expect(result).toBeUndefined() + }) }) diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts index 329a406dd..57e01d86a 100644 --- a/packages/app/src/utils/persist.ts +++ b/packages/app/src/utils/persist.ts @@ -195,6 +195,14 @@ function parse(value: string) { } } +function normalize(defaults: unknown, raw: string, migrate?: (value: unknown) => unknown) { + const parsed = parse(raw) + if (parsed === undefined) return + const migrated = migrate ? migrate(parsed) : parsed + const merged = merge(defaults, migrated) + return JSON.stringify(merged) +} + function workspaceStorage(dir: string) { const head = dir.slice(0, 12) || "workspace" const sum = checksum(dir) ?? "0" @@ -291,6 +299,7 @@ function localStorageDirect(): SyncStorage { export const PersistTesting = { localStorageDirect, localStorageWithPrefix, + normalize, } export const Persist = { @@ -358,12 +367,11 @@ export function persisted<T>( getItem: (key) => { const raw = current.getItem(key) if (raw !== null) { - const parsed = parse(raw) - if (parsed === undefined) return raw - - const migrated = config.migrate ? config.migrate(parsed) : parsed - const merged = merge(defaults, migrated) - const next = JSON.stringify(merged) + const next = normalize(defaults, raw, config.migrate) + if (next === undefined) { + current.removeItem(key) + return null + } if (raw !== next) current.setItem(key, next) return next } @@ -372,16 +380,13 @@ export function persisted<T>( const legacyRaw = legacyStore.getItem(legacyKey) if (legacyRaw === null) continue - current.setItem(key, legacyRaw) + const next = normalize(defaults, legacyRaw, config.migrate) + if (next === undefined) { + legacyStore.removeItem(legacyKey) + continue + } + current.setItem(key, next) legacyStore.removeItem(legacyKey) - - const parsed = parse(legacyRaw) - if (parsed === undefined) return legacyRaw - - const migrated = config.migrate ? config.migrate(parsed) : parsed - const merged = merge(defaults, migrated) - const next = JSON.stringify(merged) - if (legacyRaw !== next) current.setItem(key, next) return next } @@ -405,12 +410,11 @@ export function persisted<T>( getItem: async (key) => { const raw = await current.getItem(key) if (raw !== null) { - const parsed = parse(raw) - if (parsed === undefined) return raw - - const migrated = config.migrate ? config.migrate(parsed) : parsed - const merged = merge(defaults, migrated) - const next = JSON.stringify(merged) + const next = normalize(defaults, raw, config.migrate) + if (next === undefined) { + await current.removeItem(key).catch(() => undefined) + return null + } if (raw !== next) await current.setItem(key, next) return next } @@ -421,16 +425,13 @@ export function persisted<T>( const legacyRaw = await legacyStore.getItem(legacyKey) if (legacyRaw === null) continue - await current.setItem(key, legacyRaw) + const next = normalize(defaults, legacyRaw, config.migrate) + if (next === undefined) { + await legacyStore.removeItem(legacyKey).catch(() => undefined) + continue + } + await current.setItem(key, next) await legacyStore.removeItem(legacyKey) - - const parsed = parse(legacyRaw) - if (parsed === undefined) return legacyRaw - - const migrated = config.migrate ? config.migrate(parsed) : parsed - const merged = merge(defaults, migrated) - const next = JSON.stringify(merged) - if (legacyRaw !== next) await current.setItem(key, next) return next } |
