Skip to content

Architecture ​

res-downloader is a cross-platform desktop app built with Wails. The Vue frontend handles interaction and state presentation. The Go backend handles the local proxy, certificates and system integration, resource detection, plugin execution, and download tasks. A versioned plugin protocol extends site support without putting frequently changing site logic into the host.

This page describes module boundaries, runtime dependencies, and main data flows. See Plugin Development for complete plugin fields, permissions, and runtime APIs.

Overview ​

text
Browser / phone / desktop app
          │ HTTP / HTTPS proxy traffic
          ▼
┌──────────────────────────────────────────────────────────────┐
│ server.Gateway: shared Host:Port listener                     │
│                                                              │
│  Local /api requests ──► httpapi.Server                       │
│  Other requests      ──► proxy.Engine (goproxy)                │
└──────────┬───────────────────────────────┬────────────────────┘
           │                               │
           │ Settings, resources,          │ Request/response
           │ tasks, plugins                │ observations
           ▼                               ▼
┌────────────────────┐          ┌──────────────────────────────┐
│ Runtime services   │          │ plugin.PluginManager         │
│                    │          │ - Built-in generic detector  │
│ resource.Resource  │◄─────────│ - Bundled official plugins   │
│ download.Scheduler │          │ - User-installed plugins     │
│ media.Engine       │          └───────────────┬──────────────┘
│ system.Setup       │                          │ ResourceCandidate
└─────────┬──────────┘                         ▼
          │                         Resource catalog,
          │ HTTP responses /        correlation, persistence
          │ Wails events                       │
          ▼                                    │
┌──────────────────────────────────────────────┴───────────────┐
│ Vue + Pinia: resources, downloads, plugins, settings          │
└──────────────────────────────────────────────────────────────┘

internal/app.Runtime is the application's composition root. Construction prepares app metadata, settings, logging, and events; business resources are initialized after Wails checks the single-instance lock.

CLI / MCP invoke existing HTTP business handlers through a separate local control entry point: Agent / Shell → internal/automation → internal/control → httpapi.ControlHandler → resource and download services. Clients do not construct the desktop Runtime, open business databases, or start the download scheduler.

Runtime components ​

ModuleMain responsibilityKey dependencies or output
WailsLoad the embedded frontend, provide window lifecycle and a few Bind methodsfrontend/dist, app.Bind
app.RuntimeCreate modules, wire dependencies, and manage startup and shutdown orderSettings, events, proxy, HTTP, plugins, resources, downloads
server.GatewayManage the capture proxy listener and route local API versus proxy requestshttpapi.Server, proxy.Engine
control.ServerManage the local automation listener, separate credentials, and discovery fileLoopback only; reuses resource and download HTTP handlers
automationConvert CLI commands and MCP stdio tools into local control requestsReads connection information on every call; does not start desktop Runtime
proxy.EngineHandle HTTP proxying and HTTPS MITM and generate request/response observationsInterception rules, device certificate, plugin manager
plugin.PluginManagerLoad plugins, run observation hooks, correlate resources, refresh URLs, and generate download plansBuilt-in, bundled, and user plugins
operation.ServiceDiscovery, sessions, single/batch scheduling, cancellation, history, results and artifactsIndependent operations.db, plugin bridge, resource/download adapters
resource.ResourceMaintain the catalog, persist resource candidates, and execute resource actions and download plansresources.db, capture cache, media engine
download.SchedulerPersist tasks, manage workers and task state, and handle pause, resume, and retrytasks.db, plugin download plans
download.PlanRunnerAcquire inputs, process them, and place final output for a download planHTTP, HLS, captured files, FFmpeg, WASM
capture.StoreCache file bytes from the proxy, page scripts or plugin hooks for download planscapture-cache/
media.EngineInvoke user-configured FFmpeg / FFprobe for media processingRemuxing, merging, recording, and related capabilities
system.SetupManage device certificates, system proxies, and platform operationsWindows, macOS, and Linux implementations
events.EmitterEncode resource and task changes and send them to the Wails frontendA single event channel

Startup and shutdown lifecycle ​

Construction ​

NewRuntime prepares application directories, logging, settings, and events for window sizing, Bind methods, and middleware. It does not open business databases or load plugins.

Startup ​

