mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 04:26:16 -08:00
fix(stalker): load full playlist for authenticated routes
This commit is contained in:
1 parent
ce78669c17
commit
56f8ded835
4 files changed
+142
-8
No files matched your search
@@ -125,6 +125,12 @@ blank fields are not generated or forwarded to `get_profile`.
|
||||
- Generated MAG-like identity remains a future explicit setting. It must not be
|
||||
the default because strict portals can bind accounts to the first device
|
||||
fingerprint they receive.
|
||||
- Stalker workspace routes must initialize `StalkerStore` from a playlist object
|
||||
with an explicit `isFullStalkerPortal` mode. If the active route metadata is a
|
||||
lightweight playlist record without that field, the route session must load the
|
||||
full playlist by id before category/content resources run. Stalker auth
|
||||
metadata is independent from M3U playlist EPG metadata and must not depend on
|
||||
M3U-specific EPG fields.
|
||||
|
||||
## Live TV and Radio
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { PlaylistMeta, STALKER_REQUEST } from '@iptvnator/shared/interfaces';
|
||||
import {
|
||||
executeStalkerRequest,
|
||||
type StalkerRequestDeps,
|
||||
} from './stalker-request.utils';
|
||||
|
||||
const CATEGORY_PARAMS = {
|
||||
type: 'itv',
|
||||
action: 'get_genres',
|
||||
};
|
||||
|
||||
function createDeps(): StalkerRequestDeps {
|
||||
return {
|
||||
dataService: {
|
||||
sendIpcEvent: jest.fn().mockResolvedValue({ js: [] }),
|
||||
},
|
||||
stalkerSession: {
|
||||
makeAuthenticatedRequest: jest.fn().mockResolvedValue({ js: [] }),
|
||||
},
|
||||
} as unknown as StalkerRequestDeps;
|
||||
}
|
||||
|
||||
describe('executeStalkerRequest', () => {
|
||||
it('routes full Stalker portal category requests through the authenticated session path', async () => {
|
||||
const deps = createDeps();
|
||||
const playlist = {
|
||||
_id: 'stalker-full',
|
||||
title: 'Full Stalker Portal',
|
||||
portalUrl:
|
||||
'https://portal.example.test/stalker_portal/server/load.php',
|
||||
macAddress: 'has-mac-address',
|
||||
isFullStalkerPortal: true,
|
||||
stalkerSerialNumber: 'has-serial',
|
||||
stalkerDeviceId1: 'has-device-id-1',
|
||||
stalkerDeviceId2: 'has-device-id-2',
|
||||
stalkerSignature1: 'has-signature-1',
|
||||
stalkerSignature2: 'has-signature-2',
|
||||
} as PlaylistMeta;
|
||||
|
||||
await executeStalkerRequest(deps, playlist, CATEGORY_PARAMS);
|
||||
|
||||
expect(
|
||||
deps.stalkerSession.makeAuthenticatedRequest
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
...playlist,
|
||||
lastUsage: '',
|
||||
}),
|
||||
CATEGORY_PARAMS
|
||||
);
|
||||
expect(deps.dataService.sendIpcEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps lightweight Stalker portal requests on the IPC path', async () => {
|
||||
const deps = createDeps();
|
||||
const playlist = {
|
||||
_id: 'stalker-basic',
|
||||
title: 'Basic Stalker Portal',
|
||||
portalUrl: 'https://portal.example.test/load.php',
|
||||
macAddress: 'has-mac-address',
|
||||
isFullStalkerPortal: false,
|
||||
} as PlaylistMeta;
|
||||
|
||||
await executeStalkerRequest(deps, playlist, CATEGORY_PARAMS);
|
||||
|
||||
expect(deps.dataService.sendIpcEvent).toHaveBeenCalledWith(
|
||||
STALKER_REQUEST,
|
||||
{
|
||||
url: playlist.portalUrl,
|
||||
macAddress: playlist.macAddress,
|
||||
params: CATEGORY_PARAMS,
|
||||
}
|
||||
);
|
||||
expect(
|
||||
deps.stalkerSession.makeAuthenticatedRequest
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+28
-3
@@ -17,15 +17,23 @@ const ACTIVE_PLAYLIST: PlaylistMeta = {
|
||||
title: 'Test Stalker',
|
||||
} as PlaylistMeta;
|
||||
|
||||
const FULL_STALKER_PLAYLIST: PlaylistMeta = {
|
||||
...ACTIVE_PLAYLIST,
|
||||
isFullStalkerPortal: true,
|
||||
stalkerSerialNumber: 'CUSTOMSN123',
|
||||
stalkerDeviceId1: 'DEVICE-ID-1',
|
||||
stalkerDeviceId2: 'DEVICE-ID-2',
|
||||
stalkerSignature1: 'SIGNATURE-1',
|
||||
stalkerSignature2: 'SIGNATURE-2',
|
||||
} as PlaylistMeta;
|
||||
|
||||
async function flushEffects(): Promise<void> {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
function getStalkerSectionFromUrl(url: string): string | null {
|
||||
const match = url.match(
|
||||
/^\/workspace\/stalker\/[^/]+\/([^/?]+)(?:\/|$)/
|
||||
);
|
||||
const match = url.match(/^\/workspace\/stalker\/[^/]+\/([^/?]+)(?:\/|$)/);
|
||||
|
||||
return match?.[1] ?? null;
|
||||
}
|
||||
@@ -148,4 +156,21 @@ describe('StalkerWorkspaceRouteSession', () => {
|
||||
expect(stalkerStore.clearSelectedItem).toHaveBeenCalled();
|
||||
expect(stalkerStore.setSearchPhrase).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
it('loads the full Stalker playlist when the active route meta lacks auth fields', async () => {
|
||||
activePlaylist.set(ACTIVE_PLAYLIST);
|
||||
playlistsService.getPlaylistById.mockReturnValue(
|
||||
of(FULL_STALKER_PLAYLIST)
|
||||
);
|
||||
|
||||
TestBed.inject(StalkerWorkspaceRouteSession);
|
||||
await flushEffects();
|
||||
|
||||
expect(playlistsService.getPlaylistById).toHaveBeenCalledWith(
|
||||
PLAYLIST_ID
|
||||
);
|
||||
expect(stalkerStore.setCurrentPlaylist).toHaveBeenCalledWith(
|
||||
FULL_STALKER_PLAYLIST
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
StalkerStore,
|
||||
} from '@iptvnator/portal/stalker/data-access';
|
||||
import { PlaylistsService } from '@iptvnator/services';
|
||||
import { PlaylistMeta } from '@iptvnator/shared/interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class StalkerWorkspaceRouteSession {
|
||||
@@ -64,11 +65,7 @@ export class StalkerWorkspaceRouteSession {
|
||||
this.stalkerStore.setSelectedCategory(null);
|
||||
this.stalkerStore.clearSelectedItem();
|
||||
|
||||
const playlist =
|
||||
this.playlistContext.activePlaylist() ??
|
||||
(await firstValueFrom(
|
||||
this.playlistsService.getPlaylistById(playlistId)
|
||||
));
|
||||
const playlist = await this.resolveStalkerPlaylist(playlistId);
|
||||
await this.stalkerStore.setCurrentPlaylist(playlist);
|
||||
}
|
||||
|
||||
@@ -106,6 +103,34 @@ export class StalkerWorkspaceRouteSession {
|
||||
this.stalkerStore.setSearchPhrase('');
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveStalkerPlaylist(
|
||||
playlistId: string
|
||||
): Promise<PlaylistMeta | undefined> {
|
||||
const activePlaylist = this.playlistContext.activePlaylist();
|
||||
|
||||
if (this.hasExplicitStalkerPortalMode(playlistId, activePlaylist)) {
|
||||
return activePlaylist;
|
||||
}
|
||||
|
||||
return (
|
||||
(await firstValueFrom(
|
||||
this.playlistsService.getPlaylistById(playlistId)
|
||||
)) ??
|
||||
activePlaylist ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
private hasExplicitStalkerPortalMode(
|
||||
playlistId: string,
|
||||
playlist: PlaylistMeta | null
|
||||
): playlist is PlaylistMeta {
|
||||
return (
|
||||
playlist?._id === playlistId &&
|
||||
playlist.isFullStalkerPortal !== undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function provideStalkerWorkspaceRouteSession(): Provider[] {
|
||||
|
||||
Reference in new issue
Block a user