From 9a5620bf95b097e4b0e8dee373600fd10e69395d Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 22 May 2026 04:11:28 +0300 Subject: [PATCH] refactor(runtime): require downloads bridge surface --- docs/architecture/pwa-self-hosted.md | 8 ++-- .../lib/runtime-capabilities.service.spec.ts | 39 +++++++++++++++++++ .../src/lib/runtime-capabilities.service.ts | 15 ++++++- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/docs/architecture/pwa-self-hosted.md b/docs/architecture/pwa-self-hosted.md index e3ab98344..c9aba55c7 100644 --- a/docs/architecture/pwa-self-hosted.md +++ b/docs/architecture/pwa-self-hosted.md @@ -70,9 +70,11 @@ direct `window.electron` or `DataService.getAppEnvironment()` checks. Keep feature decisions expressed as capabilities such as `supportsEpg`, `supportsSqlite`, `supportsXtreamSqliteDataSource`, `supportsDownloads`, or `supportsManagedExternalPlayers` so PWA and Electron behavior stays auditable -from one shared boundary. `supportsManagedExternalPlayers` requires the MPV and -VLC preload launch methods (`openInMpv` and `openInVlc`); a partial Electron -bridge must not expose managed external-player actions in the PWA/shared UI. +from one shared boundary. `supportsDownloads` requires the complete downloads +preload API surface used by `DownloadsService`, and +`supportsManagedExternalPlayers` requires the MPV and VLC preload launch methods +(`openInMpv` and `openInVlc`); a partial Electron bridge must not expose +desktop-only actions in the PWA/shared UI. ## Runtime Limitations diff --git a/libs/services/src/lib/runtime-capabilities.service.spec.ts b/libs/services/src/lib/runtime-capabilities.service.spec.ts index 65392cc91..75ab37d2d 100644 --- a/libs/services/src/lib/runtime-capabilities.service.spec.ts +++ b/libs/services/src/lib/runtime-capabilities.service.spec.ts @@ -76,7 +76,18 @@ describe('RuntimeCapabilitiesService', () => { dbClearPlaybackPosition: jest.fn(), dbDeleteXtreamContent: jest.fn(), dbRestoreXtreamUserData: jest.fn(), + downloadsStart: jest.fn(), + downloadsCancel: jest.fn(), + downloadsRetry: jest.fn(), + downloadsRemove: jest.fn(), downloadsGetList: jest.fn(), + downloadsGet: jest.fn(), + downloadsGetDefaultFolder: jest.fn(), + downloadsSelectFolder: jest.fn(), + downloadsRevealFile: jest.fn(), + downloadsPlayFile: jest.fn(), + downloadsClearCompleted: jest.fn(), + onDownloadsUpdate: jest.fn(), openInMpv: jest.fn(), openInVlc: jest.fn(), prepareEmbeddedMpv: jest.fn(), @@ -171,4 +182,32 @@ describe('RuntimeCapabilitiesService', () => { expect(service.supportsManagedExternalPlayers).toBe(true); }); + + it('requires the complete downloads preload surface', () => { + testWindow.electron = { + downloadsGetList: jest.fn(), + }; + + const service = new RuntimeCapabilitiesService(); + + expect(service.isElectron).toBe(true); + expect(service.supportsDownloads).toBe(false); + + testWindow.electron = { + downloadsStart: jest.fn(), + downloadsCancel: jest.fn(), + downloadsRetry: jest.fn(), + downloadsRemove: jest.fn(), + downloadsGetList: jest.fn(), + downloadsGet: jest.fn(), + downloadsGetDefaultFolder: jest.fn(), + downloadsSelectFolder: jest.fn(), + downloadsRevealFile: jest.fn(), + downloadsPlayFile: jest.fn(), + downloadsClearCompleted: jest.fn(), + onDownloadsUpdate: jest.fn(), + }; + + expect(service.supportsDownloads).toBe(true); + }); }); diff --git a/libs/services/src/lib/runtime-capabilities.service.ts b/libs/services/src/lib/runtime-capabilities.service.ts index 84b6bff5d..c55e8aaa9 100644 --- a/libs/services/src/lib/runtime-capabilities.service.ts +++ b/libs/services/src/lib/runtime-capabilities.service.ts @@ -83,7 +83,20 @@ export class RuntimeCapabilitiesService { } get supportsDownloads(): boolean { - return this.hasElectronMethod('downloadsGetList'); + return [ + 'downloadsStart', + 'downloadsCancel', + 'downloadsRetry', + 'downloadsRemove', + 'downloadsGetList', + 'downloadsGet', + 'downloadsGetDefaultFolder', + 'downloadsSelectFolder', + 'downloadsRevealFile', + 'downloadsPlayFile', + 'downloadsClearCompleted', + 'onDownloadsUpdate', + ].every((methodName) => this.hasElectronMethod(methodName)); } get supportsPortalActivityStorage(): boolean {