mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 05:36:17 -08:00
feat(playback): forward portal Cookie/Authorization to built-in players
The web players (HTML5/hls.js, Video.js, ArtPlayer, Shaka) could only ever receive User-Agent/Referer/Origin, so any Stalker stream gated on the portal session cookie or Bearer token played exclusively in external MPV/VLC — the long-running "only VLC works" cluster (#849, #910, #732). - request-header-overrides.service: the scoped override now carries Cookie and Authorization, attached only to requests on the exact stream origin, in-memory only, dropped on replace/clear. Unscoped (playlist-level) calls drop credentials fail-closed; control characters in header values are rejected. Chosen over session.cookies.set(): jar cookies attach only to credentialed requests, which would force withCredentials into every engine and break against the Access-Control-Allow-Origin:* IPTV panels send, and jar scoping is port-blind. - WebPlayerViewComponent is now the single owner of the scoped override for every built-in player: it extracts the full header set from the resolved playback, configures the override BEFORE handing the source over (players render only once the source exists), and clears the scoped layer on destroy. HtmlVideoPlayerComponent's own three-header call is removed — it would overwrite the credentialed override. - Stalker VOD, series episodes and radio now build the same portal header set ITV already had (they previously carried no portal headers at all); same-origin playback sends the real User-Agent alongside X-User-Agent. - Stream classification is host-based via one shared predicate (isStalkerStreamCredentialSafe): same-host port changes and scheme upgrades keep the portal profile (the #1158 class), a foreign host or https->http downgrade keeps the credential-free KSPlayer profile. The main-process fallback context uses the same predicate so isStalkerDirectStreamProfile can no longer discard renderer headers. - setUserAgent bridge gains an optional credentials parameter; preload, ipcMain handler and ElectronBridgeApi updated together. - stalker-mock-server: gated-stream scenario (MAC 00:1A:79:00:00:09) whose create_link returns a local /stream/gated/video.mp4 that 403s without the mac cookie + current Bearer token; new Electron e2e proves a built-in player actually plays it (and that the gate refuses bare requests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
297e9fbef8
commit
f196abd963
27 files changed
+1393
-136
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
addStalkerPortal,
|
||||
closeElectronApp,
|
||||
expect,
|
||||
launchElectronApp,
|
||||
resetMockServers,
|
||||
stalkerMockServer,
|
||||
test,
|
||||
waitForStalkerCatalog,
|
||||
} from './electron-test-fixtures';
|
||||
|
||||
/**
|
||||
* End-to-end proof that a BUILT-IN player's media requests carry the portal
|
||||
* credentials (mac cookie + Bearer token) — the root of the long-running
|
||||
* "only VLC works" cluster (#849, #910, #732): the web players used to
|
||||
* receive only User-Agent/Referer/Origin, so any stream gated on the portal
|
||||
* session could never play inline.
|
||||
*
|
||||
* The mock's `gated-stream` scenario makes `create_link` return this
|
||||
* server's own `/stream/gated/video.mp4`, which answers 403 unless the
|
||||
* request presents the mac cookie AND the MAC's current access token. A unit
|
||||
* test cannot show that a header reached the video element; playback
|
||||
* advancing past that gate can only happen when the scoped Electron header
|
||||
* override attached the credentials to the actual media request.
|
||||
*/
|
||||
|
||||
const GATED_MAC = '00:1A:79:00:00:09';
|
||||
const GATED_STREAM_URL = `${stalkerMockServer}/stream/gated/video.mp4`;
|
||||
// The full-portal URL shape: the app handshakes and holds a Bearer token,
|
||||
// which is exactly what the gated stream endpoint demands.
|
||||
const FULL_PORTAL_URL = `${stalkerMockServer}/stalker_portal/server/load.php`;
|
||||
|
||||
test('@electron @stalker built-in player plays an auth-gated portal stream', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['stalker']);
|
||||
|
||||
// First prove the gate is real: a credential-less request is refused, so
|
||||
// a green playback assertion below cannot be a permissive-mock artifact.
|
||||
const bareResponse = await request.get(GATED_STREAM_URL);
|
||||
expect(bareResponse.status()).toBe(403);
|
||||
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await addStalkerPortal(app.mainWindow, {
|
||||
macAddress: GATED_MAC,
|
||||
portalUrl: FULL_PORTAL_URL,
|
||||
});
|
||||
await waitForStalkerCatalog(app.mainWindow);
|
||||
|
||||
// The portal lands on Movies; live playback lives in the ITV layout.
|
||||
await app.mainWindow
|
||||
.getByRole('link', { name: /live|itv/i })
|
||||
.click();
|
||||
await app.mainWindow.waitForURL(/stalker.*itv/);
|
||||
|
||||
// The ITV view renders channels only after a category is selected;
|
||||
// index 0 is the "All channels" pseudo-category.
|
||||
const categories = app.mainWindow.locator('.category-item');
|
||||
await expect(categories.first()).toBeVisible({ timeout: 10_000 });
|
||||
await categories.first().click();
|
||||
|
||||
const channels = app.mainWindow.locator(
|
||||
'[data-test-id="channel-item"]'
|
||||
);
|
||||
await expect(channels.first()).toBeVisible({ timeout: 20_000 });
|
||||
await channels.first().click();
|
||||
|
||||
const video = app.mainWindow
|
||||
.locator('app-web-player-view video')
|
||||
.first();
|
||||
await expect(video).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Advancing playback past the 403 gate is only possible when the
|
||||
// media requests carried the portal cookie and Authorization header.
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
video.evaluate(
|
||||
(element: HTMLVideoElement) => element.currentTime
|
||||
),
|
||||
{ timeout: 20_000 }
|
||||
)
|
||||
.toBeGreaterThan(0.5);
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('playback-diagnostic-banner')
|
||||
).toBeHidden();
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user