fix(settings): withhold Electron Xtream reads while locks are unknown, persist the switch when settings are unreadable, re-stamp after a recovered read

- ElectronXtreamDataSource serves no categories, content or search hits
  while the lock store withholds everything; its SQLite index may still
  carry a stale stamp.
- setupPin decides whether to persist the switch from the settings value
  before the PIN is stored, since enabled follows hasPin while the switch
  is unknown.
- A lock store recovered by a later read marks its playlists stale so the
  index is re-derived, a persisted entry must carry all three lists, and a
  stale Stalker search page is dropped before touching the withheld-id
  bookkeeping.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5.1 committed 2026-09-26 07:36:23 +02:00
1 parent 4f931030e1
commit bfdfe18a9c
11 files changed
+163 -27

No files matched your search

@@ -113,7 +113,7 @@ describe('parental-lock.util', () => {
});
describe('isWellFormedParentalLockStore', () => {
it('accepts what writeLocks produces, lists omitted or empty', () => {
it('accepts what writeLocks produces, lists present even when empty', () => {
expect(isWellFormedParentalLockStore({})).toBe(true);
expect(
isWellFormedParentalLockStore({
@@ -122,7 +122,7 @@ describe('isWellFormedParentalLockStore', () => {
stalker: [{ categoryType: 'itv', categoryId: '9' }],
m3u: ['Adult'],
},
q: { m3u: [] },
q: { xtream: [], stalker: [], m3u: [] },
})
).toBe(true);
});
@@ -130,19 +130,45 @@ describe('isWellFormedParentalLockStore', () => {
it.each([
['a non-object root', []],
['a non-object playlist entry', { p: 'corrupt' }],
['a non-list lock field', { p: { xtream: 'corrupt' } }],
['a non-string group title', { p: { m3u: [1] } }],
['a playlist entry missing a list', { p: { xtream: [], m3u: [] } }],
['an empty playlist entry', { p: {} }],
[
'a non-list lock field',
{ p: { xtream: 'corrupt', stalker: [], m3u: [] } },
],
[
'a non-string group title',
{ p: { xtream: [], stalker: [], m3u: [1] } },
],
[
'an unknown category type',
{ p: { xtream: [{ categoryType: 'x', xtreamId: 1 }] } },
{
p: {
xtream: [{ categoryType: 'x', xtreamId: 1 }],
stalker: [],
m3u: [],
},
},
],
[
'a non-numeric xtream id',
{ p: { xtream: [{ categoryType: 'live', xtreamId: 'nope' }] } },
{
p: {
xtream: [{ categoryType: 'live', xtreamId: 'nope' }],
stalker: [],
m3u: [],
},
},
],
[
'the Stalker "all" pseudo id',
{ p: { stalker: [{ categoryType: 'itv', categoryId: '*' }] } },
{
p: {
xtream: [],
stalker: [{ categoryType: 'itv', categoryId: '*' }],
m3u: [],
},
},
],
])('rejects %s', (_, value) => {
expect(isWellFormedParentalLockStore(value)).toBe(false);
@@ -156,16 +156,14 @@ function isWellFormedList(
value: unknown,
isWellFormedEntry: (entry: unknown) => boolean
): boolean {
return (
value === undefined ||
(Array.isArray(value) && value.every(isWellFormedEntry))
);
return Array.isArray(value) && value.every(isWellFormedEntry);
}
/**
* Whether a persisted store has the shape `writeLocks` produces: every
* playlist entry an object whose optional `xtream`/`stalker`/`m3u` lists
* hold only entries the normalizers accept. The normalizers DROP what they
* playlist entry an object whose `xtream`/`stalker`/`m3u` lists are all
* present (a missing list is corruption, not "empty") and hold only
* entries the normalizers accept. The normalizers DROP what they
* do not understand, which is right for user-supplied backups but turns a
* corrupted persisted store into "nothing is locked"; a store that fails
* this check is treated as unreadable instead.