From 253f15cee66101f27bfa334c5ffcd8368c349946 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Mon, 18 May 2026 15:10:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Kiro=20=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E6=B5=81=E7=A8=8B=E7=9A=84=20cookie=20=E6=B8=85?= =?UTF-8?q?=E7=90=86=E5=92=8C=E9=87=8D=E8=AF=95=E6=9C=BA=E5=88=B6=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=A8=E9=A1=B5=E9=9D=A2=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 1 + background/steps/kiro-device-auth.js | 189 ++++++++++++++++++ background/tab-runtime.js | 24 +++ manifest.json | 19 ++ shared/flow-registry.js | 6 + shared/source-registry.js | 37 +++- sidepanel/sidepanel.css | 11 +- sidepanel/sidepanel.html | 9 +- ...background-kiro-device-auth-module.test.js | 55 +++++ tests/background-tab-runtime-module.test.js | 54 +++++ tests/flow-registry-settings-schema.test.js | 8 + tests/sidepanel-flow-source-registry.test.js | 1 + tests/sidepanel-operation-delay.test.js | 5 +- tests/source-registry-module.test.js | 67 +++++++ 14 files changed, 471 insertions(+), 15 deletions(-) diff --git a/background.js b/background.js index 58377a9..903e078 100644 --- a/background.js +++ b/background.js @@ -13318,6 +13318,7 @@ const kiroDeviceAuthExecutor = self.MultiPageBackgroundKiroDeviceAuth?.createKir YYDS_MAIL_PROVIDER, MAIL_2925_VERIFICATION_INTERVAL_MS, MAIL_2925_VERIFICATION_MAX_ATTEMPTS, + isRetryableContentScriptTransportError, pollCloudflareTempEmailVerificationCode, pollCloudMailVerificationCode, pollHotmailVerificationCode, diff --git a/background/steps/kiro-device-auth.js b/background/steps/kiro-device-auth.js index 26d7fb3..c84f691 100644 --- a/background/steps/kiro-device-auth.js +++ b/background/steps/kiro-device-auth.js @@ -10,6 +10,25 @@ 'codewhisperer:transformations', 'codewhisperer:taskassist', ]); + const KIRO_STEP1_COOKIE_CLEAR_DOMAINS = Object.freeze([ + 'awsapps.com', + 'view.awsapps.com', + 'login.awsapps.com', + 'amazonaws.com', + 'signin.aws', + 'signin.aws.amazon.com', + 'profile.aws', + 'profile.aws.amazon.com', + ]); + const KIRO_STEP1_COOKIE_CLEAR_ORIGINS = Object.freeze([ + 'https://view.awsapps.com', + 'https://login.awsapps.com', + 'https://oidc.us-east-1.amazonaws.com', + 'https://signin.aws', + 'https://signin.aws.amazon.com', + 'https://profile.aws', + 'https://profile.aws.amazon.com', + ]); const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000; const KIRO_AWS_VERIFICATION_CODE_PATTERNS = Object.freeze([ @@ -107,6 +126,102 @@ return fallback; } + function normalizeKiroCookieDomain(domain = '') { + return String(domain || '').trim().replace(/^\.+/, '').toLowerCase(); + } + + function matchesKiroNamedHostFamily(domain = '', family = '') { + const normalizedDomain = normalizeKiroCookieDomain(domain); + const normalizedFamily = normalizeKiroCookieDomain(family); + if (!normalizedDomain || !normalizedFamily) { + return false; + } + return normalizedDomain === normalizedFamily + || normalizedDomain.endsWith(`.${normalizedFamily}`) + || normalizedDomain.startsWith(`${normalizedFamily}.`) + || normalizedDomain.includes(`.${normalizedFamily}.`); + } + + function shouldClearKiroStep1Cookie(cookie) { + const domain = normalizeKiroCookieDomain(cookie?.domain); + if (!domain) { + return false; + } + return KIRO_STEP1_COOKIE_CLEAR_DOMAINS.some((target) => ( + domain === target + || domain.endsWith(`.${target}`) + || matchesKiroNamedHostFamily(domain, target) + )); + } + + function buildKiroStep1CookieRemovalUrl(cookie) { + const host = normalizeKiroCookieDomain(cookie?.domain); + const rawPath = String(cookie?.path || '/'); + const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`; + return `https://${host}${path}`; + } + + async function collectKiroStep1Cookies(chromeApi) { + if (!chromeApi?.cookies?.getAll) { + return []; + } + + const stores = chromeApi.cookies.getAllCookieStores + ? await chromeApi.cookies.getAllCookieStores() + : [{ id: undefined }]; + const cookies = []; + const seen = new Set(); + + for (const store of stores) { + const storeId = store?.id; + const batch = await chromeApi.cookies.getAll(storeId ? { storeId } : {}); + for (const cookie of batch || []) { + if (!shouldClearKiroStep1Cookie(cookie)) { + continue; + } + const key = [ + cookie.storeId || storeId || '', + cookie.domain || '', + cookie.path || '', + cookie.name || '', + cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '', + ].join('|'); + if (seen.has(key)) { + continue; + } + seen.add(key); + cookies.push(cookie); + } + } + + return cookies; + } + + async function removeKiroStep1Cookie(chromeApi, cookie) { + const details = { + url: buildKiroStep1CookieRemovalUrl(cookie), + name: cookie.name, + }; + if (cookie.storeId) { + details.storeId = cookie.storeId; + } + if (cookie.partitionKey) { + details.partitionKey = cookie.partitionKey; + } + + try { + const result = await chromeApi.cookies.remove(details); + return Boolean(result); + } catch (error) { + console.warn('[MultiPage:kiro-step1] remove cookie failed', { + domain: cookie?.domain, + name: cookie?.name, + message: getErrorMessage(error), + }); + return false; + } + } + function buildCredentialUploadOptions(state = {}) { const next = { priority: Math.max(0, Math.floor(Number(state?.kiroRsPriority) || 0)), @@ -354,6 +469,7 @@ YYDS_MAIL_PROVIDER = 'yyds-mail', MAIL_2925_VERIFICATION_INTERVAL_MS = 15000, MAIL_2925_VERIFICATION_MAX_ATTEMPTS = 15, + isRetryableContentScriptTransportError = () => false, isTabAlive = async () => false, KIRO_DEVICE_AUTH_INJECT_FILES = null, pollCloudflareTempEmailVerificationCode = null, @@ -406,6 +522,35 @@ } } + async function clearKiroCookiesBeforeStep1() { + if (!chrome?.cookies?.getAll || !chrome.cookies?.remove) { + await log('步骤 1:当前浏览器不支持 cookies API,跳过打开 Kiro 授权页前 cookie 清理。', 'warn'); + return; + } + + await log('步骤 1:打开 Kiro 授权页前清理 AWS Builder ID 相关 cookies...', 'info'); + const cookies = await collectKiroStep1Cookies(chrome); + let removedCount = 0; + for (const cookie of cookies) { + if (await removeKiroStep1Cookie(chrome, cookie)) { + removedCount += 1; + } + } + + if (chrome.browsingData?.removeCookies) { + try { + await chrome.browsingData.removeCookies({ + since: 0, + origins: KIRO_STEP1_COOKIE_CLEAR_ORIGINS, + }); + } catch (error) { + await log(`步骤 1:browsingData 补扫 cookies 失败:${getErrorMessage(error)}`, 'warn'); + } + } + + await log(`步骤 1:已清理 ${removedCount} 个 AWS Builder ID 相关 cookies。`, 'ok'); + } + async function ensureKiroAuthTab(state = {}, options = {}) { let tabId = Number.isInteger(state?.kiroAuthTabId) ? state.kiroAuthTabId @@ -435,6 +580,42 @@ return tabId; } + async function reattachKiroContentScript(tabId, options = {}) { + if (!Number.isInteger(tabId)) { + throw new Error('缺少 Kiro 授权页标签页,无法重新连接内容脚本。'); + } + if (typeof waitForTabStableComplete === 'function') { + await waitForTabStableComplete(tabId, { + timeoutMs: 45000, + retryDelayMs: 300, + stableMs: Number(options.stableMs) || 1500, + initialDelayMs: Number(options.initialDelayMs) || 150, + }); + } + if (typeof ensureContentScriptReadyOnTab === 'function') { + await ensureContentScriptReadyOnTab('kiro-device-auth', tabId, { + inject: Array.isArray(KIRO_DEVICE_AUTH_INJECT_FILES) ? KIRO_DEVICE_AUTH_INJECT_FILES : null, + injectSource: 'kiro-device-auth', + timeoutMs: 45000, + retryDelayMs: 800, + logMessage: options.injectLogMessage || 'Kiro 授权页内容脚本未就绪,正在等待页面恢复...', + }); + } + } + + function buildKiroRetryRecovery(tabId, options = {}) { + return async (error) => { + if (!isRetryableContentScriptTransportError(error)) { + return; + } + await reattachKiroContentScript(tabId, { + stableMs: Number(options.recoveryStableMs) || Number(options.stableMs) || 1200, + initialDelayMs: Number(options.recoveryInitialDelayMs) || 120, + injectLogMessage: options.recoveryInjectLogMessage || options.injectLogMessage || 'Kiro 授权页已跳转,正在重新连接内容脚本...', + }); + }; + } + async function ensureKiroPageState(tabId, options = {}) { if (!Number.isInteger(tabId)) { throw new Error('缺少 Kiro 授权页标签页,无法继续执行。'); @@ -475,6 +656,7 @@ }, { timeoutMs: Math.max(30000, Number(options.pageTimeoutMs) || 30000), retryDelayMs: 700, + onRetryableError: buildKiroRetryRecovery(tabId, options), logMessage: options.readyLogMessage || '正在等待 Kiro 页面进入下一状态...', }); if (result?.error) { @@ -520,6 +702,7 @@ }, { timeoutMs: Math.max(30000, Number(options.pageTimeoutMs) || 30000), retryDelayMs: 700, + onRetryableError: buildKiroRetryRecovery(tabId, options), logMessage: options.readyLogMessage || '正在等待 Kiro 页面完成跳转...', }); if (result?.error) { @@ -739,6 +922,7 @@ async function executeKiroStartDeviceLogin(state = {}) { const nodeId = String(state?.nodeId || 'kiro-start-device-login').trim(); try { + await clearKiroCookiesBeforeStep1(); const auth = await startBuilderIdDeviceLogin(DEFAULT_REGION, fetchImpl); const loginUrl = cleanString(auth.verificationUriComplete || auth.verificationUri); const tabId = loginUrl ? await reuseOrCreateTab('kiro-device-auth', loginUrl) : null; @@ -834,6 +1018,7 @@ }, { timeoutMs: 30000, retryDelayMs: 700, + onRetryableError: buildKiroRetryRecovery(tabId, {}), logMessage: '步骤 2:正在向 Kiro 授权页提交邮箱...', }); if (submitResult?.error) { @@ -913,6 +1098,7 @@ }, { timeoutMs: 30000, retryDelayMs: 700, + onRetryableError: buildKiroRetryRecovery(tabId, {}), logMessage: '步骤 3:正在向 Kiro 姓名页提交姓名...', }); if (submitResult?.error) { @@ -990,6 +1176,7 @@ }, { timeoutMs: 30000, retryDelayMs: 700, + onRetryableError: buildKiroRetryRecovery(tabId, {}), logMessage: '步骤 4:正在向 Kiro 验证码页提交验证码...', }); if (submitResult?.error) { @@ -1072,6 +1259,7 @@ }, { timeoutMs: 30000, retryDelayMs: 700, + onRetryableError: buildKiroRetryRecovery(tabId, {}), logMessage: '步骤 5:正在向 Kiro 密码页提交密码...', }); if (submitResult?.error) { @@ -1155,6 +1343,7 @@ }, { timeoutMs: 60000, retryDelayMs: 700, + onRetryableError: buildKiroRetryRecovery(tabId, {}), logMessage: '步骤 6:正在处理 Kiro 授权确认页...', }); if (submitResult?.error) { diff --git a/background/tab-runtime.js b/background/tab-runtime.js index 29e8ced..7ecc9f2 100644 --- a/background/tab-runtime.js +++ b/background/tab-runtime.js @@ -616,6 +616,16 @@ return Math.max(1, Math.min(requestedTimeoutMs, Math.floor(Number(remainingTimeoutMs)))); } + function buildRetryableTransportTimeoutError(source, error) { + const rawMessage = error?.message || String(error || ''); + if (isRetryableContentScriptTransportError(error)) { + return new Error( + `${getSourceLabel(source)} 页面刚完成跳转或刷新,内容脚本还没有重新接回;扩展已自动重试,但仍未恢复。请重试当前步骤。` + ); + } + return new Error(rawMessage || `${getSourceLabel(source)} 页面通信失败。`); + } + function getMessageDebugLabel(source, message, tabId = null) { const parts = [source || 'unknown', message?.type || 'UNKNOWN']; if (Number.isInteger(message?.step)) parts.push(`step=${message.step}`); @@ -878,6 +888,7 @@ logMessage = '', logStep = null, logStepKey = '', + onRetryableError = null, responseTimeoutMs, } = options; const start = Date.now(); @@ -916,10 +927,23 @@ logged = true; } + if (typeof onRetryableError === 'function') { + await onRetryableError(err, { + attempt, + elapsedMs: Date.now() - start, + remainingTimeoutMs: Math.max(0, timeoutMs - (Date.now() - start)), + source, + message, + }); + } + await sleepOrStop(retryDelayMs); } } + if (lastError && isRetryableContentScriptTransportError(lastError)) { + throw buildRetryableTransportTimeoutError(source, lastError); + } throw lastError || new Error(`等待 ${getSourceLabel(source)} 重新就绪超时。`); } diff --git a/manifest.json b/manifest.json index b43891b..f19841d 100644 --- a/manifest.json +++ b/manifest.json @@ -116,6 +116,25 @@ "content/duck-mail.js" ], "run_at": "document_idle" + }, + { + "matches": [ + "https://view.awsapps.com/*", + "https://login.awsapps.com/*", + "https://*.awsapps.com/*", + "https://signin.aws/*", + "https://signin.aws.amazon.com/*", + "https://*.signin.aws/*", + "https://profile.aws/*", + "https://profile.aws.amazon.com/*", + "https://*.profile.aws/*" + ], + "js": [ + "shared/source-registry.js", + "content/utils.js", + "content/kiro-device-auth-page.js" + ], + "run_at": "document_idle" } ], "action": { diff --git a/shared/flow-registry.js b/shared/flow-registry.js index 88753f6..acdc2bb 100644 --- a/shared/flow-registry.js +++ b/shared/flow-registry.js @@ -53,6 +53,7 @@ 'openai-plus', 'openai-phone', 'openai-oauth', + 'openai-step6', ], sources: { cpa: { @@ -319,6 +320,11 @@ label: 'OAuth', rowIds: ['row-oauth-flow-timeout', 'row-oauth-display'], }, + 'openai-step6': { + id: 'openai-step6', + label: '第六步', + rowIds: ['row-step6-cookie-settings'], + }, 'kiro-source-kiro-rs': { id: 'kiro-source-kiro-rs', label: 'kiro.rs 配置', diff --git a/shared/source-registry.js b/shared/source-registry.js index a3ea104..8a1aa46 100644 --- a/shared/source-registry.js +++ b/shared/source-registry.js @@ -126,6 +126,32 @@ 'kiro-device-auth', ]); + function normalizeHostname(hostname = '') { + return String(hostname || '').trim().toLowerCase(); + } + + function matchesNamedHostFamily(hostname = '', family = '') { + const normalizedHost = normalizeHostname(hostname); + const normalizedFamily = normalizeHostname(family); + if (!normalizedHost || !normalizedFamily) { + return false; + } + return normalizedHost === normalizedFamily + || normalizedHost.endsWith(`.${normalizedFamily}`) + || normalizedHost.startsWith(`${normalizedFamily}.`) + || normalizedHost.includes(`.${normalizedFamily}.`); + } + + function isKiroAuthHost(hostname = '') { + const normalized = normalizeHostname(hostname); + return normalized === 'view.awsapps.com' + || normalized === 'login.awsapps.com' + || matchesNamedHostFamily(normalized, 'signin.aws') + || matchesNamedHostFamily(normalized, 'profile.aws') + || normalized === 'amazonaws.com' + || normalized.endsWith('.amazonaws.com'); + } + function getRuntimeSourceDefinitions() { return { ...(typeof flowRegistryApi.getRuntimeSourceDefinitions === 'function' @@ -227,15 +253,15 @@ } function isSignupPageHost(hostname = '') { - return AUTH_PAGE_HOSTS.has(String(hostname || '').toLowerCase()); + return AUTH_PAGE_HOSTS.has(normalizeHostname(hostname)); } function isSignupEntryHost(hostname = '') { - return ENTRY_PAGE_HOSTS.has(String(hostname || '').toLowerCase()); + return ENTRY_PAGE_HOSTS.has(normalizeHostname(hostname)); } function is163MailHost(hostname = '') { - const normalized = String(hostname || '').toLowerCase(); + const normalized = normalizeHostname(hostname); return normalized === 'mail.163.com' || normalized.endsWith('.mail.163.com') || normalized === 'mail.126.com' @@ -300,8 +326,7 @@ case 'gopay-flow': return /gopay|gojek/i.test(candidate.hostname); case 'kiro-device-auth': - return candidate.hostname === 'view.awsapps.com' - || candidate.hostname.endsWith('.amazonaws.com'); + return isKiroAuthHost(candidate.hostname); default: return false; } @@ -324,7 +349,7 @@ if (normalizedHostname === 'www.icloud.com' || normalizedHostname === 'www.icloud.com.cn') return 'icloud-mail'; if (normalizedUrl.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail'; if (normalizedUrl.includes('2925.com')) return 'mail-2925'; - if (normalizedHostname === 'view.awsapps.com' || normalizedHostname.endsWith('.amazonaws.com')) return 'kiro-device-auth'; + if (isKiroAuthHost(normalizedHostname)) return 'kiro-device-auth'; if (isSignupEntryHost(normalizedHostname)) return 'chatgpt'; return 'unknown-source'; } diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css index fe8864e..baca49d 100644 --- a/sidepanel/sidepanel.css +++ b/sidepanel/sidepanel.css @@ -2068,13 +2068,8 @@ header { flex-wrap: nowrap; } -#row-auto-delay-settings .setting-group-primary { - flex: 0 0 auto; - min-width: 116px; -} - #row-auto-delay-settings .setting-group-secondary { - margin-left: 0; + margin-left: auto; min-width: 0; } @@ -2082,6 +2077,10 @@ header { gap: 8px; } +#row-step6-cookie-settings .step6-cookie-cleanup-setting { + margin-left: auto; +} + .step-execution-range-setting { align-items: center; } diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 924ef5f..73cb5d9 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -592,9 +592,9 @@ -