fix(stalker): warm the session against the repaired portal configuration

Found while auditing my own static branch against the create_link path rather
than waiting for the next review round.

`executeStalkerRequest` applies the lazy-repair override on its first line, so
the create_link path always talks to the configuration a completed repair
proved good. The session warm-up I added did not: it handed `ensureToken` the
caller's pre-repair row, so a portal whose endpoint or mode had been repaired
would handshake against the configuration the repair had already rejected —
stranding the session precisely on the portals repair exists to rescue.

The override now happens inside `ensureStalkerSession`, mirroring
`executeStalkerRequest`'s first line, so every caller inherits the rule instead
of each having to remember it. Mutation-checked: dropping the override fails
the new test alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5 committed 2026-08-03 10:11:15 +02:00
1 parent 9afdad0279
commit 277e0a0385
3 files changed
+56 -3

No files matched your search

@@ -375,7 +375,9 @@ export class StreamResolverService {
// `ensureToken` performs the handshake + get_profile — and
// validates the identity the cached token was negotiated for,
// which the raw `getCachedToken` below cannot — without minting a
// link; a simple portal returns null immediately.
// link; a simple portal returns null immediately. The raw row goes
// in: the helper applies the repair override itself, exactly as
// `executeStalkerRequest` does on the branch below.
await this.warmStalkerSession(playlist);
return this.buildStalkerPlayback(item, playlist, {
@@ -214,6 +214,44 @@ describe('stalker-player-request.utils', () => {
expect(dataService.sendIpcEvent).not.toHaveBeenCalled();
});
it('handshakes against a repaired endpoint, not the stale one', async () => {
// `executeStalkerRequest` applies the repair override on its first
// line, so the static path must too — handshaking against the
// configuration a repair has already proven broken would strand
// the session.
const stale = {
...PLAYLIST,
portalUrl: 'http://demo.example/portal.php',
isFullStalkerPortal: true,
} as PlaylistMeta;
const repaired = {
...stale,
portalUrl: 'http://demo.example/stalker_portal/server/load.php',
} as PlaylistMeta;
await fetchStalkerPlaybackLink(
{
dataService: dataService as never,
stalkerSession: stalkerSession as StalkerSessionService,
portalRepair: {
applyOverride: jest.fn().mockReturnValue(repaired),
shouldAttemptRepair: jest.fn().mockReturnValue(false),
repairPortal: jest.fn().mockResolvedValue(null),
},
},
{
playlist: stale,
selectedContentType: 'itv',
cmd: 'ffrt3 http://cdn.example/live/42.m3u8',
linkFlags: { use_http_tmp_link: '0' },
}
);
expect(stalkerSession.ensureToken).toHaveBeenCalledWith(
expect.objectContaining({ portalUrl: repaired.portalUrl })
);
});
it('skips the handshake for a simple portal', async () => {
await fetchStalkerPlaybackLink(deps(), {
playlist: PLAYLIST,
@@ -59,13 +59,26 @@ export async function ensureStalkerSession(
playlist: PlaylistMeta | undefined,
logger?: { warn(...args: unknown[]): void }
): Promise<void> {
if (!playlist || !isFullStalkerPortalPlaylist(playlist)) {
if (!playlist) {
return;
}
// The repair override is applied here for the same reason
// `executeStalkerRequest` applies it on its first line: a completed repair
// may have moved the endpoint or the mode while the caller still holds the
// pre-repair row, and handshaking against the configuration a repair has
// already proven broken would strand the session.
const effective = deps.portalRepair
? deps.portalRepair.applyOverride(playlist)
: playlist;
if (!isFullStalkerPortalPlaylist(effective)) {
return;
}
try {
await deps.stalkerSession.ensureToken(
toStalkerSessionPlaylist(playlist)
toStalkerSessionPlaylist(effective)
);
} catch (error) {
logger?.warn('Could not establish the Stalker session', error);