From b078526befc791397c627ab85ab5c67784e083a5 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 27 Sep 2026 16:05:59 +0200 Subject: [PATCH] fix(settings): match noncanonical PWA Xtream category ids against their locks The PWA data source compared raw provider category ids such as "009" with locks stored as numbers, so such a category stayed visible while locked. Both sides are now compared in canonical numeric form. Co-Authored-By: Claude Opus 5.5 --- docs/architecture/parental-lock.md | 4 +++- .../pwa-xtream-data-source.spec.ts | 19 +++++++++++++++++++ .../data-sources/pwa-xtream-data-source.ts | 16 ++++++++++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/architecture/parental-lock.md b/docs/architecture/parental-lock.md index d1fa5e231..d72483d08 100644 --- a/docs/architecture/parental-lock.md +++ b/docs/architecture/parental-lock.md @@ -276,7 +276,9 @@ so a cancelled or refused PIN leaves it showing the real state. (`showChannelViews`) and the groups view keeps its rail instead of the generic empty state. - **Xtream (PWA):** `PwaXtreamDataSource` drops withheld categories, - streams and search hits at read time; the same reloads apply. + streams and search hits at read time; the same reloads apply. Provider + category ids are compared in canonical numeric form (`"009"` matches the + stored lock `9`), as the lock editor stores them with `Number`. - **Warm-cache detection (Electron):** the filtered category/content reads can be empty while the offline cache is complete (every category locked), so `ElectronXtreamDataSource` confirms an empty read with the unfiltered diff --git a/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.spec.ts b/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.spec.ts index caecc7746..f968bf8c4 100644 --- a/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.spec.ts +++ b/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.spec.ts @@ -98,6 +98,25 @@ describe('PwaXtreamDataSource', () => { expect(unlocked).toHaveLength(2); }); + it('matches a noncanonical provider category id against its numeric lock', async () => { + apiService.getStreams.mockResolvedValue([ + { stream_id: 1, name: 'Family film', category_id: '5' }, + { stream_id: 2, name: 'Family after dark', category_id: '009' }, + ]); + parentalLock.active.mockReturnValue(true); + parentalLock.lockedXtreamIds.mockImplementation((_id, type) => + type === 'movies' ? [9] : [] + ); + + const content = await dataSource.getContent( + 'playlist-1', + credentials, + 'movie' + ); + + expect(content.map((item) => item.name)).toEqual(['Family film']); + }); + it('withholds everything while the lock store cannot be read', async () => { apiService.getStreams.mockResolvedValue([ { stream_id: 1, name: 'Family film', category_id: '5' }, diff --git a/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.ts b/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.ts index 147a63bc0..252836900 100644 --- a/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.ts +++ b/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.ts @@ -391,7 +391,8 @@ export class PwaXtreamDataSource implements IXtreamDataSource { return withheld.size === 0 ? categories : categories.filter( - (category) => !withheld.has(String(category.category_id)) + (category) => + !withheld.has(canonicalCategoryId(category.category_id)) ); } @@ -404,7 +405,7 @@ export class PwaXtreamDataSource implements IXtreamDataSource { return withheld.size === 0 ? items : items.filter( - (item) => !withheld.has(String(item.category_id ?? '')) + (item) => !withheld.has(canonicalCategoryId(item.category_id)) ); } @@ -1585,3 +1586,14 @@ export class PwaXtreamDataSource implements IXtreamDataSource { this.contentCache.clear(); } } + +/** + * The lock store keys provider category ids as numbers (the lock editor + * converts with `Number`), so a noncanonical provider id such as `"001"` + * must be compared as `"1"`. Non-numeric and empty ids stay as they are. + */ +function canonicalCategoryId(id: string | number | null | undefined): string { + const raw = String(id ?? '').trim(); + const numeric = Number(raw); + return raw !== '' && Number.isFinite(numeric) ? String(numeric) : raw; +}