Skip to content

Latest commit

 

History

History
160 lines (136 loc) · 8.93 KB

File metadata and controls

160 lines (136 loc) · 8.93 KB

Publishing the Vitreous extension (cross-browser)

One shared source in extension/ ships to every major browser. Chromium browsers (Chrome, Edge, Brave, Opera, Vivaldi, Arc) run the same package; Firefox gets a transformed manifest generated by the build. Goal on each store: publish so the vendor signs it (no install warnings) and the app can wire it up by ID.

Browser Store Work needed
Chrome Chrome Web Store Upload vitreous-extension-<v>.zip
Brave Nothing: installs from the Chrome Web Store listing
Opera / Vivaldi / Arc Same — install from the Chrome listing
Edge Microsoft Edge Add-ons Upload the same Chrome zip
Firefox addons.mozilla.org (AMO) Upload vitreous-extension-firefox-<v>.zip
Safari App Store Not yet — needs a Mac + Xcode wrapper

1. Build the upload packages

From the repo root:

npm run pack:ext           # Chrome/Edge/Brave  -> dist/vitreous-extension-<v>.zip
npm run pack:ext:firefox   # Firefox            -> dist/vitreous-extension-firefox-<v>.zip
npm run pack:ext:all       # both at once

Each produces a zip with manifest.json at the zip root (required by the stores). The Firefox build differs only in its generated manifest (background.scripts instead of a service worker, plus a browser_specific_settings.gecko.id); the JavaScript is identical and already cross-browser (it resolves browser on Firefox and chrome everywhere else).

