diff --git a/README.md b/README.md index 5fd471a..c831739 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,8 @@ Step 3 使用的注册邮箱。 - 自动填写 localhost 回调地址 - 自动点击“提交回调 URL” -- 等待“认证成功”状态 +- 必须等到 VPS 面板出现精确的 `认证成功!` 状态徽标后,才判定成功 +- 成功后会自动关闭匹配 `http://localhost:1455/auth` 这一类前缀的 localhost 残留页面 ## Duck 邮箱自动获取 diff --git a/background.js b/background.js index 02c435d..5d8fc7f 100644 --- a/background.js +++ b/background.js @@ -222,6 +222,18 @@ function isSignupPageHost(hostname = '') { return ['auth0.openai.com', 'auth.openai.com', 'accounts.openai.com'].includes(hostname); } +function buildLocalhostCleanupPrefix(rawUrl) { + const parsed = parseUrlSafely(rawUrl); + if (!parsed || parsed.hostname !== 'localhost') return ''; + + const segments = parsed.pathname.split('/').filter(Boolean); + if (!segments.length) { + return parsed.origin; + } + + return `${parsed.origin}/${segments[0]}`; +} + function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) { const candidate = parseUrlSafely(candidateUrl); if (!candidate) return false; @@ -284,6 +296,24 @@ async function closeConflictingTabsForSource(source, currentUrl, options = {}) { await addLog(`已关闭 ${matchedIds.length} 个旧的${getSourceLabel(source)}标签页。`, 'info'); } +async function closeTabsByUrlPrefix(prefix, options = {}) { + if (!prefix) return 0; + + const { excludeTabIds = [] } = options; + const excluded = new Set(excludeTabIds.filter(id => Number.isInteger(id))); + const tabs = await chrome.tabs.query({}); + const matchedIds = tabs + .filter((tab) => Number.isInteger(tab.id) && !excluded.has(tab.id)) + .filter((tab) => typeof tab.url === 'string' && tab.url.startsWith(prefix)) + .map((tab) => tab.id); + + if (!matchedIds.length) return 0; + + await chrome.tabs.remove(matchedIds).catch(() => {}); + await addLog(`已关闭 ${matchedIds.length} 个匹配 ${prefix} 的 localhost 残留标签页。`, 'info'); + return matchedIds.length; +} + // ============================================================ // Command Queue (for content scripts not yet ready) // ============================================================ @@ -897,6 +927,13 @@ async function handleStepData(step, payload) { broadcastDataUpdate({ localhostUrl: payload.localhostUrl }); } break; + case 9: { + const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl); + if (localhostPrefix) { + await closeTabsByUrlPrefix(localhostPrefix); + } + break; + } } } diff --git a/content/vps-panel.js b/content/vps-panel.js index ac51839..ef5c004 100644 --- a/content/vps-panel.js +++ b/content/vps-panel.js @@ -76,6 +76,34 @@ function getActionText(el) { .trim(); } +function getStatusBadgeElement() { + const candidates = document.querySelectorAll('.status-badge'); + return Array.from(candidates).find(isVisibleElement) || null; +} + +function getStatusBadgeText() { + const statusEl = getStatusBadgeElement(); + return statusEl ? (statusEl.textContent || '').replace(/\s+/g, ' ').trim() : ''; +} + +async function waitForExactSuccessBadge(timeout = 30000) { + const start = Date.now(); + + while (Date.now() - start < timeout) { + throwIfStopped(); + const statusText = getStatusBadgeText(); + if (statusText === '认证成功!') { + return statusText; + } + await sleep(200); + } + + const finalText = getStatusBadgeText(); + throw new Error(finalText + ? `VPS 面板状态不是“认证成功!”,当前为“${finalText}”。` + : 'VPS 面板长时间未出现“认证成功!”状态徽标。'); +} + function findManagementKeyInput() { const candidates = document.querySelectorAll( '.LoginPage-module__loginCard___OgP-R input[type="password"], input[placeholder*="管理密钥"], input[aria-label*="管理密钥"]' @@ -300,20 +328,7 @@ async function step9_vpsVerify(payload) { simulateClick(submitBtn); log('步骤 9:已点击“提交回调 URL”,正在等待认证结果...'); - // Wait for "认证成功!" status badge to appear - try { - await waitForElementByText('.status-badge, [class*="status"]', /认证成功/, 30000); - log('步骤 9:认证成功!', 'ok'); - } catch { - // Check if there's an error message instead - const statusEl = document.querySelector('.status-badge, [class*="status"]'); - const statusText = statusEl ? statusEl.textContent : 'unknown'; - if (/成功|success/i.test(statusText)) { - log('步骤 9:认证成功!', 'ok'); - } else { - log(`步骤 9:提交后的状态为“${statusText}”,可能仍在处理中。`, 'warn'); - } - } - - reportComplete(9); + const verifiedStatus = await waitForExactSuccessBadge(); + log(`步骤 9:${verifiedStatus}`, 'ok'); + reportComplete(9, { localhostUrl, verifiedStatus }); }