Ahmed Abdelaziz

EasyDownloadDesign Docs

Architecture Decisions

Five key choices and why they were made.

Last updated August 2026

Overview

Six short decisions that shaped EasyDownload — five accepted, one deliberately deferred. Each has context, choice, why, and trade-off — no jargon wall.


ADR-001: Electron for the Desktop Shell

Status: Accepted

Context: The app must run on Windows, macOS, and Linux, manage long-running processes, and show a React UI — all locally.

Decision: Use Electron. Main process for privileged work, renderer for UI, preload as the bridge, electron-builder for installers.

Why: One codebase, proven packaging, and Node.js naturally manages yt-dlp/FFmpeg subprocesses.

Trade-off: Larger installers and higher memory than native apps. In return, all platforms share the same logic and security boundary.


ADR-002: Local-First, No Cloud

Status: Accepted

Context: Users want privacy and zero setup. Accounts and servers add cost and risk.

Decision: Everything runs on the device. Downloads, history, and settings live as small JSON files in the OS user-data folder.

Why: No infrastructure to build or secure; user history never leaves the device; JSON is inspectable and enough for the data size.

Trade-off: No sync across devices. Accepted — this is a single-device tool.


ADR-003: Bundle yt-dlp and FFmpeg

Status: Accepted

Context: Users should not install or configure anything. yt-dlp standalone needs no Python.

Decision: Download platform binaries at build time into resources/bin/, bundle as extraResources, resolve at runtime (bundled path first, PATH fallback). Pass bundled FFmpeg to yt-dlp via --ffmpeg-location.

Why: The installer works out of the box; availability is predictable.

Trade-off: ~15–20 MB added to the installer. Worth it for zero-setup.


ADR-004: One Service Per Tool, Safe Arguments

Status: Accepted

Context: yt-dlp and FFmpeg have many flags and unstructured output. Inputs (URLs, format IDs) are untrusted.

Decision: All calls are spawn(executable, args[]) — never shell strings — inside one service per tool. That service owns arg building, output parsing, and error mapping.

Why: Security handling is centralized and testable; flag changes touch one file.

Trade-off: Tool output formats must be tracked across versions. Manageable with tests.


ADR-005: Privileged Core, Unprivileged UI

Status: Accepted

Context: The interface displays web-sourced content. If it were ever compromised, it must not be able to touch the file system or run programs.

Decision: The renderer runs with contextIsolation: true and nodeIntegration: false. It only sees a small typed bridge (window.mediaDownloader) — never ipcRenderer, fs, child_process, or require. Every IPC channel has a defined input validated by zod before a service runs. New windows are denied; external links open only via the OS browser.

Why: Defense in depth. Every privileged action has an explicit, auditable contract — there is no generic "run anything" channel.

Trade-off: All privileged work routes through the main process with no escape hatch, and the bridge must stay minimal and reviewed.

Honest gap

The Chromium sandbox is currently disabled (sandbox: false). Enabling it is tracked as future hardening work in the repository's ADR.


ADR-006: Chrome Extension — Deferred with Constraints Locked In

Status: Proposed (not implemented)

Context: A browser extension could send links straight to the desktop app. Tempting — but any out-of-band channel into a privileged app is a new attack surface.

Decision: Defer implementation, but lock in the constraints now: an extension may only discover and control downloads; communication stays local-only; no shell-command execution, no arbitrary file access, no public internet API. The final mechanism (Native Messaging vs. named-pipe IPC vs. controlled localhost) is deliberately left open for a future ADR.

Why: Deciding constraints before mechanism keeps the security boundary first-class instead of retrofitting it.

Trade-off: No extension feature ships today. Accepted — the app's core workflow works without it, and the validated-IPC/service boundaries keep it "ready to land" when the mechanism is chosen.

Common thread

Smaller surfaces are easier to secure, test, and change. Each ADR chooses the smaller, more isolated option — or defers until the small option can be chosen deliberately.