mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(pwa): hydrate xtream user collections
This commit is contained in:
1 parent
1ea6914f98
commit
ec098f98b1
8 files changed
+404
-38
No files matched your search
@@ -36,6 +36,7 @@ describe('UnifiedRecentDataService', () => {
|
||||
};
|
||||
let xtreamDataSource: {
|
||||
addRecentItem: jest.Mock;
|
||||
clearRecentItems: jest.Mock;
|
||||
getContentByXtreamId: jest.Mock;
|
||||
getRecentItems: jest.Mock;
|
||||
removeRecentItem: jest.Mock;
|
||||
@@ -149,6 +150,7 @@ describe('UnifiedRecentDataService', () => {
|
||||
};
|
||||
xtreamDataSource = {
|
||||
addRecentItem: jest.fn().mockResolvedValue(undefined),
|
||||
clearRecentItems: jest.fn().mockResolvedValue(undefined),
|
||||
getContentByXtreamId: jest.fn().mockResolvedValue(null),
|
||||
getRecentItems: jest.fn().mockResolvedValue([]),
|
||||
removeRecentItem: jest.fn().mockResolvedValue(undefined),
|
||||
@@ -396,6 +398,48 @@ describe('UnifiedRecentDataService', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('clears Xtream recent localStorage during global PWA clear', async () => {
|
||||
Object.defineProperty(window, 'electron', {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
});
|
||||
playlistsService.getAllPlaylists.mockReturnValue(
|
||||
of([
|
||||
{
|
||||
_id: 'xtream-1',
|
||||
title: 'Xtream PWA',
|
||||
serverUrl: 'https://xtream.example.com',
|
||||
},
|
||||
{
|
||||
_id: 'm3u-1',
|
||||
title: 'M3U List',
|
||||
},
|
||||
{
|
||||
_id: 'stalker-1',
|
||||
title: 'Stalker Portal',
|
||||
serverUrl: 'https://stalker.example.com',
|
||||
macAddress: '00:11:22:33:44:55',
|
||||
},
|
||||
] satisfies Partial<Playlist>[])
|
||||
);
|
||||
|
||||
await service.clearRecentItems('all');
|
||||
|
||||
expect(dbService.clearGlobalRecentlyViewed).not.toHaveBeenCalled();
|
||||
expect(xtreamDataSource.clearRecentItems).toHaveBeenCalledWith(
|
||||
'xtream-1'
|
||||
);
|
||||
expect(
|
||||
playlistsService.clearPlaylistRecentlyViewed
|
||||
).toHaveBeenCalledWith('m3u-1');
|
||||
expect(
|
||||
playlistsService.clearPlaylistRecentlyViewed
|
||||
).toHaveBeenCalledWith('stalker-1');
|
||||
expect(
|
||||
playlistsService.clearPlaylistRecentlyViewed
|
||||
).not.toHaveBeenCalledWith('xtream-1');
|
||||
});
|
||||
|
||||
it('keeps Stalker radio recent items in the live collection with radio metadata', async () => {
|
||||
store.select.mockReturnValue(
|
||||
of([
|
||||
|
||||
@@ -197,12 +197,12 @@ export class UnifiedRecentDataService {
|
||||
)) as Playlist[];
|
||||
|
||||
await Promise.all(
|
||||
playlists
|
||||
.filter(
|
||||
(playlist) =>
|
||||
Boolean(playlist.macAddress) || !playlist.serverUrl
|
||||
)
|
||||
.map(async (playlist) => {
|
||||
playlists.map(async (playlist) => {
|
||||
if (!window.electron && this.isXtreamPlaylist(playlist)) {
|
||||
await this.xtreamDataSource.clearRecentItems(playlist._id);
|
||||
}
|
||||
|
||||
if (this.isPlaylistBackedRecentPlaylist(playlist)) {
|
||||
const updatedPlaylist = await firstValueFrom(
|
||||
this.playlistsService.clearPlaylistRecentlyViewed(
|
||||
playlist._id
|
||||
@@ -212,7 +212,8 @@ export class UnifiedRecentDataService {
|
||||
playlist._id,
|
||||
updatedPlaylist
|
||||
);
|
||||
})
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -695,4 +696,16 @@ export class UnifiedRecentDataService {
|
||||
): boolean {
|
||||
return !playlist.serverUrl && !playlist.macAddress;
|
||||
}
|
||||
|
||||
private isXtreamPlaylist(
|
||||
playlist: Pick<PlaylistMeta, 'serverUrl' | 'macAddress'>
|
||||
): boolean {
|
||||
return Boolean(playlist.serverUrl) && !playlist.macAddress;
|
||||
}
|
||||
|
||||
private isPlaylistBackedRecentPlaylist(
|
||||
playlist: Pick<PlaylistMeta, 'serverUrl' | 'macAddress'>
|
||||
): boolean {
|
||||
return Boolean(playlist.macAddress) || this.isM3uPlaylist(playlist);
|
||||
}
|
||||
}
|
||||
@@ -452,6 +452,18 @@ export class ElectronXtreamDataSource implements IXtreamDataSource {
|
||||
);
|
||||
}
|
||||
|
||||
async setContentBackdropIfMissing(
|
||||
contentId: number,
|
||||
playlistId: string,
|
||||
backdropUrl: string
|
||||
): Promise<void> {
|
||||
void playlistId;
|
||||
await this.dbService.setContentBackdropIfMissing(
|
||||
contentId,
|
||||
backdropUrl
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Playback Position Operations
|
||||
// =========================================================================
|
||||
|
||||
@@ -255,4 +255,104 @@ describe('PwaXtreamDataSource', () => {
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
it('hydrates stored favorites and recent items from the API when content cache is cold', async () => {
|
||||
await dataSource.createPlaylist({
|
||||
id: 'playlist-1',
|
||||
name: 'Xtream PWA',
|
||||
serverUrl: credentials.serverUrl,
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
type: 'xtream',
|
||||
});
|
||||
apiService.getStreams.mockImplementation(
|
||||
(_credentials: XtreamCredentials, type: string) =>
|
||||
Promise.resolve(
|
||||
type === 'movie'
|
||||
? [
|
||||
{
|
||||
stream_id: 202,
|
||||
name: 'Movie One',
|
||||
stream_icon: 'movie.png',
|
||||
category_id: '20',
|
||||
},
|
||||
]
|
||||
: []
|
||||
)
|
||||
);
|
||||
localStorage.setItem(
|
||||
'xtream-favorites',
|
||||
JSON.stringify({ 'playlist-1': [202] })
|
||||
);
|
||||
localStorage.setItem(
|
||||
'xtream-recent-items',
|
||||
JSON.stringify({
|
||||
'playlist-1': [
|
||||
{
|
||||
id: 202,
|
||||
viewedAt: '2026-05-21T12:00:00.000Z',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
await expect(dataSource.getFavorites('playlist-1')).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
title: 'Movie One',
|
||||
xtream_id: 202,
|
||||
}),
|
||||
]);
|
||||
await expect(dataSource.getRecentItems('playlist-1')).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
title: 'Movie One',
|
||||
viewed_at: '2026-05-21T12:00:00.000Z',
|
||||
xtream_id: 202,
|
||||
}),
|
||||
]);
|
||||
expect(apiService.getStreams).toHaveBeenCalledWith(
|
||||
credentials,
|
||||
'movie'
|
||||
);
|
||||
});
|
||||
|
||||
it('backfills PWA recent-item backdrop metadata without rewriting recency', async () => {
|
||||
apiService.getStreams.mockResolvedValue([
|
||||
{
|
||||
stream_id: 202,
|
||||
name: 'Movie One',
|
||||
stream_icon: 'movie.png',
|
||||
category_id: '20',
|
||||
},
|
||||
]);
|
||||
|
||||
await dataSource.getContent('playlist-1', credentials, 'movie');
|
||||
await dataSource.addRecentItem(202, 'playlist-1');
|
||||
const before = JSON.parse(
|
||||
localStorage.getItem('xtream-recent-items') || '{}'
|
||||
)['playlist-1'][0].viewedAt;
|
||||
|
||||
await dataSource.setContentBackdropIfMissing(
|
||||
202,
|
||||
'playlist-1',
|
||||
' https://example.com/backdrop.png '
|
||||
);
|
||||
|
||||
const stored = JSON.parse(
|
||||
localStorage.getItem('xtream-recent-items') || '{}'
|
||||
)['playlist-1'][0];
|
||||
expect(stored).toEqual(
|
||||
expect.objectContaining({
|
||||
id: 202,
|
||||
backdropUrl: 'https://example.com/backdrop.png',
|
||||
viewedAt: before,
|
||||
})
|
||||
);
|
||||
await expect(dataSource.getRecentItems('playlist-1')).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
backdrop_url: 'https://example.com/backdrop.png',
|
||||
title: 'Movie One',
|
||||
xtream_id: 202,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -36,6 +36,7 @@ const STORAGE_KEYS = {
|
||||
|
||||
interface XtreamCachedContentItem {
|
||||
readonly added?: string;
|
||||
readonly backdrop_url?: string | null;
|
||||
readonly category_id?: string | number;
|
||||
readonly cover?: string;
|
||||
readonly cover_big?: string;
|
||||
@@ -59,6 +60,7 @@ interface XtreamCachedContentItem {
|
||||
interface StoredRecentItem {
|
||||
readonly id: number;
|
||||
readonly viewedAt: string;
|
||||
readonly backdropUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,6 +72,7 @@ interface StoredRecentItem {
|
||||
export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
private readonly apiService = inject(XtreamApiService);
|
||||
private readonly logger = createLogger('PwaXtreamDataSource');
|
||||
private readonly contentTypes = ['live', 'movie', 'series'] as const;
|
||||
|
||||
// In-memory cache for the current session
|
||||
private categoryCache = new Map<string, XtreamCategory[]>();
|
||||
@@ -391,10 +394,14 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
async getFavorites(playlistId: string): Promise<XtreamContentItem[]> {
|
||||
const allFavorites = this.getFavoritesFromStorage();
|
||||
const playlistFavorites = allFavorites[playlistId] || [];
|
||||
await this.hydrateStoredCollectionContent(
|
||||
playlistId,
|
||||
playlistFavorites
|
||||
);
|
||||
|
||||
// Match favorites with cached content
|
||||
const results: XtreamCachedContentItem[] = [];
|
||||
for (const type of ['live', 'movie', 'series']) {
|
||||
for (const type of this.contentTypes) {
|
||||
const cacheKey = `${playlistId}-${type}-content`;
|
||||
const content = this.contentCache.get(cacheKey) || [];
|
||||
|
||||
@@ -614,10 +621,14 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
async getRecentItems(playlistId: string): Promise<XtreamContentItem[]> {
|
||||
const allRecent = this.getRecentItemsFromStorage();
|
||||
const playlistRecent = allRecent[playlistId] || [];
|
||||
await this.hydrateStoredCollectionContent(
|
||||
playlistId,
|
||||
playlistRecent.map((item) => item.id)
|
||||
);
|
||||
|
||||
// Match recent items with cached content
|
||||
const results: (XtreamCachedContentItem & { viewed_at: string })[] = [];
|
||||
for (const type of ['live', 'movie', 'series']) {
|
||||
for (const type of this.contentTypes) {
|
||||
const cacheKey = `${playlistId}-${type}-content`;
|
||||
const content = this.contentCache.get(cacheKey) || [];
|
||||
|
||||
@@ -627,6 +638,8 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
if (recentEntry) {
|
||||
results.push({
|
||||
...item,
|
||||
backdrop_url:
|
||||
recentEntry.backdropUrl ?? item.backdrop_url,
|
||||
viewed_at: recentEntry.viewedAt,
|
||||
});
|
||||
}
|
||||
@@ -648,11 +661,11 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
playlistId: string,
|
||||
_backdropUrl?: string
|
||||
): Promise<void> {
|
||||
void _backdropUrl;
|
||||
const normalizedContentId = this.normalizeStoredId(contentId);
|
||||
if (normalizedContentId == null) {
|
||||
return;
|
||||
}
|
||||
const normalizedBackdropUrl = _backdropUrl?.trim();
|
||||
|
||||
const allRecent = this.getRecentItemsFromStorage();
|
||||
if (!allRecent[playlistId]) {
|
||||
@@ -668,6 +681,9 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
allRecent[playlistId].unshift({
|
||||
id: normalizedContentId,
|
||||
viewedAt: new Date().toISOString(),
|
||||
...(normalizedBackdropUrl
|
||||
? { backdropUrl: normalizedBackdropUrl }
|
||||
: {}),
|
||||
});
|
||||
|
||||
// Keep only last 50 items
|
||||
@@ -768,6 +784,8 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
const rawItem = item as {
|
||||
readonly id?: unknown;
|
||||
readonly viewedAt?: unknown;
|
||||
readonly backdropUrl?: unknown;
|
||||
readonly backdrop_url?: unknown;
|
||||
};
|
||||
const id = this.normalizeStoredId(rawItem.id);
|
||||
if (
|
||||
@@ -780,6 +798,7 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
return {
|
||||
id,
|
||||
viewedAt: rawItem.viewedAt,
|
||||
...this.normalizeStoredBackdrop(rawItem),
|
||||
};
|
||||
})
|
||||
.filter((item): item is StoredRecentItem => item !== null);
|
||||
@@ -788,6 +807,59 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private normalizeStoredBackdrop(item: {
|
||||
readonly backdropUrl?: unknown;
|
||||
readonly backdrop_url?: unknown;
|
||||
}): Pick<StoredRecentItem, 'backdropUrl'> | Record<string, never> {
|
||||
const value = item.backdropUrl ?? item.backdrop_url;
|
||||
if (typeof value !== 'string') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const backdropUrl = value.trim();
|
||||
return backdropUrl ? { backdropUrl } : {};
|
||||
}
|
||||
|
||||
private async hydrateStoredCollectionContent(
|
||||
playlistId: string,
|
||||
ids: readonly number[]
|
||||
): Promise<void> {
|
||||
if (ids.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const missingTypes = this.contentTypes.filter(
|
||||
(type) => !this.contentCache.has(`${playlistId}-${type}-content`)
|
||||
);
|
||||
if (missingTypes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const playlist = await this.getPlaylist(playlistId);
|
||||
if (!playlist) {
|
||||
return;
|
||||
}
|
||||
|
||||
const credentials: XtreamCredentials = {
|
||||
serverUrl: playlist.serverUrl,
|
||||
username: playlist.username,
|
||||
password: playlist.password,
|
||||
};
|
||||
|
||||
await Promise.all(
|
||||
missingTypes.map(async (type) => {
|
||||
try {
|
||||
await this.getContent(playlistId, credentials, type);
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
'Failed to hydrate stored PWA Xtream collection content',
|
||||
{ playlistId, type, error }
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Content Lookup
|
||||
// =========================================================================
|
||||
@@ -818,6 +890,61 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
|
||||
return null;
|
||||
}
|
||||
|
||||
async setContentBackdropIfMissing(
|
||||
contentId: number,
|
||||
playlistId: string,
|
||||
backdropUrl: string
|
||||
): Promise<void> {
|
||||
const normalizedContentId = this.normalizeStoredId(contentId);
|
||||
const normalizedBackdropUrl = backdropUrl.trim();
|
||||
if (normalizedContentId == null || !normalizedBackdropUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const type of this.contentTypes) {
|
||||
const cacheKey = `${playlistId}-${type}-content`;
|
||||
const content = this.contentCache.get(cacheKey);
|
||||
if (!content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.contentCache.set(
|
||||
cacheKey,
|
||||
content.map((item) => {
|
||||
const itemId = this.getItemIdentity(item, type);
|
||||
if (itemId !== normalizedContentId || item.backdrop_url) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
backdrop_url: normalizedBackdropUrl,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const allRecent = this.getRecentItemsFromStorage();
|
||||
const playlistRecent = allRecent[playlistId];
|
||||
if (!playlistRecent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.saveRecentItemsToStorage({
|
||||
...allRecent,
|
||||
[playlistId]: playlistRecent.map((item) => {
|
||||
if (item.id !== normalizedContentId || item.backdropUrl) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
backdropUrl: normalizedBackdropUrl,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
private findContentIdentity(
|
||||
playlistId: string,
|
||||
xtreamId: number,
|
||||
|
||||
@@ -359,6 +359,16 @@ export interface IXtreamDataSource {
|
||||
contentType?: 'live' | 'movie' | 'series'
|
||||
): Promise<XtreamContentItem | null>;
|
||||
|
||||
/**
|
||||
* Persist a backdrop URL for an already-known content item without changing
|
||||
* favorites or recent ordering.
|
||||
*/
|
||||
setContentBackdropIfMissing(
|
||||
contentId: number,
|
||||
playlistId: string,
|
||||
backdropUrl: string
|
||||
): Promise<void>;
|
||||
|
||||
// =========================================================================
|
||||
// Playback Position Operations
|
||||
// =========================================================================
|
||||
|
||||
@@ -21,6 +21,7 @@ describe('withRecentItems', () => {
|
||||
const originalElectron = window.electron;
|
||||
let store: InstanceType<typeof TestRecentItemsStore>;
|
||||
let databaseService: {
|
||||
clearGlobalRecentlyViewed: jest.Mock;
|
||||
clearPlaylistRecentItems: jest.Mock;
|
||||
getContentByXtreamId: jest.Mock;
|
||||
getRecentItems: jest.Mock;
|
||||
@@ -33,6 +34,11 @@ describe('withRecentItems', () => {
|
||||
getContentByXtreamId: jest.Mock;
|
||||
getRecentItems: jest.Mock;
|
||||
removeRecentItem: jest.Mock;
|
||||
setContentBackdropIfMissing: jest.Mock;
|
||||
};
|
||||
let playlistsService: {
|
||||
clearPlaylistRecentlyViewed: jest.Mock;
|
||||
getAllPlaylists: jest.Mock;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -42,6 +48,7 @@ describe('withRecentItems', () => {
|
||||
});
|
||||
|
||||
databaseService = {
|
||||
clearGlobalRecentlyViewed: jest.fn().mockResolvedValue(undefined),
|
||||
clearPlaylistRecentItems: jest.fn().mockResolvedValue(undefined),
|
||||
getContentByXtreamId: jest.fn(),
|
||||
getRecentItems: jest.fn().mockResolvedValue([
|
||||
@@ -76,6 +83,13 @@ describe('withRecentItems', () => {
|
||||
},
|
||||
]),
|
||||
removeRecentItem: jest.fn().mockResolvedValue(undefined),
|
||||
setContentBackdropIfMissing: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
playlistsService = {
|
||||
clearPlaylistRecentlyViewed: jest
|
||||
.fn()
|
||||
.mockReturnValue(of(undefined)),
|
||||
getAllPlaylists: jest.fn().mockReturnValue(of([])),
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
@@ -91,12 +105,7 @@ describe('withRecentItems', () => {
|
||||
},
|
||||
{
|
||||
provide: PlaylistsService,
|
||||
useValue: {
|
||||
clearPlaylistRecentlyViewed: jest
|
||||
.fn()
|
||||
.mockReturnValue(of(undefined)),
|
||||
getAllPlaylists: jest.fn().mockReturnValue(of([])),
|
||||
},
|
||||
useValue: playlistsService,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -220,7 +229,7 @@ describe('withRecentItems', () => {
|
||||
});
|
||||
|
||||
it('backfills a backdrop without rewriting recent ordering', async () => {
|
||||
databaseService.getContentByXtreamId.mockResolvedValue({
|
||||
dataSource.getContentByXtreamId.mockResolvedValue({
|
||||
id: 3941697,
|
||||
title: 'Krypton',
|
||||
type: 'series',
|
||||
@@ -234,18 +243,21 @@ describe('withRecentItems', () => {
|
||||
backdropUrl: ' https://example.com/krypton-backdrop.png ',
|
||||
});
|
||||
|
||||
expect(databaseService.getContentByXtreamId).toHaveBeenCalledWith(
|
||||
expect(dataSource.getContentByXtreamId).toHaveBeenCalledWith(
|
||||
290,
|
||||
'playlist-1',
|
||||
'series'
|
||||
);
|
||||
expect(
|
||||
databaseService.setContentBackdropIfMissing
|
||||
).toHaveBeenCalledWith(
|
||||
expect(dataSource.setContentBackdropIfMissing).toHaveBeenCalledWith(
|
||||
3941697,
|
||||
'playlist-1',
|
||||
'https://example.com/krypton-backdrop.png'
|
||||
);
|
||||
expect(dataSource.addRecentItem).not.toHaveBeenCalled();
|
||||
expect(databaseService.getContentByXtreamId).not.toHaveBeenCalled();
|
||||
expect(
|
||||
databaseService.setContentBackdropIfMissing
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clears recent items through the active data source in PWA', async () => {
|
||||
@@ -262,6 +274,46 @@ describe('withRecentItems', () => {
|
||||
expect(store.recentItems()).toEqual([]);
|
||||
});
|
||||
|
||||
it('clears Xtream recent items during global PWA clear', async () => {
|
||||
Object.defineProperty(window, 'electron', {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
});
|
||||
playlistsService.getAllPlaylists.mockReturnValue(
|
||||
of([
|
||||
{
|
||||
_id: 'xtream-1',
|
||||
serverUrl: 'https://xtream.example.com',
|
||||
},
|
||||
{
|
||||
_id: 'm3u-1',
|
||||
},
|
||||
{
|
||||
_id: 'stalker-1',
|
||||
serverUrl: 'https://stalker.example.com',
|
||||
macAddress: '00:11:22:33:44:55',
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
await store.clearGlobalRecentlyViewed();
|
||||
|
||||
expect(
|
||||
databaseService.clearGlobalRecentlyViewed
|
||||
).not.toHaveBeenCalled();
|
||||
expect(dataSource.clearRecentItems).toHaveBeenCalledWith('xtream-1');
|
||||
expect(
|
||||
playlistsService.clearPlaylistRecentlyViewed
|
||||
).toHaveBeenCalledWith('m3u-1');
|
||||
expect(
|
||||
playlistsService.clearPlaylistRecentlyViewed
|
||||
).toHaveBeenCalledWith('stalker-1');
|
||||
expect(
|
||||
playlistsService.clearPlaylistRecentlyViewed
|
||||
).not.toHaveBeenCalledWith('xtream-1');
|
||||
expect(store.recentItems()).toEqual([]);
|
||||
});
|
||||
|
||||
it('removes recent items through the active data source in PWA', async () => {
|
||||
Object.defineProperty(window, 'electron', {
|
||||
value: undefined,
|
||||
|
||||
@@ -148,10 +148,6 @@ export const withRecentItems = function () {
|
||||
playlist: Signal<{ id: string }>;
|
||||
backdropUrl?: string;
|
||||
}): Promise<void> {
|
||||
if (!window.electron) {
|
||||
return;
|
||||
}
|
||||
|
||||
const playlistId = playlist().id;
|
||||
const normalizedXtreamId = Number(xtreamId);
|
||||
const normalizedBackdropUrl = backdropUrl?.trim();
|
||||
@@ -164,7 +160,7 @@ export const withRecentItems = function () {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await dbService.getContentByXtreamId(
|
||||
const content = await dataSource.getContentByXtreamId(
|
||||
normalizedXtreamId,
|
||||
playlistId,
|
||||
contentType
|
||||
@@ -173,8 +169,9 @@ export const withRecentItems = function () {
|
||||
return;
|
||||
}
|
||||
|
||||
await dbService.setContentBackdropIfMissing(
|
||||
await dataSource.setContentBackdropIfMissing(
|
||||
content.id,
|
||||
playlistId,
|
||||
normalizedBackdropUrl
|
||||
);
|
||||
},
|
||||
@@ -276,24 +273,35 @@ export const withRecentItems = function () {
|
||||
},
|
||||
async clearGlobalRecentlyViewed() {
|
||||
try {
|
||||
await dbService.clearGlobalRecentlyViewed();
|
||||
if (window.electron) {
|
||||
await dbService.clearGlobalRecentlyViewed();
|
||||
}
|
||||
const playlists = (await firstValueFrom(
|
||||
playlistsService.getAllPlaylists()
|
||||
)) as Playlist[];
|
||||
await Promise.all(
|
||||
playlists
|
||||
.filter(
|
||||
(playlist) =>
|
||||
Boolean(playlist.macAddress) ||
|
||||
!playlist.serverUrl
|
||||
)
|
||||
.map((playlist) =>
|
||||
firstValueFrom(
|
||||
playlists.map(async (playlist) => {
|
||||
if (
|
||||
!window.electron &&
|
||||
playlist.serverUrl &&
|
||||
!playlist.macAddress
|
||||
) {
|
||||
await dataSource.clearRecentItems(
|
||||
playlist._id
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
Boolean(playlist.macAddress) ||
|
||||
!playlist.serverUrl
|
||||
) {
|
||||
await firstValueFrom(
|
||||
playlistsService.clearPlaylistRecentlyViewed(
|
||||
playlist._id
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
patchState(store, { recentItems: [] });
|
||||
} catch (error) {
|
||||
|
||||
Reference in new issue
Block a user