Files
iptvnator/docs/architecture/download-manager.md
T
Salem 2c032cd3c8 fix(security): complete Electron hardening and review follow-ups
* fix(security): harden Electron IPC against MITM, SSRF, path and injection risks

S1 TLS: validate certs by default on playlist/EPG fetches (opt-out via IPTVNATOR_ALLOW_INSECURE_TLS); new util/secure-https.ts.
S2: write-file IPC restricted to save-dialog-authorized paths.
S3: XTREAM_PROBE_URL guarded by assertRemoteUrlAllowed + maxRedirects:0; new events/url-safety.ts (+19 tests).
S4: EPG titles rendered via interpolation, not [innerHTML].
S5: downloads reveal/play limited to recorded download paths.
S6: Stalker cmd encoded (slash-preserving) to block query injection.
EPG-worker and Stalker fetches reject file://-style/credentialed URLs; LAN/self-hosted targets remain allowed.

* perf(player): lazy-load web video players via @defer

Wrap Video.js/HTML5/ArtPlayer in @defer (on immediate) so video.js, hls.js,
artplayer and mpegts.js split into a deferred chunk loaded on first playback
instead of eagerly on the player route. Embedded MPV (native) stays eager.
Spec uses DeferBlockBehavior.Playthrough.

* fix(player): remove leaked HTML video listeners on destroy

volumechange used a mismatched removeEventListener reference, while
loadedmetadata and timeupdate were never removed at all. Bind all three to
stable handler fields used for both add and remove, and add a teardown
regression test asserting each listener is detached on destroy.

* refactor(dashboard): extract pure navigation helpers from DashboardDataService

Move the 8 stateless link/navigation-state/type-kind helpers into a new
dashboard-navigation.util.ts so the routing logic is independently testable and
the 1260-line god-service shrinks. DashboardDataService keeps the public methods
as thin delegators (facade) so the public API and the single consumer
(workspace-dashboard-rails) are unchanged. First slice of the DashboardDataService
decomposition; verified by the existing service spec (33/33) and the app typecheck.

* fix(review): address PR feedback (IPv6 link-local, write-path cap, @defer placeholder)

- url-safety: broaden IPv6 link-local detection to the full fe80::/10 range
  (fe80:: through febf::), not just the fe80:: prefix (+ regression tests).
- playlist.events: cap authorizedWritePaths (evict oldest past 32) so a save
  dialog opened without a following write cannot accumulate entries until restart.
- web-player-view: add a @placeholder to each @defer (on immediate) player block
  to avoid the one-frame blank/layout-shift before the chunk resolves.

* fix(security): close Electron network and download gaps

* test(downloads): cover cancellation and restart cleanup

* fix(downloads): address Greptile review gaps

* test(security): reproduce remaining Greptile findings

* fix(security): close remaining Greptile findings

* test(downloads): reproduce early database queue stall

* fix(downloads): release queue after setup failures

* test(downloads): reproduce completion queue stall

* fix(downloads): release queue after completion failures
2026-06-12 15:24:29 +02:00

6.0 KiB

Download Manager Architecture

The download manager is a desktop-only feature that layers a curated queue, progress tracking, storage configuration, and playback controls on top of the existing Xtream (xtream-electron folder) + Stalker viewers. Backend work is handled in the Electron process while the Angular renderer surface exposes a dedicated /downloads route, contextual buttons, and theme-aware styling.

