Files
陈银军 1b0c3c012c feat: 按域名授权、iframe 多帧填充、自动提交限流修正,并补齐工程规范与文档
安全与权限:
- 站点访问权限改为 optional_host_permissions,保存配置时逐域名授权;移除 host_permissions 与 tabs,安装提示不再出现全站数据访问与浏览记录
- 主口令不再写入 storage.session,改用 IndexedDB 中不可导出的 CryptoKey 句柄恢复解锁,锁定即丢弃
- content script 注入范围只注册已授权域名,并随权限变化即时收敛

自动登录:
- 支持 iframe 内的登录表单:脚本注入所有帧,逐帧按自身地址匹配配置
- 手动填充改为逐帧探测,定向发送到真正含密码框的帧
- 限流修正:只在真正触发提交后计数;判定登录成功后立即清零;达到上限时页面给出可见提示;重置判断只看启用中的配置
- SPA 重试改为 DOM 变更门控 + 退避,并单独监听已发现的 Shadow Root

工程化与文档:
- 引入 ESLint(扁平配置)与 Prettier,CI 增加 lint 与 format:check
- 信息类日志改为 debugLog(默认静默,chrome.storage.local.debugLog 开关)
- 测试 123 → 128 用例(新增权限、iframe、限流相关用例)
- README / PRIVACY / CHANGELOG 同步
2026-09-18 00:05:20 +08:00