The desktop entry point uses Wails SingleInstanceLock; another launch activates the existing window. OnDomReady calls Runtime.Start asynchronously after the instance check, initializing device certificates, capture storage, the resource database, plugins, the task database, proxy, and HTTP services. Frontend assets load immediately, while app information and API requests wait for backend readiness. Failed initialization releases business resources already created. Shutdown cancels subsequent startup stages; the current synchronous initialization operation finishes before cleanup proceeds safely.

When settings are applied, the runtime updates upstream proxy transport, download worker count, and HTTPS interception rules as needed without reconstructing the entire app.

Runtime.Start initializes proxy handlers, starts the shared HTTP gateway, then starts the download scheduler. The scheduler restores persisted tasks: waiting tasks are queued again; tasks previously resolving, downloading, or processing are marked interrupted until the user chooses to resume or retry.

Next, control.Server starts on a dynamic 127.0.0.1 port and writes control/session.json, protected by current-user access permissions. This entry point uses a separate random token and a route allowlist, independent of the capture proxy's Host / Port settings. Automation startup failures are logged; desktop capture and downloads continue.

Shutdown ​

App coordinates exit, restart, reset, and update handoff. Runtime.Close only releases business resources: the automation listener and this instance's discovery file, the app-managed system proxy, the HTTP gateway, the download scheduler, the resource database, and the capture cache. After successful cleanup, App performs the requested follow-up action and closes logging. Failed cleanup cancels installation and prevents reset or relaunch. Resource cleanup and follow-up actions are each protected against duplicate execution.

Restarts after a port change or reset wait for the previous process to exit before entering the single-instance check. Update helpers and CLI / MCP bypass the desktop instance flow.

Resource capture flow ​

  1. The user enables the system proxy or manually connects other devices and apps to the configured listen address.
  2. server.Gateway accepts the connection. Recognized local /api requests go to the HTTP API; other requests go to proxy.Engine.
  3. For HTTPS CONNECT, rules.Set decides between MITM and pass-through based on domain policy. Only host information is available at this point; resource-type and MIME matching happen later during observation.
  4. The proxy converts requests or responses into versioned Observation objects. It reads bodies, bounded by bodyLimit, only when matching rules and permissions require them.
  5. plugin.PluginManager invokes enabled plugins by priority, including the built-in generic detector, bundled official plugins, and user plugins. Declared operations are sent through the unified operation service to an explicitly selected bridge page.
  6. Plugins can emit resource candidates, correlate requests, request controlled response modifications or page scripts, and write file bytes to capture.Store through response capture or api.capture.save.
  7. The resource service normalizes, correlates, and saves emitted ResourceCandidate objects in the in-memory catalog and resources.db.
  8. Wails events push changes to the frontend, which updates the resource list using the current filters.

Reading a request body reconnects the consumed prefix to the original stream. Response observation likewise preserves the complete response for the client. Observation should not change normal proxy transport unless a plugin explicitly requests and returns a validated modification.

Download execution flow ​

  1. The frontend requests a download through the local API. download.Scheduler creates a task record and writes it to tasks.db.
  2. The scheduler takes tasks from the queue according to configured concurrency. Collections split into child tasks while retaining parent task state.
  3. If a resource has expired or requires refresh, the scheduler first asks its original plugin to refresh it. If this is impossible, it returns an explicit state requiring recapture.
  4. The plugin creates a DownloadPlan describing inputs, executors, processing steps, and final output rather than directly controlling host objects.
  5. resource.Resource calculates the final path from the filename template and conflict policy, then passes the plan to download.PlanRunner.
  6. PlanRunner acquires inputs using HTTP, HLS, captured files, FFmpeg, or other executors, and runs host processing or plugin WASM processors as needed.
  7. Completed temporary output is atomically placed at the final path. Progress and state are persisted in the task database and sent to the frontend through events.

The scheduler owns each task execution's cancellation context, which propagates to PlanRunner and its input executors through child contexts. The resource service executes plans, resolves filenames, and installs output without holding the scheduler or indexing active download instances. Progress and processing callbacks are bound to the task ID and execution context; late callbacks from cancelled executions are ignored. Collections count completed children, and child byte progress cannot update an independent download task for the same resource. The resource list and download center share the downloadTaskUpdated event.

Plain HTTP and captured-file plans can retain checkpoint state. Pause/resume support for live recording, HLS, or media processing depends on the executor. When the app exits, unfinished tasks are recorded as resumable or interrupted instead of keeping background processes running.

