安全与权限: - 站点访问权限改为 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 同步
8.1 KiB
8.1 KiB
auto-login 项目长期记忆
自动提交限流语义(2026-09-18 修正)
- 计数的是自动提交次数(不是"登录失败次数",扩展无法知道凭据对不对),key 为
envId::domain,存chrome.storage.local.loginAttempts。上限 3 次,冷却 5 分钟。 - 计数时机:只在真正触发提交后(
fillForm的onSubmitted回调)累加,fillSingleConfig不再提前计数。填写阶段(未提交)不计数。 - 成功检测(两条路径,命中任一即清零):
- 提交后
submitResultCheckMs(默认 4s)观察:登录框消失或已离开匹配地址 →resetDomainAttempts - 整页跳转导致脚本销毁时,由新页面兜底:命中配置但
fillTimeoutMs(默认 30s)内始终没有登录框 →resetDomainAttempts
- 提交后
- 达到上限时页面必须给可见提示(
showAttemptsPausedToast,i18nattemptToastTitle/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 已修复前两条)
- 已修复:原实现把主口令明文存
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。
- 已核实:
- 已修复:原 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/。 - 产物分三类,不可混为一谈:
- UMD 全局脚本
utils/env-store/crypto-store:必须不打包、不指定 format。一旦指定 iife,esbuild 会把module.exports换成合成模块对象,Node 测试require(dist/xxx.js)就取不到导出。 - 独立入口
background/content:不打包(运行期靠importScripts与全局变量协作)。 popup/main.ts:唯一真正打包的入口(ESM,静态依赖全部内联,由 popup.html 以type="module"加载)。
- UMD 全局脚本
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。