Same code, same transport, same experience as the Chrome extension — with one platform limit that cannot be worked around (see below).
The extension code in extensions/chrome is browser-neutral: it binds
whichever namespace exists (browser on Safari, chrome on Chromium),
handles both native-messaging dialects (Safari returns a promise, Chrome
takes a callback), and feature-detects every API it uses. What differs per
browser is the manifest and the download-capture path, and Safari has no
downloads API to capture with — so scripts/sync-extension-resources.sh safari
copies the shared files next to Safari's own manifest into Resources/,
with core.js (the browser-neutral part of the extension) taking the
background.js name the manifest points at.
Edit extensions/chrome/*, never extensions/safari/Resources/* — the
latter is regenerated on every sync.
The transport is the reason this works so cleanly: since the extension talks
to Hydra over a WebSocket on a fixed loopback port (6799), Safari needs no
special IPC at all. The app accepts safari-web-extension://… origins
exactly as it accepts chrome-extension://….
| Feature | Chrome | Safari |
|---|---|---|
| Right-click → Download with Hydra | ✅ | ✅ |
| Selection pill over highlighted links | ✅ | ✅ |
| Download all links | ✅ | ✅ |
| Popup, capture toggle, status badge | ✅ | ✅ |
| Media sniffing | ✅ | webRequest |
| Automatic download capture | ✅ | ❌ impossible |
Safari has no downloads API — Apple does not expose download events to
extensions at all, so there is nothing to intercept. Every other browser
integration on macOS has the same limit; it is not a gap in this code. Use
the right-click menu or the selection pill instead.
Requires full Xcode (Command Line Tools alone cannot build app extensions):
sudo xcode-select -s /Applications/Xcode.app/Contents/DeveloperThen:
scripts/build-safari-extension.shThe script syncs the resources, generates the wrapper app, installs
SafariWebExtensionHandler.swift, disables
the app sandbox (the handler must read ~/.config/hydra/ipc.json and launch
the app), allows cleartext loopback via ATS, and builds.
Afterwards: open the built app once, then Safari → Settings → Extensions → enable Hydra. For an unsigned dev build also tick Develop → Allow Unsigned Extensions (Safari clears this on restart).
SafariWebExtensionHandler.swift is the
fallback path, mirroring hydra-host: it forwards a request to the app's
line-protocol socket using the token from ipc.json, and launches
"Hydra Download Manager" when nothing answers. Day to day the WebSocket
carries the traffic; the handler exists so the first click can start the app.