diff --git a/lib/src/lib/themes/store.test.ts b/lib/src/lib/themes/store.test.ts index 89e6c59b..fec69936 100644 --- a/lib/src/lib/themes/store.test.ts +++ b/lib/src/lib/themes/store.test.ts @@ -56,6 +56,22 @@ describe('theme store', () => { expect(getInstalledThemes().map((t) => t.id)).toEqual(['recover']); }); + it('drops malformed array elements while keeping well-formed themes', () => { + // Corrupted or externally tampered storage: a valid array whose elements + // are the wrong shape (null / missing id). Before the per-element guard, + // Array.isArray passed and these reached getTheme()'s `.find(t => t.id)` + // and addInstalledTheme()'s `.filter(t => t.id)`, throwing on `null.id`. + localStorage.setItem( + INSTALLED_KEY, + JSON.stringify([null, { label: 'no id' }, makeInstalledTheme('good')]), + ); + + expect(getInstalledThemes().map((t) => t.id)).toEqual(['good']); + expect(() => getAllThemes()).not.toThrow(); + expect(() => addInstalledTheme(makeInstalledTheme('recover'))).not.toThrow(); + expect(getInstalledThemes().map((t) => t.id)).toEqual(['good', 'recover']); + }); + it('returns [] for non-JSON garbage in storage', () => { localStorage.setItem(INSTALLED_KEY, 'not json at all'); expect(getInstalledThemes()).toEqual([]); diff --git a/lib/src/lib/themes/store.ts b/lib/src/lib/themes/store.ts index ca55a0b2..cf9cc87c 100644 --- a/lib/src/lib/themes/store.ts +++ b/lib/src/lib/themes/store.ts @@ -29,11 +29,17 @@ export function getInstalledThemes(): DormouseTheme[] { const raw = storage.getItem(INSTALLED_KEY); if (!raw) return []; // Guard against valid-but-wrong-shaped JSON (corrupted or externally - // tampered storage): a non-array value would otherwise be returned cast - // as DormouseTheme[], and the later `.filter`/spread callers would throw - // an uncaught TypeError that breaks theme listing and installation. + // tampered storage): a non-array value, or an array with malformed + // elements, would otherwise be returned cast as DormouseTheme[], and the + // later `.filter`/`.find`/spread callers that dereference `.id` would + // throw an uncaught TypeError that breaks theme listing and installation. + // Drop only the malformed entries so well-formed themes still load. const parsed: unknown = JSON.parse(raw); - return Array.isArray(parsed) ? (parsed as DormouseTheme[]) : []; + if (!Array.isArray(parsed)) return []; + return parsed.filter( + (t): t is DormouseTheme => + typeof t === 'object' && t !== null && typeof (t as { id?: unknown }).id === 'string', + ); } catch { return []; }