8 Commits
Author SHA1 Message Date
andwati 3fa70b1df3 Make the Chrome Web Store ID the single canonical extension ID
The extension is now published (ID: mkiffclapimhbjjlgkndlillglopbcic).
Drop the pinned "key" field from manifest.json (the Store rejects it
anyway) and use the Store-assigned ID everywhere instead of juggling
two IDs: native-host/com.kget_capture.host.json's allowed_origins,
keys/extension-id.txt, and docs. This also removes the need for a
separate key-stripped release zip, since there's no key left to strip.
2026-08-12 16:03:13 +03:00
andwati 8b5fd86cce Add privacy policy for Chrome Web Store listing 2026-08-12 15:50:14 +03:00
andwati 59b18e69a8 Bump version to 1.0.6 2026-08-12 15:21:35 +03:00
andwati 816ae5797a Publish a key-stripped zip for Chrome Web Store uploads
The Web Store dashboard rejects any manifest containing a "key" field
("key field is not allowed in manifest"), but extension/manifest.json
pins one so unpacked/dev installs get a stable ID. Add a third release
asset with key stripped from a copy, so future releases have an
upload-ready zip without touching the pinned manifest.
2026-08-12 15:21:13 +03:00
andwati 79b72494db Bump version to 1.0.5 2026-08-12 15:10:33 +03:00
andwati 08744915c4 Add a curl/wget one-line installer and popup setup detection
Add install-remote.sh, which fetches the latest release and runs its
install.sh so setup no longer needs a git clone. The popup now probes
the native host with a "ping" action on open and, if it's not
registered yet, shows the one-liner (with copy-to-clipboard) instead
of the auto-capture toggle -- so a Web Store install can be completed
end-to-end by clicking the extension icon and pasting one command.
2026-08-12 15:07:55 +03:00
andwati bd24586113 Publish separate extension-only zip alongside source archive
Chrome requires manifest.json at the zip root for Web Store submission
and manual unpacked loading. The whole-repo git archive added for the
native-host installer buried it under extension/, so publish both: a
manifest-at-root extension zip and the full-repo source zip.
2026-08-12 14:54:58 +03:00
andwati 6ef2133760 Refactor README for distro install docs, fix release archive
Trim intro fluff, add per-distro dependency install commands, and make
the Chrome Web Store the primary extension install path with unpacked
install demoted to a dev-only section. Also switch the release workflow
to archive the whole repo instead of just extension/, since install.sh
and native-host/ are now what the README points users at downloading.
2026-08-12 14:50:12 +03:00
13 changed files with 390 additions and 127 deletions
+17 -2
View File
@@ -16,12 +16,27 @@ jobs:
uses: actions/checkout@v4
- name: Package extension
# Chrome requires manifest.json at the zip root, both for Web Store
# upload and for manually loading unpacked -- so this one archives
# only extension/'s contents, not the whole repo. No "key" field to
# worry about: the Web Store's own assigned ID is now the single
# canonical one (see keys/README.md), so this same zip works for
# both a Store upload and a manual unpacked install.
run: |
cd extension
zip -r "../kget-capture-${GITHUB_REF_NAME}.zip" .
zip -r "../kget-capture-extension-${GITHUB_REF_NAME}.zip" .
- name: Package source
# Separately, the native-host installer bundle (install.sh,
# native-host/, keys/) needs the whole repo. git archive only
# includes tracked files, so the gitignored private key (never
# committed) can't end up in it.
run: git archive --format=zip -o "kget-capture-${GITHUB_REF_NAME}.zip" HEAD
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: kget-capture-${{ github.ref_name }}.zip
files: |
kget-capture-${{ github.ref_name }}.zip
kget-capture-extension-${{ github.ref_name }}.zip
generate_release_notes: true
+33
View File
@@ -0,0 +1,33 @@
# Privacy Policy
KGet Capture does not collect, store, transmit, or sell any user data.
## What the extension does
When you right-click a link and choose "Download with KGet", or when
auto-capture is on and Chrome starts a download, the extension sends that
download's URL and filename to a native messaging host, a small helper
program installed locally on your own computer (see
[Install](README.md#install)). That helper hands the URL to KGet, the KDE
download manager, over D-Bus, also entirely on your own machine.
The only other data the extension stores is your auto-capture on/off
preference, saved locally via the `chrome.storage.local` API. It never
leaves your browser.
## What never happens
- No data is sent to any server operated by the developer or anyone else.
- No analytics, tracking, or telemetry of any kind.
- No data is sold, shared, or transferred to third parties.
- No account, sign-in, or personal information is collected.
## Source code
KGet Capture is open source. You can review exactly what it does at
[github.com/andwati/kget-capture](https://github.com/andwati/kget-capture).
## Contact
Questions about this policy can be raised via
[GitHub Issues](https://github.com/andwati/kget-capture/issues).
+124 -73
View File
@@ -1,56 +1,101 @@
# KGet Capture
A Chrome (and other Chromium-browser) extension that sends downloads to
[KGet](https://apps.kde.org/kget/), KDE's download manager, instead of (or in
addition to) the browser's built-in downloader.
A Chromium browser extension that sends downloads to
[KGet](https://apps.kde.org/kget/), KDE's download manager, instead of the
browser's built-in downloader.
- Right-click any link, image, video, or audio element and choose
**"Download with KGet"** to send it straight to KGet — it starts
downloading immediately into your Downloads folder, no dialog.
- A toolbar toggle enables **auto-capture**: while on, every download Chrome
starts is cancelled and handed off to KGet the same way, automatically,
with the correct filename (resolved by Chrome itself, so it's right even
for URLs whose path is just an opaque ID). Defaults to ON.
- A "Download complete" desktop notification appears once a capture actually
finishes downloading. This is driven by the extension itself (not KGet's
own per-transfer notification, which KGet silently skips for downloads
that finish very quickly — see Troubleshooting), so it fires reliably
regardless of file size or speed.
- Right-click a link/image/video/audio and choose **"Download with KGet"**.
- Or toggle **auto-capture** from the toolbar so every download is handed off
automatically, with the correct filename.
- A desktop notification fires when a capture finishes downloading.
## Requirements
- Linux with KGet installed (and running, or set to autostart)
- One of: Google Chrome, Chromium, Brave, Vivaldi, Microsoft Edge
- `python3` and `dbus-send` on `PATH` (standard on virtually all Linux/KDE
systems already)
- `notify-send` (ships with most Linux desktops via libnotify) — optional,
only used for the download-complete notification; captures still work
without it, just silently
- `kwriteconfig6` (ships with KDE Plasma) — optional, only used to disable
KGet's own unreliable per-transfer notification so it doesn't double up
with the extension's; install still works without it
- Linux with KGet installed and running (or set to autostart)
- A Chromium-based browser: Chrome, Chromium, Brave, Vivaldi, or Edge
- `python3`, `dbus-send`
- `curl` or `wget`, for the one-line remote installer below (skip if you'd
rather download and run `install.sh` yourself)
- `notify-send`, optional, for the completion notification
- `kwriteconfig6` (KDE Frameworks), optional, silences KGet's own duplicate
notification
Install everything on your distro:
**Fedora**
```bash
sudo dnf install kget python3 dbus-tools libnotify kf6-kconfig
```
**Arch / Manjaro**
```bash
sudo pacman -S kget python dbus libnotify kconfig
```
**Debian / Ubuntu / KDE neon**
```bash
sudo apt install kget python3 dbus-bin libnotify-bin kf6-kconfig-bin
```
**openSUSE**
```bash
sudo zypper install kget python3 dbus-1 libnotify-tools kconfig-tools
```
(`notify-send` and `kwriteconfig6` package names shift around between distro
versions. If a command above fails, drop that one package and search your
package manager for the binary name instead. Everything else still works
without them.)
## Install
1. Install the extension from the Chrome Web Store: **`<CHROME_WEB_STORE_URL>`**
*(placeholder, fill in once the listing is live)*.
2. Click the extension's toolbar icon. The popup detects that the native
messaging host isn't set up yet and shows a one-line command; run it in a
terminal, then click "I ran it, check again" in the popup:
```bash
curl -fsSL https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash
```
(or with `wget`: `wget -qO- https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash`)
This downloads the latest release and runs its `install.sh`, which
registers the native host for every supported browser it finds installed
and prints any remaining setup warnings. No git clone required. If you'd
rather run it yourself: download and extract `kget-capture-vX.Y.Z.zip`
(the source archive, not `kget-capture-extension-vX.Y.Z.zip`) from the
[Releases page](https://github.com/andwati/kget-capture/releases), then
from inside that folder run `./install.sh` directly.
3. Open your browser's download settings (`chrome://settings/downloads`,
`brave://settings/downloads`, etc.) and turn **off** "Ask where to save
each file before downloading". Chrome shows this dialog itself, before the
extension gets a chance to intercept the download, so this one-time change
is required (no extension API can do it for you).
### Building from source
Contributing, or want to run an unreleased version? Load the extension
unpacked instead of step 1 above:
```bash
git clone <this-repo-url> kget-capture
git clone https://github.com/andwati/kget-capture.git
cd kget-capture
./install.sh
```
`install.sh` registers a native messaging host for every supported browser it
finds installed, then prints the extension's fixed ID and next steps:
(Or, for an already-tagged version without cloning: download and extract
`kget-capture-extension-vX.Y.Z.zip` from the
[Releases page](https://github.com/andwati/kget-capture/releases) instead.
It's already just `extension/`'s contents, unpacked at the zip root the way
Chrome expects. You'll still need `kget-capture-vX.Y.Z.zip` for `install.sh`,
per step 2 above.)
1. Open `chrome://extensions` (or your browser's equivalent).
2. Enable **Developer mode** (top-right toggle).
3. Click **Load unpacked** and select this repo's `extension/` directory.
4. Confirm the loaded extension's ID matches the one `install.sh` printed.
5. Open `chrome://settings/downloads` (or your browser's equivalent, e.g.
`brave://settings/downloads`, `edge://settings/downloads`) and turn **off**
"Ask where to save each file before downloading". Chrome shows this dialog
itself, before the extension ever gets a chance to intercept the download —
no extension API can read or suppress it, so this one-time setting change
is required for captures to work without an extra prompt.
Then in `chrome://extensions`: enable **Developer mode**, click **Load
unpacked**, and select the `extension/` directory (from your clone, or the
extracted extension zip). Chrome assigns this unpacked copy its own ID,
different from the published extension's, so add it to `allowed_origins` in
`~/.config/<browser>/NativeMessagingHosts/com.kget_capture.host.json` (see
[keys/README.md](keys/README.md)) for the native host to accept it.
## Usage
@@ -64,47 +109,50 @@ finds installed, then prints the extension's fixed ID and next steps:
./uninstall.sh
```
This removes the native messaging host registrations and the installed host
script. You'll still need to remove the unpacked extension yourself via
`chrome://extensions` — that step can't be scripted.
Removes the native messaging host registrations and installed host script.
Remove the extension itself via `chrome://extensions` (or the Chrome Web
Store listing); that step can't be scripted.
## Releases
Pushing a tag matching `v*.*.*` (e.g. `v1.0.0`) triggers a GitHub Actions
workflow ([.github/workflows/release.yml](.github/workflows/release.yml))
that zips `extension/` and publishes it as a GitHub Release with
auto-generated release notes. Bump `"version"` in `extension/manifest.json`
before tagging so the packaged extension's version matches.
Prebuilt releases are published on the
[Releases page](https://github.com/andwati/kget-capture/releases); each
`vX.Y.Z` tag push builds and publishes one automatically, as two zips:
```bash
git tag v1.0.0
git push origin v1.0.0
```
- `kget-capture-vX.Y.Z.zip`: full source, for `install.sh`/`install-remote.sh`.
- `kget-capture-extension-vX.Y.Z.zip`: just the extension, manifest at the
zip root, ready for either a manual "Load unpacked" install or a Chrome
Web Store upload.
The published extension's canonical ID
(`mkiffclapimhbjjlgkndlillglopbcic`) is hardcoded into
`native-host/com.kget_capture.host.json`'s `allowed_origins`; see
[keys/README.md](keys/README.md) for why, and what to do for local unpacked
testing (which gets a different, machine-specific ID).
## Known limitations
- **`blob:`/`data:`/`file:`/`filesystem:` URLs can't be captured.** There's
no real network URL to hand to KGet — this includes saving a page you
navigated to directly via `file://` (Ctrl+S on it is still a "download" as
far as Chrome is concerned) — so these downloads proceed normally through
Chrome (a console warning is logged in the extension's service worker).
no real network URL to hand to KGet, so these proceed normally through
Chrome.
- **Auto-capture has a brief head start.** Manifest V3 removed blocking
`webRequest`, so Chrome begins the download before the extension can cancel
it. A small/partial temp file may transiently appear before it's erased.
- **No editable dialog.** Captures start immediately with no chance to
rename the file or pick a different folder beforehand — rename/move it
afterward in KGet or your file manager if needed. This is intentional: KGet
only skips its interactive "New Download" dialog when given a real
destination path up front, so instant capture and an editable prompt are
mutually exclusive given KGet's D-Bus API.
- **Chrome may still show its own "Save As" dialog** if you skipped step 5 of
Install above — see there for why and how to fix it.
- **KGet must already be running.** If it's not, captures fail silently from
the user's perspective — check the service worker console for the actual
error (see Troubleshooting).
rename the file or pick a folder beforehand: KGet only skips its
interactive dialog when given a destination path up front, so instant
capture and an editable prompt are mutually exclusive.
- **Chrome may still show its own "Save As" dialog** if you skipped step 3 of
Install above.
- **KGet must already be running.** If it's not, captures fail silently;
check the service worker console for the actual error (see
Troubleshooting).
## Troubleshooting
- **`install-remote.sh` fails to look up the latest release?** It queries
GitHub's API, which rate-limits unauthenticated requests (60/hour per IP);
wait a bit, or fall back to the manual `install.sh` steps in Install above.
- **Check native host registration:** confirm
`~/.config/<browser>/NativeMessagingHosts/com.kget_capture.host.json` exists
and its `path` points at an executable file
@@ -118,19 +166,22 @@ git push origin v1.0.0
```
If this fails with `ServiceUnknown`, KGet isn't running.
- **Download-complete notification not showing?** Confirm `notify-send` is
installed (`command -v notify-send`) — without it, `kget_capture_host.py`
installed (`command -v notify-send`); without it, `kget_capture_host.py`
skips the notification entirely rather than erroring. Otherwise, check that
the transfer actually reached 100%: `kget_capture_host.py` polls
`org.kde.kget.transfer.percent` on the new transfer's D-Bus object path
(e.g. `/KGet/Transfers/9`, printed by the `addTransfer` call above) until it
hits 100, so a stuck/erroring transfer never fires one. Note this
notification is separate from KGet's own built-in "Download Finished"
event — `install.sh` disables that one (`kwriteconfig6 --file knotifyrc
--group "Event/kget/finished" --key Action None`) because KGet only fires
it for transfers it observed in a non-finished state at least once, so it
silently skips small/fast downloads that complete before that first
observation.
hits 100, so a stuck/erroring transfer never fires one. This notification
is separate from KGet's own built-in "Download Finished" event.
`install.sh` disables that one because KGet only fires it for transfers it
observed in a non-finished state at least once, so it silently skips
small/fast downloads that complete before that first observation.
## Privacy
KGet Capture doesn't collect, transmit, or store any user data. See
[PRIVACY.md](PRIVACY.md) for details.
## License
MIT — see [LICENSE](LICENSE).
MIT, see [LICENSE](LICENSE).
+1 -2
View File
@@ -1,9 +1,8 @@
{
"manifest_version": 3,
"name": "KGet Capture",
"version": "1.0.4",
"version": "1.0.6",
"description": "Send browser downloads to KGet (KDE Download Manager) on Linux.",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtM1nFg5pPd3UBzynUJHj6aUy7bjudZaX9AQTWGMqAX3fXylw1lubSzP1/BaS9ivvgOJg/loyEgzEZlcNPzblFVlg8BqEJ3tntdk2OzYvUs0pUdcPzzyjso9TykCkUUTI7ev8JkreW/FYlinfvNwSYN4/c1s3h8/I6XHtxOdmO1G3bS98+BQaXPldbDmxoKwyWR8u1f8gTO2Mg3GY4BHau3RWf1kgtNzh20p9wE3sEFdWkFu7ArgRCIrWLh6m3O6lolnHQSCWPwtOTx893Qkp9Eu6FwUnoWc8rsYx8sqa0eNCTCS6Kjr1Iu3yEJ5y9390jUiDZTQ/c62ueCTQaYv2bwIDAQAB",
"permissions": ["downloads", "contextMenus", "storage", "nativeMessaging"],
"background": {
"service_worker": "background.js"
+36 -1
View File
@@ -1,5 +1,5 @@
body {
width: 240px;
width: 320px;
padding: 12px;
font-family: system-ui, sans-serif;
font-size: 13px;
@@ -36,3 +36,38 @@ h1 {
font-family: inherit;
cursor: pointer;
}
.cmd-row {
display: flex;
align-items: center;
gap: 6px;
margin-top: 8px;
}
.cmd-row code {
flex: 1;
min-width: 0;
overflow-x: auto;
white-space: nowrap;
background: #f0f0f0;
padding: 4px 6px;
border-radius: 3px;
font-size: 11px;
}
.cmd-row button {
flex: none;
padding: 4px 8px;
font-size: 12px;
font-family: inherit;
cursor: pointer;
}
#recheck-setup {
display: block;
margin-top: 10px;
padding: 4px 8px;
font-size: 12px;
font-family: inherit;
cursor: pointer;
}
+26 -11
View File
@@ -6,17 +6,32 @@
</head>
<body>
<h1>KGet Capture</h1>
<label class="toggle-row">
<input type="checkbox" id="auto-capture-toggle">
<span>Auto-capture all downloads</span>
</label>
<p class="status" id="status-line"></p>
<p class="hint">Right-click any link, image, video, or audio and choose
"Download with KGet" to send it to KGet on demand.</p>
<p class="hint">Downloads still prompting for a location? Chrome shows that
dialog itself, before this extension can intercept anything.
<button id="open-download-settings" type="button">Turn off Chrome's download prompt</button>
</p>
<div id="setup-needed" hidden>
<p class="hint">One-time setup needed: KGet Capture needs a small local
helper installed before it can talk to KGet. Run this in a terminal:</p>
<div class="cmd-row">
<code id="setup-command"></code>
<button id="copy-command" type="button">Copy</button>
</div>
<p class="status" id="copy-feedback"></p>
<button id="recheck-setup" type="button">I ran it, check again</button>
</div>
<div id="ready" hidden>
<label class="toggle-row">
<input type="checkbox" id="auto-capture-toggle">
<span>Auto-capture all downloads</span>
</label>
<p class="status" id="status-line"></p>
<p class="hint">Right-click any link, image, video, or audio and choose
"Download with KGet" to send it to KGet on demand.</p>
<p class="hint">Downloads still prompting for a location? Chrome shows that
dialog itself, before this extension can intercept anything.
<button id="open-download-settings" type="button">Turn off Chrome's download prompt</button>
</p>
</div>
<script src="popup.js"></script>
</body>
</html>
+50 -3
View File
@@ -1,13 +1,60 @@
const NATIVE_HOST = "com.kget_capture.host";
const INSTALL_COMMAND =
"curl -fsSL https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash";
const setupPanel = document.getElementById("setup-needed");
const readyPanel = document.getElementById("ready");
const checkbox = document.getElementById("auto-capture-toggle");
const statusLine = document.getElementById("status-line");
const copyFeedback = document.getElementById("copy-feedback");
function renderStatus(enabled) {
statusLine.textContent = `Auto-capture: ${enabled ? "ON" : "OFF"}`;
}
chrome.storage.local.get({ autoCaptureEnabled: true }).then(({ autoCaptureEnabled }) => {
checkbox.checked = autoCaptureEnabled;
renderStatus(autoCaptureEnabled);
function showReady() {
setupPanel.hidden = true;
readyPanel.hidden = false;
chrome.storage.local.get({ autoCaptureEnabled: true }).then(({ autoCaptureEnabled }) => {
checkbox.checked = autoCaptureEnabled;
renderStatus(autoCaptureEnabled);
});
}
function showSetupNeeded() {
document.getElementById("setup-command").textContent = INSTALL_COMMAND;
readyPanel.hidden = true;
setupPanel.hidden = false;
}
// The native host is only reachable once install.sh (or install-remote.sh)
// has registered it -- sendNativeMessage fails with a lastError like
// "Specified native messaging host not found" until then, which is exactly
// the signal we want to gate the rest of the popup on.
function checkNativeHost() {
chrome.runtime.sendNativeMessage(NATIVE_HOST, { action: "ping" }, () => {
if (chrome.runtime.lastError) {
showSetupNeeded();
} else {
showReady();
}
});
}
checkNativeHost();
document.getElementById("recheck-setup").addEventListener("click", checkNativeHost);
document.getElementById("copy-command").addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(INSTALL_COMMAND);
copyFeedback.textContent = "Copied to clipboard.";
} catch (e) {
copyFeedback.textContent = "Couldn't copy -- select and copy the command manually.";
}
setTimeout(() => {
copyFeedback.textContent = "";
}, 2000);
});
checkbox.addEventListener("change", () => {
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Fetches the latest release source archive and runs install.sh from it, so
# setup doesn't require a git clone:
#
# curl -fsSL https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash
# wget -qO- https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash
set -euo pipefail
REPO="andwati/kget-capture"
fetch_stdout() {
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$1"
elif command -v wget >/dev/null 2>&1; then
wget -qO- "$1"
else
echo "ERROR: this installer needs 'curl' or 'wget' on PATH." >&2
exit 1
fi
}
fetch_to_file() {
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$1" -o "$2"
else
wget -qO "$2" "$1"
fi
}
echo "Looking up the latest release..."
tag="$(fetch_stdout "https://api.github.com/repos/$REPO/releases/latest" \
| grep -m1 '"tag_name"' | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/')"
if [ -z "$tag" ]; then
echo "ERROR: couldn't determine the latest release tag from GitHub." >&2
exit 1
fi
echo "Latest release: $tag"
if ! command -v python3 >/dev/null 2>&1; then
echo "ERROR: 'python3' is required but not found on PATH." >&2
exit 1
fi
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
zip_path="$tmp_dir/kget-capture.zip"
zip_url="https://github.com/$REPO/releases/download/$tag/kget-capture-$tag.zip"
echo "Downloading $zip_url..."
fetch_to_file "$zip_url" "$zip_path"
echo "Extracting..."
src_dir="$tmp_dir/src"
python3 -c "
import sys, zipfile
zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])
" "$zip_path" "$src_dir"
echo "Running install.sh..."
bash "$src_dir/install.sh"
+7 -5
View File
@@ -129,19 +129,21 @@ if ! command -v notify-send >/dev/null 2>&1; then
echo "WARNING: 'notify-send' not found -- captures will still work, but you won't get a notification when they finish"
fi
EXT_ID=$(cat "$SCRIPT_DIR/keys/extension-id.txt")
cat <<EOF
Install complete.
Extension ID: $EXT_ID
If you installed the extension from the Chrome Web Store, you're done --
reopen its toolbar popup and click "I ran it, check again" to confirm.
Next steps:
If you're loading it unpacked for development instead:
1. Open chrome://extensions (or the equivalent for your browser)
2. Enable "Developer mode" (top-right toggle)
3. Click "Load unpacked" and select: $SCRIPT_DIR/extension
4. Confirm the loaded extension's ID matches: $EXT_ID
4. Chrome will assign this unpacked copy its own ID, different from the
published extension's. Find it on chrome://extensions and add it to
"allowed_origins" in ~/.config/<browser>/NativeMessagingHosts/com.kget_capture.host.json
so the native host will accept connections from it too.
Make sure KGet is running (or set to autostart) for captures to work.
EOF
+23 -27
View File
@@ -1,32 +1,28 @@
# Extension signing key
# Extension ID
This directory holds the RSA key pair used to pin the extension's Chrome ID so
it's identical across every install (needed so the native messaging host's
`allowed_origins` can be hardcoded instead of asking each user to paste their
extension ID).
`extension-id.txt` holds the extension's one canonical ID,
`mkiffclapimhbjjlgkndlillglopbcic`, assigned by the Chrome Web Store on
first publish. It's hardcoded into
`native-host/com.kget_capture.host.json`'s `allowed_origins`, which is what
lets the native host trust connections from the extension without asking
each user to paste their own ID.
- `kget-capture-private.pem` — **not committed** (see `.gitignore`). Keep it
somewhere safe if you want to be able to reproduce the same key deterministically
later; losing it just means a future re-key would get a new extension ID, it's
not a security incident.
- `kget-capture-public.der` / `kget-capture-public.b64` — derived from the
private key. The base64 contents of `kget-capture-public.b64` is pasted
verbatim into `extension/manifest.json`'s `"key"` field. These are already
effectively public once checked into `manifest.json`, so committing them is
fine (and `extension-id.txt` too).
- `extension-id.txt` — the 32-character Chrome extension ID derived from the
public key (also hardcoded into `native-host/com.kget_capture.host.json`).
`extension/manifest.json` intentionally has no `"key"` field: the Chrome Web
Store dashboard rejects any manifest that includes one ("key field is not
allowed in manifest"), since it assigns its own permanent ID on first
publish instead.
## Regenerating (only if rotating the key)
`kget-capture-private.pem` / `kget-capture-public.der` / `kget-capture-public.b64`
are leftover from an earlier approach that pinned a separate ID for unpacked
installs via a local key pair. They're no longer referenced anywhere (the
`.pem` was never committed; see `.gitignore`) and can be ignored or removed.
```bash
openssl genrsa -out keys/kget-capture-private.pem 2048
openssl rsa -in keys/kget-capture-private.pem -pubout -outform DER -out keys/kget-capture-public.der
openssl base64 -A -in keys/kget-capture-public.der -out keys/kget-capture-public.b64
openssl dgst -sha256 -binary keys/kget-capture-public.der | head -c 16 | od -An -tx1 \
| tr -d ' \n' | tr '0123456789abcdef' 'abcdefghijklmnop' > keys/extension-id.txt
```
## Implication for local "Load unpacked" development
Rotating the key changes the extension ID, which means `extension/manifest.json`'s
`"key"` and `native-host/com.kget_capture.host.json`'s `allowed_origins` both
need updating to match.
Since there's no pinned key, Chrome derives an unpacked install's ID from a
hash of its local path, which won't match
`mkiffclapimhbjjlgkndlillglopbcic` and differs per machine/checkout. To test
an unpacked build against the native host locally, add your own unpacked
ID to `allowed_origins` in `~/.config/<browser>/NativeMessagingHosts/com.kget_capture.host.json`
after running `install.sh` (Chrome shows the unpacked ID on
`chrome://extensions` once loaded).
+1 -1
View File
@@ -1 +1 @@
lgkhlfhejbffjnpcdlpjclicihnbjijp
mkiffclapimhbjjlgkndlillglopbcic
+1 -1
View File
@@ -3,5 +3,5 @@
"description": "Native messaging host that hands off download URLs to KGet via D-Bus",
"path": "__HOST_SCRIPT_PATH__",
"type": "stdio",
"allowed_origins": ["chrome-extension://lgkhlfhejbffjnpcdlpjclicihnbjijp/"]
"allowed_origins": ["chrome-extension://mkiffclapimhbjjlgkndlillglopbcic/"]
}
+11 -1
View File
@@ -154,7 +154,17 @@ def spawn_notifier(transfer_path, dest):
def main():
message = read_message()
if not message or message.get("action") != "addTransfer" or "url" not in message:
if not message:
write_message({"ok": False, "error": "invalid request"})
return
# The extension popup uses this to detect whether the native host is
# installed/registered at all, distinct from whether a transfer succeeds.
if message.get("action") == "ping":
write_message({"ok": True})
return
if message.get("action") != "addTransfer" or "url" not in message:
write_message({"ok": False, "error": "invalid request"})
return