Frontend/backend communication ​

The frontend does not reference internal Go modules directly. It primarily uses three channels:

  • Wails Bind: a small set of startup capabilities such as app information, a settings snapshot, the API session token, and reset operations.
  • Local HTTP API: resources, previews, download tasks, plugins, certificates, and settings. Except for a few public certificate-download or HLS-preview paths, requests require a per-launch random bearer token and pass origin and method checks.
  • Wails events: the backend wraps business events as {type, data} and sends them through a single event channel. A Pinia event store dispatches them to business stores and pages.

A shared Pinia store loads download tasks and handles downloadTaskUpdated and downloadTaskRemoved events. The download center displays tasks by task ID, while the resource list selects the latest associated task by resource ID instead of maintaining a second writable download state on each resource row. Updates and removals received during loading are preserved, and older task snapshots cannot overwrite newer state.

The Wails AssetServer uses the same HTTP API middleware so the embedded frontend can access /api. The gateway's separate listener allows proxy traffic, phone certificate downloads, and local previews to share the configured Host and Port.

CLI and MCP use an additional local control listener for resource queries and download operations. It rejects browser Origins, non-local Hosts, and routes outside its allowlist. Its control token cannot be used directly with the desktop API. Clients ignore environment proxies, do not follow redirects, and do not automatically retry writes. MCP uses the official Go SDK for stdio; standard output contains protocol messages only. Tools query business progress. See CLI and MCP for usage.

Plugin system boundaries ​

Plugins receive structured observations and return structured resource candidates or download plans. They do not hold Go objects or have arbitrary access to the filesystem, shell, or host network interfaces. The host validates Manifests, domains, capabilities, body limits, page scripts, resource actions, download plans, and processor declarations at load and execution time. Operations declare input/output schemas, page scripts and effects. Resource buttons use parameters loaded from saved resources after host ownership checks; site-specific requests stay in plugins.

Plugins have three sources:

  • Built-in plugins: implemented in Go, such as the generic resource detector.
  • Bundled official plugins: source snapshots in internal/plugin/bundled/, shipped with the app and installed into the user's plugin directory.
  • User plugins: installed under plugins/ in the user data directory, from the extension store or a local ZIP.

Site-specific API detection, correlation, signature calculation, URL refresh, and non-DRM byte transformations belong in plugins. Extend the host API only for reusable capabilities that multiple plugins may need and that the existing protocol cannot safely express. Such changes must synchronize Go models, runtime validation, Schema, TypeScript declarations, examples, and documentation.

See Plugin Development, Plugin SDK v1, and Extension Store Publishing for details.

Data and state ​

Application state lives in the operating system's user configuration directory for res-downloader:

PathContentsLifecycle
config.jsonUI, listen address, proxy, download, naming, and media tool settingsAtomically updated when settings are saved
logs/app.log and timestamped backupsRelease-build logs and enabled plugin debug logs10 MiB per file, up to 5 backups; backups older than 7 days are cleaned on open and rotation. See logs
mitm-ca.crt, mitm-ca.keyDevice-generated HTTPS interception certificate and private keyCleared on reset
resources.dbPersistent catalog of discovered resourcesUpdated when resources are cleared or the app is reset
operations.dbOperation summaries, permitted results, associations, settings and idempotencyDefaults to 7 days of history and 2 hours of results; supports adjustable retention and cleanup; removed on app reset
tasks.dbDownload tasks, children, progress, and recovery stateUpdated with task changes
control/session.jsonThis launch's automation address and separate tokenWritten at automation startup, removed on normal shutdown, replaced on the next launch after a crash
capture-cache/Temporary bytes from proxy responses and page media segmentsExpires and is cleared on reset
plugins/Installed external and bundled plugin copiesUpdated on install, upgrade, rollback, or uninstall
plugin-state.jsonPlugin enablement stateUpdated when plugin settings change
plugin-settings.jsonPer-plugin user settingsUpdated when plugin settings are saved
plugin-removed.jsonRecords of bundled plugins explicitly removed by the userPrevents unexpected restoration on upgrade
plugin-sources.jsonInstalled plugin originsUpdated on installation and upgrade
plugin-backups/One retained rollback copy for external plugin upgradesUpdated on upgrade, rollback, and uninstall

