From 6f88486a7de5d0dd4638c69805825c178332f6bd Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Tue, 19 May 2026 14:49:46 +0800 Subject: [PATCH] Fix Kiro existing web session desktop auth --- background.js | 12 +- background/kiro/desktop-authorize-runner.js | 188 +++++++++++++++++- content/kiro/register-page.js | 76 +++++++ ...ro-desktop-authorize-runner-module.test.js | 120 ++++++++++- tests/background-skip-step-linking.test.js | 58 +++++- tests/kiro-register-page-fatal-state.test.js | 15 ++ 6 files changed, 452 insertions(+), 17 deletions(-) diff --git a/background.js b/background.js index 367fe93..5a56c28 100644 --- a/background.js +++ b/background.js @@ -10014,10 +10014,15 @@ async function skipNode(nodeId) { await setNodeStatus(normalizedNodeId, 'skipped'); await addLog(`节点 ${normalizedNodeId} 已跳过`, 'warn'); - if (normalizedNodeId === 'open-chatgpt') { + const linkedSkipNodeIdsByRoot = { + 'open-chatgpt': ['submit-signup-email', 'fill-password', 'fetch-signup-code', 'fill-profile', 'wait-registration-success'], + 'kiro-open-register-page': ['kiro-submit-email', 'kiro-submit-name', 'kiro-submit-verification-code', 'kiro-submit-password', 'kiro-complete-register-consent'], + }; + const linkedSkipNodeIds = linkedSkipNodeIdsByRoot[normalizedNodeId] || []; + if (linkedSkipNodeIds.length) { const latestState = await getState(); const skippedNodes = []; - for (const linkedNodeId of ['submit-signup-email', 'fill-password', 'fetch-signup-code', 'fill-profile', 'wait-registration-success']) { + for (const linkedNodeId of linkedSkipNodeIds) { const linkedStatus = latestState.nodeStatuses?.[linkedNodeId]; const linkedNodeAllowed = typeof isNodeExecutionAllowedForState === 'function' ? isNodeExecutionAllowedForState(linkedNodeId, latestState) @@ -10028,7 +10033,7 @@ async function skipNode(nodeId) { } } if (skippedNodes.length) { - await addLog(`节点 open-chatgpt 已跳过,节点 ${skippedNodes.join('、')} 也已同时跳过。`, 'warn'); + await addLog(`节点 ${normalizedNodeId} 已跳过,节点 ${skippedNodes.join('、')} 也已同时跳过。`, 'warn'); } } @@ -13290,6 +13295,7 @@ const kiroRegisterRunner = self.MultiPageBackgroundKiroRegisterRunner?.createKir getState, HOTMAIL_PROVIDER, isTabAlive, + KIRO_REGISTER_INJECT_FILES, LUCKMAIL_PROVIDER, CLOUDFLARE_TEMP_EMAIL_PROVIDER, CLOUD_MAIL_PROVIDER, diff --git a/background/kiro/desktop-authorize-runner.js b/background/kiro/desktop-authorize-runner.js index 45e7d9b..2857ba5 100644 --- a/background/kiro/desktop-authorize-runner.js +++ b/background/kiro/desktop-authorize-runner.js @@ -7,7 +7,12 @@ const DEFAULT_REGION = kiroStateApi?.DEFAULT_REGION || desktopClientApi?.DEFAULT_REGION || 'us-east-1'; const DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS = kiroTimeoutApi?.DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS || (3 * 60 * 1000); const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000; + const KIRO_REGISTER_PAGE_SOURCE_ID = 'kiro-register-page'; const KIRO_DESKTOP_SOURCE_ID = 'kiro-desktop-authorize'; + const KIRO_WEB_TAB_URL_PATTERNS = Object.freeze([ + 'https://app.kiro.dev/*', + 'https://kiro.dev/*', + ]); const KIRO_AWS_VERIFICATION_CODE_PATTERNS = Object.freeze([ Object.freeze({ source: '(?:verification\\s*code|验证码|Your code is|code is)[::\\s]*(\\d{6})', @@ -143,6 +148,20 @@ return error instanceof Error ? error.message : String(error ?? '未知错误'); } + function isKiroWebUrl(rawUrl = '') { + const normalizedUrl = cleanString(rawUrl); + if (!normalizedUrl) { + return false; + } + try { + const parsed = new URL(normalizedUrl); + const hostname = parsed.hostname.toLowerCase(); + return hostname === 'app.kiro.dev' || hostname === 'kiro.dev'; + } catch (_error) { + return false; + } + } + function parseDesktopCallbackUrl(rawUrl, expectedState = '', expectedPort = 0) { const normalizedUrl = cleanString(rawUrl); if (!normalizedUrl) { @@ -345,6 +364,7 @@ MAIL_2925_VERIFICATION_INTERVAL_MS = 15000, MAIL_2925_VERIFICATION_MAX_ATTEMPTS = 15, isTabAlive = async () => false, + KIRO_REGISTER_INJECT_FILES = null, KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = null, pollCloudflareTempEmailVerificationCode = null, pollCloudMailVerificationCode = null, @@ -650,6 +670,161 @@ }); } + async function collectKiroWebSessionTabs(currentState = {}) { + const runtimeState = readKiroRuntime(currentState); + const candidates = []; + const seen = new Set(); + const addTab = (tab) => { + const tabId = Number(tab?.id); + if (!Number.isInteger(tabId) || seen.has(tabId) || !isKiroWebUrl(tab?.url)) { + return; + } + seen.add(tabId); + candidates.push(tab); + }; + + const registeredTabId = runtimeState.session?.registerTabId; + if (Number.isInteger(registeredTabId) && chrome?.tabs?.get) { + const tab = await chrome.tabs.get(registeredTabId).catch(() => null); + addTab(tab); + } + + if (chrome?.tabs?.query) { + const queryKiroTabs = async (queryInfo) => { + const tabs = await chrome.tabs.query(queryInfo).catch(() => []); + for (const tab of tabs || []) { + addTab(tab); + } + }; + + await queryKiroTabs({ url: KIRO_WEB_TAB_URL_PATTERNS }); + await queryKiroTabs({ active: true, currentWindow: true }); + } + + return candidates; + } + + async function readKiroWebSessionStateFromTab(tabId, options = {}) { + const timeoutBudget = resolveTimeoutBudget(options); + if (typeof waitForTabStableComplete === 'function') { + await waitForTabStableComplete(tabId, { + timeoutMs: timeoutBudget.getRemainingMs(1000), + retryDelayMs: 300, + stableMs: Number(options.stableMs) || 1500, + initialDelayMs: Number(options.initialDelayMs) || 150, + }); + } + if (typeof ensureContentScriptReadyOnTab === 'function') { + await ensureContentScriptReadyOnTab(KIRO_REGISTER_PAGE_SOURCE_ID, tabId, { + inject: Array.isArray(KIRO_REGISTER_INJECT_FILES) ? KIRO_REGISTER_INJECT_FILES : null, + injectSource: KIRO_REGISTER_PAGE_SOURCE_ID, + timeoutMs: timeoutBudget.getRemainingMs(1000), + retryDelayMs: 800, + logMessage: options.injectLogMessage || '步骤 7:正在连接已登录的 Kiro Web 页面...', + }); + } + if (typeof sendToContentScriptResilient !== 'function') { + return null; + } + const stateWaitTimeoutMs = timeoutBudget.getRemainingMs(1000); + const result = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { + type: 'GET_KIRO_REGISTER_PAGE_STATE', + step: 7, + source: 'background', + }, { + timeoutMs: stateWaitTimeoutMs, + retryDelayMs: 700, + responseTimeoutMs: Math.min(stateWaitTimeoutMs, 10000), + logMessage: '步骤 7:正在读取 Kiro Web 登录态...', + }); + if (result?.error) { + throw new Error(result.error); + } + return result || null; + } + + async function restoreKiroWebSessionFromOpenTabs(currentState = {}, nodeId = '') { + const runtimeState = readKiroRuntime(currentState); + const existingEmail = cleanString(runtimeState.register?.email || currentState?.email); + const registerCompleted = cleanString(runtimeState.register?.status) === 'completed'; + const webSignedIn = cleanString(runtimeState.webAuth?.status) === 'signed_in'; + if (existingEmail && registerCompleted && webSignedIn) { + return { + currentState, + runtimeState, + restored: false, + }; + } + + const tabs = await collectKiroWebSessionTabs(currentState); + let detectedSignedInWithoutEmail = false; + for (const tab of tabs) { + try { + const pageState = await readKiroWebSessionStateFromTab(tab.id, { + timeoutMs: DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, + injectLogMessage: '步骤 7:Kiro Web 页面内容脚本未就绪,正在等待页面恢复...', + }); + if (pageState?.state !== 'kiro_web_signed_in') { + continue; + } + const detectedEmail = cleanString(pageState.accountEmail || pageState.email || existingEmail); + if (!detectedEmail) { + detectedSignedInWithoutEmail = true; + continue; + } + + const restoredAt = Date.now(); + const payload = await applyRuntimeState(currentState, { + session: { + currentStage: 'desktop-authorize', + registerTabId: tab.id, + pageState: pageState.state || '', + pageUrl: pageState.url || tab.url || '', + lastError: '', + }, + register: { + email: detectedEmail, + status: 'completed', + completedAt: restoredAt, + }, + webAuth: { + status: 'signed_in', + completedAt: restoredAt, + }, + upload: { + status: 'waiting_desktop_authorize', + error: '', + }, + }, { + email: detectedEmail, + accountIdentifierType: 'email', + accountIdentifier: detectedEmail, + }); + const nextState = { + ...currentState, + ...payload, + }; + await log(`步骤 7:检测到已有 Kiro Web 登录态,已恢复账号 ${detectedEmail},继续启动桌面授权。`, 'ok', nodeId); + return { + currentState: nextState, + runtimeState: readKiroRuntime(nextState), + restored: true, + }; + } catch (error) { + console.warn('[MultiPage:kiro-desktop-authorize] restore web session failed', { + tabId: tab?.id, + url: tab?.url, + message: getErrorMessage(error), + }); + } + } + + if (detectedSignedInWithoutEmail) { + throw new Error('已检测到 Kiro Web 登录态,但未能识别账号邮箱。请打开 Kiro 账号设置页后重试步骤 7。'); + } + throw new Error('Kiro Web 登录态尚未建立。请先完成步骤 6,或打开已登录的 Kiro Web 页面后从步骤 7 继续。'); + } + function buildDesktopOtpPollPayload(step, state = {}, mail = {}, filterAfterTimestamp = 0) { const runtimeState = readKiroRuntime(state); const targetEmail = cleanString(runtimeState.register?.email || state?.email).toLowerCase(); @@ -779,16 +954,11 @@ async function executeKiroStartDesktopAuthorize(state = {}) { const nodeId = String(state?.nodeId || 'kiro-start-desktop-authorize').trim(); - const currentState = await getExecutionState(state); + let currentState = await getExecutionState(state); try { - const runtimeState = readKiroRuntime(currentState); - if (!cleanString(runtimeState.register?.email || currentState?.email)) { - throw new Error('缺少已注册邮箱,请先完成注册页步骤。'); - } - if (cleanString(runtimeState.register?.status) !== 'completed' - || cleanString(runtimeState.webAuth?.status) !== 'signed_in') { - throw new Error('Kiro Web 登录态尚未建立,请先完成步骤 6。'); - } + const sessionState = await restoreKiroWebSessionFromOpenTabs(currentState, nodeId); + currentState = sessionState.currentState; + const runtimeState = sessionState.runtimeState; const client = await desktopClientApi.registerDesktopClient({ region: DEFAULT_REGION, diff --git a/content/kiro/register-page.js b/content/kiro/register-page.js index b8f2533..9716c8e 100644 --- a/content/kiro/register-page.js +++ b/content/kiro/register-page.js @@ -9,6 +9,13 @@ const KIRO_ALLOW_ACCESS_TEXT_PATTERN = /allow access|允许访问/i; const KIRO_SUCCESS_TEXT_PATTERN = /authorization successful|you may now close this window|you are now signed in|授权成功|可以关闭此窗口|已登录/i; const KIRO_CLOUDFRONT_403_TEXT_PATTERN = /403 error|the request could not be satisfied|generated by cloudfront/i; const KIRO_AWS_REQUEST_ERROR_TEXT_PATTERN = /sorry,\s*there was an error processing your request\.?\s*please try again\.?|抱歉,处理您的请求时出错。请重试。?/i; +const KIRO_EMAIL_TEXT_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi; +const KIRO_NON_ACCOUNT_EMAIL_DOMAINS = new Set([ + 'amazon.com', + 'aws.amazon.com', + 'example.com', + 'kiro.dev', +]); const KIRO_EMAIL_INPUT_SELECTOR = [ 'input[placeholder="username@example.com"]', @@ -67,6 +74,58 @@ function getKiroPageText() { .trim(); } +function normalizeKiroEmailCandidate(value = '') { + const matched = String(value || '').match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i); + return matched ? matched[0].trim().toLowerCase() : ''; +} + +function isLikelyKiroAccountEmail(value = '') { + const normalized = normalizeKiroEmailCandidate(value); + if (!normalized) { + return false; + } + const [, domain = ''] = normalized.split('@'); + const normalizedDomain = domain.toLowerCase(); + if (KIRO_NON_ACCOUNT_EMAIL_DOMAINS.has(normalizedDomain)) { + return false; + } + return !normalized.startsWith('no-reply@') + && !normalized.startsWith('noreply@') + && !normalized.startsWith('support@') + && !normalizedDomain.endsWith('.amazon.com') + && !normalizedDomain.endsWith('.amazonaws.com') + && !normalizedDomain.endsWith('.kiro.dev') + && !normalizedDomain.endsWith('.signin.aws') + && !normalizedDomain.endsWith('.profile.aws'); +} + +function extractFirstKiroAccountEmailFromText(text = '') { + const matches = String(text || '').match(KIRO_EMAIL_TEXT_PATTERN) || []; + return matches.map((entry) => normalizeKiroEmailCandidate(entry)).find(isLikelyKiroAccountEmail) || ''; +} + +function findKiroSignedInAccountEmail(pageText = '') { + const selector = [ + 'input[type="email"]', + 'input[name*="email" i]', + '[data-testid*="email" i]', + '[aria-label*="email" i]', + '[title*="email" i]', + ].join(', '); + for (const element of collectVisibleElements(selector)) { + const candidate = extractFirstKiroAccountEmailFromText([ + element?.value, + element?.textContent, + element?.getAttribute?.('aria-label'), + element?.getAttribute?.('title'), + ].filter(Boolean).join(' ')); + if (candidate) { + return candidate; + } + } + return extractFirstKiroAccountEmailFromText(pageText); +} + function collectVisibleElements(selector) { return Array.from(document.querySelectorAll(selector)) .filter((element) => isVisibleKiroElement(element)); @@ -309,9 +368,12 @@ function detectKiroRegisterPageState() { }; } if (isKiroWebSignedInPage(pageText)) { + const accountEmail = findKiroSignedInAccountEmail(pageText); return { state: 'kiro_web_signed_in', url: currentUrl, + accountEmail, + email: accountEmail, }; } } @@ -642,6 +704,19 @@ async function confirmKiroRegisterConsent(payload = {}) { async function handleKiroRegisterCommand(message) { switch (message.type) { + case 'GET_KIRO_REGISTER_PAGE_STATE': { + const detected = detectKiroRegisterPageState(); + return { + state: detected?.state || '', + url: detected?.url || location.href, + accountEmail: detected?.accountEmail || '', + email: detected?.email || detected?.accountEmail || '', + fatalMessage: detected?.fatalMessage || '', + authorizationActionKind: detected?.authorizationActionKind || '', + authorizationActionText: detected?.authorizationActionText || '', + actionText: detected?.actionText || '', + }; + } case 'ENSURE_KIRO_PAGE_STATE': return ensureKiroRegisterPageState(message.payload || {}); case 'ENSURE_KIRO_STATE_CHANGE': @@ -679,6 +754,7 @@ if (document.documentElement.getAttribute(KIRO_REGISTER_PAGE_LISTENER_SENTINEL) if ( message.type === 'ENSURE_KIRO_PAGE_STATE' || message.type === 'ENSURE_KIRO_STATE_CHANGE' + || message.type === 'GET_KIRO_REGISTER_PAGE_STATE' || message.type === 'EXECUTE_NODE' ) { resetStopState(); diff --git a/tests/background-kiro-desktop-authorize-runner-module.test.js b/tests/background-kiro-desktop-authorize-runner-module.test.js index 6c03451..f05bc4e 100644 --- a/tests/background-kiro-desktop-authorize-runner-module.test.js +++ b/tests/background-kiro-desktop-authorize-runner-module.test.js @@ -85,9 +85,125 @@ test('kiro desktop authorize runner uses a shared 3-minute page-load timeout bud test('kiro desktop authorization is gated by completed Kiro Web sign-in', () => { const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8'); - assert.match(source, /runtimeState\.register\?\.status/); - assert.match(source, /runtimeState\.webAuth\?\.status/); + assert.match(source, /restoreKiroWebSessionFromOpenTabs/); + assert.match(source, /GET_KIRO_REGISTER_PAGE_STATE/); assert.match(source, /Kiro Web 登录态尚未建立/); + assert.match(source, /已检测到 Kiro Web 登录态/); +}); + +test('executeKiroStartDesktopAuthorize restores existing Kiro Web session before desktop auth', async () => { + const api = loadDesktopAuthorizeRunnerApi(); + let currentState = { + kiroRuntime: { + session: {}, + register: {}, + webAuth: {}, + desktopAuth: {}, + upload: {}, + }, + }; + const setStateCalls = []; + const logs = []; + let completedPayload = null; + let openedAuthorizeUrl = ''; + let registerReadySource = ''; + let desktopRegisteredTabId = null; + + const runner = api.createKiroDesktopAuthorizeRunner({ + addLog: async (message, level) => { + logs.push({ message, level }); + }, + chrome: { + tabs: { + get: async (tabId) => ({ + id: tabId, + status: 'complete', + url: tabId === 77 + ? 'https://app.kiro.dev/settings/account' + : 'https://oidc.us-east-1.amazonaws.com/authorize', + }), + query: async (queryInfo) => { + if (Array.isArray(queryInfo.url)) { + return [{ + id: 77, + status: 'complete', + url: 'https://app.kiro.dev/settings/account', + }]; + } + return []; + }, + update: async () => ({}), + }, + webNavigation: { + onBeforeNavigate: { addListener: () => {} }, + onCommitted: { addListener: () => {} }, + }, + webRequest: { + onBeforeRequest: { addListener: () => {} }, + }, + }, + completeNodeFromBackground: async (_nodeId, payload) => { + completedPayload = payload; + }, + ensureContentScriptReadyOnTab: async (source, tabId, options = {}) => { + assert.equal(tabId, 77); + registerReadySource = source; + assert.equal(options.injectSource, 'kiro-register-page'); + }, + fetchImpl: async () => ({ + ok: true, + text: async () => JSON.stringify({ + clientId: 'restored-client-id', + clientSecret: 'restored-client-secret', + }), + }), + getState: async () => currentState, + getTabId: async () => null, + isTabAlive: async () => false, + KIRO_REGISTER_INJECT_FILES: ['content/kiro/register-page.js'], + registerTab: async (source, tabId) => { + if (source === 'kiro-desktop-authorize') { + desktopRegisteredTabId = tabId; + } + }, + reuseOrCreateTab: async (_source, url) => { + openedAuthorizeUrl = url; + return 88; + }, + sendToContentScriptResilient: async (source, message) => { + assert.equal(source, 'kiro-register-page'); + assert.equal(message.type, 'GET_KIRO_REGISTER_PAGE_STATE'); + return { + ok: true, + state: 'kiro_web_signed_in', + url: 'https://app.kiro.dev/settings/account', + accountEmail: 'restored@duck.com', + }; + }, + setState: async (patch) => { + setStateCalls.push(patch); + currentState = { ...currentState, ...patch }; + }, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + waitForTabStableComplete: async () => ({ status: 'complete' }), + }); + + await runner.executeKiroStartDesktopAuthorize(currentState); + + assert.equal(registerReadySource, 'kiro-register-page'); + assert.equal(desktopRegisteredTabId, 88); + assert.match(openedAuthorizeUrl, /client_id=restored-client-id/); + assert.equal(currentState.email, 'restored@duck.com'); + assert.equal(currentState.kiroRuntime.register.email, 'restored@duck.com'); + assert.equal(currentState.kiroRuntime.register.status, 'completed'); + assert.equal(currentState.kiroRuntime.webAuth.status, 'signed_in'); + assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.clientId, 'restored-client-id'); + assert.equal( + logs.some(({ message }) => message.includes('检测到已有 Kiro Web 登录态,已恢复账号 restored@duck.com')), + true + ); + assert.equal(setStateCalls.length >= 2, true); }); test('executeKiroCompleteDesktopAuthorize finishes from callback page without waiting for tracker replay', async () => { diff --git a/tests/background-skip-step-linking.test.js b/tests/background-skip-step-linking.test.js index d9b5607..68f31ab 100644 --- a/tests/background-skip-step-linking.test.js +++ b/tests/background-skip-step-linking.test.js @@ -17,6 +17,18 @@ const OPENAI_NODE_IDS = [ 'platform-verify', ]; +const KIRO_NODE_IDS = [ + 'kiro-open-register-page', + 'kiro-submit-email', + 'kiro-submit-name', + 'kiro-submit-verification-code', + 'kiro-submit-password', + 'kiro-complete-register-consent', + 'kiro-start-desktop-authorize', + 'kiro-complete-desktop-authorize', + 'kiro-upload-credential', +]; + function extractFunction(name) { const markers = [`async function ${name}(`, `function ${name}(`]; const start = markers @@ -61,7 +73,7 @@ function extractFunction(name) { return source.slice(start, end); } -function createApi() { +function createApi(nodeIds = OPENAI_NODE_IDS) { const bundle = [ extractFunction('isStepDoneStatus'), extractFunction('skipNode'), @@ -70,7 +82,7 @@ function createApi() { return new Function(` const DEFAULT_STATE = { nodeStatuses: {} }; function getNodeIdsForState() { - return ${JSON.stringify(OPENAI_NODE_IDS)}; + return ${JSON.stringify(nodeIds)}; } function normalizeStatusMapForNodes(statuses = {}) { return { ...statuses }; @@ -80,7 +92,7 @@ function isNodeExecutionAllowedForState() { return true; } function getExecutionAllowedNodeIdsForState() { - return ${JSON.stringify(OPENAI_NODE_IDS)}; + return ${JSON.stringify(nodeIds)}; } ${bundle} return { skipNode }; @@ -174,3 +186,43 @@ test('skipNode from open-chatgpt skips only unfinished linked signup nodes', asy true ); }); + +test('skipNode cascades from kiro open-register through the whole register branch', async () => { + const statuses = Object.fromEntries(KIRO_NODE_IDS.map((nodeId) => [nodeId, 'pending'])); + const events = { + setNodeStatusCalls: [], + logs: [], + }; + const api = createApi(KIRO_NODE_IDS); + + globalThis.ensureManualInteractionAllowed = async () => ({ + nodeStatuses: { ...statuses }, + }); + globalThis.getState = async () => ({ + nodeStatuses: { ...statuses }, + }); + globalThis.setNodeStatus = async (nodeId, status) => { + events.setNodeStatusCalls.push({ nodeId, status }); + statuses[nodeId] = status; + }; + globalThis.addLog = async (message, level) => { + events.logs.push({ message, level }); + }; + + const result = await api.skipNode('kiro-open-register-page'); + + assert.deepStrictEqual(result, { ok: true, nodeId: 'kiro-open-register-page', status: 'skipped' }); + assert.deepStrictEqual(events.setNodeStatusCalls, [ + { nodeId: 'kiro-open-register-page', status: 'skipped' }, + { nodeId: 'kiro-submit-email', status: 'skipped' }, + { nodeId: 'kiro-submit-name', status: 'skipped' }, + { nodeId: 'kiro-submit-verification-code', status: 'skipped' }, + { nodeId: 'kiro-submit-password', status: 'skipped' }, + { nodeId: 'kiro-complete-register-consent', status: 'skipped' }, + ]); + assert.equal(events.logs[0]?.message, '节点 kiro-open-register-page 已跳过'); + assert.equal( + events.logs[1]?.message, + '节点 kiro-open-register-page 已跳过,节点 kiro-submit-email、kiro-submit-name、kiro-submit-verification-code、kiro-submit-password、kiro-complete-register-consent 也已同时跳过。' + ); +}); diff --git a/tests/kiro-register-page-fatal-state.test.js b/tests/kiro-register-page-fatal-state.test.js index f70fdf2..95e6a94 100644 --- a/tests/kiro-register-page-fatal-state.test.js +++ b/tests/kiro-register-page-fatal-state.test.js @@ -90,3 +90,18 @@ test('kiro register content treats Kiro web success callback as signed in', () = assert.equal(detected.state, 'kiro_web_signed_in'); }); + +test('kiro register content extracts signed-in account email from Kiro account page text', () => { + const harness = createHarness({ + href: 'https://app.kiro.dev/settings/account', + hostname: 'app.kiro.dev', + title: 'Account', + bodyText: 'Account Email scrap-aged-quirk@duck.com support@kiro.dev', + }); + + const detected = harness.detectKiroRegisterPageState(); + + assert.equal(detected.state, 'kiro_web_signed_in'); + assert.equal(detected.accountEmail, 'scrap-aged-quirk@duck.com'); + assert.equal(detected.email, 'scrap-aged-quirk@duck.com'); +});