From b5450643c5e11df7aca62ad4b2e7136001ef35e6 Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Sat, 20 Jun 2026 18:48:37 -0400 Subject: [PATCH] webextension: split manifests; introduce context menu placements --- .github/workflows/ci.yml | 18 +++++++++++----- justfile | 25 ++++++++++++++++------ webextension/esbuild.config.mjs | 2 +- webextension/manifest.firefox.json | 26 +++++++++++++++++++++++ webextension/manifest.json | 5 ++++- webextension/package-lock.json | 4 ++-- webextension/src/background.ts | 34 ++++++++++++++++++++++++++++++ 7 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 webextension/manifest.firefox.json create mode 100644 webextension/src/background.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98a9b32..3c86e67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,9 +45,13 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "lts/*" - - name: Build extension - working-directory: webextension - run: npm ci && npm run build + - uses: extractions/setup-just@v2 + - name: Bundle Firefox extension + run: just bundle-extension + - name: Validate Firefox manifest + run: | + unzip -p switchyard-webextension.zip manifest.json > /tmp/manifest.json + jq -e '.background.scripts == ["build/background.js"] and (.background.service_worker | not)' /tmp/manifest.json build-flatpak: name: "Build Flatpak (${{ matrix.variant.arch }})" @@ -85,8 +89,12 @@ jobs: working-directory: webextension run: npm ci && npm run build - name: Submit to AMO - working-directory: webextension - run: npx web-ext sign --channel=listed --approval-timeout=0 --api-key=${{ secrets.AMO_JWT_ISSUER }} --api-secret=${{ secrets.AMO_JWT_SECRET }} + run: | + staging="$(mktemp -d)" + cp -R webextension/build webextension/icons webextension/popup.html "${staging}/" + rm -f "${staging}/build/manifest.firefox.json" + cp webextension/manifest.firefox.json "${staging}/manifest.json" + npx web-ext sign --source-dir="${staging}" --channel=listed --approval-timeout=0 --api-key=${{ secrets.AMO_JWT_ISSUER }} --api-secret=${{ secrets.AMO_JWT_SECRET }} publish-cws: name: "Publish to Chrome Web Store" diff --git a/justfile b/justfile index 44a4fe7..6867d74 100644 --- a/justfile +++ b/justfile @@ -82,10 +82,14 @@ bundle-extension: build-extension root="{{justfile_directory()}}" extdir="${root}/webextension" outfile="${root}/switchyard-webextension.zip" + staging="$(mktemp -d)" + trap 'rm -rf "${staging}"' EXIT + rm -f "${outfile}" - cd "${extdir}" && zip -r "${outfile}" . \ - -x '.git*' 'node_modules/*' 'src/*' 'web-ext-artifacts/*' \ - -x 'package.json' 'package-lock.json' 'tsconfig.json' 'esbuild.config.mjs' + cp -R "${extdir}/build" "${extdir}/icons" "${extdir}/popup.html" "${staging}/" + rm -f "${staging}/build/manifest.firefox.json" + cp "${extdir}/manifest.firefox.json" "${staging}/manifest.json" + cd "${staging}" && zip -r "${outfile}" . echo "Extension bundled: switchyard-webextension.zip" # Cut a new release: bump version, commit, tag, and push master + tag @@ -96,6 +100,7 @@ release version: tag="v${version}" metainfo="data/{{APPID}}.metainfo.xml" manifest="webextension/manifest.json" + firefox_manifest="webextension/manifest.firefox.json" pkgjson="webextension/package.json" if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then @@ -109,10 +114,10 @@ release version: exit 1 fi - # Allow src/app.go, metainfo, extension manifest, and package.json to be dirty; nothing else. - dirty="$(git status --porcelain -- ':!src/app.go' ":!${metainfo}" ":!${manifest}" ":!${pkgjson}")" + # Allow src/app.go, metainfo, extension manifests, and package.json to be dirty; nothing else. + dirty="$(git status --porcelain -- ':!src/app.go' ":!${metainfo}" ":!${manifest}" ":!${firefox_manifest}" ":!${pkgjson}")" if [ -n "$dirty" ]; then - echo "error: working tree has changes outside src/app.go, ${metainfo}, ${manifest}:" >&2 + echo "error: working tree has changes outside src/app.go, ${metainfo}, ${manifest}, ${firefox_manifest}:" >&2 echo "$dirty" >&2 exit 1 fi @@ -140,13 +145,19 @@ release version: exit 1 fi + sed -i -E 's/^(\s*"version"\s*:\s*)"[^"]+"/\1"'"${version}"'"/' "${firefox_manifest}" + if ! grep -qE '"version"\s*:\s*"'"${version}"'"' "${firefox_manifest}"; then + echo "error: failed to update version in ${firefox_manifest}" >&2 + exit 1 + fi + sed -i -E 's/^(\s*"version"\s*:\s*)"[^"]+"/\1"'"${version}"'"/' "${pkgjson}" if ! grep -qE '"version"\s*:\s*"'"${version}"'"' "${pkgjson}"; then echo "error: failed to update version in ${pkgjson}" >&2 exit 1 fi - git add src/app.go "${metainfo}" "${manifest}" "${pkgjson}" + git add src/app.go "${metainfo}" "${manifest}" "${firefox_manifest}" "${pkgjson}" git commit -m "update for ${tag}" git tag -a "${tag}" -m "${tag}" git push origin master diff --git a/webextension/esbuild.config.mjs b/webextension/esbuild.config.mjs index 43bb6bb..d270986 100644 --- a/webextension/esbuild.config.mjs +++ b/webextension/esbuild.config.mjs @@ -2,7 +2,7 @@ import * as esbuild from "esbuild"; const watch = process.argv.includes("--watch"); const ctx = await esbuild.context({ - entryPoints: ["src/App.tsx"], + entryPoints: ["src/App.tsx", "src/background.ts"], bundle: true, outdir: "build", format: "esm", diff --git a/webextension/manifest.firefox.json b/webextension/manifest.firefox.json new file mode 100644 index 0000000..523f99f --- /dev/null +++ b/webextension/manifest.firefox.json @@ -0,0 +1,26 @@ +{ + "manifest_version": 3, + "name": "Switchyard", + "description": "Open the current page in Switchyard for browser selection", + "version": "0.16.1", + "homepage_url": "https://github.com/alyraffauf/Switchyard", + "permissions": ["activeTab", "contextMenus", "nativeMessaging"], + "background": { + "scripts": ["build/background.js"] + }, + "icons": { + "16": "icons/switchyard-16.png", + "48": "icons/switchyard-48.png", + "128": "icons/switchyard-128.png" + }, + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icons/switchyard-16.png", + "48": "icons/switchyard-48.png" + } + }, + "browser_specific_settings": { + "gecko": { "id": "switchyard@alyraffauf.github.io" } + } +} diff --git a/webextension/manifest.json b/webextension/manifest.json index 92d013f..f985621 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -4,7 +4,10 @@ "description": "Open the current page in Switchyard for browser selection", "version": "0.16.1", "homepage_url": "https://github.com/alyraffauf/Switchyard", - "permissions": ["activeTab", "nativeMessaging"], + "permissions": ["activeTab", "contextMenus", "nativeMessaging"], + "background": { + "service_worker": "build/background.js" + }, "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmV3D3go08Nhd2PfSt7t5IbK2dT8n01umhIEbExICCWhkO6qZx4E/TW+OXH9CyOIDaUnItt+ZV3VWD1TkLvHNLij5RQLxym6VSzNA4WPplVJvY/E1x3qiBVE+3KZK8tF3bWQ0d7VBFn8sUd4JkyKCs69rn+K8fuej30RCXwqtnFSP2H+nD69vsbGoy2YQ8v8RRN1TlIcSEJEfmcMXT20H7gVH1j/egULrzpey/blrCMRjxFAwbb1eNKa4OwVe4i/g72moo6RvOl1gbxem8RqoXhzWxIBfhpetf0fNqsgS3ZwKgVLr+teyX4MNELA5En1TItjeyYYFNn++1Y4QB90OpwIDAQAB", "icons": { "16": "icons/switchyard-16.png", diff --git a/webextension/package-lock.json b/webextension/package-lock.json index 0a3e7b7..8ca450a 100644 --- a/webextension/package-lock.json +++ b/webextension/package-lock.json @@ -1,12 +1,12 @@ { "name": "switchyard-webextension", - "version": "0.14.0", + "version": "0.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "switchyard-webextension", - "version": "0.14.0", + "version": "0.16.1", "dependencies": { "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/webextension/src/background.ts b/webextension/src/background.ts new file mode 100644 index 0000000..7098d15 --- /dev/null +++ b/webextension/src/background.ts @@ -0,0 +1,34 @@ +const ROUTABLE = /^https?:/; +const ROUTABLE_PATTERNS = ["http://*/*", "https://*/*"]; + +function openInSwitchyard(url: string, tabId: number) { + const params = new URLSearchParams({ url }); + chrome.tabs.update(tabId, { url: `switchyard://open?${params}` }); +} + +chrome.runtime.onInstalled.addListener(() => { + chrome.contextMenus.removeAll(() => { + chrome.contextMenus.create({ + id: "open-link", + title: "Open in Switchyard", + contexts: ["link"], + targetUrlPatterns: ROUTABLE_PATTERNS, + }); + chrome.contextMenus.create({ + id: "open-page", + title: "Open in Switchyard", + contexts: ["page"], + documentUrlPatterns: ROUTABLE_PATTERNS, + }); + }); +}); + +chrome.contextMenus.onClicked.addListener((info, tab) => { + const tabId = tab?.id; + if (tabId == null) return; + if (info.menuItemId === "open-link" && info.linkUrl && ROUTABLE.test(info.linkUrl)) { + openInSwitchyard(info.linkUrl, tabId); + } else if (info.menuItemId === "open-page" && tab?.url && ROUTABLE.test(tab.url)) { + openInSwitchyard(tab.url, tabId); + } +}); -- 2.51.2