65 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# auto-login 项目长期记忆
## 自动提交限流语义(2026-09-18 修正)
- 计数的是**自动提交次数**(不是"登录失败次数",扩展无法知道凭据对不对),key 为 `envId::domain`,存 `chrome.storage.local.loginAttempts`。上限 3 次,冷却 5 分钟。
- 计数时机:只在**真正触发提交后**(`fillForm` 的 `onSubmitted` 回调)累加,`fillSingleConfig` 不再提前计数。填写阶段(未提交)不计数。
- 成功检测(两条路径,命中任一即清零):
1. 提交后 `submitResultCheckMs`(默认 4s)观察:登录框消失或已离开匹配地址 → `resetDomainAttempts`
2. 整页跳转导致脚本销毁时,由新页面兜底:命中配置但 `fillTimeoutMs`(默认 30s)内始终没有登录框 → `resetDomainAttempts`
- 达到上限时**页面必须给可见提示**(`showAttemptsPausedToast`,i18n `attemptToastTitle/Body`),仅写日志用户看不懂。
- 测试钩子:`window.__autoLoginTiming = { fillTimeoutMs, submitResultCheckMs }`,避免测试真等数十秒。
- 重置路径还有:URL 变化后不再匹配任何**启用中**配置、popup 手动填充。
## iframe / 多帧约定(2026-09-17)
- 注入一律带 `allFrames: true`(`registerContentScripts` 与所有 `executeScript`),否则 iframe 内的登录表单填不了。
- content.ts 每个帧都用自己的 `window.location.href` 匹配配置;`isTopFrame()`(`window.top === window`)决定是否渲染浮窗:**浮窗只在顶层帧**,子帧改用 `pickConfigForFrame(matches, activeEnvId)`(优先激活环境)确定性选择。
- `background.loadAllConfigs` 的响应包含 `activeEnvId`,供子帧选择凭据。
- popup 手动填充:先 `executeScript({allFrames:true, func})` 逐帧探测已注入标记(只对未注入的帧用 `frameIds` 补注入,避免重复注入报错),再探测哪帧有 `input[type=password]`(`utils.pickFrameWithForm`),最后带 `{frameId}` 定向 `tabs.sendMessage`。
- 坑:`chrome.runtime.onMessage` 监听器必须保持**同步**返回 true,子帧里的异步填充要包在内部 IIFE 里,不能把监听器设成 async。
## 权限模型(2026-09-17 定为"方案 C")
- `manifest.json`:**不声明 `host_permissions`**,改为 `optional_host_permissions: ["*://*/*"]`;同时移除了 `tabs`(安装提示不再出现"所有网站数据"与"浏览记录")。当前 permissions = storage / activeTab / scripting / sidePanel / bookmarks。
- 授权流程:保存配置(新增或编辑)时 `Perms.requestForDomain(domain)` 逐域名询问;必须紧跟用户手势,因此放在其它 await 之前。
- `src/permissions.ts`(UMD 全局 `Perms`,依赖 utils.js 的 `toMatchPatterns`):`patternsForDomain` / `isPatternGranted` / `partitionPatterns` / `partitionDomains` / `requestForDomain`。
- background 只注册**已授权**的 match pattern(未授权会让整批注册失败);监听 `chrome.permissions.onAdded/onRemoved` 重算注入范围。
- 手动填充与"填入当前域名"走 `activeTab`(点扩展图标即授权,零弹框);因此切标签页后可能需要重新点图标。
- 未授权域名在配置列表显示"未授权"标签,点击即可重新申请(i18n: tagUnauthorized 等 7 个新键)。
- 兜底:若 `registerContentScripts` 失败(文档未明确"运行时授权的主机权限"能否用于动态注册),自动降级为 `chrome.tabs.onUpdated` + `executeScript` 按需注入(`fallbackInjection` 开关)。**待真机验证**。
- 未声明 `tabs` 的副作用:`chrome.tabs.query({url})` 的 url 过滤被忽略而非报错 → 任何按 url 过滤的结果必须再自行校验 `isDomainMatch`(见 ui-utils.fetchTitleByDomain)。
## 已知安全债(2026-09-17 已修复前两条)
1. **已修复**:原实现把主口令明文存 `chrome.storage.session.__autoPwd`。现改为**把 non-extractable CryptoKey 句柄存入 IndexedDB**(`src/key-store.ts`),SW 重启后用 `CryptoStore.unlockWithKey(key)` 恢复解锁,口令不落任何存储;`doLock()` 会删除句柄。旧版 `__autoPwd` 保留一次性迁移分支(读到即转句柄并删除明文)。
- 已核实:`storage.session` 默认**不向 content script 开放**(仅受信任上下文)——早前"任何 content script 都能读到"的说法是错的。
- 已核实:CryptoKey 支持 structured clone 存入 IndexedDB,取回后 extractable 仍为 false(exportKey 抛错)但可正常 decrypt。
2. **已修复**:原 manifest 静态 `content_scripts` + `<all_urls>` 无条件注入。现移除静态声明,background 通过 `chrome.scripting.registerContentScripts` 只注册**用户配置过的域名**(`syncContentScripts`,由 `utils.toMatchPatterns` 生成 match pattern);popup 手动填充改为"探测 `window.__autoLoginInjected` → `chrome.scripting.executeScript` 按需注入"。
- 仍保留 `host_permissions: ["<all_urls>"]`(按需注入需要),若要进一步收窄安装提示可改 `optional_host_permissions` 并按域名申请。
- 锁定时 session 无明文域名 → 注入范围自动清空;解锁后才重新注册。
## 项目性质
Chrome MV3「Auto Login Manager」扩展(域名配置自动填充 + 加密存储 + 多环境)。自建 Gitea 仓库:`http://218.61.196.156:41695/Chrome-extension/auto-login.git`。
## 环境注意事项
- npm 全局缓存 `~/.npm` 有权限问题,`npm install` 必须加 `--cache /tmp/npm-cache-autologin`。
- 全局安装不可用(权限),依赖一律装为 devDependency。
## 代码规范与日志约定(2026-09-17 建立)
- ESLint 扁平配置 `eslint.config.mjs`:js.recommended + typescript-eslint.recommended,但关掉与 tsc 重叠/噪音大的规则(no-undef、no-explicit-any、no-non-null-assertion),重点保留 `no-unused-vars`(`^_` 前缀豁免、caughtErrors: none)、`prefer-const`、`no-var`、`no-empty(allowEmptyCatch)`。测试文件按 CJS 编写,已单独关闭 `no-require-imports`。
- Prettier:`.prettierrc.json`(printWidth 110 / 双引号 / 分号 / trailingComma all)。格式化范围**仅** `src`、`scripts`、`tests`,不含 popup.html / popup.css / _locales(避免上千行空白 diff)。
- 命令:`npm run lint` / `lint:fix` / `format` / `format:check`;CI 在测试前执行 lint + format:check。改代码后请跑 `npm run format && npm run lint`。
- 日志:信息类日志一律用全局 `debugLog(...)`(utils.ts 提供,默认静默),**不要**直接 `console.log`。异常告警用 `console.warn`。开关:`chrome.storage.local.set({ debugLog: true })`。
## 构建约定(2026-09-17 建立)
- 类型检查与产物生成分离:`tsc --noEmit` 只做类型检查(tsconfig 已设 `noEmit: true`),**esbuild 负责产出 dist/**。
- 产物分三类,不可混为一谈:
1. UMD 全局脚本 `utils/env-store/crypto-store`:**必须不打包、不指定 format**。一旦指定 iife,esbuild 会把 `module.exports` 换成合成模块对象,Node 测试 `require(dist/xxx.js)` 就取不到导出。
2. 独立入口 `background/content`:不打包(运行期靠 `importScripts` 与全局变量协作)。
3. `popup/main.ts`:唯一真正打包的入口(ESM,静态依赖全部内联,由 popup.html 以 `type="module"` 加载)。
- `scripts/build.mjs` 会校验 manifest.json / popup.html 引用的文件是否都存在(11 处),并检查 manifest 与 package.json 版本一致性。
- dist/ 已从 git 移除(`.gitignore`),克隆后必须先 `npm run build` 才能加载扩展。
- 测试依赖 dist:`npm test` = build + `node --test tests/*.test.js`;只想跑测试用 `npm run test:only`(需已有 dist)。
- 发布:`npm run release -- patch|minor|major|x.y.z [--codebase <url>]` → 同步 manifest.json / package.json / updates.xml(只替换 version 行,保留原格式)→ 发布构建 → `release/auto-login-v<ver>.zip`。不自动 commit/tag。
## 部署产物
- `updates.xml`:自托管自更新清单,`appid=dpcmpkmjkcenjpfcmkoohkcnhbomgmej`,codebase 指向 Windows 本地路径的 .crx。
- `install-policy.reg`:Windows 强制安装策略。两者都不打进扩展 zip。