diff --git a/background.js b/background.js index 3657472a..52510c63 100644 --- a/background.js +++ b/background.js @@ -769,6 +769,7 @@ async function refreshHotmailAccessToken(account) { formData.set('grant_type', 'refresh_token'); formData.set('refresh_token', account.refreshToken); formData.set('scope', scopes.join(' ')); + formData.set('redirect_uri', 'https://login.microsoftonline.com/common/oauth2/nativeclient'); let response; try { @@ -801,7 +802,11 @@ async function refreshHotmailAccessToken(account) { } if (!response.ok || !payload?.access_token) { - const errorText = payload?.error_description || payload?.error?.message || payload?.error || payload?.message || text || `HTTP ${response.status}`; + const rawErrorText = payload?.error_description || payload?.error?.message || payload?.error || payload?.message || text || `HTTP ${response.status}`; + const isCrossOriginError = typeof rawErrorText === 'string' && rawErrorText.includes('AADSTS90023'); + const errorText = isCrossOriginError + ? `Azure AD 拒绝了跨域令牌请求(AADSTS90023)。请在 Azure AD 应用注册中将应用平台改为"单页应用程序(SPA)",并将重定向 URI 设置为 https://login.microsoftonline.com/common/oauth2/nativeclient,或将应用类型改为"移动和桌面应用程序(Native)"。` + : rawErrorText; const error = new Error(`Hotmail 令牌刷新失败:${errorText}`); error.code = 'HOTMAIL_TOKEN_REFRESH_FAILED'; throw error; @@ -1263,6 +1268,30 @@ async function closeLocalhostCallbackTabs(callbackUrl, options = {}) { return matchedIds.length; } +function buildLocalhostCleanupPrefix(rawUrl) { + if (!isLocalhostOAuthCallbackUrl(rawUrl)) return ''; + const parsed = parseUrlSafely(rawUrl); + return parsed ? `${parsed.origin}/auth` : ''; +} + +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; +} + async function pingContentScriptOnTab(tabId) { if (!Number.isInteger(tabId)) return null; diff --git a/tests/step8-stop-cleanup.test.js b/tests/step8-stop-cleanup.test.js index f770a14a..baa018c3 100644 --- a/tests/step8-stop-cleanup.test.js +++ b/tests/step8-stop-cleanup.test.js @@ -125,6 +125,12 @@ async function addLog() {} async function broadcastStopToContentScripts() {} async function markRunningStepsStopped() {} async function broadcastAutoRunStatus() {} +async function getState() { + return { autoRunning: false }; +} +function isAutoRunScheduledState() { + return false; +} function getStep8CallbackUrlFromNavigation() { return ''; } function getStep8CallbackUrlFromTabUpdate() { return ''; } async function completeStepFromBackground() {} diff --git a/tests/step9-localhost-cleanup-scope.test.js b/tests/step9-localhost-cleanup-scope.test.js index edd73198..8b550ddc 100644 --- a/tests/step9-localhost-cleanup-scope.test.js +++ b/tests/step9-localhost-cleanup-scope.test.js @@ -56,6 +56,8 @@ const bundle = [ extractFunction('isLocalhostOAuthCallbackUrl'), extractFunction('isLocalhostOAuthCallbackTabMatch'), extractFunction('closeLocalhostCallbackTabs'), + extractFunction('buildLocalhostCleanupPrefix'), + extractFunction('closeTabsByUrlPrefix'), extractFunction('handleStepData'), ].join('\n');