修复填入当前域名:错误提示层级与侧边栏取址
This commit is contained in:
@@ -966,7 +966,8 @@ h2 {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
z-index: 9999;
|
/* 必须高于弹窗遮罩(.modal z-index: 10000),否则弹窗内操作触发的提示会被遮罩盖住 */
|
||||||
|
z-index: 10001;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
+37
-1
@@ -5,8 +5,11 @@ let autoLockTimer: ReturnType<typeof setTimeout> | null = null;
|
|||||||
const AUTO_LOCK_MS = 10 * 60 * 1000; // 10 分钟无操作自动锁定
|
const AUTO_LOCK_MS = 10 * 60 * 1000; // 10 分钟无操作自动锁定
|
||||||
|
|
||||||
chrome.runtime.onInstalled.addListener(() => {
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
|
// 关闭"点击图标即开面板"的内置行为,改由 action.onClicked 接管:
|
||||||
|
// 点击图标那一刻 Chrome 会授予 activeTab,onClicked 里趁机捕获当前标签页
|
||||||
|
// 地址缓存起来,侧边栏里"填入当前域名"在切换标签页后也能取到地址。
|
||||||
chrome.sidePanel
|
chrome.sidePanel
|
||||||
.setPanelBehavior({ openPanelOnActionClick: true })
|
.setPanelBehavior({ openPanelOnActionClick: false })
|
||||||
.catch((err) => console.warn("[AutoLogin] 设置 sidePanel 行为失败:", err));
|
.catch((err) => console.warn("[AutoLogin] 设置 sidePanel 行为失败:", err));
|
||||||
|
|
||||||
migrateLegacyVault();
|
migrateLegacyVault();
|
||||||
@@ -20,10 +23,43 @@ chrome.runtime.onStartup.addListener(() => {
|
|||||||
|
|
||||||
chrome.action.onClicked.addListener((tab) => {
|
chrome.action.onClicked.addListener((tab) => {
|
||||||
if (tab?.id !== undefined) {
|
if (tab?.id !== undefined) {
|
||||||
|
// 点击图标的用户手势已授予 activeTab,此刻 tab.url 一定可读;
|
||||||
|
// 缓存地址供侧边栏后续使用(侧边栏持久的场景下切换标签页后权限会失效)
|
||||||
|
if (tab.url && /^https?:/.test(tab.url)) {
|
||||||
|
chrome.storage.session
|
||||||
|
.set({
|
||||||
|
[LAST_AUTHORIZED_TAB]: {
|
||||||
|
tabId: tab.id,
|
||||||
|
url: tab.url,
|
||||||
|
title: tab.title || "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => console.warn("[AutoLogin] 缓存当前标签页地址失败:", err));
|
||||||
|
}
|
||||||
chrome.sidePanel.open({ tabId: tab.id }).catch(() => {});
|
chrome.sidePanel.open({ tabId: tab.id }).catch(() => {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 标签页导航 / 关闭后,缓存的地址作废,避免把导航前的旧地址填进表单
|
||||||
|
const LAST_AUTHORIZED_TAB = "lastAuthorizedTab";
|
||||||
|
|
||||||
|
async function clearLastAuthorizedTab(tabId: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
const data = await chrome.storage.session.get(LAST_AUTHORIZED_TAB);
|
||||||
|
const cached = data[LAST_AUTHORIZED_TAB] as { tabId?: number } | undefined;
|
||||||
|
if (cached?.tabId === tabId) {
|
||||||
|
await chrome.storage.session.remove(LAST_AUTHORIZED_TAB);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 缓存清理失败不影响主流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
||||||
|
if (changeInfo.url) clearLastAuthorizedTab(tabId);
|
||||||
|
});
|
||||||
|
chrome.tabs.onRemoved.addListener((tabId) => clearLastAuthorizedTab(tabId));
|
||||||
|
|
||||||
// 旧版明文数据迁移:把 loginConfigs 转为 environments 明文结构
|
// 旧版明文数据迁移:把 loginConfigs 转为 environments 明文结构
|
||||||
async function migrateLegacyVault() {
|
async function migrateLegacyVault() {
|
||||||
const data = await chrome.storage.local.get(["environments", "loginConfigs", "vaultMeta"]);
|
const data = await chrome.storage.local.get(["environments", "loginConfigs", "vaultMeta"]);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { t } from "../i18n.js";
|
|||||||
import { resolveAlias, fetchFaviconByDomain } from "../ui-utils.js";
|
import { resolveAlias, fetchFaviconByDomain } from "../ui-utils.js";
|
||||||
import type { DomainConfig } from "../types.js";
|
import type { DomainConfig } from "../types.js";
|
||||||
import { $ } from "./dom";
|
import { $ } from "./dom";
|
||||||
|
import { getActiveTabInfo } from "./current-tab";
|
||||||
import type { PopupCtx } from "./context";
|
import type { PopupCtx } from "./context";
|
||||||
|
|
||||||
const editConfigModal = $("editConfigModal");
|
const editConfigModal = $("editConfigModal");
|
||||||
@@ -129,19 +130,16 @@ export function initEditConfigModal(ctx: PopupCtx) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 填入当前标签页域名
|
// 填入当前标签页域名
|
||||||
editFillDomainBtn.addEventListener("click", async () => {
|
editFillDomainBtn.addEventListener("click", async () => {
|
||||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
// 侧边栏场景下 activeTab 授权不可靠,统一走 getActiveTabInfo 的缓存兜底
|
||||||
if (!tab) {
|
const info = await getActiveTabInfo();
|
||||||
ctx.showToast(t("toastNoTab"));
|
if (!info) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!tab.url) {
|
|
||||||
ctx.showToast(t("toastNoTabUrl"));
|
ctx.showToast(t("toastNoTabUrl"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const hostname = new URL(tab.url).hostname;
|
const hostname = new URL(info.url).hostname;
|
||||||
if (!hostname) {
|
if (!hostname) {
|
||||||
ctx.showToast(t("toastNoDomain"));
|
ctx.showToast(t("toastNoDomain"));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { t } from "../i18n.js";
|
|||||||
import { resolveAlias, fetchFaviconByDomain } from "../ui-utils.js";
|
import { resolveAlias, fetchFaviconByDomain } from "../ui-utils.js";
|
||||||
import type { DomainConfig } from "../types.js";
|
import type { DomainConfig } from "../types.js";
|
||||||
import { $ } from "./dom";
|
import { $ } from "./dom";
|
||||||
|
import { getActiveTabInfo } from "./current-tab";
|
||||||
import type { PopupCtx } from "./context";
|
import type { PopupCtx } from "./context";
|
||||||
|
|
||||||
const domainInput = $("domain");
|
const domainInput = $("domain");
|
||||||
@@ -135,20 +136,16 @@ export function initConfigForm(ctx: PopupCtx) {
|
|||||||
ctx.refreshEnvManage();
|
ctx.refreshEnvManage();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 填入当前标签页域名
|
// 填入当前标签页域名
|
||||||
fillDomainBtn.addEventListener("click", async () => {
|
fillDomainBtn.addEventListener("click", async () => {
|
||||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
// 侧边栏场景下 activeTab 授权不可靠,统一走 getActiveTabInfo 的缓存兜底
|
||||||
if (!tab) {
|
const info = await getActiveTabInfo();
|
||||||
ctx.showToast(t("toastNoTab"));
|
if (!info) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!tab.url) {
|
|
||||||
// 未授权时读不到地址:提示用户点扩展图标重新授予 activeTab
|
|
||||||
ctx.showToast(t("toastNoTabUrl"));
|
ctx.showToast(t("toastNoTabUrl"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const u = new URL(tab.url);
|
const u = new URL(info.url);
|
||||||
const host = u.host;
|
const host = u.host;
|
||||||
if (!host) {
|
if (!host) {
|
||||||
ctx.showToast(t("toastNoDomain"));
|
ctx.showToast(t("toastNoDomain"));
|
||||||
@@ -156,7 +153,7 @@ export function initConfigForm(ctx: PopupCtx) {
|
|||||||
}
|
}
|
||||||
const value = u.pathname && u.pathname !== "/" ? host + u.pathname : host;
|
const value = u.pathname && u.pathname !== "/" ? host + u.pathname : host;
|
||||||
domainInput.value = value;
|
domainInput.value = value;
|
||||||
if (tab.title) aliasInput.value = tab.title;
|
if (info.title) aliasInput.value = info.title;
|
||||||
saveDraft();
|
saveDraft();
|
||||||
ctx.showToast(t("toastDomainFilled", value));
|
ctx.showToast(t("toastDomainFilled", value));
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 取当前活动标签页的 URL / 标题,供"填入当前域名"等按钮使用。
|
||||||
|
*
|
||||||
|
* 本扩展以侧边栏(side panel)承载 popup.html,而 activeTab 是临时权限:
|
||||||
|
* 只有用户点击浏览器工具栏的扩展图标那一瞬间才会授予"当时激活的标签页",
|
||||||
|
* 切换标签页或页面导航后权限即失效,chrome.tabs.query 返回的 tab.url 会是
|
||||||
|
* undefined(扩展也没有 tabs / 站点 host 权限时读不到地址)。
|
||||||
|
*
|
||||||
|
* 因此取址按两级兜底:
|
||||||
|
* 1. 直接读 tab.url —— 刚点过扩展图标、或该站点已授权 host 权限时可用;
|
||||||
|
* 2. 读 background 在"点击图标授权瞬间"缓存到 session storage 的
|
||||||
|
* lastAuthorizedTab —— 仅当缓存的 tabId 与当前活动标签页一致才使用,
|
||||||
|
* 防止把别的标签页/导航前的旧地址填进表单。
|
||||||
|
*/
|
||||||
|
export interface ActiveTabInfo {
|
||||||
|
/** 当前活动标签页 id(用于注入 content script 等场景) */
|
||||||
|
tabId: number;
|
||||||
|
url: string;
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LAST_AUTHORIZED_TAB = "lastAuthorizedTab";
|
||||||
|
|
||||||
|
export async function getActiveTabInfo(): Promise<ActiveTabInfo | null> {
|
||||||
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
if (!tab || tab.id === undefined) return null;
|
||||||
|
if (tab.url) return { tabId: tab.id, url: tab.url, title: tab.title };
|
||||||
|
|
||||||
|
// 直接读不到:尝试 background 在授权瞬间缓存的地址
|
||||||
|
const data = await chrome.storage.session.get(LAST_AUTHORIZED_TAB);
|
||||||
|
const cached = data[LAST_AUTHORIZED_TAB] as
|
||||||
|
| { tabId?: number; url?: string; title?: string }
|
||||||
|
| undefined;
|
||||||
|
if (cached && cached.tabId === tab.id && cached.url) {
|
||||||
|
return { tabId: tab.id, url: cached.url, title: cached.title };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
+10
-12
@@ -11,6 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
import { t } from "../i18n.js";
|
import { t } from "../i18n.js";
|
||||||
import { $ } from "./dom";
|
import { $ } from "./dom";
|
||||||
|
import { getActiveTabInfo } from "./current-tab";
|
||||||
import type { PopupCtx } from "./context";
|
import type { PopupCtx } from "./context";
|
||||||
|
|
||||||
/** content script 已注入的标记,定义在 content.ts 顶部 */
|
/** content script 已注入的标记,定义在 content.ts 顶部 */
|
||||||
@@ -73,20 +74,17 @@ export function initFillCurrent(ctx: PopupCtx) {
|
|||||||
const fillCurrentBtn = $("fillCurrent");
|
const fillCurrentBtn = $("fillCurrent");
|
||||||
|
|
||||||
fillCurrentBtn.addEventListener("click", async () => {
|
fillCurrentBtn.addEventListener("click", async () => {
|
||||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
// 侧边栏场景下 activeTab 授权不可靠,统一走 getActiveTabInfo 的缓存兜底
|
||||||
if (!tab || tab.id === undefined) {
|
const info = await getActiveTabInfo();
|
||||||
ctx.showToast(t("toastNoTab"));
|
if (!info) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!tab.url) {
|
|
||||||
// 该标签页没有 activeTab 授权(例如切换标签页后侧边栏仍开着)
|
|
||||||
ctx.showToast(t("toastNoTabUrl"));
|
ctx.showToast(t("toastNoTabUrl"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const tabId = info.tabId;
|
||||||
|
|
||||||
// 跨所有环境查找匹配当前标签页的配置
|
// 跨所有环境查找匹配当前标签页的配置
|
||||||
const allConfigs = await EnvStore.loadAllConfigs();
|
const allConfigs = await EnvStore.loadAllConfigs();
|
||||||
const matched = allConfigs.filter((c) => isDomainMatch(tab.url, c.domain));
|
const matched = allConfigs.filter((c) => isDomainMatch(info.url, c.domain));
|
||||||
|
|
||||||
if (matched.length === 0) {
|
if (matched.length === 0) {
|
||||||
ctx.showToast(t("toastNoMatch"));
|
ctx.showToast(t("toastNoMatch"));
|
||||||
@@ -95,8 +93,8 @@ export function initFillCurrent(ctx: PopupCtx) {
|
|||||||
|
|
||||||
if (matched.length === 1) {
|
if (matched.length === 1) {
|
||||||
try {
|
try {
|
||||||
const frameId = await ensureContentScript(tab.id);
|
const frameId = await ensureContentScript(tabId);
|
||||||
const resp: any = await sendToTab(tab.id, { action: "manualFill", config: matched[0] }, frameId);
|
const resp: any = await sendToTab(tabId, { action: "manualFill", config: matched[0] }, frameId);
|
||||||
if (resp && (resp as any).success) {
|
if (resp && (resp as any).success) {
|
||||||
ctx.showToast(t("toastFilled"));
|
ctx.showToast(t("toastFilled"));
|
||||||
} else if (resp && resp.diag) {
|
} else if (resp && resp.diag) {
|
||||||
@@ -112,8 +110,8 @@ export function initFillCurrent(ctx: PopupCtx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const frameId = await ensureContentScript(tab.id);
|
const frameId = await ensureContentScript(tabId);
|
||||||
await sendToTab(tab.id, { action: "manualFillMulti", configs: matched, tabUrl: tab.url }, frameId);
|
await sendToTab(tabId, { action: "manualFillMulti", configs: matched, tabUrl: info.url }, frameId);
|
||||||
ctx.showToast(t("toastMultiMatch", String(matched.length)));
|
ctx.showToast(t("toastMultiMatch", String(matched.length)));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toastSendError(ctx, err);
|
toastSendError(ctx, err);
|
||||||
|
|||||||
Reference in New Issue
Block a user