Files
samiandClaude Sonnet 5 5e3e21543a proto: give the generated C++ Dispatcher a real error channel (P1, 1.4.0)
DAEMON's daemon/docs/proto-requests-m1.md P1: velox::proto::Dispatcher's
on_* methods returned Result<T> = expected<T, ParseError>, and dispatch()
mapped every handler error to -32603 InternalError. A handler had no way to
return -32010 (download.get not-found), -32011 (download.add invalid-path)
or -32013 (probe-failed) with their data payloads -- three error fixtures a
conformant server must satisfy were unreachable, blocking DAEMON's
"conformance as a server" M1 DoD.

Two error channels now, kept separate on purpose:
  - parse: Result<T> / ParseError -- dispatch() failing to turn the wire into
    typed params. Always -32602, always structural.
  - handler: HandlerResult<T> / HandlerError -- a handler deciding the request
    can't be fulfilled. Carries any ErrorCode + message + free-form data.

    struct HandlerError {
        ErrorCode code{ErrorCode::InternalError};  // bare {} is a valid -32603
        std::string message;
        nlohmann::json data = nullptr;             // straight into the error's data
    };
    template <class T> using HandlerResult = std::expected<T, HandlerError>;

dispatch()'s handler branch is now
  make_error(id, r.error().code, r.error().message, r.error().data)
instead of a hard-coded InternalError. -32001/-32002/-32003 stay the server
layer's to raise around dispatch(), as DAEMON already does.

Verified end to end against the real dispatch() path: a handler returning
TaskNotFound/InvalidPath/ProbeFailed produces -32010/-32011/-32013 with the
data object intact, and a bare HandlerError{} still yields a clean -32603
with no data field. The `= nullptr` on the member (not `{nullptr}`) matters:
brace-init of nlohmann::json from nullptr is the array [null], not JSON null.

FixtureDispatcher regenerated to HandlerResult; conformance_main.cpp only
inspects dispatch()'s JSON and needed no change. TS side is untouched beyond
the version string -- no server Dispatcher is generated there.

P2 also handled: session.hello.version-mismatch's data.expected was a stale
"1.0.0"; now $any, with a note that the error-fixture compare is on `code`
only so a server echoing kProtocolVersion there is fine.

Version: minor, 1.3.0 -> 1.4.0. Wire is byte-identical (no schema, fixture,
or OpenRPC change) but every Dispatcher implementer must swap Result ->
HandlerResult on regen, and the bump is how lanes are told to. Not an ADR:
one lane consumes this binding, it's the one that asked, and the shape is
the one they proposed. Answered in contracts/proto-answers-daemon-m1.md.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
2026-09-10 15:10:13 +04:00

2022 lines
95 KiB
C++