2. Upload to the Chrome Web Store

  1. Go to the Chrome Web Store Developer Dashboard (https://chrome.google.com/webstore/devconsole) and sign in with your existing developer account.
  2. Add new item → upload vitreous-extension-<version>.zip.
  3. Fill the store listing:
    • Name: Vitreous Focus
    • Summary: Blocks distracting websites behind a focus page. Companion to the Vitreous focus app.
    • Category: Productivity
    • Icon: extension/icons/icon128.png
    • Screenshots: at least one 1280×800 or 640×400 image (a shot of the block page works).
  4. Privacy → Permission justifications (paste these; they speed up review):
    • webNavigation + tabs — detect when the user navigates to a blocked site and redirect that tab to the local block page.
    • host_permissions: <all_urls> — a block can apply to any site the user adds, so navigations on any host must be checkable.
    • storage — remember the on/off state and the blocked-domain list locally.
    • Data use: collects no user data; sends nothing to any remote server. The extension talks only to the local Vitreous app over 127.0.0.1.
  5. Visibility: Unlisted.
  6. Submit for review.

3. After approval

Copy the Item ID (the 32-character extension ID shown in the dashboard) and put it in src/main/extension-config.js as WEBSTORE_EXTENSION_ID. That ID drives force-install for Chrome and Brave (both fetch from the Chrome Web Store). Edge is separate — it needs its own Edge Add-ons ID (see the Edge section below).

Brave / Opera / Vivaldi / Arc — nothing to do

These are Chromium browsers that install directly from the Chrome Web Store. Once the Chrome listing is live, they all work — no separate build, store, or upload. The download page can simply say "works in Brave, Opera, Vivaldi, and other Chromium browsers."

Edge — needs its OWN Edge Add-ons listing (not the CWS one)

Edge runs the Chrome MV3 build unchanged, but you must publish it to the Microsoft Edge Add-ons store and give the app Edge's own item ID.

Why Edge can't reuse the Chrome listing for force-install. On an unmanaged Windows device (not domain- / Azure-AD-joined, not Chrome-Enterprise-enrolled — i.e. every normal user's PC), Edge refuses to force-install a Chrome Web Store extension. Edge's ExtensionInstallForcelist only silently installs items hosted in the Microsoft Edge Add-ons store. Pointing it at the CWS update URL writes the policy but Edge ignores it ("This device isn't managed by your organization…"). Chrome and Firefox honor force-install on unmanaged devices; Edge does not. So Edge needs a distinct Edge Add-ons ID + update URL. (Verified live on Edge 149, 2026-06-14.)

  1. Register once at the Microsoft Partner Center → Edge program (https://partner.microsoft.com/dashboard/microsoftedge) — free.
  2. Create new extension → upload the same vitreous-extension-<version>.zip.
  3. Reuse the Chrome listing copy, screenshots, and the permission justifications from §2. Set visibility to Unlisted (Edge calls it "Hidden" / not discoverable).
  4. Submit for review. Edge review is separate from Chrome's and usually faster.
  5. After approval: copy Edge's 32-char Extension ID (from the Partner Center overview, not the Chrome ID — they differ) and put it in src/main/extension-config.js as EDGE_EXTENSION_ID. That's the only code change: the app already pairs it with the Edge Add-ons update URL (EDGE_UPDATE_URL) and force-installs Edge in the same single-UAC run as Chrome/Brave/Firefox. While EDGE_EXTENSION_ID is empty, the app deliberately leaves Edge out (so it never writes a policy Edge would ignore) and the "Add to my browser" card simply doesn't offer Edge.

Firefox — addons.mozilla.org (AMO)

  1. Build the Firefox package: npm run pack:ext:firefox.
  2. Create a free developer account at https://addons.mozilla.org and Submit a New Add-on → upload vitreous-extension-firefox-<version>.zip.
  3. Choose "On your own" (self / unlisted) if you want it installable by link rather than publicly listed; Mozilla still signs it so it installs without warnings. Choose "On this site" to list it publicly in AMO search.
  4. Reuse the same listing copy and the permission justifications from §2.
  5. AMO signs the package; download the signed .xpi it returns.

App-side Firefox force-install (self-distributed)

We self-distribute the signed .xpi — there is no public AMO page. The app force-installs it itself:

  1. Drop the signed .xpi at extension/signed/vitreous-firefox.xpi (version-less name — an extension update is a drop-in file replace, no code change).
  2. The "Add to my browser" button writes the Mozilla policy HKLM\SOFTWARE\Policies\Mozilla\Firefox\ExtensionSettings\<gecko-id> with installation_mode=force_installed + install_url = a file:// URL to that bundled .xpi. One UAC covers Chromium browsers + Firefox together. See src/main/forceinstall.js (firefox* helpers) and extension-config.js (FIREFOX_EXTENSION_ID, FIREFOX_XPI_FILENAME).
  3. Packaging caveat: when electron-builder is added, extension/signed/ must be asarUnpack-ed so Firefox can read the .xpi from a real file:// path (Firefox can't install from inside an app.asar archive).
  4. Restart required (all browsers): a force-installed extension only loads on the browser's next launch — enterprise policies are read at startup, so it never appears in an already-open window. The "Add to my browser" result message tells the user to fully close (every window) and reopen the browser. There's no runtime reload of ExtensionSettings; the app deliberately does NOT kill/relaunch the browser (that would drop the user's tabs).

Firefox specifics (live-verified working):

  • CSP must NOT upgrade ws://wss://. Firefox's default MV3 CSP includes upgrade-insecure-requests, which silently rewrites the bridge's ws://127.0.0.1:8736 to wss:// and the connection then hangs forever (no error, no packet). The manifest overrides content_security_policy.extension_pages to drop that directive and allow ws://127.0.0.1:8736 in connect-src. Do not remove this — it's the single thing that makes the Firefox bridge connect. (Chrome doesn't upgrade ws://localhost, so it never needed it.)
  • The background uses an ES-module background script (type: "module"), supported on Firefox 128+ — hence strict_min_version: "128.0".
  • Temporary add-on (web-ext) testing caveat: host permission isn't granted by the (absent) install prompt and resets on reload, so local web-ext testing is flaky. A real AMO-signed install grants <all_urls> at the install prompt, like Chrome's force-install — that's the reliable way to validate.
  • For local testing without signing: about:debuggingThis FirefoxLoad Temporary Add-on → pick the built manifest.json. Or use npx web-ext run from the staged Firefox folder for live reload.

Notes

  • A privacy-policy URL may be required (Chrome and Firefox both ask because of host_permissions). A short page on the project's GitHub Pages site stating "no data is collected, nothing leaves your machine" satisfies all three stores.
  • The local load-unpacked copy keeps working for Chromium development; use about:debugging for Firefox development.