Skip to content

Latest commit

 

History

History
170 lines (145 loc) · 11.5 KB

README.md

File metadata and controls

170 lines (145 loc) · 11.5 KB

Downloader — Browser Integration (extension)

A cross-browser Manifest V3 extension that hands download links and detected media to the Downloader desktop app.

Features

  • Context menu — right-click a link, image, video or audio → “Download with Downloader”. Right-click a text selection → “Download selected links with Downloader”.
  • Popup — paste a link, send the detected media for the current tab, or scan the page for downloadable links, then send one or all to the app.
  • Media capture — watches network responses and surfaces video / audio / HLS (.m3u8) streams, with a badge count on the toolbar icon. DASH (.mpd) is not listed: it can be neither size-probed nor read for a quality, so it could only ever be a row with nothing to say for itself. The app still downloads DASH — paste the .mpd link into the popup's box, or add it in the app.
  • Size, resolution and quality picker — each detected item is probed for its file size, and an HLS master playlist expands into a quality dropdown (resolution or bitrate per option) instead of one opaque .m3u8 row. Probing never blocks the popup: it renders immediately, then upgrades rows in place as results arrive.
  • One list, best copy first — everything detected on the page is listed together: an HLS master (.m3u8) leads, then the highest quality (1080p above 720p), and where a link names no quality the largest file. A row without a quality picker shows the quality it was ranked on, so the order explains itself. There is no collapsed section and nothing to expand. Ordering by file type was tried first and dropped: type cannot say which copy of a video is the good one. (This replaced a "Main media vs. Other detected" split that promoted a group only when a visibility hint happened to be fresh at the moment the popup opened — on a feed page whose player had finished autoplaying, e.g. x.com, it usually was not, so the video you were looking at ended up hidden.)
  • Previews — each row carries a thumbnail so you can tell which video a link is without reading a signed CDN URL: a frame captured from the page's own player when the browser allows it (a cross-origin video blocks that), otherwise the player's poster image, otherwise the page's og:image, otherwise a file-type placeholder. Previews are used only to draw the popup — they are never sent to the app or anywhere else.
  • Download folder — set one in Settings and every link the extension sends is saved there, so the app doesn't ask. The field is prefilled with the folder the app itself is configured to use (read from its local API), so you start from a correct absolute path; leave it empty to let the app decide as before.
  • Known-unsupported-site message — on sites that stream via MSE/DRM with no fetchable file URL (YouTube, Netflix, …), the popup explains why nothing was found instead of showing a blank list that looks broken.
  • Auto-send — every captured link is forwarded to the app's local listener. By default it is added silently and starts downloading (the app's /api/add endpoint); untick “Add silently (no dialog)” in the popup to review each link in the app's Add dialog (/add?url=…) instead. On an app version without the API the extension falls back to the dialog automatically. That choice governs every hand-off, including a download the extension intercepts from the browser: those go to /api/add with confirm: true, so the dialog opens carrying the link's cookies, referer, headers, mirrors and save folder rather than the bare URL. If you cancel that dialog, the browser's own download is left running and you keep the file. A link where you picked a specific quality is always sent silently, since the dialog would discard the pick.
  • Download interception (off by default) — take over downloads the browser starts and hand them to the app instead. Turn it on in Settings (the popup's Settings & download interception link, or your browser's extension options), where you also control what gets taken over: which file types (an allow list of archive/installer types by default), an optional minimum size, and sites to leave alone. If a download isn't being taken over, the file-type list is almost always why. The type is judged from every source that names the file — the browser's own file name, the server's Content-Disposition, the URL path and the MIME type — so a download whose path ends in something that only looks like an extension (…/XAPK/com.instagram.android) is still matched on what the server actually called it. Both addresses the browser had are handed over — the one it was actually fetching the file from leads, and the link you clicked follows as a fallback — and the app tries them in turn, so a site that serves its file from a different address than the page still downloads. The browser's own download is cancelled only after the app has accepted the hand-off, so a closed app, an unreachable app or a refused add all just leave the browser downloading as usual — interception can never cost you a file. Each takeover shows a notification and counts on the badge, so a download disappearing from the browser is never unexplained. Requires the downloads permission, which is unused while the setting is off (PRIVACY.md).
  • Signed-in session hand-off — when you send a link, the extension also passes the cookies for that one URL to the app, so a site that needs you to be logged in (e.g. a YouTube video handled by the app's video downloader) can be fetched with your live session. Cookies are read only for the URL you send, go only to your local app, are never logged, and the app deletes them right after the download. An intercepted download carries the referring page as well, because the browser would have sent it — without it a site that checks the referer would refuse a file it was about to serve. See PRIVACY.md.

Running another intercepting download manager alongside this one is not supported. If two extensions both take over downloads, whichever reacts first wins and the outcome is not something either can arbitrate. Turn interception off in one of them.

Direct media capture doesn't work on DRM/encrypted streaming sites (Netflix, and YouTube's in-page player) — they don't expose a fetchable media URL. A YouTube video page link can still be sent to the app, which downloads it via its video-site plugin (using the session cookies above when the video requires sign-in).

