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