diff --git a/CHANGELOG.md b/CHANGELOG.md index 56bce7d..15dfe11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2026-10-16 + +- Preferences now links the Firefox extension to the [extension releases on GitHub](https://github.com/jonocodes/savr/releases?q=extension&expanded=true) (install the `.xpi` from the newest one) instead of the `savr.link/extension/firefox.xpi` redirect, which Firefox was blocking. The Chrome link is hidden by default (`VITE_CHROME_EXTENSION_URL` now defaults to empty) until a Chrome Web Store listing exists + ## 2026-10-15 - The shared CORS proxy now only serves allowlisted calling sites — savr.link, Netlify preview/branch deploys (`*--savrlist.netlify.app`) and localhost — via a new optional `ALLOWED_ORIGINS` setting. It still fetches articles from any public site; other websites calling it get a 404, recorded in the stats. Self-hosted Savr frontends on their own domain need their own proxy. Unset (the default for self-hosted proxies) changes nothing (ADR 0006; rate limiting tracked in #117) diff --git a/README.md b/README.md index af5afcd..02bcb55 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ A working example lives in [`browser-extension/youtube-transcript.js`](browser-e On desktop Firefox and Chrome, the optional [browser extension](browser-extension/) adds a toolbar button with **Save current page** and **Visit Savr**. Unlike a URL save, it scrapes the page you already have open, so it works behind logins, and no CORS proxy is involved for the page itself. The article's images are downloaded by the extension too, so an extension save needs no proxy at all; any image the extension cannot fetch falls back to your configured proxy (or a placeholder). URL saves and bookmarklets still use the proxy as before. -Firefox (142+) installs a signed build from **https://savr.link/extension/firefox.xpi** and keeps it updated; on Android that link downloads the file, which you then install from a hidden settings menu (steps in the extension README). Chrome has no store listing yet, so it installs with **Load unpacked**. See [browser-extension/README.md](browser-extension/README.md) for install, settings, how releases are published, and how the delivery flow works — or use the links in **Preferences** (configurable at build time). +Firefox (142+) installs a signed build from the `.xpi` on the newest **[extension release](https://github.com/jonocodes/savr/releases?q=extension&expanded=true)** and keeps it updated; on Android tapping the `.xpi` only downloads it, which you then install from a hidden settings menu (steps in the extension README). Chrome has no store listing yet, so it installs with **Load unpacked**. See [browser-extension/README.md](browser-extension/README.md) for install, settings, how releases are published, and how the delivery flow works — or use the links in **Preferences** (configurable at build time). ## In browser diff --git a/browser-extension/README.md b/browser-extension/README.md index 07e1589..17daaf8 100644 --- a/browser-extension/README.md +++ b/browser-extension/README.md @@ -28,12 +28,13 @@ There is deliberately no page context-menu entry. ### Firefox (desktop and Android) -Open **https://savr.link/extension/firefox.xpi** in Firefox (the app's -Preferences screen links to it). Firefox asks to allow savr.link to install an +Open the [extension releases](https://github.com/jonocodes/savr/releases?q=extension&expanded=true) in +Firefox (the app's Preferences screen links there) and click the +`savr-firefox-.xpi` asset of the newest one. Firefox asks to allow github.com to install an add-on, then shows the permission prompt. It needs Firefox 142 or newer, and updates itself (see [Updates](#updates)). -On Android, opening that link only downloads the file — Firefox for Android +On Android, tapping the `.xpi` only downloads it — Firefox for Android does not install add-ons from a web link outside addons.mozilla.org. Install the downloaded file instead ([Mozilla's instructions](https://extensionworkshop.com/documentation/publish/install-self-distributed/)): @@ -79,8 +80,9 @@ is a documented path: ship one last self-hosted version **without** `update_url`, and Firefox switches those installs to AMO for updates. - **Install link.** `https://savr.link/extension/firefox.xpi` is a Netlify - redirect (`public/_redirects`) to the newest release asset, so the link the - app shows never changes. The asset is uploaded as + redirect (`public/_redirects`) to the newest release asset, so that link + never changes. Preferences links to the GitHub release list instead: clicked + from the app, Firefox blocked the install through the redirect. The asset is uploaded as `Content-Type: application/x-xpinstall`, which is what makes Firefox start its install flow instead of downloading the file. Any site other than AMO gets Firefox's "Allow *site* to install an add-on?" prompt first; that is @@ -105,8 +107,8 @@ can override it), so a hosted `.crx` would not work for most people. The plan is an **unlisted** Chrome Web Store item (a one-time developer registration fee; review takes days); Edge, Brave and Vivaldi can install from it too, and Edge Add-ons is a free alternative. Until then, the release's Chromium zip is for -**Load unpacked**. The Preferences link (`VITE_CHROME_EXTENSION_URL`) points at -this README until a listing exists. +**Load unpacked**. The Preferences screen shows no Chrome link +(`VITE_CHROME_EXTENSION_URL` defaults to empty) until a listing exists. ### Versioning diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md index 293c5b9..4abaef1 100644 --- a/docs/ENVIRONMENT_VARIABLES.md +++ b/docs/ENVIRONMENT_VARIABLES.md @@ -44,7 +44,7 @@ Deploy a token-locked proxy of your own from `infra/cors-worker/` (there is a De ### `VITE_FIREFOX_EXTENSION_URL` / `VITE_CHROME_EXTENSION_URL` -- **Default**: Firefox → `https://savr.link/extension/firefox.xpi` (stable link to the newest signed XPI; see [Releasing](../browser-extension/README.md#releasing)). Chrome → the [browser extension README](https://github.com/jonocodes/savr/tree/main/browser-extension) (install instructions) until a Chrome Web Store listing exists. +- **Default**: Firefox → `https://github.com/jonocodes/savr/releases?q=extension&expanded=true` (GitHub releases filtered to the extension; install the signed `.xpi` from the newest one). Chrome → empty (hidden) until a Chrome Web Store listing exists. - **Description**: Where the Preferences screen links to install the browser extension. Set one to an empty string to hide that link. - **Usage**: `VITE_CHROME_EXTENSION_URL=https://chromewebstore.google.com/detail/… npm run build` diff --git a/src/config/environment.ts b/src/config/environment.ts index ca1c211..c3ed422 100644 --- a/src/config/environment.ts +++ b/src/config/environment.ts @@ -23,18 +23,18 @@ export interface EnvironmentConfig { }; extension: { // Where Preferences links to install the browser extension, per browser. - // Defaults to the extension's README (install instructions); the hosted - // build can point these at the AMO listing / a hosted signed XPI and the - // Chrome Web Store once published. An empty value hides that link. + // Firefox defaults to the latest GitHub release; Chrome is hidden until a + // Chrome Web Store listing exists. An empty value hides that link. firefoxUrl: string; chromeUrl: string; }; } -const EXTENSION_README_URL = "https://github.com/jonocodes/savr/tree/main/browser-extension"; -// Stable install link for the signed, self-hosted Firefox build: a savr.link -// redirect (public/_redirects) to the newest XPI on GitHub Releases. -const FIREFOX_EXTENSION_INSTALL_URL = "https://savr.link/extension/firefox.xpi"; +// GitHub releases filtered to the extension (newest first; the repo's "latest" +// release belongs to the app). The signed XPI is installed from the +// release's asset list. (Linking the savr.link/extension/firefox.xpi redirect +// directly got blocked by Firefox's install prompt.) +const FIREFOX_EXTENSION_INSTALL_URL = "https://github.com/jonocodes/savr/releases?q=extension&expanded=true"; // Get environment variables const getEnvVar = (key: string, defaultValue?: string): string | undefined => { @@ -84,7 +84,7 @@ export const environmentConfig: EnvironmentConfig = { }, extension: { firefoxUrl: getEnvVar("VITE_FIREFOX_EXTENSION_URL", FIREFOX_EXTENSION_INSTALL_URL) || "", - chromeUrl: getEnvVar("VITE_CHROME_EXTENSION_URL", EXTENSION_README_URL) || "", + chromeUrl: getEnvVar("VITE_CHROME_EXTENSION_URL", "") || "", }, }; diff --git a/tests/e2e/preferences-extension.spec.ts b/tests/e2e/preferences-extension.spec.ts index 2c40c8b..cc548f9 100644 --- a/tests/e2e/preferences-extension.spec.ts +++ b/tests/e2e/preferences-extension.spec.ts @@ -3,9 +3,9 @@ import { test, expect } from "@playwright/test"; /** * Preferences offers the browser extension next to the bookmarklets. The * install targets are build-configurable (VITE_FIREFOX_EXTENSION_URL / - * VITE_CHROME_EXTENSION_URL). Firefox defaults to the stable savr.link install - * link for the signed self-hosted XPI; Chrome, until a store listing exists, - * defaults to the extension README. + * VITE_CHROME_EXTENSION_URL). Firefox defaults to the extension's GitHub releases + * (whose assets include the signed XPI); Chrome is hidden until a store + * listing exists. */ test.describe("Preferences: browser extension", () => { test("links to the extension install targets", async ({ page }) => { @@ -16,11 +16,8 @@ test.describe("Preferences: browser extension", () => { await expect(page.getByTestId("extension-firefox-link")).toHaveAttribute( "href", - "https://savr.link/extension/firefox.xpi" - ); - await expect(page.getByTestId("extension-chrome-link")).toHaveAttribute( - "href", - /browser-extension/ + "https://github.com/jonocodes/savr/releases?q=extension&expanded=true" ); + await expect(page.getByTestId("extension-chrome-link")).toHaveCount(0); }); });