Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ _Avoid_: 下载、匯出
**L1 零泄漏**:
左栏 IP 列表(扫描层)只显示共识徽章与多源一致国家;任何单源数据(运营商、分数、城市)不进 L1,留在详情层。
_Avoid_: 列表层泄漏

**通道**:
同一套插件代码面向特定浏览器(Chrome/Firefox)的发行变体;各有独立的 manifest 派生与「检查更新」比对的 Release 资产前缀。
_Avoid_: 浏览器版、浏览器分支
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Firefox 通道经 AMO unlisted 签名分发,manifest 构建期派生

Chrome 与 Firefox 共用 `extension/` 同一套代码,`public/manifest.json` 是唯一 manifest 事实源(Chrome 版原样);Firefox 版由 `scripts/make-firefox-manifest.mjs` 在构建时派生 `dist/manifest.json`:background 以事件页 `scripts` 取代 `service_worker`(`type: module` 必须保留——background.js 与 index 共享 rollup 代码分块,顶层有 import),并加入 `browser_specific_settings.gecko`(id=`ip-radar-extension@steponeerror.github.io`,strict_min_version=127,即 optional_host_permissions 的支持门槛)。发行走 AMO unlisted 自签名(免费,不公开上架):`web-ext sign` 产物 xpi 直接分发,新版本重复上传签名后,已安装者由 AMO 更新服务自动更新。唯一不可逆点:gecko id 首次签名后即为 AMO 永久身份,换 id 等于发布一个新扩展。

## Considered Options

- 未签名 zip + `about:debugging` 临时加载 — 否决:重启即失,日常自用不可接受。
- 单 manifest 双写 background(`service_worker`+`scripts` 并存)— 否决:Chrome 侧产生未知键告警,公共 manifest 混入通道细节;派生保 Chrome 版零污染。
2 changes: 2 additions & 0 deletions extension/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules/
dist/
*.zip
web-ext-artifacts/
amo.env
11 changes: 11 additions & 0 deletions extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ npm run build

也可解壓 `ip-radar-extension.zip`,載入解壓後包含 `manifest.json` 的資料夾。

## Firefox

在 `extension` 目錄執行 `npm run build:firefox`:產出 Firefox 版 `dist`(manifest 由 Chrome 版派生,`background` 改為事件頁並加入 `browser_specific_settings`),並打包 `IP.Radar.Firefox_v<版本>.zip`。

- 臨時安裝:Firefox 開啟 `about:debugging#/runtime/this-firefox`,按「Load Temporary Add-on」,選擇 `dist` 內的 `manifest.json`。臨時附加元件重啟 Firefox 後即消失。
- 永久安裝需 AMO 簽名(免費,unlisted 不會公開上架):建立 `amo.env`(內含 `WEB_EXT_API_KEY`、`WEB_EXT_API_SECRET`),執行 `set -a; . amo.env; set +a` 後再 `npx web-ext sign --source-dir dist --channel unlisted`,簽好的 XPI 在 `web-ext-artifacts/`。
- 新版本重複簽名上傳後,已安裝者由 AMO 自動更新。
- gecko id 為 `ip-radar-extension@steponeerror.github.io`,一經簽名即永久綁定,不可更換。
- Firefox 通道的「檢查更新」比對 Release 的 `IP.Radar.Firefox_*.zip` 資產。
- 發佈時需在 GitHub Release 上傳名為 `IP.Radar.Firefox_v<版本>.zip` 的資產,Firefox 端「檢查更新」才會提示就緒;已簽名安裝者由 AMO 自動更新。

## 功能