Backend responsibilities

  • Queue control (apps/electron-backend/src/app/events/database/download-runtime.ts) DownloadTask mirrors the shared DownloadItem table plus transient cancel/progress helpers. Request validation and row creation live in download-requests.ts, while downloads.events.ts stays focused on IPC registration. enqueueDownload() pushes the task onto downloadQueue and triggers processQueue(). processQueue() keeps one active download, updates the row to downloading, and calls startDownload().
  • electron-dl integration startDownload() calls electron-dl's download() helper. Headers (user agent, referer, origin) are attached, and the onStarted, onProgress, onCompleted, and onCancel callbacks translate the helper's payload into Drizzle updates. A cancellation requested before onStarted is remembered and applied as soon as Electron supplies the DownloadItem, so the request cannot be lost in the startup race.
  • Destination collision policy Existing destination files are never overwritten. Before starting Electron's download, the backend atomically reserves a free numbered filename with an exclusive filesystem create. Electron may overwrite that empty reservation, but cannot overwrite a file that existed before the reservation. The selected filePath and fileName are persisted before transfer begins. Errors, cancellations, and startup recovery remove that exact partial path and clear it from the row; completed downloads replace it with Electron's final values.
  • IPC surface
    The backend exposes DOWNLOADS_* handlers for list retrieval, start/cancel/retry/remove operations, folder selection/reveal, and the DOWNLOADS_UPDATE_EVENT emitter that the renderer listens to in order to refresh its signal store.

Renderer architecture

  • Downloads service (libs/services/src/lib/downloads.service.ts) Signals back the current download list while hasDownloads and isAvailable gates UI rendering. Before each download the service asks the main process for the authorized folder and calls downloadsStart. The backend extracts the file extension from the URL or falls back to mp4. onDownloadsUpdate updates the signal, while helper methods retryDownload, removeDownload, cancelDownload, and playDownload talk to the corresponding IPC commands so retries reuse existing rows and completed items can open the recorded path.
  • Downloads view (libs/portal/downloads/feature)
    A standalone page exposes the queue, desktop-only messaging, folder picker, and action buttons. downloads.component.html now wraps the list inside a scrollable panel (downloads__list-wrapper) so long queues stay reachable, and downloads.component.scss drives a bold two-tone aesthetic inspired by the frontend-design mandate—gradient cards, floating avatars, and theme-aware variables triggered via body.dark-theme. Failed/canceled cards now show retry/delete controls, queued/downloading cards show a cancel icon, and completed cards render inline play/open buttons with mat-icon cues. The header also shows the resolved download folder and a CHANGE FOLDER action.
  • Theme fixes
    To keep typography legible in both modes, app-search-result-item now inherits color from :host-context(body.dark-theme) and :host-context(body:not(.dark-theme)), ensuring dense light-theme grids no longer show white text on white backgrounds.

Global API surface

  • Preload + types
    apps/electron-backend/src/app/api/main.preload.ts wires every download IPC command plus the onDownloadsUpdate listener to window.electron. The shared ElectronBridgeApi contract in libs/shared/interfaces/src/lib/electron-api.interface.ts owns the download and playback-position method types; global.d.ts and apps/web/src/typings.d.ts reference that contract instead of redeclaring the bridge.

Routing and navigation

  • /downloads is available under both portal flavors: the Xtream routes already load DownloadsComponent, and the Stalker routes now import the same component so the sidebar link can target /stalker/:id/downloads without returning to the startup screen.
  • The navigation component already points routerLink="./downloads" inside the shared nav pane, so both portals reuse the same download page.

Queuing, persistence, and UX notes

  • Every download row writes to the shared downloads table with statuses (queued, downloading, completed, failed, canceled) plus metadata such as bytesDownloaded, totalBytes, errorMessage, and Xtream identifiers. On startup, download-recovery.ts deletes persisted partial reservations before stale queued/downloading rows become failed.
  • Queue cancellation removes a queued task or records an active cancellation request and calls downloadItem.cancel() when the item is available; retries reuse the same database entry, preventing duplicate rows.
  • The OS downloads path is always authorized. A custom folder becomes authorized only after native folder selection, and the main process persists that selection under Electron userData. Renderer settings may display the path, but they are not trusted as authorization.
  • The new UI leverages CSS variables for theme-specific backgrounds/borders, ensures .downloads__list can scroll inside its panel, and brings consistent badge/typography treatments to each card.

Keeping the backend queue, IPC handlers, shared schema, and renderer signals synchronized minimizes drift between platform rules and the UI. Future work might cover download list filters, cancel-all actions, or integration with upcoming playback analytics.