fix(xtream): improve portal compatibility

This commit is contained in:
4gray committed 2026-06-14 13:21:56 +02:00
1 parent bc4a5958dc
commit 254879f92b
26 files changed
+1212 -186

No files matched your search

@@ -159,6 +159,78 @@ describe('PlaylistInfoComponent', () => {
expect(dialogRef.close).toHaveBeenCalledTimes(1);
});
it('normalizes edited Xtream playlist credentials before saving metadata', async () => {
const xtreamPlaylist = {
...playlist,
title: 'Old Xtream',
serverUrl: 'http://old.example:8080',
username: 'old-user',
password: 'old-pass',
url: undefined,
} as Playlist & { id: string };
TestBed.overrideProvider(MAT_DIALOG_DATA, {
useValue: xtreamPlaylist,
});
createComponent();
await component.saveChanges({
_id: 'playlist-1',
title: 'Updated Xtream',
serverUrl:
' http://new.example:8080/live/player_api.php?username=ignored&password=ignored ',
username: ' new-user ',
password: ' new-pass ',
});
expect(store.dispatch).toHaveBeenCalledWith(
PlaylistActions.updatePlaylistMeta({
playlist: {
_id: 'playlist-1',
title: 'Updated Xtream',
serverUrl: 'http://new.example:8080/live',
username: 'new-user',
password: 'new-pass',
},
})
);
});
it('normalizes edited Xtream playlist details before updating the Electron database', async () => {
const xtreamPlaylist = {
...playlist,
title: 'Old Xtream',
serverUrl: 'http://old.example:8080',
username: 'old-user',
password: 'old-pass',
url: undefined,
} as Playlist & { id: string };
runtime.supportsXtreamSqliteDataSource = true;
databaseService.updateXtreamPlaylistDetails.mockResolvedValue(true);
TestBed.overrideProvider(MAT_DIALOG_DATA, {
useValue: xtreamPlaylist,
});
createComponent();
await component.saveChanges({
_id: 'playlist-1',
title: 'Updated Xtream',
serverUrl:
' http://new.example:8080/get.php?username=ignored&password=ignored&type=m3u_plus&output=ts ',
username: ' new-user ',
password: ' new-pass ',
});
expect(
databaseService.updateXtreamPlaylistDetails
).toHaveBeenCalledWith({
id: 'playlist-1',
title: 'Updated Xtream',
serverUrl: 'http://new.example:8080',
username: 'new-user',
password: 'new-pass',
});
});
it('uses the Electron save dialog when desktop file saving is available', async () => {
runtime.isElectron = true;
runtime.supportsDesktopFileSave = true;
@@ -28,7 +28,11 @@ import {
PlaylistsService,
RuntimeCapabilitiesService,
} from '@iptvnator/services';
import { Playlist, PlaylistMeta } from '@iptvnator/shared/interfaces';
import {
normalizeXtreamServerUrl,
Playlist,
PlaylistMeta,
} from '@iptvnator/shared/interfaces';
type DesktopFileSaveBridge = Pick<
typeof window.electron,
@@ -160,6 +164,8 @@ export class PlaylistInfoComponent {
async saveChanges(playlist: PlaylistMeta): Promise<void> {
try {
const normalizedPlaylist =
this.normalizeXtreamPlaylistMeta(playlist);
const isXtream =
this.playlist &&
this.playlist.username &&
@@ -167,12 +173,14 @@ export class PlaylistInfoComponent {
this.playlist.serverUrl;
if (isXtream && this.runtime.supportsXtreamSqliteDataSource) {
await this.updateXtreamPlaylist(playlist);
await this.updateXtreamPlaylist(normalizedPlaylist);
}
// Dispatch store action to update UI
this.store.dispatch(
PlaylistActions.updatePlaylistMeta({ playlist })
PlaylistActions.updatePlaylistMeta({
playlist: normalizedPlaylist,
})
);
this.snackBar.open(
@@ -195,6 +203,19 @@ export class PlaylistInfoComponent {
}
}
private normalizeXtreamPlaylistMeta(playlist: PlaylistMeta): PlaylistMeta {
if (!playlist.serverUrl || !playlist.username || !playlist.password) {
return playlist;
}
return {
...playlist,
password: playlist.password.trim(),
serverUrl: normalizeXtreamServerUrl(playlist.serverUrl),
username: playlist.username.trim(),
};
}
async updateXtreamPlaylist(playlist: PlaylistMeta) {
const success = await this.databaseService.updateXtreamPlaylistDetails({
id: this.playlist._id,