From 0dd8c1a57d58af0edbb3749632fecc10c95ecde1 Mon Sep 17 00:00:00 2001 From: 4gray Date: Tue, 4 Aug 2026 18:28:01 +0200 Subject: [PATCH] fix(playback): remove derived tracking ownership --- docs/architecture/embedded-inline-playback.md | 12 +++++---- ...alker-episode-playback-session-key.spec.ts | 27 +++++++++++++++++++ .../stalker-episode-playback-session-key.ts | 7 +++-- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/docs/architecture/embedded-inline-playback.md b/docs/architecture/embedded-inline-playback.md index 5ce489beb..abcacbe7a 100644 --- a/docs/architecture/embedded-inline-playback.md +++ b/docs/architecture/embedded-inline-playback.md @@ -48,11 +48,13 @@ stream or catch-up URL. VOD and series hosts use the route or catalog content identity, with series episode coordinates. Stalker episode identity also includes the explicit series mode, normalized parent, exact season key, season number, and episode number; synthesized episode hashes are not identity. -The original provider command or episode ID exists only in the short-lived -pending-request guard used to locate the exact selected episode and reject -stale or out-of-order completion. It is never serialized into the recovery -session key. This keeps token refreshes in one logical session while ensuring -colliding tracking hashes cannot select a sibling episode. +Mapped episodes retain the original provider command or episode ID for playback +resolution. Recovery ownership never serializes that value into the session +key or retained recovery state; its recovery-ownership use is limited to a +short-lived exact pending-request snapshot/guard that locates the selected +episode and rejects stale or out-of-order completion. This keeps token refreshes +in one logical session while ensuring colliding tracking hashes cannot select a +sibling episode. Shared wrappers (`VodDetailsComponent` and `PortalInlinePlayerComponent`) pass the key unchanged to `WebPlayerViewComponent`. diff --git a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-episode-playback-session-key.spec.ts b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-episode-playback-session-key.spec.ts index 7328ee903..7cdfc0ec0 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-episode-playback-session-key.spec.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-episode-playback-session-key.spec.ts @@ -76,6 +76,33 @@ describe('createStalkerEpisodePlaybackSessionKey', () => { } ); + it('retains the exact pending guard without a synthesized tracking hash', () => { + const originalCmd = + 'https://stream.example.invalid/episode.mpg?access_token=example-token'; + const state = episodeState({ + id: 'command-derived-tracking-hash', + originalCmd, + }); + const identity = captureStalkerEpisodePlaybackSessionIdentity({ + sourceId: 'playlist', + parentSeriesId: 'series', + seriesMode: STALKER_SERIES_DOWNLOAD_MODES.RegularSeries, + episodeState: state, + }); + if (!identity) throw new Error('Expected a captured request identity'); + + expect(identity).toEqual({ + sourceId: 'playlist', + parentSeriesId: 'series', + seriesMode: STALKER_SERIES_DOWNLOAD_MODES.RegularSeries, + originalEpisodeIdentity: originalCmd, + seasonKey: state.seasonKey, + seasonNumber: state.seasonNumber, + episodeNumber: state.episodeNumber, + sessionKey: identity.sessionKey, + }); + }); + it('keeps lazy provider ids transient while rejecting an id refresh for a pending request', () => { const stateA = episodeState({ originalId: 'provider|episode:token-a' }); const stateB = episodeState({ originalId: 'provider|episode:token-b' }); diff --git a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-episode-playback-session-key.ts b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-episode-playback-session-key.ts index 894f417d1..eb046690f 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-episode-playback-session-key.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-episode-playback-session-key.ts @@ -25,7 +25,6 @@ export interface StalkerEpisodePlaybackSessionIdentity { readonly seasonKey: string; readonly seasonNumber: number; readonly episodeNumber: number; - readonly episodeTrackingId: string | number; readonly sessionKey: string; } @@ -67,8 +66,9 @@ export function captureStalkerEpisodePlaybackSessionIdentity({ return null; } - // Provider commands and IDs remain transient request guards below. They - // may contain credentials and must never enter recovery ownership state. + // Recovery ownership uses provider commands and IDs only for the transient + // request guard below. They may contain credentials and must never enter + // the session key or retained recovery state. const structuralContentId = JSON.stringify([ seriesMode, seriesId, @@ -92,7 +92,6 @@ export function captureStalkerEpisodePlaybackSessionIdentity({ seasonKey, seasonNumber, episodeNumber, - episodeTrackingId: episodeState.episode.id, sessionKey, }); }