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 同步
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* 添加按钮提交、"填入当前域名"按钮。
|
||||
*/
|
||||
import { t } from "../i18n.js";
|
||||
import { fetchTitleByDomain } from "../ui-utils.js";
|
||||
import { resolveAlias } from "../ui-utils.js";
|
||||
import type { DomainConfig } from "../types.js";
|
||||
import { $ } from "./dom";
|
||||
import type { PopupCtx } from "./context";
|
||||
@@ -24,9 +24,16 @@ const fillDomainBtn = $("fillDomain");
|
||||
const DRAFT_KEY = "formDraft";
|
||||
|
||||
const draftFields = [
|
||||
domainInput, aliasInput, usernameInput, passwordInput, autoSubmitSelect,
|
||||
usernameSelectorInput, passwordSelectorInput, submitSelectorInput,
|
||||
enabledCheckbox, delayMsInput,
|
||||
domainInput,
|
||||
aliasInput,
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
autoSubmitSelect,
|
||||
usernameSelectorInput,
|
||||
passwordSelectorInput,
|
||||
submitSelectorInput,
|
||||
enabledCheckbox,
|
||||
delayMsInput,
|
||||
];
|
||||
|
||||
export function initConfigForm(ctx: PopupCtx) {
|
||||
@@ -98,10 +105,13 @@ export function initConfigForm(ctx: PopupCtx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 申请站点访问权限必须紧跟用户手势,因此放在其它 await 之前
|
||||
const authorized = await Perms.requestForDomain(domain);
|
||||
|
||||
const config: DomainConfig = {
|
||||
id: EnvStore.genCfgId(),
|
||||
domain,
|
||||
alias: aliasInput.value.trim() || await fetchTitleByDomain(domain) || null,
|
||||
alias: aliasInput.value.trim() || (await resolveAlias(domain)) || null,
|
||||
username,
|
||||
password,
|
||||
autoSubmit: autoSubmitSelect.value === "true",
|
||||
@@ -115,7 +125,7 @@ export function initConfigForm(ctx: PopupCtx) {
|
||||
const configs = await EnvStore.loadConfigs();
|
||||
|
||||
configs.push(config);
|
||||
ctx.showToast(t("toastAdded"));
|
||||
ctx.showToast(authorized ? t("toastAdded") : t("toastUnauthorized"));
|
||||
|
||||
await EnvStore.saveConfigs(configs);
|
||||
resetForm();
|
||||
@@ -127,10 +137,15 @@ export function initConfigForm(ctx: PopupCtx) {
|
||||
// 填入当前标签页域名
|
||||
fillDomainBtn.addEventListener("click", async () => {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (!tab || !tab.url) {
|
||||
if (!tab) {
|
||||
ctx.showToast(t("toastNoTab"));
|
||||
return;
|
||||
}
|
||||
if (!tab.url) {
|
||||
// 未授权时读不到地址:提示用户点扩展图标重新授予 activeTab
|
||||
ctx.showToast(t("toastNoTabUrl"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const u = new URL(tab.url);
|
||||
const host = u.host;
|
||||
|
||||
Reference in New Issue
Block a user