feat: 添加源地址管理功能,确保在打开新标签前清理旧标签
This commit is contained in:
@@ -297,6 +297,7 @@ Step 3 使用的注册邮箱。
|
|||||||
- localhost 回调地址
|
- localhost 回调地址
|
||||||
- 账号记录
|
- 账号记录
|
||||||
- tab 注册信息
|
- tab 注册信息
|
||||||
|
- 各来源最近一次打开的地址(用于打开新地址前清理旧标签)
|
||||||
- 自定义设置
|
- 自定义设置
|
||||||
- Auto 当前阶段、当前轮次、暂停信息
|
- Auto 当前阶段、当前轮次、暂停信息
|
||||||
|
|
||||||
@@ -305,6 +306,7 @@ Step 3 使用的注册邮箱。
|
|||||||
- 浏览器会话级存储
|
- 浏览器会话级存储
|
||||||
- 扩展运行期间可在多个步骤之间共享
|
- 扩展运行期间可在多个步骤之间共享
|
||||||
- 代码里已启用 `storage.session` 对 content script 的访问
|
- 代码里已启用 `storage.session` 对 content script 的访问
|
||||||
|
- 同一来源打开新地址前,会先按来源/站点范围关闭旧标签,避免旧页面残留
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const DEFAULT_STATE = {
|
|||||||
localhostUrl: null,
|
localhostUrl: null,
|
||||||
flowStartTime: null,
|
flowStartTime: null,
|
||||||
tabRegistry: {},
|
tabRegistry: {},
|
||||||
|
sourceLastUrls: {},
|
||||||
logs: [],
|
logs: [],
|
||||||
vpsUrl: '',
|
vpsUrl: '',
|
||||||
vpsPassword: '',
|
vpsPassword: '',
|
||||||
@@ -93,6 +94,7 @@ async function resetState() {
|
|||||||
'seenInbucketMailIds',
|
'seenInbucketMailIds',
|
||||||
'accounts',
|
'accounts',
|
||||||
'tabRegistry',
|
'tabRegistry',
|
||||||
|
'sourceLastUrls',
|
||||||
'vpsUrl',
|
'vpsUrl',
|
||||||
'vpsPassword',
|
'vpsPassword',
|
||||||
'customPassword',
|
'customPassword',
|
||||||
@@ -108,6 +110,7 @@ async function resetState() {
|
|||||||
seenInbucketMailIds: prev.seenInbucketMailIds || [],
|
seenInbucketMailIds: prev.seenInbucketMailIds || [],
|
||||||
accounts: prev.accounts || [],
|
accounts: prev.accounts || [],
|
||||||
tabRegistry: prev.tabRegistry || {},
|
tabRegistry: prev.tabRegistry || {},
|
||||||
|
sourceLastUrls: prev.sourceLastUrls || {},
|
||||||
vpsUrl: prev.vpsUrl || '',
|
vpsUrl: prev.vpsUrl || '',
|
||||||
vpsPassword: prev.vpsPassword || '',
|
vpsPassword: prev.vpsPassword || '',
|
||||||
customPassword: prev.customPassword || '',
|
customPassword: prev.customPassword || '',
|
||||||
@@ -180,6 +183,81 @@ async function getTabId(source) {
|
|||||||
return registry[source]?.tabId || null;
|
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)
|
// Command Queue (for content scripts not yet ready)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -226,6 +304,7 @@ async function reuseOrCreateTab(source, url, options = {}) {
|
|||||||
const alive = await isTabAlive(source);
|
const alive = await isTabAlive(source);
|
||||||
if (alive) {
|
if (alive) {
|
||||||
const tabId = await getTabId(source);
|
const tabId = await getTabId(source);
|
||||||
|
await closeConflictingTabsForSource(source, url, { excludeTabIds: [tabId] });
|
||||||
const currentTab = await chrome.tabs.get(tabId);
|
const currentTab = await chrome.tabs.get(tabId);
|
||||||
const sameUrl = currentTab.url === url;
|
const sameUrl = currentTab.url === url;
|
||||||
const shouldReloadOnReuse = sameUrl && options.reloadIfSameUrl;
|
const shouldReloadOnReuse = sameUrl && options.reloadIfSameUrl;
|
||||||
@@ -273,6 +352,7 @@ async function reuseOrCreateTab(source, url, options = {}) {
|
|||||||
await new Promise(r => setTimeout(r, 500));
|
await new Promise(r => setTimeout(r, 500));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await rememberSourceLastUrl(source, url);
|
||||||
return tabId;
|
return tabId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,10 +397,12 @@ async function reuseOrCreateTab(source, url, options = {}) {
|
|||||||
// Wait a bit for content script to inject and send READY
|
// Wait a bit for content script to inject and send READY
|
||||||
await new Promise(r => setTimeout(r, 500));
|
await new Promise(r => setTimeout(r, 500));
|
||||||
|
|
||||||
|
await rememberSourceLastUrl(source, url);
|
||||||
return tabId;
|
return tabId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new tab
|
// Create new tab
|
||||||
|
await closeConflictingTabsForSource(source, url);
|
||||||
const tab = await chrome.tabs.create({ url, active: true });
|
const tab = await chrome.tabs.create({ url, active: true });
|
||||||
console.log(LOG_PREFIX, `Created new tab ${source} (${tab.id})`);
|
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;
|
return tab.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1810,9 +1893,11 @@ async function executeStep9(state) {
|
|||||||
const alive = tabId && await isTabAlive('vps-panel');
|
const alive = tabId && await isTabAlive('vps-panel');
|
||||||
|
|
||||||
if (!alive) {
|
if (!alive) {
|
||||||
|
await closeConflictingTabsForSource('vps-panel', state.vpsUrl);
|
||||||
// Create new tab
|
// Create new tab
|
||||||
const tab = await chrome.tabs.create({ url: state.vpsUrl, active: true });
|
const tab = await chrome.tabs.create({ url: state.vpsUrl, active: true });
|
||||||
tabId = tab.id;
|
tabId = tab.id;
|
||||||
|
await rememberSourceLastUrl('vps-panel', state.vpsUrl);
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
const listener = (tid, info) => {
|
const listener = (tid, info) => {
|
||||||
if (tid === tabId && info.status === 'complete') {
|
if (tid === tabId && info.status === 'complete') {
|
||||||
@@ -1823,7 +1908,9 @@ async function executeStep9(state) {
|
|||||||
chrome.tabs.onUpdated.addListener(listener);
|
chrome.tabs.onUpdated.addListener(listener);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
await closeConflictingTabsForSource('vps-panel', state.vpsUrl, { excludeTabIds: [tabId] });
|
||||||
await chrome.tabs.update(tabId, { active: true });
|
await chrome.tabs.update(tabId, { active: true });
|
||||||
|
await rememberSourceLastUrl('vps-panel', state.vpsUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject scripts directly and wait for them to be ready
|
// Inject scripts directly and wait for them to be ready
|
||||||
|
|||||||
Reference in New Issue
Block a user