Requirements

  1. The Downloader desktop app must be running.
  2. Settings → Browser extension & local API must be on in the app (it opens the local listener on port 15151, falling back to 15152–15155 if that port is taken by another program — the extension finds the right one automatically). It is enabled by default; the effective address is shown in the app's Settings. If the app answers on none of those ports, the popup says so and names the ports it probed, rather than showing an unexplained red dot.

Getting it installed

The easiest route for a user is inside the desktop app: Settings → Browser extension & local API → Get the files. The app detects installed browsers, downloads the build for each from the matching GitHub Release, verifies its sha256 before unpacking, puts it in a stable folder under the app's own data directory, and shows the per-browser steps. It never writes to a browser profile, never touches browser policy, and never asks for elevation — see the install-browser-extension OpenSpec change for why installing into a browser programmatically is off the table.

The manual route below is the same thing done by hand, and is what you want when developing.

Load it for testing (unpacked)

Chrome / Edge

  1. Go to chrome://extensions (or edge://extensions).
  2. Turn on Developer mode.
  3. Load unpacked → select this browser-extension folder.

Firefox

  1. Copy manifest.firefox.json over manifest.json (Firefox reads manifest.json):
    cp manifest.firefox.json manifest.json
    (Keep a backup of the Chrome manifest.json — they differ only in the background shape and the Firefox browser_specific_settings block.)
  2. Go to about:debugging#/runtime/this-firefox → Load Temporary Add-on → pick manifest.json.

How it talks to the app

The extension only needs the app's loopback endpoints:

GET http://127.0.0.1:<port>/api/add?url=<url-encoded link>   # silent add (default)
GET http://127.0.0.1:<port>/add?url=<url-encoded link>       # open the Add dialog instead
GET http://127.0.0.1:<port>/ping                              # reachability check

POST http://127.0.0.1:<port>/api/add                          # hand-off with cookies/referer/headers
                                                              #   + "confirm": true to ask the user
GET http://127.0.0.1:<port>/api/add-status?ticket=<ticket>    # follow a confirmation to added/cancelled

<port> is normally 15151; if another program holds it, the app falls back within the declared range 15151–15155 and the extension probes /ping across that range (last-known-good port first) to rediscover it. These are served by Services/LocalApiService.cs in the desktop app (see docs/local-api.md in the main repo for the full API). No other ports, servers or accounts are involved.

Every one of those requests also carries which extension is asking — extv (this extension's version) and extb (a coarse browser label: chrome, edge or firefox), plus an X-Downloader-Extension: <version>; <browser> header; the POST form of /api/add carries the same two as JSON fields. That is what lets the app tell you your extension is out of date instead of leaving you on a build that quietly stopped working. It needs no extra permission and adds no extra request, the app keeps it in memory only (never in its config file, never in its log), and a request without it is served exactly as before — so an older extension, the CLI and any other local tool are unaffected.

Files

File Role
manifest.json Chrome/Edge MV3 manifest (service-worker background)
manifest.firefox.json Firefox MV3 manifest (scripts background + gecko id)
common.js Shared helpers: media detection, sendToApp(), size/HLS probing, grouping
background.js Context menus, response sniffing, badge, message handler, probing coordinator, download interception
popup.html / popup.css / popup.js Toolbar popup UI (one type-ordered list, previews, quality picker)
options.html / options.css / options.js Settings page: download folder + download-interception rules
icons/ Toolbar/store icons (16/48/128)

Run the unit tests (pure helpers in common.js) with node --test src/browser-extension/common.test.js.

Publishing

  • Firefox (AMO) — automatic: .github/workflows/extension.yml submits the version for review on every push to develop/main that touches this folder. See PUBLISHING.md for the failure history and how the packaging bug that broke it for two months was fixed.
  • Chrome Web Store / Edge Add-ons — still a manual dashboard upload (needs the author's developer account); zip this folder (scripts/build-extension.sh) and upload it.

Bump version in both manifests for each release — a test enforces they agree.