mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
feat(electron): harden renderer security
Scope playback request header overrides to active stream origins and document the Electron runtime security contract.
This commit is contained in:
1 parent
2184fd7c45
commit
fc449ca73e
16 files changed
+734
-67
No files matched your search
@@ -2,15 +2,19 @@ import { SimpleChange } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DataService } from '@iptvnator/services';
|
||||
import { Channel } from '@iptvnator/shared/interfaces';
|
||||
import { HtmlVideoPlayerComponent } from './html-video-player.component';
|
||||
|
||||
describe('HtmlVideoPlayerComponent', () => {
|
||||
let component: HtmlVideoPlayerComponent;
|
||||
let fixture: ComponentFixture<HtmlVideoPlayerComponent>;
|
||||
const electronApi = {
|
||||
setUserAgent: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let dataService: DataService;
|
||||
|
||||
const TEST_CHANNEL = {
|
||||
const TEST_CHANNEL: Channel = {
|
||||
id: '1234',
|
||||
url: 'http://test.ts',
|
||||
name: 'Test channel',
|
||||
@@ -18,8 +22,18 @@ describe('HtmlVideoPlayerComponent', () => {
|
||||
title: 'News group',
|
||||
},
|
||||
http: {
|
||||
origin: '',
|
||||
referrer: '',
|
||||
'user-agent': 'localhost',
|
||||
},
|
||||
radio: 'false',
|
||||
tvg: {
|
||||
id: '',
|
||||
logo: '',
|
||||
name: '',
|
||||
rec: '',
|
||||
url: '',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
@@ -34,12 +48,21 @@ describe('HtmlVideoPlayerComponent', () => {
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, 'electron', {
|
||||
configurable: true,
|
||||
value: electronApi,
|
||||
});
|
||||
electronApi.setUserAgent.mockClear();
|
||||
fixture = TestBed.createComponent(HtmlVideoPlayerComponent);
|
||||
component = fixture.componentInstance;
|
||||
dataService = TestBed.inject(DataService);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete (window as unknown as { electron?: unknown }).electron;
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
@@ -57,9 +80,57 @@ describe('HtmlVideoPlayerComponent', () => {
|
||||
expect(component.playChannel).toHaveBeenCalledWith(TEST_CHANNEL);
|
||||
});
|
||||
|
||||
it('passes channel headers and stream URL to Electron header overrides', () => {
|
||||
jest.spyOn(
|
||||
component.videoPlayer.nativeElement,
|
||||
'play'
|
||||
).mockResolvedValue(undefined);
|
||||
|
||||
component.playChannel({
|
||||
...TEST_CHANNEL,
|
||||
http: {
|
||||
'user-agent': 'ChannelAgent/1.0',
|
||||
origin: '',
|
||||
referrer: 'https://portal.example/referrer',
|
||||
},
|
||||
radio: 'false',
|
||||
url: 'https://stream.example/video.mp4',
|
||||
});
|
||||
|
||||
expect(electronApi.setUserAgent).toHaveBeenCalledWith(
|
||||
'ChannelAgent/1.0',
|
||||
'https://portal.example/referrer',
|
||||
'https://stream.example/video.mp4'
|
||||
);
|
||||
});
|
||||
|
||||
it('clears Electron header overrides for channels without custom headers', () => {
|
||||
jest.spyOn(
|
||||
component.videoPlayer.nativeElement,
|
||||
'play'
|
||||
).mockResolvedValue(undefined);
|
||||
|
||||
component.playChannel({
|
||||
...TEST_CHANNEL,
|
||||
http: {
|
||||
'user-agent': '',
|
||||
origin: '',
|
||||
referrer: '',
|
||||
},
|
||||
radio: 'false',
|
||||
url: 'https://stream.example/video.mp4',
|
||||
});
|
||||
|
||||
expect(electronApi.setUserAgent).toHaveBeenCalledWith(
|
||||
'',
|
||||
'',
|
||||
'https://stream.example/video.mp4'
|
||||
);
|
||||
});
|
||||
|
||||
it('emits a playback issue when the native video element reports an unsupported source', () => {
|
||||
const issues: unknown[] = [];
|
||||
component.channel = TEST_CHANNEL as never;
|
||||
component.channel = TEST_CHANNEL;
|
||||
component.playbackIssue.subscribe((issue) => issues.push(issue));
|
||||
|
||||
Object.defineProperty(component.videoPlayer.nativeElement, 'error', {
|
||||
|
||||
@@ -150,13 +150,11 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
const url = channel.url + (channel.epgParams ?? '');
|
||||
const extension = getPlaybackMediaExtensionFromUrl(channel.url);
|
||||
|
||||
// Set user agent if specified on channel
|
||||
if (channel.http?.['user-agent']) {
|
||||
window.electron?.setUserAgent(
|
||||
channel.http['user-agent'],
|
||||
channel.http.referrer
|
||||
);
|
||||
}
|
||||
window.electron?.setUserAgent(
|
||||
channel.http?.['user-agent'],
|
||||
channel.http?.referrer,
|
||||
channel.url
|
||||
);
|
||||
|
||||
if ((extension === 'ts' || !extension) && mpegts.isSupported()) {
|
||||
debugHtmlPlayer(
|
||||
|
||||
Reference in new issue
Block a user