mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 20:56:19 -08:00
fix(stalker): only mint a temporary link when the row asks for one
`create_link` ran on every Stalker playback. The reference client — the portal's own `player.js`, mirrored by Kodi's pvr.stalker — mints a link only when the catalog row sets `use_http_tmp_link` or `use_load_balancing`; otherwise it plays the static `cmd` that `get_all_channels` / `get_ordered_list` already returned. Neither flag was read anywhere in the codebase, so every channel paid a round trip and gained a failure point the reference client does not have. One helper now owns the decision (`resolveStalkerStaticPlaybackUrl`), used by `fetchStalkerPlaybackLink()` for ITV/VOD/radio, by the download path, and by `StreamResolverService` for Favorites/Recently Viewed. Its guards are deliberately wider than the flags alone and can only route a row back onto the `create_link` path: no row to read flags from, a relative or query-only command (the VOD `has_files` rewrite), a non-HTTP scheme, or a loopback host. An episode always mints, since `series` selects it server-side. Radio joins the same decision, so a station the portal proxies now gets its link instead of playing a URL the portal never meant to serve. Temporary links live ~5 s, so the audit that came with this: favorites and recently-viewed persist the `cmd`, playback positions store ids, and the main-process context map stores headers keyed by origin+path — none replay a resolved URL. Downloads are the documented exception, and honouring the flags shrinks even that, since an unflagged movie now yields a permanent URL that survives retry. `forced_storage` and `play_token` stay unwired, with the reasoning recorded in the docs rather than left ambiguous. The mock's ITV/radio rows now carry both flags, and the new `static-channel-cmd` scenario (MAC 00:1A:79:00:00:0A) serves unflagged rows with a playable command so the e2e can assert that NO `create_link` request reaches the portal — verified to fail when the change is reverted, with a companion test proving the recorder sees a link when one is due. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d3cc18dc72
commit
2adf687b9b
24 files changed
+1139
-141
No files matched your search
@@ -65,6 +65,13 @@ const EMBEDDED_SERIES_MAC = '00:1A:79:00:00:05';
|
||||
/** Legacy pagination MAC — portal without get_all_channels support */
|
||||
const LEGACY_PAGINATION_MAC = '00:1A:79:00:00:06';
|
||||
|
||||
/**
|
||||
* Static-cmd MAC — ITV rows carrying a directly playable `cmd` with
|
||||
* `use_http_tmp_link` and `use_load_balancing` both `'0'`, i.e. a portal that
|
||||
* expects no `create_link` call at all.
|
||||
*/
|
||||
const STATIC_CMD_MAC = '00:1A:79:00:00:0A';
|
||||
|
||||
/**
|
||||
* Dedicated MACs for the full-portal authentication tests. Mock state is keyed
|
||||
* by MAC, so keeping these distinct from the content scenarios above means an
|
||||
@@ -120,6 +127,7 @@ const OWNED_MACS = [
|
||||
MINIMAL_MAC,
|
||||
EMBEDDED_SERIES_MAC,
|
||||
LEGACY_PAGINATION_MAC,
|
||||
STATIC_CMD_MAC,
|
||||
AUTH_FLOW_MAC,
|
||||
AUTH_REAUTH_MAC,
|
||||
AUTH_REJECTED_MAC,
|
||||
@@ -681,6 +689,62 @@ test('@stalker create_link returns a playable stream URL', async ({
|
||||
expect(streamUrl).toMatch(/\.m3u8$/);
|
||||
});
|
||||
|
||||
/**
|
||||
* Play the first channel of the first ITV category and report every
|
||||
* `create_link` request the page made while doing so.
|
||||
*/
|
||||
async function playFirstItvChannel(page: Page): Promise<string[]> {
|
||||
const createLinkRequests: string[] = [];
|
||||
page.on('request', (request) => {
|
||||
if (request.url().includes('action=create_link')) {
|
||||
createLinkRequests.push(request.url());
|
||||
}
|
||||
});
|
||||
|
||||
await page.getByRole('link', { name: /live|itv/i }).click();
|
||||
await page.waitForURL(/stalker.*itv/);
|
||||
|
||||
const categories = page.locator('.category-item');
|
||||
await expect(categories.nth(1)).toBeVisible({ timeout: 10_000 });
|
||||
await categories.nth(1).click();
|
||||
|
||||
const channels = page.locator('[data-test-id="channel-item"]');
|
||||
await expect(channels.first()).toBeVisible({ timeout: 20_000 });
|
||||
await channels.first().click();
|
||||
await expect(channels.first()).toHaveClass(/active/, { timeout: 20_000 });
|
||||
await expect(page.locator('app-web-player-view')).toBeVisible({
|
||||
timeout: 20_000,
|
||||
});
|
||||
|
||||
return createLinkRequests;
|
||||
}
|
||||
|
||||
test('@stalker ITV plays an unflagged channel without minting a link', async ({
|
||||
page,
|
||||
}) => {
|
||||
// The reference client only calls create_link when the row sets
|
||||
// use_http_tmp_link or use_load_balancing; this portal sets neither, so
|
||||
// the static cmd must reach the player untouched. The companion test
|
||||
// below proves the recorder does see a create_link when one is due.
|
||||
await addStalkerPortal(page, {
|
||||
name: 'Static Cmd Portal',
|
||||
mac: STATIC_CMD_MAC,
|
||||
});
|
||||
|
||||
expect(await playFirstItvChannel(page)).toEqual([]);
|
||||
});
|
||||
|
||||
test('@stalker ITV mints a link for a channel that asks for one', async ({
|
||||
page,
|
||||
}) => {
|
||||
await addStalkerPortal(page, { name: 'Tmp Link Portal' });
|
||||
|
||||
const createLinkRequests = await playFirstItvChannel(page);
|
||||
|
||||
expect(createLinkRequests.length).toBeGreaterThan(0);
|
||||
expect(createLinkRequests[0]).toContain('type=itv');
|
||||
});
|
||||
|
||||
test('@stalker mock server returns radio categories and stations', async ({
|
||||
request,
|
||||
}) => {
|
||||
|
||||
Reference in new issue
Block a user