If the resource or task database cannot open, that module logs an error and falls back to in-memory state. The app can still start, but that session's state cannot survive a restart.

Main directories ​

DirectoryResponsibility
frontend/Vue, Pinia, Naive UI frontend and Wails-generated bridge code
internal/app/Composition root, Wails Bind, and backend module adapters
internal/server/Shared listener and API/proxy routing, without business handlers
internal/control/Local control listener, discovery, and platform-specific credential file permissions
internal/automation/CLI arguments, MCP tools, and shared local API client
internal/httpapi/Local API, authentication, previews, and desktop operations
internal/proxy/HTTP proxy, HTTPS MITM, observations, and page script injection
internal/rules/Domain interception and pass-through policies for CONNECT
internal/plugin/Plugin loading, permissions, runtime, store, installation, and developer CLI
internal/operation/Unified operation service and independent BoltDB storage
internal/resource/Catalog, correlation, persistence, resource actions, and download plan entry point
internal/download/Task scheduling, persistence, executors, and plan runner
internal/capture/Temporary response cache supporting range writes or appended segments
internal/media/FFmpeg / FFprobe capability detection and process execution
internal/system/Certificates, system proxies, and platform differences
internal/config/Defaults, validation, snapshots, and persistence
internal/events/Events from Go to the Wails frontend
internal/model/Shared resource, plugin, and download protocol models
internal/naming/Download filename templates and conflict policies
internal/logging/Application logging wrapper
examples/plugins/Committable protocol examples and sanitized fixtures
plugins/Local workspace for standalone site plugins; excluded from host commits and packages by default
cmd/githubctl/CLI generating stable-release metadata and the extension index
cmd/resdctl/Separately buildable CLI / MCP console client; still requires the running desktop app
build/Wails platform configuration, icons, and installer resources
docs/User guides, Plugin SDK, and developer documentation

Change boundaries ​

  • Add or fix site support in standalone plugins first. Do not add private site logic to internal/proxy/ or internal/resource/.
  • For new host plugin capabilities, define the generic protocol and permissions first, then synchronize runtime, validation, SDK files, examples, and documentation.
  • When changing the local API, check origins, authentication, methods, body limits, and frontend types together.
  • When changing download states or resource models, check BoltDB recovery, event payloads, and frontend state mappings.
  • Keep constructors free of listener and background-task side effects when changing startup/shutdown, and release created resources on failure paths.
  • Keep platform-specific certificates and capture-proxy settings in internal/system/, and update proxy resolution in internal/netproxy/. Shared business code should call them through interfaces or adapters.

Read Contributing before developing code. For plugin work, also read Plugin Development and the relevant SDK documentation.

Operation lifecycle ​

Runtime creates the operation service after resources, plugins and downloads are ready, before exposing desktop/control APIs. Backend interfaces provide plugin/page discovery, invocation delivery and resource/download result mapping; the service contains no site logic. Page handlers perform asynchronous work, while synchronous Goja hooks map results. Startup marks unfinished executions interrupted without replay. Shutdown closes operations before their dependencies. Unavailable storage explicitly rejects calls.

Page bridge reports carry the revision under which the execution was claimed, never the current page revision. After a page state change, an old execution may only acknowledge that its handler ended and release its lease; it cannot commit results or overwrite a terminal state. Ordinary page messages and operation readiness reports share page context, guarded by the page session lock.

Resource rows query the desktop-only /api/operations/resources endpoint with up to 1000 resource IDs. Each resource selects an active execution first, then the newest record by creation time, without a result payload. This query covers only currently filtered resources and their children, independently of history pagination. The frontend stops polling when it leaves the resource page or the window is hidden. History queries deep-copy only the selected page of summaries instead of all result payloads.

The operation page refreshes according to the content being viewed: history on the history tab, page sessions on the run tab, and state for open execution details and active batches. Operation definitions and service settings refresh less frequently. Hidden windows send no polling requests.

Retention cleanup reuses its last scan until a record write, settings change, manual cleanup or the nearest expiry, with a maximum interval of one minute. Results and artifacts still become unreadable as soon as they expire. App reset also removes operations.db, preventing history, settings and idempotency records from referring to cleared resources and tasks. See Plugin operations for states, bounds and retention.

Make sure you have the rights to the resources you process and comply with local laws, platform terms, and copyright requirements.