- 一批預設最多 100 個 IPv4 / IPv6,可在設定調整單次最大查詢數(1–1,000);換行、逗號、分號或空格分隔,去重並提示無效項目。
Expand Down
1 change: 1 addition & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build",
"build:firefox": "VITE_CHANNEL=firefox npm run build && node scripts/make-firefox-manifest.mjs && rm -f IP.Radar.Firefox_v$npm_package_version.zip && cd dist && zip -qr ../IP.Radar.Firefox_v$npm_package_version.zip .",
"test": "vitest run"
},
"dependencies": {
Expand Down
Binary file modified extension/public/icons/icon-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified extension/public/icons/icon-32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified extension/public/icons/icon-64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions extension/scripts/make-firefox-manifest.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Firefox manifest 从 Chrome 版派生:单一事实源 public/manifest.json,不双维护
import { readFile, writeFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";

const GECKO_ID = "ip-radar-extension@steponeerror.github.io";

export function firefoxManifest(manifest) {
return {
...manifest,
// Firefox MV3 用事件页(background.scripts),不支持 service_worker;type:module 必须
// 保留:background.js 与 index 共享 rollup 代码分块,顶层有 import
background: { scripts: ["background.js"], type: "module" },
browser_specific_settings: {
gecko: { id: GECKO_ID, strict_min_version: "127.0" }, // 127 = optional_host_permissions
},
};
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const manifest = JSON.parse(await readFile("public/manifest.json", "utf8"));
await writeFile(
"dist/manifest.json",
JSON.stringify(firefoxManifest(manifest), null, 2) + "\n",
);
console.log(`dist/manifest.json → Firefox (gecko id ${GECKO_ID})`);
}
19 changes: 19 additions & 0 deletions extension/scripts/make-firefox-manifest.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { readFileSync } from "node:fs";
import { test, expect } from "vitest";
import { firefoxManifest } from "./make-firefox-manifest.mjs";

test("firefox manifest derives from the chrome manifest", () => {
const chrome = JSON.parse(
readFileSync(new URL("../public/manifest.json", import.meta.url), "utf8"),
);
const fx = firefoxManifest(chrome);
expect(fx.background).toEqual({ scripts: ["background.js"], type: "module" });
expect(fx.browser_specific_settings.gecko).toEqual({
id: "ip-radar-extension@steponeerror.github.io",
strict_min_version: "127.0",
});
// 除 background/gecko 外逐字段继承 Chrome manifest:权限/图标/popup 不双维护
const { background, browser_specific_settings, ...rest } = fx;
const { background: chromeBackground, ...chromeRest } = chrome;
expect(rest).toEqual(chromeRest);
});
7 changes: 6 additions & 1 deletion extension/src/UpdateCheck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ const RELEASES_API =
"https://api.github.com/repos/steponeerror/ip-radar-desktop/releases/latest";
const RELEASES_URL =
"https://github.com/steponeerror/ip-radar-desktop/releases/latest";
// 通道资产前缀:Firefox 构建(VITE_CHANNEL=firefox)比对 Firefox zip,默认 Chrome
const ASSET_PREFIX =
import.meta.env.VITE_CHANNEL === "firefox"
? "IP.Radar.Firefox_"
: "IP.Radar.Chrome_";
export function UpdateCheck() {
const { t } = useI18n();
const [busy, setBusy] = useState(false);
Expand Down Expand Up @@ -39,7 +44,7 @@ export function UpdateCheck() {
!Array.isArray(release.assets) ||
!release.assets.some(
(asset: { name?: string }) =>
asset.name?.startsWith("IP.Radar.Chrome_") && asset.name.endsWith(".zip"),
asset.name?.startsWith(ASSET_PREFIX) && asset.name.endsWith(".zip"),
)
) {
setMessage("新版本的擴充功能下載尚未就緒,請稍後再試。");
Expand Down
38 changes: 38 additions & 0 deletions extension/src/__tests__/background-firefox.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { it, expect, vi, afterEach } from "vitest";
afterEach(() => vi.unstubAllGlobals());
// Firefox 没有 storage.local.setAccessLevel;initialize 必须跳过它并照常完成其余初始化。
// 独立文件:background 模块的监听注册副作用需要全新的模块注册表。
it("initialize survives without setAccessLevel (Firefox)", async () => {
let onInstalled: Function = () => {};
const setPopup = vi.fn();
let state: Record<string, unknown> = {};
vi.stubGlobal("chrome", {
runtime: {
id: "test",
getURL: (p: string) => `chrome-extension://test/${p}`,
onMessage: { addListener: vi.fn() },
onInstalled: { addListener: (f: Function) => (onInstalled = f) },
onStartup: { addListener: vi.fn() },
},
storage: {
local: {
get: async () => state,
set: async (v: Record<string, unknown>) => {
state = v;
},
// 故意不提供 setAccessLevel
},
},
alarms: {
create: vi.fn(),
clear: vi.fn(),
onAlarm: { addListener: vi.fn() },
},
action: { setPopup, onClicked: { addListener: vi.fn() } },
tabs: { create: vi.fn() },
});
await import("../background");
onInstalled();
await vi.waitFor(() => expect(setPopup).toHaveBeenCalled());
expect(setPopup).toHaveBeenCalledWith({ popup: "index.html?mode=popup" });
});
8 changes: 5 additions & 3 deletions extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,11 @@ chrome.action.onClicked.addListener(() => {
});
});
async function initialize() {
await chrome.storage.local.setAccessLevel({
accessLevel: "TRUSTED_CONTEXTS",
});
// Firefox 没有 setAccessLevel(Chrome 专属);缺省即仅受信上下文可读,跳过不阻塞其余初始化
if (chrome.storage.local.setAccessLevel)
await chrome.storage.local.setAccessLevel({
accessLevel: "TRUSTED_CONTEXTS",
});
const state = await read();
await chrome.action.setPopup({
popup: state.settings.openInTab ? "" : "index.html?mode=popup",
Expand Down
Loading