安全与权限: - 站点访问权限改为 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 同步
65 lines
8.1 KiB
Markdown
65 lines
8.1 KiB
Markdown
# 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。
|