fix(extension): Chrome background 改为能力探测;README 纠正检测描述

This commit is contained in:
JianFeeeee
2026-10-02 11:24:55 +08:00
parent 292e77097d
commit 85abe9f728
2 changed files with 65 additions and 7 deletions

View File

@ -29,7 +29,7 @@
|------|------|
| 导航服务地址 | 你的门户根地址,如 `https://nav.example.com`(不要带 `/search` 等路径) |
| 同步令牌 | 服务端环境变量 `NAVI_SYNC_TOKEN` 的值(**服务端开启鉴权时必填**) |
| 设备名称 | 可选,留空自动检测(desktop-chrome / desktop-firefox / mobile-*) |
| 设备名称 | 可选,留空则自动检测。**Firefox 版**按 UA 识别(desktop-firefox / mobile-* / desktop-edge 等);**Chrome 版**的 MV3 service worker 里没有 `navigator`,只能回退为 `desktop-browser`(需要区分设备时请手填) |
## 行为
@ -43,6 +43,11 @@
## 技术细节
- Chrome/Edge: Manifest V3,service worker + `chrome.alarms`(最小周期 30s)
- Firefox: Manifest V2,event page + `browser.alarms`
- Firefox: Manifest V2,event page(`persistent: true`)+ `browser.alarms`
- 存储: `storage.local`(pending 队列 + 配置)
- API: `history.search` + `alarms` + `runtime.onMessage`
- `visit_count` 上报的是 **Chrome/Firefox history API 的绝对累计值**,不是增量。
服务端(`history-server`)按设备取绝对值再求和,所以重复上报不会把计数放大。
- 两个目录的 `background.js` **不是同一份文件**:Firefox 额外做设备名识别
(MV2 常驻后台,`navigator` 可用),并过滤 `moz-extension://`。
改动时两边都要改,但不要直接整份覆盖。

View File

@ -31,8 +31,25 @@ let pendingItems = [];
// ── 检测标记:只向用户配置的导航站注入 content script ──
// 已安装的浏览器上,导航站会拿到 data-navi-ext 属性,从而隐藏安装入口。
//
// MV3 与 MV2 的注册方式不同,必须分别处理(此前只写了 MV3 那条,
// Firefox 走 chrome.scripting 早退,于是 data-navi-ext 永不写入,
// 安装入口在 Firefox 上永远藏不住):
// · MV3 / Chromium:chrome.scripting.registerContentScripts,callback 风格
// · MV2 / Firefox :browser.contentScripts.register,返回 Promise,
// 注销只能靠 register() 返回的句柄上的 .unregister()(无按 id 注销的 API)
const DETECT_SCRIPT_ID = 'navi-detect';
const DETECT_FILE = 'detect.js';
// MV2 句柄:MV2 没有「按 id 查询已注册脚本」的 API,只能自己留住上次注册
// 返回的对象,换地址时调它的 unregister()。MV3 另有 storage 字段标记。
let mv2RegisteredHandle = null;
function extApi() {
// Firefox 注入 browser.* 兼 chrome.* 兼容层;优先用它拿 MV2 的 contentScripts。
return (typeof browser !== 'undefined' && browser && browser.contentScripts)
? browser
: (typeof chrome !== 'undefined' ? chrome : null);
}
function hostMatchPattern(serverUrl) {
try {
@ -43,19 +60,55 @@ function hostMatchPattern(serverUrl) {
}
}
async function syncDetectScript(serverUrl) {
if (!chrome.scripting || !chrome.scripting.registerContentScripts) return;
// mv2HasContentScripts 判断当前运行的是 Firefox MV2 那条路。
function mv2HasContentScripts(api) {
return !!(api && api.contentScripts && typeof api.contentScripts.register === 'function');
}
async function unregisterMV2() {
if (!mv2RegisteredHandle) return;
const h = mv2RegisteredHandle;
mv2RegisteredHandle = null;
try {
const existing = await chrome.scripting.getRegisteredContentScripts({ ids: [DETECT_SCRIPT_ID] });
if (typeof h.unregister === 'function') await h.unregister();
} catch (e) { /* 从未注册 / 已被注销 */ }
}
async function syncDetectScript(serverUrl) {
const api = extApi();
if (!api) return;
// ① Firefox MV2:contentScripts.register(Promise)
if (mv2HasContentScripts(api)) {
await unregisterMV2();
const pattern = hostMatchPattern(serverUrl);
if (!pattern) return;
try {
mv2RegisteredHandle = await api.contentScripts.register({
matches: [pattern],
js: [DETECT_FILE],
runAt: 'document_start',
allFrames: false,
});
} catch (e) {
// 静默降级:未装扩展时只是多显示一个安装入口,不影响同步
}
return;
}
// ② Chromium MV3:chrome.scripting.registerContentScripts(callback)
if (!api.scripting || !api.scripting.registerContentScripts) return;
try {
const existing = await api.scripting.getRegisteredContentScripts({ ids: [DETECT_SCRIPT_ID] });
if (existing && existing.length) {
await chrome.scripting.unregisterContentScripts({ ids: [DETECT_SCRIPT_ID] });
await api.scripting.unregisterContentScripts({ ids: [DETECT_SCRIPT_ID] });
}
} catch (e) { /* 从未注册过 */ }
const pattern = hostMatchPattern(serverUrl);
if (!pattern) return;
try {
await chrome.scripting.registerContentScripts([{
await api.scripting.registerContentScripts([{
id: DETECT_SCRIPT_ID,
matches: [pattern],
js: [DETECT_FILE],