// ---------------------------------------------------------------------------
// GENERATED FILE — DO NOT EDIT.
//
// Source: contracts/schema/**
// Generator: contracts/codegen/gen_cpp.py
// Contract: v1.4.0
//
// Hand-editing this file is a merge blocker. Fix the schema and regenerate:
// python3 contracts/codegen/gen_cpp.py
// Only lane PROTO commits to contracts/.
// ---------------------------------------------------------------------------
#pragma once
#include <cstdint>
#include <expected>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <nlohmann/json.hpp>
// This is libveloxproto, NOT libveloxcore. The layering rule in CLAUDE.md forbids
// JSON inside the engine; the engine does not link this target. See
// docs/adr/0009-generated-protocol-library.md.
namespace velox::proto {
inline constexpr std::string_view kProtocolVersion = "1.4.0";
/// Why a payload could not be turned into a typed value. `path` is a JSON Pointer
/// into the offending document, so a conformance failure names the exact field.
struct ParseError {
std::string path;
std::string message;
};
/// Every parse in this file returns one of these. Nothing here throws.
template <class T>
using Result = std::expected<T, ParseError>;
/// Which listener a request arrived on. Decides whether a privileged method is
/// allowed: see `is_allowed_on`.
enum class Transport { Uds, Ws };
/// Every error code the daemon may return. Adding one is a minor bump; changing the meaning of
/// one is a major bump.
enum class ErrorCode : std::int32_t {
/// Malformed JSON on the wire.
ParseError = -32700,
/// Not a valid JSON-RPC 2.0 request object.
InvalidRequest = -32600,
/// Unknown method name.
MethodNotFound = -32601,
/// Params failed schema validation.
InvalidParams = -32602,
/// Unhandled daemon-side failure.
InternalError = -32603,
/// Protocol major version mismatch. GUI renders this as 'Velox needs updating'.
VersionMismatch = -32001,
/// Missing or invalid token on the WebSocket transport.
NotPaired = -32002,
/// Method is privileged and was called over a transport that may not use it.
TransportForbidden = -32003,
/// No task with that id.
TaskNotFound = -32010,
/// Destination is outside the allowed roots, or is not writable. data.path is set.
InvalidPath = -32011,
/// Not enough free space to preallocate.
DiskFull = -32012,
/// Could not probe the URL. data.httpStatus is set when there was an HTTP response.
ProbeFailed = -32013,
/// Pairing brute-force lockout. data.retryAfterSec is set.
RateLimited = -32014,
};
std::string_view to_string(ErrorCode v) noexcept;
std::optional<ErrorCode> errorcode_from_int(std::int32_t v) noexcept;
/// Lifecycle of one download. The daemon is the only writer; clients render it and nothing
/// more. Terminal states are complete, failed and cancelled.
enum class TaskState {
New, // "new"
Probing, // "probing"
Queued, // "queued"
Connecting, // "connecting"
Downloading, // "downloading"
Paused, // "paused"
RetryWait, // "retry_wait"
Assembling, // "assembling"
Verifying, // "verifying"
Complete, // "complete"
Failed, // "failed"
Cancelled, // "cancelled"
};
std::string_view to_string(TaskState v) noexcept;
Result<TaskState> parse_TaskState(std::string_view s);
/// The modifier key a user holds to make one click bypass capture and let Firefox download
/// normally. Shared by Settings and CaptureRules so the daemon's setting and the extension's
/// mirror of it are literally the same type.
enum class BypassModifier {
Alt, // "alt"
Ctrl, // "ctrl"
Shift, // "shift"
None, // "none"
};
std::string_view to_string(BypassModifier v) noexcept;
Result<BypassModifier> parse_BypassModifier(std::string_view s);
enum class ChecksumAlgorithm {
Md5, // "md5"
Sha1, // "sha1"
Sha256, // "sha256"
Sha512, // "sha512"
};
std::string_view to_string(ChecksumAlgorithm v) noexcept;
Result<ChecksumAlgorithm> parse_ChecksumAlgorithm(std::string_view s);
/// HTTP request headers, verbatim as the browser would have sent them. Needed for signed-URL
/// and referrer-gated CDNs.
using Headers = std::map<std::string, std::string>;
/// What the daemon does with a task the moment it is added. 'later' is the File Info dialog's
/// Download Later button and lands the task in paused.
enum class StartMode {
Now, // "now"
Later, // "later"
Queue, // "queue"
};
std::string_view to_string(StartMode v) noexcept;
Result<StartMode> parse_StartMode(std::string_view s);
enum class MediaVariantContainer {
Ts, // "ts"
Mp4, // "mp4"
Webm, // "webm"
Mkv, // "mkv"
};
std::string_view to_string(MediaVariantContainer v) noexcept;
Result<MediaVariantContainer> parse_MediaVariantContainer(std::string_view s);
enum class MediaVariantKind {
Video, // "video"
Audio, // "audio"
Muxed, // "muxed"
Subtitle, // "subtitle"
};
std::string_view to_string(MediaVariantKind v) noexcept;
Result<MediaVariantKind> parse_MediaVariantKind(std::string_view s);
/// shutdown goes through org.freedesktop.login1 and must be confirmed by the user.
enum class QueueOnComplete {
Nothing, // "nothing"
Exit, // "exit"
Shutdown, // "shutdown"
Hangup, // "hangup"
};
std::string_view to_string(QueueOnComplete v) noexcept;
Result<QueueOnComplete> parse_QueueOnComplete(std::string_view s);
enum class QueueState {
Running, // "running"
Stopped, // "stopped"
};
std::string_view to_string(QueueState v) noexcept;
Result<QueueState> parse_QueueState(std::string_view s);
enum class ScheduleMode {
Once, // "once"
Periodic, // "periodic"
};
std::string_view to_string(ScheduleMode v) noexcept;
Result<ScheduleMode> parse_ScheduleMode(std::string_view s);
/// Lets a rule veto capture for a host without touching the exclusion list.
enum class RuleActionCapture {
Take, // "take"
Ignore, // "ignore"
};
std::string_view to_string(RuleActionCapture v) noexcept;
Result<RuleActionCapture> parse_RuleActionCapture(std::string_view s);
/// 'downloading' is spelled as in TaskState, not 'receiving'. 'pending' is a range that has
/// been planned but not yet dialled.
enum class SegmentState {
Pending, // "pending"
Connecting, // "connecting"
Downloading, // "downloading"
Stalled, // "stalled"
Complete, // "complete"
Failed, // "failed"
};
std::string_view to_string(SegmentState v) noexcept;
Result<SegmentState> parse_SegmentState(std::string_view s);
/// Every settings key that exists. The Options dialog maps 1:1 onto this list and the GUI must
/// not invent a key that is not here. Kept in lockstep with Settings.schema.json by a
/// conformance check.
enum class SettingKey {
GeneralLaunchOnLogin, // "general.launchOnLogin"
GeneralMinimizeToTray, // "general.minimizeToTray"
GeneralShowDropTarget, // "general.showDropTarget"
GeneralConfirmOnExit, // "general.confirmOnExit"
GeneralLanguage, // "general.language"
GeneralCheckForUpdates, // "general.checkForUpdates"
CaptureEnabled, // "capture.enabled"
CaptureMonitoredExtensions, // "capture.monitoredExtensions"
CaptureMonitoredMimeTypes, // "capture.monitoredMimeTypes"
CaptureMinSizeBytes, // "capture.minSizeBytes"
CaptureExcludedHosts, // "capture.excludedHosts"
CaptureBypassModifier, // "capture.bypassModifier"
CaptureAutoStartTypes, // "capture.autoStartTypes"
SaveToDefaultDir, // "saveTo.defaultDir"
SaveToTempDir, // "saveTo.tempDir"
SaveToAllowedRoots, // "saveTo.allowedRoots"
SaveToFileExistsPolicy, // "saveTo.fileExistsPolicy"
SaveToCreateSubfolderPerSite, // "saveTo.createSubfolderPerSite"
ConnectionPreset, // "connection.preset"
ConnectionMaxSegmentsPerDownload, // "connection.maxSegmentsPerDownload"
ConnectionBufferBytes, // "connection.bufferBytes"
ConnectionMaxTotalBufferBytes, // "connection.maxTotalBufferBytes"
ConnectionMaxActiveSegments, // "connection.maxActiveSegments"
ConnectionMaxConcurrentDownloads, // "connection.maxConcurrentDownloads"
ConnectionTimeoutSec, // "connection.timeoutSec"
ConnectionMaxRetries, // "connection.maxRetries"
ConnectionRetryBackoffSec, // "connection.retryBackoffSec"
DownloadsSpeedLimitBps, // "downloads.speedLimitBps"
DownloadsSpeedLimitEnabled, // "downloads.speedLimitEnabled"
DownloadsVirusScanCommand, // "downloads.virusScanCommand"
DownloadsPostDownloadCommand, // "downloads.postDownloadCommand"
DownloadsDuplicatePolicy, // "downloads.duplicatePolicy"
DownloadsVerifyChecksums, // "downloads.verifyChecksums"
ProxyMode, // "proxy.mode"
ProxyHost, // "proxy.host"
ProxyPort, // "proxy.port"
ProxyUsername, // "proxy.username"
ProxyBypassHosts, // "proxy.bypassHosts"
ProxyPacUrl, // "proxy.pacUrl"
SoundsEnabled, // "sounds.enabled"
SoundsOnComplete, // "sounds.onComplete"
SoundsOnQueueComplete, // "sounds.onQueueComplete"
SoundsOnError, // "sounds.onError"
};
std::string_view to_string(SettingKey v) noexcept;
Result<SettingKey> parse_SettingKey(std::string_view s);
enum class SettingsConnectionPreset {
Auto, // "auto"
Lan, // "lan"
Broadband, // "broadband"
Slow, // "slow"
};
std::string_view to_string(SettingsConnectionPreset v) noexcept;
Result<SettingsConnectionPreset> parse_SettingsConnectionPreset(std::string_view s);
enum class SettingsDownloadsDuplicatePolicy {
Ask, // "ask"
Skip, // "skip"
Rename, // "rename"
Redownload, // "redownload"
};
std::string_view to_string(SettingsDownloadsDuplicatePolicy v) noexcept;
Result<SettingsDownloadsDuplicatePolicy> parse_SettingsDownloadsDuplicatePolicy(std::string_view s);
enum class SettingsProxyMode {
System, // "system"
None, // "none"
Http, // "http"
Https, // "https"
Socks5, // "socks5"
Pac, // "pac"
};
std::string_view to_string(SettingsProxyMode v) noexcept;
Result<SettingsProxyMode> parse_SettingsProxyMode(std::string_view s);
enum class SettingsSaveToFileExistsPolicy {
Ask, // "ask"
Rename, // "rename"
Overwrite, // "overwrite"
Resume, // "resume"
};
std::string_view to_string(SettingsSaveToFileExistsPolicy v) noexcept;
Result<SettingsSaveToFileExistsPolicy> parse_SettingsSaveToFileExistsPolicy(std::string_view s);
/// Why a download failed. This is the WIRE failure taxonomy and it is deliberately NOT the
/// JSON-RPC ErrorCode space: ErrorCode says why a *call* failed, TaskErrorCode says why a
/// *download* failed. A task can fail while every RPC involved succeeded. The values mirror
/// vdm::Error in core/include/vdm/util/error.hpp one-for-one, by name, so DAEMON's projection
/// from the engine taxonomy onto the wire is lossless and the GUI can tell 'the file on the
/// server changed' from 'the checksum did not match'. CORE's 'ok' has no wire spelling: a
/// TaskError only exists when there is a failure. Adding a value here is a minor bump; renaming
/// or removing one is major, and would desynchronise the engine.
enum class TaskErrorCode {
Canceled, // "canceled"
ResolveFailed, // "resolve_failed"
ConnectFailed, // "connect_failed"
TlsFailed, // "tls_failed"
ConnectionReset, // "connection_reset"
Timeout, // "timeout"
TooManyRedirects, // "too_many_redirects"
HttpClientError, // "http_client_error"
HttpServerError, // "http_server_error"
AuthRequired, // "auth_required"
Forbidden, // "forbidden"
NotFound, // "not_found"
RangeNotSatisfiable, // "range_not_satisfiable"
Gone, // "gone"
ServerFileChanged, // "server_file_changed"
ContentLengthMismatch, // "content_length_mismatch"
ChecksumMismatch, // "checksum_mismatch"
DiskFull, // "disk_full"
IoError, // "io_error"
PathRejected, // "path_rejected"
PermissionDenied, // "permission_denied"
MetaCorrupt, // "meta_corrupt"
MetaVersionUnsupported, // "meta_version_unsupported"
ProbeFailed, // "probe_failed"
UnsupportedUrlScheme, // "unsupported_url_scheme"
MaxRetriesExhausted, // "max_retries_exhausted"
Internal, // "internal"
};
std::string_view to_string(TaskErrorCode v) noexcept;
Result<TaskErrorCode> parse_TaskErrorCode(std::string_view s);
enum class TaskSortDirection {
Asc, // "asc"
Desc, // "desc"
};
std::string_view to_string(TaskSortDirection v) noexcept;
Result<TaskSortDirection> parse_TaskSortDirection(std::string_view s);
enum class TaskSortField {
Filename, // "filename"
SizeBytes, // "sizeBytes"
State, // "state"
EtaSeconds, // "etaSeconds"
SpeedBps, // "speedBps"
LastTryAt, // "lastTryAt"
CreatedAt, // "createdAt"
QueuePosition, // "queuePosition"
Description, // "description"
};
std::string_view to_string(TaskSortField v) noexcept;
Result<TaskSortField> parse_TaskSortField(std::string_view s);
enum class CaptureOfferParamsMethod {
GET, // "GET"
POST, // "POST"
};
std::string_view to_string(CaptureOfferParamsMethod v) noexcept;
Result<CaptureOfferParamsMethod> parse_CaptureOfferParamsMethod(std::string_view s);
enum class CaptureOfferResultAction {
Take, // "take"
Ignore, // "ignore"
};
std::string_view to_string(CaptureOfferResultAction v) noexcept;
Result<CaptureOfferResultAction> parse_CaptureOfferResultAction(std::string_view s);
/// Why the offer was declined. Set when action is 'ignore'; the extension logs it in the
/// popup's diagnostics.
enum class CaptureOfferResultReason {
ExcludedHost, // "excluded_host"
TypeNotMonitored, // "type_not_monitored"
BelowMinSize, // "below_min_size"
Duplicate, // "duplicate"
CaptureDisabled, // "capture_disabled"
UserDeclined, // "user_declined"
RuleIgnore, // "rule_ignore"
};
std::string_view to_string(CaptureOfferResultReason v) noexcept;
Result<CaptureOfferResultReason> parse_CaptureOfferResultReason(std::string_view s);
enum class GrabberStatusResultState {
Crawling, // "crawling"
Done, // "done"
Failed, // "failed"
Cancelled, // "cancelled"
};
std::string_view to_string(GrabberStatusResultState v) noexcept;
Result<GrabberStatusResultState> parse_GrabberStatusResultState(std::string_view s);
enum class MediaListVariantsResultManifestType {
Hls, // "hls"
Dash, // "dash"
};
std::string_view to_string(MediaListVariantsResultManifestType v) noexcept;
Result<MediaListVariantsResultManifestType> parse_MediaListVariantsResultManifestType(std::string_view s);
enum class SessionHelloParamsClientType {
Gui, // "gui"
Cli, // "cli"
Extension, // "extension"
Nmhost, // "nmhost"
Test, // "test"
};
std::string_view to_string(SessionHelloParamsClientType v) noexcept;
Result<SessionHelloParamsClientType> parse_SessionHelloParamsClientType(std::string_view s);
/// How the daemon sees this connection. Lets a client know up front which privileged methods
/// will be refused.
enum class SessionHelloResultTransport {
Uds, // "uds"
Ws, // "ws"
};
std::string_view to_string(SessionHelloResultTransport v) noexcept;
Result<SessionHelloResultTransport> parse_SessionHelloResultTransport(std::string_view s);
enum class SessionSubscribeParamsEventsItem {
EventTaskAdded, // "event.task.added"
EventTaskRemoved, // "event.task.removed"
EventTaskState, // "event.task.state"
EventTaskProgress, // "event.task.progress"
EventSpeedGlobal, // "event.speed.global"
EventAuthRequired, // "event.auth.required"
EventNotify, // "event.notify"
EventSettingsChanged, // "event.settings.changed"
EventGrabberProgress, // "event.grabber.progress"
};
std::string_view to_string(SessionSubscribeParamsEventsItem v) noexcept;
Result<SessionSubscribeParamsEventsItem> parse_SessionSubscribeParamsEventsItem(std::string_view s);
enum class AuthRequiredEventScheme {
Basic, // "basic"
Digest, // "digest"
Ntlm, // "ntlm"
Negotiate, // "negotiate"
Proxy, // "proxy"
};
std::string_view to_string(AuthRequiredEventScheme v) noexcept;
Result<AuthRequiredEventScheme> parse_AuthRequiredEventScheme(std::string_view s);
enum class NotifyEventLevel {
Info, // "info"
Success, // "success"
Warning, // "warning"
Error, // "error"
};
std::string_view to_string(NotifyEventLevel v) noexcept;
Result<NotifyEventLevel> parse_NotifyEventLevel(std::string_view s);
enum class NotifyEventSound {
Complete, // "complete"
QueueComplete, // "queueComplete"
Error, // "error"
};
std::string_view to_string(NotifyEventSound v) noexcept;
Result<NotifyEventSound> parse_NotifyEventSound(std::string_view s);
struct BulkTaskResultFailedItem {
std::string taskId{};
ErrorCode code{};
std::string message{};
};
struct BulkTaskResultUpdatedItem {
std::string taskId{};
TaskState state{};
bool changed{};
};
/// Result of a state transition applied to many tasks. A bulk call never fails as a whole
/// because one id was bad: the ids that moved come back in 'updated' and the rest are explained
/// in 'failed'. This is what lets the GUI's toolbar act on a multi-selection without
/// pre-validating it.
struct BulkTaskResult {
/// One entry per task that actually changed. A task already in the target state is reported
/// here with changed false rather than as a failure.
std::vector<BulkTaskResultUpdatedItem> updated{};
std::vector<BulkTaskResultFailedItem> failed{};
};
/// The daemon's capture policy, mirrored into the extension so the two can never disagree about
/// what should be intercepted. The extension refreshes this on connect and on
/// event.settings.changed.
struct CaptureRules {
bool enabled{};
std::vector<std::string> monitoredExtensions{};
std::vector<std::string> monitoredMimeTypes{};
std::int64_t minSizeBytes{};
std::vector<std::string> excludedHosts{};
std::optional<BypassModifier> bypassModifier{};
/// Bumped on every change. The extension re-fetches when it sees a higher value.
std::int64_t rulesVersion{};
};
/// A destination folder plus the extensions that route to it. The extension mirrors the
/// extension lists so its capture decision agrees with the daemon's.
struct Category {
std::string categoryId{};
std::string name{};
std::string saveDir{};
/// Without the leading dot, lowercase.
std::vector<std::string> extensions{};
std::optional<std::vector<std::string>> mimeTypes{};
/// Compressed, Documents, Music, Programs, Video. Cannot be removed; can be renamed and
/// re-pointed.
bool builtin{};
std::optional<std::int64_t> sortOrder{};
};
/// Optional integrity check, verified during the verifying state. A mismatch moves the task to
/// failed and never overwrites a good file.
struct Checksum {
ChecksumAlgorithm algorithm{};
std::string value{};
};
/// One cookie the daemon replays so an authenticated download works outside the browser.
struct Cookie {
std::string name{};
std::string value{};
std::optional<std::string> domain{};
std::optional<std::string> path{};
std::optional<bool> secure{};
std::optional<bool> httpOnly{};
};
/// Everything needed to create one task. Shared by download.add and each item of
/// download.addBatch, so the two can never drift apart.
struct DownloadSpec {
std::string url{};
std::optional<Headers> headers{};
std::optional<std::vector<Cookie>> cookies{};
std::optional<std::string> referrer{};
std::optional<std::string> userAgent{};
/// Overrides the name derived from Content-Disposition or the URL.
std::optional<std::string> filename{};
/// Canonicalized and checked against the allowed roots before any write. -32011 if it fails.
std::optional<std::string> saveDir{};
/// null means the rules engine picks one.
std::optional<std::string> categoryId{};
/// Required when startMode is 'queue'.
std::optional<std::string> queueId{};
/// The REQUESTED connection count. An upper bound, not a promise: the engine lowers it to the
/// per-host cap, and to 1 when the source turns out not to be resumable. What is actually in
/// use comes back as TaskSummary.segments. null means use connection.maxSegmentsPerDownload.
std::optional<std::int64_t> segments{};
/// Requested write buffer per segment, in bytes. null means use connection.bufferBytes. Default
/// 1 MiB; range 64 KiB - 16 MiB. Silently reduced to fit connection.maxTotalBufferBytes across
/// all live segments; the effective value is reported back as TaskDetail.effectiveBufferBytes.
std::optional<std::int64_t> bufferBytes{};
std::optional<StartMode> startMode{};
std::optional<std::string> description{};
std::optional<Checksum> checksum{};
};
/// One candidate found by the Site Grabber crawl. Nothing is downloaded until grabber.harvest
/// selects it.
struct GrabberFile {
std::string fileId{};
std::string url{};
std::optional<std::string> filename{};
/// From a HEAD, when the server answered one.
std::optional<std::int64_t> sizeBytes{};
std::optional<std::string> contentType{};
std::int64_t depth{};
/// The page this link was found on.
std::optional<std::string> foundOn{};
};
/// Global token-bucket speed limit. Applies across every active task, not per task.
struct Limiter {
bool enabled{};
/// Bytes per second. 0 with enabled true means 'stop everything', which the GUI must not offer.
std::int64_t globalBps{};
/// Re-tune already-running transfers instead of waiting for the next task.
std::optional<bool> applyToRunning{};
};
/// One quality rendition from an HLS or DASH manifest. The daemon parses the manifest; the
/// extension only renders this list. DRM-protected variants are reported with drm true and must
/// be shown greyed out rather than failing later.
struct MediaVariant {
std::string variantId{};
MediaVariantKind kind{};
std::optional<std::string> resolution{};
std::optional<std::int64_t> bitrateBps{};
std::optional<std::string> codec{};
std::optional<MediaVariantContainer> container{};
std::optional<double> frameRate{};
std::optional<std::string> language{};
/// bitrate x duration. Never exact — the GUI must label it as approximate.
std::optional<std::int64_t> sizeEstimate{};
/// Widevine/EME detected. Explicitly out of scope; refuse rather than fail mysteriously.
bool drm{};
};
/// When a queue may run. Times are local wall-clock in HH:MM; the daemon re-evaluates them on a
/// DST change rather than caching absolute instants.
struct Schedule {
bool enabled{};
ScheduleMode mode{};
std::optional<std::string> startTime{};
/// null means run until the queue drains.
std::optional<std::string> stopTime{};
/// 0 = Sunday. Ignored when mode is 'once'.
std::optional<std::vector<std::int64_t>> daysOfWeek{};
/// Set only when mode is 'once'.
std::optional<std::string> onceDate{};
};
/// An ordered run of tasks with its own concurrency cap and optional schedule.
struct Queue {
std::string queueId{};
std::string name{};
QueueState state{};
std::int64_t maxConcurrent{};
/// In run order. queue.reorder rewrites this.
std::optional<std::vector<std::string>> taskIds{};
std::optional<Schedule> schedule{};
/// shutdown goes through org.freedesktop.login1 and must be confirmed by the user.
std::optional<QueueOnComplete> onComplete{};
};
/// What to do with a matching download.
struct RuleAction {
std::optional<std::string> categoryId{};
std::optional<std::string> saveDir{};
std::optional<std::string> queueId{};
std::optional<std::int64_t> segments{};
std::optional<StartMode> startMode{};
/// Lets a rule veto capture for a host without touching the exclusion list.
std::optional<RuleActionCapture> capture{};
};
/// All present clauses must match. An absent clause is not a constraint.
struct RuleMatch {
std::optional<std::vector<std::string>> extensions{};
std::optional<std::vector<std::string>> mimeTypes{};
/// Glob against the effective URL's host, e.g. *.example.com
std::optional<std::string> hostPattern{};
/// Glob against the whole effective URL.
std::optional<std::string> urlPattern{};
std::optional<std::int64_t> minSizeBytes{};
std::optional<std::int64_t> maxSizeBytes{};
};
/// One row of the rules engine: match on extension, MIME, host or size, then route. First match
/// by priority wins; no rule matching means the default category.
struct Rule {
std::string ruleId{};
std::optional<std::string> name{};
bool enabled{};
/// Lower runs first.
std::int64_t priority{};
/// All present clauses must match. An absent clause is not a constraint.
RuleMatch match{};
/// What to do with a matching download.
RuleAction action{};
};
/// One byte range being fetched by one connection. This is the deepest the contract ever
/// exposes the engine: the GUI draws a bar per segment and is never told what a segment steal
/// is. RANGE CONVENTION — READ THIS BEFORE IMPLEMENTING. The range is CLOSED and INCLUSIVE on
/// both ends: [startByte, endByte]. The segment covers endByte - startByte + 1 bytes, and
/// endByte is the index of the LAST byte in the range, not one past it. This deliberately
/// matches the HTTP Range header the engine actually sends ('Range:
/// bytes=<startByte>-<endByte>' is a byte-for-byte copy of these two fields, and RFC 9110
/// ranges are inclusive), so no arithmetic happens between the wire and the socket and there is
/// nowhere for an off-by-one to hide. CORE asked for half-open [start, end); PROTO chose
/// inclusive for that reason and this note exists so nobody discovers the difference at
/// integration. A segment always covers at least one byte: endByte >= startByte always holds.
/// An empty range is not representable and is not needed — a zero-length download carries an
/// empty segmentDetail array, and a segment that has donated its remainder to a steal keeps the
/// bytes it already wrote.
struct Segment {
/// Position in TaskDetail.segmentDetail. Spelled 'index' here and in event.task.progress; there
/// is no 'i' spelling anywhere in the contract.
std::int64_t index{};
/// Absolute offset of the first byte of the range. Inclusive.
std::int64_t startByte{};
/// Absolute offset of the LAST byte of the range. Inclusive — this is not one-past-the-end.
/// Always >= startByte.
std::int64_t endByte{};
/// Bytes written for this range so far, out of endByte - startByte + 1.
std::int64_t downloadedBytes{};
std::optional<std::int64_t> speedBps{};
/// 'downloading' is spelled as in TaskState, not 'receiving'. 'pending' is a range that has
/// been planned but not yet dialled.
SegmentState state{};
/// The status this segment's request got. 206 on a healthy ranged fetch.
std::optional<std::int64_t> httpStatus{};
};
/// A sparse bag of settings. Every property is optional because settings.get returns only the
/// keys that were asked for and settings.set carries only the keys that changed. Property names
/// must match SettingKey exactly. NOTE: no password lives here — proxy and site-login
/// credentials go to the Secret Service, never to SQLite and never over the wire.
struct Settings {
std::optional<bool> general_launchOnLogin{};
std::optional<bool> general_minimizeToTray{};
std::optional<bool> general_showDropTarget{};
std::optional<bool> general_confirmOnExit{};
/// BCP 47, or 'system'.
std::optional<std::string> general_language{};
std::optional<bool> general_checkForUpdates{};
std::optional<bool> capture_enabled{};
std::optional<std::vector<std::string>> capture_monitoredExtensions{};
std::optional<std::vector<std::string>> capture_monitoredMimeTypes{};
std::optional<std::int64_t> capture_minSizeBytes{};
std::optional<std::vector<std::string>> capture_excludedHosts{};
std::optional<BypassModifier> capture_bypassModifier{};
/// Extensions that skip the File Info dialog and start immediately.
std::optional<std::vector<std::string>> capture_autoStartTypes{};
std::optional<std::string> saveTo_defaultDir{};
std::optional<std::string> saveTo_tempDir{};
/// Every write target is canonicalized and must resolve inside one of these. Read-only over the
/// WebSocket transport.
std::optional<std::vector<std::string>> saveTo_allowedRoots{};
std::optional<SettingsSaveToFileExistsPolicy> saveTo_fileExistsPolicy{};
std::optional<bool> saveTo_createSubfolderPerSite{};
std::optional<SettingsConnectionPreset> connection_preset{};
std::optional<std::int64_t> connection_maxSegmentsPerDownload{};
/// Default per-segment write buffer, in bytes, when a task does not request its own. Default 1
/// MiB (1048576); range 64 KiB - 16 MiB. This is the single biggest throughput knob and is
/// exposed in Options -> Downloads -> 'Write buffer per connection'.
std::optional<std::int64_t> connection_bufferBytes{};
std::optional<std::int64_t> connection_maxConcurrentDownloads{};
std::optional<std::int64_t> connection_timeoutSec{};
std::optional<std::int64_t> connection_maxRetries{};
std::optional<std::int64_t> connection_retryBackoffSec{};
std::optional<std::int64_t> downloads_speedLimitBps{};
std::optional<bool> downloads_speedLimitEnabled{};
std::optional<std::string> downloads_virusScanCommand{};
std::optional<std::string> downloads_postDownloadCommand{};
std::optional<SettingsDownloadsDuplicatePolicy> downloads_duplicatePolicy{};
std::optional<bool> downloads_verifyChecksums{};
std::optional<SettingsProxyMode> proxy_mode{};
std::optional<std::string> proxy_host{};
std::optional<std::int64_t> proxy_port{};
std::optional<std::string> proxy_username{};
std::optional<std::vector<std::string>> proxy_bypassHosts{};
std::optional<std::string> proxy_pacUrl{};
std::optional<bool> sounds_enabled{};
std::optional<std::string> sounds_onComplete{};
std::optional<std::string> sounds_onQueueComplete{};
std::optional<std::string> sounds_onError{};
/// Global cap on write-buffer memory across every live segment, in bytes. Default 128 MiB
/// (134217728). Every live segment's buffer is reduced to fit maxTotalBufferBytes / (live
/// segment count, capped at maxActiveSegments); the reduced value is reported per task as
/// TaskDetail.effectiveBufferBytes. Exists so a burst of large downloads with a large
/// per-segment buffer cannot exhaust memory.
std::optional<std::int64_t> connection_maxTotalBufferBytes{};
/// Global ceiling on segments actually transferring at once, across every task. Default 32.
/// This is the real bound behind '20 active downloads': the rest of each download's segments
/// queue rather than all dialling out simultaneously. DAEMON's scheduler needs this value to
/// decide what to admit; CORE enforces it.
std::optional<std::int64_t> connection_maxActiveSegments{};
};
/// Why a task is in the failed, retry_wait, or (when the daemon paused it on its own initiative
/// rather than the user) paused state. Distinct from the JSON-RPC Error, which describes a
/// failed call rather than a failed download — the two live in different code spaces on
/// purpose, and `code` here is a TaskErrorCode string, never a JSON-RPC integer. A pause the
/// user or the scheduler requested carries no error: this field only explains a paused state
/// the daemon entered unilaterally (auth_required, server_file_changed, disk_full and the
/// like), never a deliberate one.
struct TaskError {
TaskErrorCode code{};
/// Human-readable, safe to show a user. Never carries a credential, a token or a full local
/// path outside the download roots.
std::string message{};
/// Set for the codes listed in TaskErrorCode's x-carriesHttpStatus, and null otherwise.
std::optional<std::int64_t> httpStatus{};
/// Whether the scheduler will pick this task up again on its own. Carried per-occurrence rather
/// than derived from the code, because 'probe_failed' is retryable or not depending on what the
/// probe hit.
bool retryable{};
/// The underlying failure, for codes that wrap one. max_retries_exhausted sets it to whatever
/// the last attempt actually failed with, so a user learns the reason rather than just that
/// Velox gave up.
std::optional<TaskErrorCode> cause{};
/// How many attempts have been made so far.
std::optional<std::int64_t> attempt{};
std::optional<std::string> nextRetryAt{};
};
/// One row of the main download list. Everything the GUI table needs, and nothing more.
/// TaskDetail is the same shape plus the fields only the progress dialog and File Info dialog
/// need.
struct TaskSummary {
std::string taskId{};
std::string filename{};
/// Absolute, canonicalized, inside an allowed root.
std::string saveDir{};
/// The URL as the user or the extension supplied it.
std::string url{};
/// After redirects. null until the first probe succeeds.
std::optional<std::string> effectiveUrl{};
/// null when the server did not report a length.
std::optional<std::int64_t> sizeBytes{};
std::int64_t downloadedBytes{};
TaskState state{};
std::int64_t speedBps{};
/// null when the size or the speed is unknown.
std::optional<std::int64_t> etaSeconds{};
bool resumable{};
/// The EFFECTIVE connection count in use right now — not the number that was requested. It is
/// what remains after the per-host connection cap has been applied and after the demotion to 1
/// for a non-resumable source, so a task the user asked for 16 connections on legitimately
/// reports 4, or 1. The GUI displays this value and must not assume it equals what download.add
/// asked for. The requested value lives in DownloadSpec.segments and is not echoed back on this
/// type. TaskDetail.segmentDetail always has exactly this many entries.
std::int64_t segments{};
std::optional<std::string> categoryId{};
std::optional<std::string> queueId{};
/// The Q column.
std::optional<std::int64_t> queuePosition{};
std::optional<std::string> description{};
std::string createdAt{};
std::optional<std::string> lastTryAt{};
std::optional<std::string> completedAt{};
/// Set when state is failed or retry_wait, and also when state is paused and the daemon entered
/// that state on its own initiative rather than at a user's or scheduler's request. null on
/// every other state, including a deliberate pause.
std::optional<TaskError> error{};
};
/// Everything TaskSummary carries, plus what only the progress dialog and the File Info dialog
/// need. Returned by download.get; never sent in a list or an event, because it is expensive to
/// build.
struct TaskDetail {
TaskSummary summary{};
/// Exactly TaskSummary.segments entries, in index order, covering [0, sizeBytes) with no gaps
/// and no overlaps. Empty for a zero-length download, and empty before the task has been
/// segmented.
std::vector<Segment> segmentDetail{};
std::optional<Headers> headers{};
std::optional<std::string> referrer{};
std::optional<std::string> userAgent{};
std::optional<std::string> mime{};
/// The REQUESTED write buffer per segment. See effectiveBufferBytes for what is actually in
/// use.
std::optional<std::int64_t> bufferBytes{};
/// The write buffer actually in use per live segment, right now. May be well below bufferBytes:
/// the engine reduces every live segment's buffer to fit connection.maxTotalBufferBytes across
/// connection.maxActiveSegments concurrently-transferring segments, and reports the reduced
/// value here so the GUI can show '16 MiB (using 4 MiB)'. null before the task has started its
/// first segment.
std::optional<std::int64_t> effectiveBufferBytes{};
/// Absolute path of the .veloxpart file while the task is unfinished.
std::optional<std::string> partPath{};
std::optional<Checksum> checksum{};
/// null until the verifying state has run.
std::optional<bool> checksumVerified{};
std::optional<std::int64_t> averageSpeedBps{};
std::optional<std::int64_t> retryCount{};
};
/// Which rows download.list returns. This is the category tree and the All/Unfinished/Finished
/// nodes, expressed on the wire. Absent clauses are not constraints.
struct TaskFilter {
std::optional<std::vector<TaskState>> states{};
std::optional<std::string> categoryId{};
std::optional<std::string> queueId{};
/// Case-insensitive substring of filename or url.
std::optional<std::string> query{};
std::optional<std::string> addedAfter{};
std::optional<std::string> addedBefore{};
};
/// Sort order for download.list. The GUI persists the user's choice and sends it on every list
/// call; the daemon does the sorting so a 100k-row list never has to be materialized
/// client-side.
struct TaskSort {
TaskSortField field{};
TaskSortDirection direction{};
};
struct CaptureGetRulesParams {
// No fields: this method takes no parameters.
};
struct CaptureOfferParams {
std::string url{};
CaptureOfferParamsMethod method{};
std::string tabUrl{};
std::optional<Headers> headers{};
/// Cookies for the URL, so authenticated downloads work outside the browser.
std::optional<std::vector<Cookie>> cookies{};
std::optional<std::string> contentType{};
std::optional<std::int64_t> contentLength{};
std::optional<std::string> contentDisposition{};
/// The extension's best guess; the daemon may override it.
std::optional<std::string> filename{};
std::optional<std::string> userAgent{};
std::optional<std::string> referrer{};
/// moz-extension://... The daemon verifies this on the WS transport and refuses anything else.
std::optional<std::string> origin{};
/// The extension's webRequest id, echoed in logs so a capture decision can be traced back to
/// one browser request.
std::optional<std::string> requestId{};
};
struct CaptureOfferResult {
CaptureOfferResultAction action{};
/// Set when action is 'take'.
std::optional<std::string> taskId{};
/// Why the offer was declined. Set when action is 'ignore'; the extension logs it in the
/// popup's diagnostics.
std::optional<CaptureOfferResultReason> reason{};
};
struct CategoryListParams {
// No fields: this method takes no parameters.
};
struct CategoryListResult {
std::vector<Category> items{};
};
struct CategoryRemoveParams {
std::string categoryId{};
std::optional<std::string> reassignTo{};
};
struct CategoryRemoveResult {
bool removed{};
std::vector<std::string> reassignedTaskIds{};
};
struct CategoryUpsertParams {
Category category{};
};
/// The stored category, with categoryId filled in on create.
struct CategoryUpsertResult {
Category category{};
};
struct DownloadAddResult {
std::string taskId{};
TaskState state{};
/// The existing task this URL matched, when downloads.duplicatePolicy resolved to 'skip'.
/// taskId then names that existing task.
std::optional<std::string> duplicate{};
};
struct DownloadAddBatchParams {
std::vector<DownloadSpec> items{};
/// Applied to any field an item left unset. Its url is ignored.
std::optional<DownloadSpec> defaults{};
};
struct DownloadAddBatchResultFailedItem {
std::int64_t index{};
ErrorCode code{};
std::string message{};
};
struct DownloadAddBatchResult {
/// In the same order as the accepted items.
std::vector<std::string> taskIds{};
/// One entry per item that could not be added. index refers to params.items.
std::vector<DownloadAddBatchResultFailedItem> failed{};
};
struct DownloadCancelParams {
std::vector<std::string> taskIds{};
};
struct DownloadGetParams {
std::string taskId{};
};
struct DownloadListParams {
std::optional<TaskFilter> filter{};
std::optional<TaskSort> sort{};
std::optional<std::int64_t> offset{};
/// Defaults to 500. The GUI pages; the extension popup asks for far fewer.
std::optional<std::int64_t> limit{};
};
struct DownloadListResult {
/// Rows matching the filter, ignoring offset and limit.
std::int64_t total{};
std::vector<TaskSummary> items{};
};
struct DownloadPauseParams {
std::vector<std::string> taskIds{};
};
struct DownloadProbeParams {
std::string url{};
std::optional<Headers> headers{};
std::optional<std::vector<Cookie>> cookies{};
std::optional<std::string> referrer{};
std::optional<std::string> userAgent{};
};
struct DownloadProbeResult {
/// From Content-Disposition when present, else the URL path, sanitized.
std::string filename{};
std::optional<std::int64_t> sizeBytes{};
std::string mime{};
/// Accept-Ranges: bytes and a validator (ETag or Last-Modified) are both present.
bool resumable{};
std::string effectiveUrl{};
/// What the rules engine would pick. The dialog preselects it; the user may override.
std::string suggestedCategoryId{};
std::optional<std::string> suggestedSaveDir{};
std::optional<std::string> etag{};
std::optional<std::string> lastModified{};
std::optional<bool> acceptRanges{};
/// Every hop, so the user can see where a shortener actually led.
std::optional<std::vector<std::string>> redirectChain{};
/// The probe got a 401/407. The GUI should collect credentials before adding.
std::optional<bool> requiresAuth{};
};
struct DownloadProvideAuthParams {
std::string taskId{};
std::string username{};
std::string password{};
/// true persists the credential in the Secret Service, keyed by host and realm, for future
/// downloads from the same site. false or null uses it for this task's retry only. Never
/// affects SQLite or the daemon's logs either way.
std::optional<bool> save{};
};
struct DownloadProvideAuthResult {
bool ok{};
};
struct DownloadRefreshUrlParams {
std::string taskId{};
std::string url{};
std::optional<Headers> headers{};
std::optional<std::vector<Cookie>> cookies{};
};
struct DownloadRefreshUrlResult {
bool ok{};
bool resumable{};
/// true when size or validator differ from what was recorded. The GUI must ask before
/// restarting from zero — never discard bytes without consent.
bool contentChanged{};
std::optional<std::int64_t> sizeBytes{};
std::optional<std::string> effectiveUrl{};
};
struct DownloadRemoveParams {
std::vector<std::string> taskIds{};
/// Explicit and required — there is no default for deleting a user's file.
bool deleteFile{};
};
struct DownloadRemoveResultFailedItem {
std::string taskId{};
ErrorCode code{};
std::string message{};
};
struct DownloadRemoveResult {
std::vector<std::string> removed{};
std::vector<DownloadRemoveResultFailedItem> failed{};
};
struct DownloadResumeParams {
std::vector<std::string> taskIds{};
};
struct DownloadStartParams {
std::vector<std::string> taskIds{};
};
/// Only the present fields change. An explicit null clears a nullable field.
struct DownloadUpdateParamsPatch {
std::optional<std::string> filename{};
std::optional<std::string> saveDir{};
std::optional<std::string> categoryId{};
std::optional<std::string> queueId{};
std::optional<std::string> description{};
/// The REQUESTED connection count, subject to the same per-host cap and non-resumable demotion
/// as DownloadSpec.segments. Takes effect on the next start; a running task is not re-segmented
/// underneath the user.
std::optional<std::int64_t> segments{};
/// The REQUESTED write buffer per segment. Subject to the same maxTotalBufferBytes reduction as
/// DownloadSpec.bufferBytes; the effective value comes back on the next download.get.
std::optional<std::int64_t> bufferBytes{};
std::optional<Checksum> checksum{};
};
struct DownloadUpdateParams {
std::string taskId{};
/// Only the present fields change. An explicit null clears a nullable field.
DownloadUpdateParamsPatch patch{};
};
struct GrabberHarvestParams {
std::string jobId{};
/// fileIds from grabber.status.
std::vector<std::string> select{};
std::optional<DownloadSpec> defaults{};
};
struct GrabberHarvestResultFailedItem {
std::string fileId{};
ErrorCode code{};
std::string message{};
};
struct GrabberHarvestResult {
std::vector<std::string> taskIds{};
std::vector<GrabberHarvestResultFailedItem> failed{};
};
struct GrabberStartParams {
std::string startUrl{};
std::int64_t depth{};
std::optional<std::vector<std::string>> includePatterns{};
std::optional<std::vector<std::string>> excludePatterns{};
/// Extensions, without the dot. null means every type.
std::optional<std::vector<std::string>> fileTypes{};
std::optional<bool> sameHostOnly{};
std::optional<std::int64_t> maxFiles{};
std::optional<Headers> headers{};
std::optional<std::vector<Cookie>> cookies{};
};
struct GrabberStartResult {
std::string jobId{};
};
struct GrabberStatusParams {
std::string jobId{};
};
struct GrabberStatusResult {
std::string jobId{};
GrabberStatusResultState state{};
std::int64_t crawled{};
std::int64_t found{};
std::vector<GrabberFile> files{};
std::optional<std::string> error{};
};
struct LimiterGetParams {
// No fields: this method takes no parameters.
};
/// spec carries the same destination and queueing fields as download.add; its url is ignored
/// because the manifest and variant determine the source.
struct MediaAddVariantParams {
std::string manifestUrl{};
std::string variantId{};
/// For DASH and HLS renditions where audio is a separate track to be muxed in.
std::optional<std::string> audioVariantId{};
std::optional<DownloadSpec> spec{};
};
struct MediaAddVariantResult {
std::string taskId{};
TaskState state{};
std::optional<std::int64_t> estimatedBytes{};
};
struct MediaListVariantsParams {
std::string manifestUrl{};
std::optional<Headers> headers{};
std::optional<std::vector<Cookie>> cookies{};
std::optional<std::string> referrer{};
};
struct MediaListVariantsResult {
std::vector<MediaVariant> variants{};
MediaListVariantsResultManifestType manifestType{};
std::optional<double> durationSec{};
std::optional<std::string> title{};
/// The manifest as a whole is DRM-protected. Refuse with a clear message rather than
/// downloading undecryptable segments.
bool drmProtected{};
};
struct QueueListParams {
// No fields: this method takes no parameters.
};
struct QueueListResult {
std::vector<Queue> items{};
};
struct QueueReorderParams {
std::string queueId{};
std::vector<std::string> taskIds{};
};
struct QueueReorderResult {
Queue queue{};
};
struct QueueStartParams {
std::string queueId{};
};
struct QueueStartResult {
Queue queue{};
std::vector<std::string> startedTaskIds{};
};
struct QueueStopParams {
std::string queueId{};
std::optional<bool> pauseRunning{};
};
struct QueueStopResult {
Queue queue{};
std::vector<std::string> pausedTaskIds{};
};
struct QueueUpsertParams {
Queue queue{};
};
struct QueueUpsertResult {
Queue queue{};
};
struct RulesListParams {
// No fields: this method takes no parameters.
};
struct RulesListResult {
std::vector<Rule> items{};
};
struct RulesUpsertParams {
std::vector<Rule> upsert{};
std::optional<std::vector<std::string>> remove{};
};
/// The full table after the write, in priority order.
struct RulesUpsertResult {
std::vector<Rule> items{};
};
struct ScheduleGetParams {
std::optional<std::string> queueId{};
};
struct ScheduleGetResultItemsItem {
std::string queueId{};
std::optional<Schedule> schedule{};
};
struct ScheduleGetResult {
std::vector<ScheduleGetResultItemsItem> items{};
};
struct ScheduleSetParams {
std::string queueId{};
std::optional<Schedule> schedule{};
};
struct ScheduleSetResult {
std::string queueId{};
std::optional<Schedule> schedule{};
std::optional<std::string> nextRunAt{};
};
struct SessionHelloParams {
SessionHelloParamsClientType clientType{};
/// Human-readable, shown in the pairing prompt and the logs.
std::string clientName{};
std::string protocolVersion{};
/// Required on the WebSocket transport once paired. Ignored on the Unix socket, where
/// SO_PEERCRED is the authorization.
std::optional<std::string> token{};
};
struct SessionHelloResult {
std::string daemonVersion{};
std::string protocolVersion{};
/// Optional features this build has, e.g. 'media', 'grabber', 'secretservice'. A client must
/// degrade gracefully when one is absent rather than assuming it.
std::vector<std::string> capabilities{};
std::string sessionId{};
/// How the daemon sees this connection. Lets a client know up front which privileged methods
/// will be refused.
std::optional<SessionHelloResultTransport> transport{};
};
struct SessionPairParams {
std::string clientName{};
/// The moz-extension origin UUID. Must match the Origin header verified on the WS upgrade.
std::string extensionId{};
/// Set when the user typed the code into the extension's Options page instead of clicking Allow
/// in the GUI.
std::optional<std::string> code{};
};
struct SessionPairResult {
/// 256 bits, base64url. Stored by the extension in browser.storage.local and sent on every
/// later connect.
std::string token{};
/// null means the token does not expire; it is revoked from Options -> Unpair.
std::optional<std::string> expiresAt{};
};
struct SessionSubscribeParams {
std::vector<SessionSubscribeParamsEventsItem> events{};
/// Narrow task events to these ids. The extension popup uses it to avoid receiving progress for
/// downloads it is not showing. null means all tasks.
std::optional<std::vector<std::string>> taskIds{};
};
struct SessionSubscribeResult {
bool ok{};
/// Echoed back so a client can detect that it asked for an event this daemon does not emit.
std::vector<std::string> events{};
};
struct SettingsGetParams {
std::optional<std::vector<SettingKey>> keys{};
};
struct SettingsGetResult {
Settings values{};
};
struct SettingsSetParams {
Settings values{};
};
/// The stored values for the keys that were set, and the list of keys that actually changed.
struct SettingsSetResult {
Settings values{};
std::vector<SettingKey> changed{};
};
struct AuthRequiredEvent {
std::string taskId{};
std::string host{};
std::optional<std::string> realm{};
AuthRequiredEventScheme scheme{};
};
struct GrabberProgressEvent {
std::string jobId{};
std::int64_t found{};
std::int64_t crawled{};
bool done{};
std::optional<std::string> currentUrl{};
};
struct NotifyEvent {
NotifyEventLevel level{};
std::string title{};
std::string body{};
std::optional<std::string> taskId{};
std::optional<NotifyEventSound> sound{};
};
struct SettingsChangedEvent {
std::vector<SettingKey> keys{};
};
struct SpeedGlobalEvent {
std::int64_t downBps{};
std::int64_t activeCount{};
std::optional<std::int64_t> queuedCount{};
/// null when the limiter is off.
std::optional<std::int64_t> limitBps{};
};
struct TaskAddedEvent {
std::string taskId{};
TaskSummary summary{};
};
/// Only what a segment bar needs. Full segment state comes from download.get.
struct TaskProgressEventTasksItemSegmentsItem {
std::int64_t index{};
std::int64_t downloadedBytes{};
std::int64_t speedBps{};
};
struct TaskProgressEventTasksItem {
std::string taskId{};
std::int64_t downloadedBytes{};
std::int64_t speedBps{};
std::optional<std::int64_t> etaSeconds{};
std::optional<std::vector<TaskProgressEventTasksItemSegmentsItem>> segments{};
};
struct TaskProgressEvent {
std::vector<TaskProgressEventTasksItem> tasks{};
std::string at{};
};
struct TaskRemovedEvent {
std::string taskId{};
bool deletedFile{};
};
struct TaskStateEvent {
std::string taskId{};
TaskState state{};
std::optional<TaskState> previousState{};
std::optional<TaskSummary> summary{};
/// Set when the new state is failed or retry_wait, and also when it is paused and the daemon
/// entered that state on its own initiative — auth_required, server_file_changed, disk_full and
/// the like — rather than because of a user action, a schedule window closing, or an
/// admission-control decision. null on every other transition, including every
/// deliberately-requested pause. A client must not assume a paused task has no error just
/// because it usually doesn't; check this field rather than the state name alone.
std::optional<TaskError> error{};
};
// --- serialisation ---------------------------------------------------------
// ADL hooks, so `nlohmann::json j = value;` works. Outbound only: there is no
// generated from_json, because nlohmann's inbound path throws and the wire is
// never trusted. Use parse<T> below.
void to_json(nlohmann::json& j, const ErrorCode& v);
void to_json(nlohmann::json& j, const BulkTaskResultFailedItem& v);
void to_json(nlohmann::json& j, const TaskState& v);
void to_json(nlohmann::json& j, const BulkTaskResultUpdatedItem& v);
void to_json(nlohmann::json& j, const BulkTaskResult& v);
void to_json(nlohmann::json& j, const BypassModifier& v);
void to_json(nlohmann::json& j, const CaptureRules& v);
void to_json(nlohmann::json& j, const Category& v);
void to_json(nlohmann::json& j, const ChecksumAlgorithm& v);
void to_json(nlohmann::json& j, const Checksum& v);
void to_json(nlohmann::json& j, const Cookie& v);
void to_json(nlohmann::json& j, const StartMode& v);
void to_json(nlohmann::json& j, const DownloadSpec& v);
void to_json(nlohmann::json& j, const GrabberFile& v);
void to_json(nlohmann::json& j, const Limiter& v);
void to_json(nlohmann::json& j, const MediaVariantContainer& v);
void to_json(nlohmann::json& j, const MediaVariantKind& v);
void to_json(nlohmann::json& j, const MediaVariant& v);
void to_json(nlohmann::json& j, const QueueOnComplete& v);
void to_json(nlohmann::json& j, const QueueState& v);
void to_json(nlohmann::json& j, const ScheduleMode& v);
void to_json(nlohmann::json& j, const Schedule& v);
void to_json(nlohmann::json& j, const Queue& v);
void to_json(nlohmann::json& j, const RuleActionCapture& v);
void to_json(nlohmann::json& j, const RuleAction& v);
void to_json(nlohmann::json& j, const RuleMatch& v);
void to_json(nlohmann::json& j, const Rule& v);
void to_json(nlohmann::json& j, const SegmentState& v);
void to_json(nlohmann::json& j, const Segment& v);
void to_json(nlohmann::json& j, const SettingKey& v);
void to_json(nlohmann::json& j, const SettingsConnectionPreset& v);
void to_json(nlohmann::json& j, const SettingsDownloadsDuplicatePolicy& v);
void to_json(nlohmann::json& j, const SettingsProxyMode& v);
void to_json(nlohmann::json& j, const SettingsSaveToFileExistsPolicy& v);
void to_json(nlohmann::json& j, const Settings& v);
void to_json(nlohmann::json& j, const TaskErrorCode& v);
void to_json(nlohmann::json& j, const TaskError& v);
void to_json(nlohmann::json& j, const TaskSummary& v);
void to_json(nlohmann::json& j, const TaskDetail& v);
void to_json(nlohmann::json& j, const TaskFilter& v);
void to_json(nlohmann::json& j, const TaskSortDirection& v);
void to_json(nlohmann::json& j, const TaskSortField& v);
void to_json(nlohmann::json& j, const TaskSort& v);
void to_json(nlohmann::json& j, const CaptureGetRulesParams& v);
void to_json(nlohmann::json& j, const CaptureOfferParamsMethod& v);
void to_json(nlohmann::json& j, const CaptureOfferParams& v);
void to_json(nlohmann::json& j, const CaptureOfferResultAction& v);
void to_json(nlohmann::json& j, const CaptureOfferResultReason& v);
void to_json(nlohmann::json& j, const CaptureOfferResult& v);
void to_json(nlohmann::json& j, const CategoryListParams& v);
void to_json(nlohmann::json& j, const CategoryListResult& v);
void to_json(nlohmann::json& j, const CategoryRemoveParams& v);
void to_json(nlohmann::json& j, const CategoryRemoveResult& v);
void to_json(nlohmann::json& j, const CategoryUpsertParams& v);
void to_json(nlohmann::json& j, const CategoryUpsertResult& v);
void to_json(nlohmann::json& j, const DownloadAddResult& v);
void to_json(nlohmann::json& j, const DownloadAddBatchParams& v);
void to_json(nlohmann::json& j, const DownloadAddBatchResultFailedItem& v);
void to_json(nlohmann::json& j, const DownloadAddBatchResult& v);
void to_json(nlohmann::json& j, const DownloadCancelParams& v);
void to_json(nlohmann::json& j, const DownloadGetParams& v);
void to_json(nlohmann::json& j, const DownloadListParams& v);
void to_json(nlohmann::json& j, const DownloadListResult& v);
void to_json(nlohmann::json& j, const DownloadPauseParams& v);
void to_json(nlohmann::json& j, const DownloadProbeParams& v);
void to_json(nlohmann::json& j, const DownloadProbeResult& v);
void to_json(nlohmann::json& j, const DownloadProvideAuthParams& v);
void to_json(nlohmann::json& j, const DownloadProvideAuthResult& v);
void to_json(nlohmann::json& j, const DownloadRefreshUrlParams& v);
void to_json(nlohmann::json& j, const DownloadRefreshUrlResult& v);
void to_json(nlohmann::json& j, const DownloadRemoveParams& v);
void to_json(nlohmann::json& j, const DownloadRemoveResultFailedItem& v);
void to_json(nlohmann::json& j, const DownloadRemoveResult& v);
void to_json(nlohmann::json& j, const DownloadResumeParams& v);
void to_json(nlohmann::json& j, const DownloadStartParams& v);
void to_json(nlohmann::json& j, const DownloadUpdateParamsPatch& v);
void to_json(nlohmann::json& j, const DownloadUpdateParams& v);
void to_json(nlohmann::json& j, const GrabberHarvestParams& v);
void to_json(nlohmann::json& j, const GrabberHarvestResultFailedItem& v);
void to_json(nlohmann::json& j, const GrabberHarvestResult& v);
void to_json(nlohmann::json& j, const GrabberStartParams& v);
void to_json(nlohmann::json& j, const GrabberStartResult& v);
void to_json(nlohmann::json& j, const GrabberStatusParams& v);
void to_json(nlohmann::json& j, const GrabberStatusResultState& v);
void to_json(nlohmann::json& j, const GrabberStatusResult& v);
void to_json(nlohmann::json& j, const LimiterGetParams& v);
void to_json(nlohmann::json& j, const MediaAddVariantParams& v);
void to_json(nlohmann::json& j, const MediaAddVariantResult& v);
void to_json(nlohmann::json& j, const MediaListVariantsParams& v);
void to_json(nlohmann::json& j, const MediaListVariantsResultManifestType& v);
void to_json(nlohmann::json& j, const MediaListVariantsResult& v);
void to_json(nlohmann::json& j, const QueueListParams& v);
void to_json(nlohmann::json& j, const QueueListResult& v);
void to_json(nlohmann::json& j, const QueueReorderParams& v);
void to_json(nlohmann::json& j, const QueueReorderResult& v);
void to_json(nlohmann::json& j, const QueueStartParams& v);
void to_json(nlohmann::json& j, const QueueStartResult& v);
void to_json(nlohmann::json& j, const QueueStopParams& v);
void to_json(nlohmann::json& j, const QueueStopResult& v);
void to_json(nlohmann::json& j, const QueueUpsertParams& v);
void to_json(nlohmann::json& j, const QueueUpsertResult& v);
void to_json(nlohmann::json& j, const RulesListParams& v);
void to_json(nlohmann::json& j, const RulesListResult& v);
void to_json(nlohmann::json& j, const RulesUpsertParams& v);
void to_json(nlohmann::json& j, const RulesUpsertResult& v);
void to_json(nlohmann::json& j, const ScheduleGetParams& v);
void to_json(nlohmann::json& j, const ScheduleGetResultItemsItem& v);
void to_json(nlohmann::json& j, const ScheduleGetResult& v);
void to_json(nlohmann::json& j, const ScheduleSetParams& v);
void to_json(nlohmann::json& j, const ScheduleSetResult& v);
void to_json(nlohmann::json& j, const SessionHelloParamsClientType& v);
void to_json(nlohmann::json& j, const SessionHelloParams& v);
void to_json(nlohmann::json& j, const SessionHelloResultTransport& v);
void to_json(nlohmann::json& j, const SessionHelloResult& v);
void to_json(nlohmann::json& j, const SessionPairParams& v);
void to_json(nlohmann::json& j, const SessionPairResult& v);
void to_json(nlohmann::json& j, const SessionSubscribeParamsEventsItem& v);
void to_json(nlohmann::json& j, const SessionSubscribeParams& v);
void to_json(nlohmann::json& j, const SessionSubscribeResult& v);
void to_json(nlohmann::json& j, const SettingsGetParams& v);
void to_json(nlohmann::json& j, const SettingsGetResult& v);
void to_json(nlohmann::json& j, const SettingsSetParams& v);
void to_json(nlohmann::json& j, const SettingsSetResult& v);
void to_json(nlohmann::json& j, const AuthRequiredEventScheme& v);
void to_json(nlohmann::json& j, const AuthRequiredEvent& v);
void to_json(nlohmann::json& j, const GrabberProgressEvent& v);
void to_json(nlohmann::json& j, const NotifyEventLevel& v);
void to_json(nlohmann::json& j, const NotifyEventSound& v);
void to_json(nlohmann::json& j, const NotifyEvent& v);
void to_json(nlohmann::json& j, const SettingsChangedEvent& v);
void to_json(nlohmann::json& j, const SpeedGlobalEvent& v);
void to_json(nlohmann::json& j, const TaskAddedEvent& v);
void to_json(nlohmann::json& j, const TaskProgressEventTasksItemSegmentsItem& v);
void to_json(nlohmann::json& j, const TaskProgressEventTasksItem& v);
void to_json(nlohmann::json& j, const TaskProgressEvent& v);
void to_json(nlohmann::json& j, const TaskRemovedEvent& v);
void to_json(nlohmann::json& j, const TaskStateEvent& v);
// --- parsing ---------------------------------------------------------------
/// Turn an untrusted JSON value into a typed one. Specialised below for every
/// contract type; the primary template is intentionally not defined, so asking
/// for a type the contract does not have is a compile error, not a runtime one.
template <class T>
Result<T> parse(const nlohmann::json& j, std::string_view path = "");
template <> Result<ErrorCode> parse<ErrorCode>(const nlohmann::json& j, std::string_view path);
template <> Result<BulkTaskResultFailedItem> parse<BulkTaskResultFailedItem>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskState> parse<TaskState>(const nlohmann::json& j, std::string_view path);
template <> Result<BulkTaskResultUpdatedItem> parse<BulkTaskResultUpdatedItem>(const nlohmann::json& j, std::string_view path);
template <> Result<BulkTaskResult> parse<BulkTaskResult>(const nlohmann::json& j, std::string_view path);
template <> Result<BypassModifier> parse<BypassModifier>(const nlohmann::json& j, std::string_view path);
template <> Result<CaptureRules> parse<CaptureRules>(const nlohmann::json& j, std::string_view path);
template <> Result<Category> parse<Category>(const nlohmann::json& j, std::string_view path);
template <> Result<ChecksumAlgorithm> parse<ChecksumAlgorithm>(const nlohmann::json& j, std::string_view path);
template <> Result<Checksum> parse<Checksum>(const nlohmann::json& j, std::string_view path);
template <> Result<Cookie> parse<Cookie>(const nlohmann::json& j, std::string_view path);
template <> Result<Headers> parse<Headers>(const nlohmann::json& j, std::string_view path);
template <> Result<StartMode> parse<StartMode>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadSpec> parse<DownloadSpec>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberFile> parse<GrabberFile>(const nlohmann::json& j, std::string_view path);
template <> Result<Limiter> parse<Limiter>(const nlohmann::json& j, std::string_view path);
template <> Result<MediaVariantContainer> parse<MediaVariantContainer>(const nlohmann::json& j, std::string_view path);
template <> Result<MediaVariantKind> parse<MediaVariantKind>(const nlohmann::json& j, std::string_view path);
template <> Result<MediaVariant> parse<MediaVariant>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueOnComplete> parse<QueueOnComplete>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueState> parse<QueueState>(const nlohmann::json& j, std::string_view path);
template <> Result<ScheduleMode> parse<ScheduleMode>(const nlohmann::json& j, std::string_view path);
template <> Result<Schedule> parse<Schedule>(const nlohmann::json& j, std::string_view path);
template <> Result<Queue> parse<Queue>(const nlohmann::json& j, std::string_view path);
template <> Result<RuleActionCapture> parse<RuleActionCapture>(const nlohmann::json& j, std::string_view path);
template <> Result<RuleAction> parse<RuleAction>(const nlohmann::json& j, std::string_view path);
template <> Result<RuleMatch> parse<RuleMatch>(const nlohmann::json& j, std::string_view path);
template <> Result<Rule> parse<Rule>(const nlohmann::json& j, std::string_view path);
template <> Result<SegmentState> parse<SegmentState>(const nlohmann::json& j, std::string_view path);
template <> Result<Segment> parse<Segment>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingKey> parse<SettingKey>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingsConnectionPreset> parse<SettingsConnectionPreset>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingsDownloadsDuplicatePolicy> parse<SettingsDownloadsDuplicatePolicy>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingsProxyMode> parse<SettingsProxyMode>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingsSaveToFileExistsPolicy> parse<SettingsSaveToFileExistsPolicy>(const nlohmann::json& j, std::string_view path);
template <> Result<Settings> parse<Settings>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskErrorCode> parse<TaskErrorCode>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskError> parse<TaskError>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskSummary> parse<TaskSummary>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskDetail> parse<TaskDetail>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskFilter> parse<TaskFilter>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskSortDirection> parse<TaskSortDirection>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskSortField> parse<TaskSortField>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskSort> parse<TaskSort>(const nlohmann::json& j, std::string_view path);
template <> Result<CaptureGetRulesParams> parse<CaptureGetRulesParams>(const nlohmann::json& j, std::string_view path);
template <> Result<CaptureOfferParamsMethod> parse<CaptureOfferParamsMethod>(const nlohmann::json& j, std::string_view path);
template <> Result<CaptureOfferParams> parse<CaptureOfferParams>(const nlohmann::json& j, std::string_view path);
template <> Result<CaptureOfferResultAction> parse<CaptureOfferResultAction>(const nlohmann::json& j, std::string_view path);
template <> Result<CaptureOfferResultReason> parse<CaptureOfferResultReason>(const nlohmann::json& j, std::string_view path);
template <> Result<CaptureOfferResult> parse<CaptureOfferResult>(const nlohmann::json& j, std::string_view path);
template <> Result<CategoryListParams> parse<CategoryListParams>(const nlohmann::json& j, std::string_view path);
template <> Result<CategoryListResult> parse<CategoryListResult>(const nlohmann::json& j, std::string_view path);
template <> Result<CategoryRemoveParams> parse<CategoryRemoveParams>(const nlohmann::json& j, std::string_view path);
template <> Result<CategoryRemoveResult> parse<CategoryRemoveResult>(const nlohmann::json& j, std::string_view path);
template <> Result<CategoryUpsertParams> parse<CategoryUpsertParams>(const nlohmann::json& j, std::string_view path);
template <> Result<CategoryUpsertResult> parse<CategoryUpsertResult>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadAddResult> parse<DownloadAddResult>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadAddBatchParams> parse<DownloadAddBatchParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadAddBatchResultFailedItem> parse<DownloadAddBatchResultFailedItem>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadAddBatchResult> parse<DownloadAddBatchResult>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadCancelParams> parse<DownloadCancelParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadGetParams> parse<DownloadGetParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadListParams> parse<DownloadListParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadListResult> parse<DownloadListResult>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadPauseParams> parse<DownloadPauseParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadProbeParams> parse<DownloadProbeParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadProbeResult> parse<DownloadProbeResult>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadProvideAuthParams> parse<DownloadProvideAuthParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadProvideAuthResult> parse<DownloadProvideAuthResult>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadRefreshUrlParams> parse<DownloadRefreshUrlParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadRefreshUrlResult> parse<DownloadRefreshUrlResult>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadRemoveParams> parse<DownloadRemoveParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadRemoveResultFailedItem> parse<DownloadRemoveResultFailedItem>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadRemoveResult> parse<DownloadRemoveResult>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadResumeParams> parse<DownloadResumeParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadStartParams> parse<DownloadStartParams>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadUpdateParamsPatch> parse<DownloadUpdateParamsPatch>(const nlohmann::json& j, std::string_view path);
template <> Result<DownloadUpdateParams> parse<DownloadUpdateParams>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberHarvestParams> parse<GrabberHarvestParams>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberHarvestResultFailedItem> parse<GrabberHarvestResultFailedItem>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberHarvestResult> parse<GrabberHarvestResult>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberStartParams> parse<GrabberStartParams>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberStartResult> parse<GrabberStartResult>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberStatusParams> parse<GrabberStatusParams>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberStatusResultState> parse<GrabberStatusResultState>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberStatusResult> parse<GrabberStatusResult>(const nlohmann::json& j, std::string_view path);
template <> Result<LimiterGetParams> parse<LimiterGetParams>(const nlohmann::json& j, std::string_view path);
template <> Result<MediaAddVariantParams> parse<MediaAddVariantParams>(const nlohmann::json& j, std::string_view path);
template <> Result<MediaAddVariantResult> parse<MediaAddVariantResult>(const nlohmann::json& j, std::string_view path);
template <> Result<MediaListVariantsParams> parse<MediaListVariantsParams>(const nlohmann::json& j, std::string_view path);
template <> Result<MediaListVariantsResultManifestType> parse<MediaListVariantsResultManifestType>(const nlohmann::json& j, std::string_view path);
template <> Result<MediaListVariantsResult> parse<MediaListVariantsResult>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueListParams> parse<QueueListParams>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueListResult> parse<QueueListResult>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueReorderParams> parse<QueueReorderParams>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueReorderResult> parse<QueueReorderResult>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueStartParams> parse<QueueStartParams>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueStartResult> parse<QueueStartResult>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueStopParams> parse<QueueStopParams>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueStopResult> parse<QueueStopResult>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueUpsertParams> parse<QueueUpsertParams>(const nlohmann::json& j, std::string_view path);
template <> Result<QueueUpsertResult> parse<QueueUpsertResult>(const nlohmann::json& j, std::string_view path);
template <> Result<RulesListParams> parse<RulesListParams>(const nlohmann::json& j, std::string_view path);
template <> Result<RulesListResult> parse<RulesListResult>(const nlohmann::json& j, std::string_view path);
template <> Result<RulesUpsertParams> parse<RulesUpsertParams>(const nlohmann::json& j, std::string_view path);
template <> Result<RulesUpsertResult> parse<RulesUpsertResult>(const nlohmann::json& j, std::string_view path);
template <> Result<ScheduleGetParams> parse<ScheduleGetParams>(const nlohmann::json& j, std::string_view path);
template <> Result<ScheduleGetResultItemsItem> parse<ScheduleGetResultItemsItem>(const nlohmann::json& j, std::string_view path);
template <> Result<ScheduleGetResult> parse<ScheduleGetResult>(const nlohmann::json& j, std::string_view path);
template <> Result<ScheduleSetParams> parse<ScheduleSetParams>(const nlohmann::json& j, std::string_view path);
template <> Result<ScheduleSetResult> parse<ScheduleSetResult>(const nlohmann::json& j, std::string_view path);
template <> Result<SessionHelloParamsClientType> parse<SessionHelloParamsClientType>(const nlohmann::json& j, std::string_view path);
template <> Result<SessionHelloParams> parse<SessionHelloParams>(const nlohmann::json& j, std::string_view path);
template <> Result<SessionHelloResultTransport> parse<SessionHelloResultTransport>(const nlohmann::json& j, std::string_view path);
template <> Result<SessionHelloResult> parse<SessionHelloResult>(const nlohmann::json& j, std::string_view path);
template <> Result<SessionPairParams> parse<SessionPairParams>(const nlohmann::json& j, std::string_view path);
template <> Result<SessionPairResult> parse<SessionPairResult>(const nlohmann::json& j, std::string_view path);
template <> Result<SessionSubscribeParamsEventsItem> parse<SessionSubscribeParamsEventsItem>(const nlohmann::json& j, std::string_view path);
template <> Result<SessionSubscribeParams> parse<SessionSubscribeParams>(const nlohmann::json& j, std::string_view path);
template <> Result<SessionSubscribeResult> parse<SessionSubscribeResult>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingsGetParams> parse<SettingsGetParams>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingsGetResult> parse<SettingsGetResult>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingsSetParams> parse<SettingsSetParams>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingsSetResult> parse<SettingsSetResult>(const nlohmann::json& j, std::string_view path);
template <> Result<AuthRequiredEventScheme> parse<AuthRequiredEventScheme>(const nlohmann::json& j, std::string_view path);
template <> Result<AuthRequiredEvent> parse<AuthRequiredEvent>(const nlohmann::json& j, std::string_view path);
template <> Result<GrabberProgressEvent> parse<GrabberProgressEvent>(const nlohmann::json& j, std::string_view path);
template <> Result<NotifyEventLevel> parse<NotifyEventLevel>(const nlohmann::json& j, std::string_view path);
template <> Result<NotifyEventSound> parse<NotifyEventSound>(const nlohmann::json& j, std::string_view path);
template <> Result<NotifyEvent> parse<NotifyEvent>(const nlohmann::json& j, std::string_view path);
template <> Result<SettingsChangedEvent> parse<SettingsChangedEvent>(const nlohmann::json& j, std::string_view path);
template <> Result<SpeedGlobalEvent> parse<SpeedGlobalEvent>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskAddedEvent> parse<TaskAddedEvent>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskProgressEventTasksItemSegmentsItem> parse<TaskProgressEventTasksItemSegmentsItem>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskProgressEventTasksItem> parse<TaskProgressEventTasksItem>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskProgressEvent> parse<TaskProgressEvent>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskRemovedEvent> parse<TaskRemovedEvent>(const nlohmann::json& j, std::string_view path);
template <> Result<TaskStateEvent> parse<TaskStateEvent>(const nlohmann::json& j, std::string_view path);
// --- method surface --------------------------------------------------------
/// Every method in the contract. Generated, so a daemon cannot answer a method
/// the contract does not define, and cannot silently fail to answer one it does.
enum class Method {
CaptureGetRules, // capture.getRules
CaptureOffer, // capture.offer
CategoryList, // category.list
CategoryRemove, // category.remove
CategoryUpsert, // category.upsert
DownloadAdd, // download.add
DownloadAddBatch, // download.addBatch
DownloadCancel, // download.cancel
DownloadGet, // download.get
DownloadList, // download.list
DownloadPause, // download.pause
DownloadProbe, // download.probe
DownloadProvideAuth, // download.provideAuth
DownloadRefreshUrl, // download.refreshUrl
DownloadRemove, // download.remove
DownloadResume, // download.resume
DownloadStart, // download.start
DownloadUpdate, // download.update
GrabberHarvest, // grabber.harvest
GrabberStart, // grabber.start
GrabberStatus, // grabber.status
LimiterGet, // limiter.get
LimiterSet, // limiter.set
MediaAddVariant, // media.addVariant
MediaListVariants, // media.listVariants
QueueList, // queue.list
QueueReorder, // queue.reorder
QueueStart, // queue.start
QueueStop, // queue.stop
QueueUpsert, // queue.upsert
RulesList, // rules.list
RulesUpsert, // rules.upsert
ScheduleGet, // schedule.get
ScheduleSet, // schedule.set
SessionHello, // session.hello
SessionPair, // session.pair
SessionSubscribe, // session.subscribe
SettingsGet, // settings.get
SettingsSet, // settings.set
};
inline constexpr std::size_t kMethodCount = 39;
std::string_view to_string(Method m) noexcept;
std::optional<Method> method_from_string(std::string_view s) noexcept;
/// True for methods refused over the WebSocket transport with -32003. The
/// extension is not allowed to reconfigure the daemon or destroy user data.
bool is_privileged(Method m) noexcept;
bool is_allowed_on(Method m, Transport t) noexcept;
/// The contract's answer deadline. capture.offer's 750 ms is the one that
/// matters: past it the extension has already let Firefox take the download.
std::int32_t deadline_ms(Method m) noexcept;
/// Server-to-client notifications.
enum class Event {
AuthRequired, // event.auth.required
GrabberProgress, // event.grabber.progress
Notify, // event.notify
SettingsChanged, // event.settings.changed
SpeedGlobal, // event.speed.global
TaskAdded, // event.task.added
TaskProgress, // event.task.progress
TaskRemoved, // event.task.removed
TaskState, // event.task.state
};
std::string_view to_string(Event e) noexcept;
std::optional<Event> event_from_string(std::string_view s) noexcept;
// --- dispatch --------------------------------------------------------------
/// Build a JSON-RPC error response. `id` may be null for a request that could
/// not be parsed far enough to have one.
nlohmann::json make_error(const nlohmann::json& id, ErrorCode code, std::string_view message,
nlohmann::json data = nullptr);
nlohmann::json make_result(const nlohmann::json& id, nlohmann::json result);
nlohmann::json make_notification(Event e, nlohmann::json params);
/// A handler's own failure — as opposed to ParseError, which is the wire failing
/// to become typed params. Carries any contract error code, a message, and a
/// free-form `data` object that goes straight into the JSON-RPC error's `data`
/// field: `{"taskId": ...}` for TaskNotFound, `{"path": ...}` for InvalidPath,
/// `{"httpStatus": ...}` for ProbeFailed. `code` defaults to InternalError so a
/// handler that sets only a message still produces a valid error response.
///
/// -32001/-32002/-32003 are the server layer's to raise around dispatch(), not a
/// handler's: they are decided before or without reference to method params.
struct HandlerError {
ErrorCode code{ErrorCode::InternalError};
std::string message;
// `= nullptr`, not `{nullptr}`: brace-init of nlohmann::json from nullptr
// yields the array [null], not JSON null. make_error() drops a null data.
nlohmann::json data = nullptr;
};
template <class T>
using HandlerResult = std::expected<T, HandlerError>;
/// One virtual per method. The daemon implements this; `dispatch` below does the
/// envelope handling, the transport check and the parameter parsing, so a handler
/// only ever sees a validated, typed params struct. Return `std::unexpected(
/// HandlerError{...})` to answer with a specific error code and data.
class Dispatcher {
public:
virtual ~Dispatcher() = default;
/// The daemon's capture policy, so the extension's shouldCapture decision cannot drift from the
/// daemon's. Fetched on connect and whenever event.settings.changed names a capture.* key. If
/// this call fails the extension keeps its last known rules and stays fail-open.
virtual HandlerResult<CaptureRules> on_capture_getRules(const CaptureGetRulesParams& params) = 0;
/// Firefox offers an intercepted response to the daemon. The daemon MUST reply within 750 ms;
/// the extension abandons the offer and lets Firefox download normally on timeout. This
/// deadline is the whole reason capture fails open, and it is conformance-tested: a daemon that
/// is slow, down, or erroring must never cost the user a download.
virtual HandlerResult<CaptureOfferResult> on_capture_offer(const CaptureOfferParams& params) = 0;
/// Every category with its folder and extension list. The extension calls this to populate its
/// default-category picker, which is why it is not privileged; it is read-only and exposes only
/// paths the user already configured.
virtual HandlerResult<CategoryListResult> on_category_list(const CategoryListParams& params) = 0;
/// Delete a user-created category. Built-in categories are refused with -32602. Tasks filed
/// under it are reassigned to reassignTo, or to the default category when that is null; no task
/// is ever orphaned.
virtual HandlerResult<CategoryRemoveResult> on_category_remove(const CategoryRemoveParams& params) = 0;
/// Create or replace a category. Omit categoryId to create; supply it to replace. Changing
/// saveDir does not move existing files — the GUI asks separately and issues download.update
/// per task, so a re-point is never a surprise mass file move.
virtual HandlerResult<CategoryUpsertResult> on_category_upsert(const CategoryUpsertParams& params) = 0;
/// Create one task. saveDir is canonicalized and checked against saveTo.allowedRoots before
/// anything is written; a path that escapes them is refused with -32011 and no file is created.
virtual HandlerResult<DownloadAddResult> on_download_add(const DownloadSpec& params) = 0;
/// Create many tasks in one call: the clipboard blob, the wildcard expander, and the
/// extension's 'Download all links'. Partial success is normal and is reported per item rather
/// than failing the whole batch.
virtual HandlerResult<DownloadAddBatchResult> on_download_addBatch(const DownloadAddBatchParams& params) = 0;
/// Stop the given tasks and mark them cancelled. The .veloxpart file is kept so the user can
/// still resume from the list; download.remove is what deletes bytes.
virtual HandlerResult<BulkTaskResult> on_download_cancel(const DownloadCancelParams& params) = 0;
/// Full detail for one task, including per-segment state. Backs the progress dialog. Poll it no
/// faster than the progress dialog repaints; the table must use events instead.
virtual HandlerResult<TaskDetail> on_download_get(const DownloadGetParams& params) = 0;
/// The main table. Filtering, sorting and paging all happen in the daemon so the GUI never
/// materializes 100k rows to show 40. Called once on connect; after that the table is
/// maintained from events, never re-fetched on a progress tick.
virtual HandlerResult<DownloadListResult> on_download_list(const DownloadListParams& params) = 0;
/// Suspend transfers and flush every segment's progress to the .veloxpart.meta file, so a pause
/// is indistinguishable from a crash as far as resume is concerned. Never loses bytes already
/// written.
virtual HandlerResult<BulkTaskResult> on_download_pause(const DownloadPauseParams& params) = 0;
/// Ask what is at a URL without creating a task. Populates the File Info dialog. Runs a HEAD,
/// falling back to a ranged GET when HEAD is refused, which is also how resumability is
/// established. Never blocks the RPC loop; the dialog opens immediately and fills in when this
/// lands.
virtual HandlerResult<DownloadProbeResult> on_download_probe(const DownloadProbeParams& params) = 0;
/// Answer an event.auth.required challenge. The task sits in retry_wait until this arrives; on
/// success the daemon retries with the credentials attached and the task resumes on its own —
/// this method does not itself start the transfer. Privileged and Unix-socket-only: a
/// credential-bearing method must never be reachable from the browser, which is exactly the
/// boundary event.auth.required's own description draws ('never back through this event, never
/// into a log') — this is the other half of that promise. Credentials are handed to the Secret
/// Service, never to SQLite and never logged; save only tells the daemon whether to persist
/// them there for next time, or use them for this attempt alone.
virtual HandlerResult<DownloadProvideAuthResult> on_download_provideAuth(const DownloadProvideAuthParams& params) = 0;
/// IDM's 'Refresh Download Address'. Point an existing task at a freshly-issued URL when a
/// signed link has expired, keeping every byte already on disk. The daemon re-probes and
/// compares size and validator: if they still match, the transfer resumes from where it
/// stopped; if they do not, it says so rather than silently restarting.
virtual HandlerResult<DownloadRefreshUrlResult> on_download_refreshUrl(const DownloadRefreshUrlParams& params) = 0;
/// Drop tasks from the list, optionally deleting the bytes on disk. Privileged: this is the
/// only method that destroys user data, and the extension is never allowed to reach it. The
/// daemon deletes the .veloxpart and .veloxpart.meta pair, and the finished file only when
/// deleteFile is true.
virtual HandlerResult<DownloadRemoveResult> on_download_remove(const DownloadRemoveParams& params) = 0;
/// Continue paused tasks. Resumption is revalidated with If-Range against the stored ETag or
/// Last-Modified; a 200 where 206 was expected means the file changed on the server, and the
/// task moves to failed with a clear error rather than corrupting the part file.
virtual HandlerResult<BulkTaskResult> on_download_resume(const DownloadResumeParams& params) = 0;
/// Begin or restart the given tasks. A task in 'queued' jumps its queue; a task already
/// downloading is a no-op reported as changed false.
virtual HandlerResult<BulkTaskResult> on_download_start(const DownloadStartParams& params) = 0;
/// Change a task's mutable fields. Moving saveDir or filename moves the file on disk in the
/// same operation, which is what makes dragging a row onto a category work as one RPC.
/// Privileged: it can name a destination path.
virtual HandlerResult<TaskSummary> on_download_update(const DownloadUpdateParams& params) = 0;
/// Turn selected crawl results into tasks. This is the only grabber call that creates
/// downloads, and it names exactly the files the user ticked — a crawl never starts a download
/// on its own.
virtual HandlerResult<GrabberHarvestResult> on_grabber_harvest(const GrabberHarvestParams& params) = 0;
/// Start a depth-limited crawl. Nothing is downloaded by this call: it only walks pages and
/// collects candidate links, which the wizard then shows for selection. Privileged because an
/// unbounded crawl is a resource commitment the browser must not be able to make on the user's
/// behalf.
virtual HandlerResult<GrabberStartResult> on_grabber_start(const GrabberStartParams& params) = 0;
/// Poll one crawl. Also delivered as event.grabber.progress; the poll exists so the wizard can
/// be reopened on a job it did not start and still catch up.
virtual HandlerResult<GrabberStatusResult> on_grabber_status(const GrabberStatusParams& params) = 0;
/// Current global speed limit. Privileged: changing or reading the limiter belongs to the GUI
/// and CLI; the extension shows throughput from event.speed.global instead.
virtual HandlerResult<Limiter> on_limiter_get(const LimiterGetParams& params) = 0;
/// Set the global token-bucket limit. With applyToRunning true the change re-tunes transfers
/// already in flight instead of taking effect only on the next task — the Speed Limiter
/// window's 'apply now' button.
virtual HandlerResult<Limiter> on_limiter_set(const Limiter& params) = 0;
/// Turn one enumerated variant into a task. The daemon fetches the segments in parallel and
/// muxes them with ffmpeg; the result is an ordinary task that appears in the list like any
/// other download. Refused with -32602 when the variant is DRM-protected.
virtual HandlerResult<MediaAddVariantResult> on_media_addVariant(const MediaAddVariantParams& params) = 0;
/// Parse an HLS or DASH manifest in the daemon and enumerate its renditions. The extension
/// never parses a manifest — that logic lives in one language, in one place. Variants with drm
/// true are reported so the UI can grey them out; DRM-protected streams are refused, not
/// attempted.
virtual HandlerResult<MediaListVariantsResult> on_media_listVariants(const MediaListVariantsParams& params) = 0;
/// Every queue with its run state and ordering. Not privileged: the extension's 'Add to Queue'
/// picker needs it.
virtual HandlerResult<QueueListResult> on_queue_list(const QueueListParams& params) = 0;
/// Rewrite a queue's run order. taskIds must be a permutation of the queue's current
/// membership; anything else is -32602 rather than a partial reorder, so a stale drag from an
/// out-of-date view cannot quietly reshuffle the queue.
virtual HandlerResult<QueueReorderResult> on_queue_reorder(const QueueReorderParams& params) = 0;
/// Start a queue running. The scheduler then admits up to maxConcurrent tasks from it, in
/// order, and keeps that many running until the queue drains or is stopped.
virtual HandlerResult<QueueStartResult> on_queue_start(const QueueStartParams& params) = 0;
/// Stop admitting new tasks from a queue. Tasks already running are paused when pauseRunning is
/// true, and otherwise allowed to finish — the difference between 'stop the queue' and 'stop
/// everything', which IDM conflates and users trip over.
virtual HandlerResult<QueueStopResult> on_queue_stop(const QueueStopParams& params) = 0;
/// Create or replace a queue, including its schedule and concurrency cap. Omit queueId to
/// create. taskIds in the payload is ignored — membership changes through download.update and
/// queue.reorder so that two clients editing at once cannot silently drop a task.
virtual HandlerResult<QueueUpsertResult> on_queue_upsert(const QueueUpsertParams& params) = 0;
/// The rules engine's table, in priority order. Privileged: these are the daemon's routing
/// policy. The extension gets its own narrowed view through capture.getRules instead.
virtual HandlerResult<RulesListResult> on_rules_list(const RulesListParams& params) = 0;
/// Create, replace, or delete rules in one atomic write. 'upsert' carries the rules to store
/// and 'remove' the ruleIds to drop; applying both at once means a reprioritisation never
/// leaves the table in a half-valid state.
virtual HandlerResult<RulesUpsertResult> on_rules_upsert(const RulesUpsertParams& params) = 0;
/// The schedule for one queue, or every schedule when queueId is null. Backs the Scheduler
/// window.
virtual HandlerResult<ScheduleGetResult> on_schedule_get(const ScheduleGetParams& params) = 0;
/// Set or clear a queue's schedule. A null schedule clears it and leaves the queue under manual
/// control. Times are local wall-clock and are re-evaluated on a DST change rather than being
/// resolved to absolute instants at set time.
virtual HandlerResult<ScheduleSetResult> on_schedule_set(const ScheduleSetParams& params) = 0;
/// First call on every connection, on every transport. The daemon compares protocolVersion
/// majors and refuses a mismatch with -32001 so a stale GUI or extension fails loudly on
/// connect instead of subtly at the tenth field. On the WebSocket transport a valid token is
/// required unless the client is about to call session.pair.
virtual HandlerResult<SessionHelloResult> on_session_hello(const SessionHelloParams& params) = 0;
/// WebSocket transport only. Triggers a GUI or desktop-notification prompt showing a four-digit
/// code; the user must approve before a token is issued. Failed attempts are rate-limited to
/// 5/min followed by a 60 s lockout (-32014) so a token cannot be brute-forced by another local
/// process. The daemon stores only a hash of the token.
virtual HandlerResult<SessionPairResult> on_session_pair(const SessionPairParams& params) = 0;
/// Choose which notifications this connection receives. Subscribing replaces the previous
/// selection rather than adding to it, so a client can narrow its firehose without
/// reconnecting. Nothing is delivered until this is called.
virtual HandlerResult<SessionSubscribeResult> on_session_subscribe(const SessionSubscribeParams& params) = 0;
/// Read settings. keys null means everything. Privileged: the settings bag names local
/// filesystem paths and the allowed write roots, which the extension has no business
/// enumerating — it gets capture.getRules instead.
virtual HandlerResult<SettingsGetResult> on_settings_get(const SettingsGetParams& params) = 0;
/// Write settings. Only the keys present in values change. Rejected with -32602 if a key is
/// unknown or a value fails the Settings schema, and with -32011 if a directory key names a
/// path that cannot be written. Emits event.settings.changed with exactly the keys that took
/// effect.
virtual HandlerResult<SettingsSetResult> on_settings_set(const SettingsSetParams& params) = 0;
};
/// Parse one JSON-RPC request, route it, and return the response to write back.
/// Never throws. Returns a null json for a notification that needs no reply.
nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann::json& request);
} // namespace velox::proto