Răsfoiți Sursa

fix: restore step9 localhost cleanup and hotmail validation handling

- 合并 PR #31 的核心改动:恢复 Step 9 标签页清理函数、补齐 Step 8/9 测试 mock,并优化 Hotmail AADSTS90023 报错提示\n- 本地补充修复:按真实 localhost callback 首段路径生成清理前缀,兼容 /auth/callback 与 /codex/callback\n- 影响范围:background Step 9 cleanup、Hotmail 账号校验、Step 8/9 回归测试
QLHazyCoder 4 luni în urmă
părinte
comite
6a90b52f6d

+ 37 - 1
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,37 @@ async function closeLocalhostCallbackTabs(callbackUrl, options = {}) {
   return matchedIds.length;
 }
 
+function buildLocalhostCleanupPrefix(rawUrl) {
+  if (!isLocalhostOAuthCallbackUrl(rawUrl)) return '';
+  const parsed = parseUrlSafely(rawUrl);
+  if (!parsed) return '';
+
+  const segments = parsed.pathname.split('/').filter(Boolean);
+  if (!segments.length) {
+    return parsed.origin;
+  }
+
+  return `${parsed.origin}/${segments[0]}`;
+}
+
+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;
 

+ 6 - 0
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() {}

+ 7 - 1
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');
 
@@ -159,7 +161,11 @@ return {
 
   await api.handleStepData(9, { localhostUrl: codexCallbackUrl });
   let snapshot = api.snapshot();
-  assert.deepStrictEqual(snapshot.removedBatches, [[1]], 'handleStepData(9) 只应关闭当前 callback 页');
+  assert.deepStrictEqual(
+    snapshot.removedBatches,
+    [[1], [2]],
+    'handleStepData(9) 应先关闭当前 callback 页,再按同源首段路径清理残留页'
+  );
   assert.strictEqual(
     snapshot.currentState.tabRegistry['signup-page'],
     null,