瀏覽代碼

feat: 添加源地址管理功能,确保在打开新标签前清理旧标签

QLHazyCoder 4 月之前
父節點
當前提交
9d4b86e7e0
共有 2 個文件被更改,包括 89 次插入0 次删除
  1. 2 0
      README.md
  2. 87 0
      background.js

+ 2 - 0
README.md

@@ -297,6 +297,7 @@ Step 3 使用的注册邮箱。
 - localhost 回调地址
 - 账号记录
 - tab 注册信息
+- 各来源最近一次打开的地址(用于打开新地址前清理旧标签)
 - 自定义设置
 - Auto 当前阶段、当前轮次、暂停信息
 
@@ -305,6 +306,7 @@ Step 3 使用的注册邮箱。
 - 浏览器会话级存储
 - 扩展运行期间可在多个步骤之间共享
 - 代码里已启用 `storage.session` 对 content script 的访问
+- 同一来源打开新地址前,会先按来源/站点范围关闭旧标签,避免旧页面残留
 
 ## 项目结构
 

+ 87 - 0
background.js

@@ -30,6 +30,7 @@ const DEFAULT_STATE = {
   localhostUrl: null,
   flowStartTime: null,
   tabRegistry: {},
+  sourceLastUrls: {},
   logs: [],
   vpsUrl: '',
   vpsPassword: '',
@@ -93,6 +94,7 @@ async function resetState() {
     'seenInbucketMailIds',
     'accounts',
     'tabRegistry',
+    'sourceLastUrls',
     'vpsUrl',
     'vpsPassword',
     'customPassword',
@@ -108,6 +110,7 @@ async function resetState() {
     seenInbucketMailIds: prev.seenInbucketMailIds || [],
     accounts: prev.accounts || [],
     tabRegistry: prev.tabRegistry || {},
+    sourceLastUrls: prev.sourceLastUrls || {},
     vpsUrl: prev.vpsUrl || '',
     vpsPassword: prev.vpsPassword || '',
     customPassword: prev.customPassword || '',
@@ -180,6 +183,81 @@ async function getTabId(source) {
   return registry[source]?.tabId || null;
 }
 
+function parseUrlSafely(rawUrl) {
+  if (!rawUrl) return null;
+  try {
+    return new URL(rawUrl);
+  } catch {
+    return null;
+  }
+}
+
+function isSignupPageHost(hostname = '') {
+  return ['auth0.openai.com', 'auth.openai.com', 'accounts.openai.com'].includes(hostname);
+}
+
+function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
+  const candidate = parseUrlSafely(candidateUrl);
+  if (!candidate) return false;
+
+  const reference = parseUrlSafely(referenceUrl);
+
+  switch (source) {
+    case 'signup-page':
+      return isSignupPageHost(candidate.hostname);
+    case 'duck-mail':
+      return candidate.hostname === 'duckduckgo.com' && candidate.pathname.startsWith('/email/');
+    case 'qq-mail':
+      return candidate.hostname === 'mail.qq.com' || candidate.hostname === 'wx.mail.qq.com';
+    case 'mail-163':
+      return candidate.hostname === 'mail.163.com';
+    case 'inbucket-mail':
+      return Boolean(reference)
+        && candidate.origin === reference.origin
+        && candidate.pathname.startsWith('/m/');
+    case 'vps-panel':
+      return Boolean(reference) && candidate.origin === reference.origin;
+    default:
+      return false;
+  }
+}
+
+async function rememberSourceLastUrl(source, url) {
+  if (!source || !url) return;
+  const state = await getState();
+  const sourceLastUrls = { ...(state.sourceLastUrls || {}) };
+  sourceLastUrls[source] = url;
+  await setState({ sourceLastUrls });
+}
+
+async function closeConflictingTabsForSource(source, currentUrl, options = {}) {
+  const { excludeTabIds = [] } = options;
+  const excluded = new Set(excludeTabIds.filter(id => Number.isInteger(id)));
+  const state = await getState();
+  const lastUrl = state.sourceLastUrls?.[source];
+  const referenceUrls = [currentUrl, lastUrl].filter(Boolean);
+
+  if (!referenceUrls.length) return;
+
+  const tabs = await chrome.tabs.query({});
+  const matchedIds = tabs
+    .filter((tab) => Number.isInteger(tab.id) && !excluded.has(tab.id))
+    .filter((tab) => referenceUrls.some((refUrl) => matchesSourceUrlFamily(source, tab.url, refUrl)))
+    .map(tab => tab.id);
+
+  if (!matchedIds.length) return;
+
+  await chrome.tabs.remove(matchedIds).catch(() => {});
+
+  const registry = await getTabRegistry();
+  if (registry[source]?.tabId && matchedIds.includes(registry[source].tabId)) {
+    registry[source] = null;
+    await setState({ tabRegistry: registry });
+  }
+
+  await addLog(`已关闭 ${matchedIds.length} 个旧的${getSourceLabel(source)}标签页。`, 'info');
+}
+
 // ============================================================
 // Command Queue (for content scripts not yet ready)
 // ============================================================
@@ -226,6 +304,7 @@ async function reuseOrCreateTab(source, url, options = {}) {
   const alive = await isTabAlive(source);
   if (alive) {
     const tabId = await getTabId(source);
+    await closeConflictingTabsForSource(source, url, { excludeTabIds: [tabId] });
     const currentTab = await chrome.tabs.get(tabId);
     const sameUrl = currentTab.url === url;
     const shouldReloadOnReuse = sameUrl && options.reloadIfSameUrl;
@@ -273,6 +352,7 @@ async function reuseOrCreateTab(source, url, options = {}) {
         await new Promise(r => setTimeout(r, 500));
       }
 
+      await rememberSourceLastUrl(source, url);
       return tabId;
     }
 
@@ -317,10 +397,12 @@ async function reuseOrCreateTab(source, url, options = {}) {
     // Wait a bit for content script to inject and send READY
     await new Promise(r => setTimeout(r, 500));
 
+    await rememberSourceLastUrl(source, url);
     return tabId;
   }
 
   // Create new tab
+  await closeConflictingTabsForSource(source, url);
   const tab = await chrome.tabs.create({ url, active: true });
   console.log(LOG_PREFIX, `Created new tab ${source} (${tab.id})`);
 
@@ -352,6 +434,7 @@ async function reuseOrCreateTab(source, url, options = {}) {
     });
   }
 
+  await rememberSourceLastUrl(source, url);
   return tab.id;
 }
 
@@ -1810,9 +1893,11 @@ async function executeStep9(state) {
   const alive = tabId && await isTabAlive('vps-panel');
 
   if (!alive) {
+    await closeConflictingTabsForSource('vps-panel', state.vpsUrl);
     // Create new tab
     const tab = await chrome.tabs.create({ url: state.vpsUrl, active: true });
     tabId = tab.id;
+    await rememberSourceLastUrl('vps-panel', state.vpsUrl);
     await new Promise(resolve => {
       const listener = (tid, info) => {
         if (tid === tabId && info.status === 'complete') {
@@ -1823,7 +1908,9 @@ async function executeStep9(state) {
       chrome.tabs.onUpdated.addListener(listener);
     });
   } else {
+    await closeConflictingTabsForSource('vps-panel', state.vpsUrl, { excludeTabIds: [tabId] });
     await chrome.tabs.update(tabId, { active: true });
+    await rememberSourceLastUrl('vps-panel', state.vpsUrl);
   }
 
   // Inject scripts directly and wait for them to be ready