diff --git a/flows/kiro/background/register-runner.js b/flows/kiro/background/register-runner.js index 30b2150..d799b65 100644 --- a/flows/kiro/background/register-runner.js +++ b/flows/kiro/background/register-runner.js @@ -601,6 +601,73 @@ return tabId; } + async function injectKiroRegisterContentScripts(tabId) { + if ( + !Number.isInteger(tabId) + || !chrome?.scripting?.executeScript + || !Array.isArray(KIRO_REGISTER_INJECT_FILES) + || !KIRO_REGISTER_INJECT_FILES.length + ) { + return false; + } + + await chrome.scripting.executeScript({ + target: { tabId }, + func: (injectedSource) => { + window.__MULTIPAGE_SOURCE = injectedSource; + }, + args: [KIRO_REGISTER_PAGE_SOURCE_ID], + }); + await chrome.scripting.executeScript({ + target: { tabId }, + files: KIRO_REGISTER_INJECT_FILES, + }); + await sleepWithStop(250); + return true; + } + + function hasKiroRegisterPageState(result = {}) { + return Boolean(result && typeof result === 'object' && cleanString(result.state)); + } + + async function sendKiroStateDriverMessage(tabId, message, options = {}) { + const { + timeoutBudget, + timeoutMs = 30000, + } = options; + let result = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, message, { + timeoutMs, + retryDelayMs: 700, + onRetryableError: buildKiroRetryRecovery(tabId, { + ...options, + timeoutBudget, + }), + logMessage: options.readyLogMessage || '正在等待 Kiro 页面进入下一状态...', + }); + + if (!hasKiroRegisterPageState(result) && !result?.error) { + await log('Kiro 注册页通用脚本已响应,但专用页面识别脚本未返回状态,正在重新注入 Kiro 注册页识别脚本...', 'warn'); + await injectKiroRegisterContentScripts(tabId); + result = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, message, { + timeoutMs: timeoutBudget?.getRemainingMs?.(1000) || timeoutMs, + retryDelayMs: 700, + onRetryableError: buildKiroRetryRecovery(tabId, { + ...options, + timeoutBudget, + }), + logMessage: options.readyLogMessage || '正在等待 Kiro 页面进入下一状态...', + }); + } + + if (result?.error) { + throw new Error(result.error); + } + if (!hasKiroRegisterPageState(result)) { + throw new Error('Kiro 注册页专用页面识别脚本未返回页面状态,请刷新当前 AWS 页面或重新执行当前步骤。'); + } + return result; + } + async function reattachKiroRegisterPage(tabId, options = {}) { if (!Number.isInteger(tabId)) { throw new Error('缺少 Kiro 注册页标签页,无法重新连接内容脚本。'); @@ -675,7 +742,7 @@ }; } const stateWaitTimeoutMs = timeoutBudget.getRemainingMs(1000); - const result = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { + const message = { type: 'ENSURE_KIRO_PAGE_STATE', step: options.step || 0, source: 'background', @@ -685,19 +752,13 @@ retryDelayMs: Number(options.pageRetryDelayMs) || 250, timeoutMessage: options.timeoutMessage || '', }, - }, { + }; + + return sendKiroStateDriverMessage(tabId, message, { + ...options, + timeoutBudget, timeoutMs: stateWaitTimeoutMs, - retryDelayMs: 700, - onRetryableError: buildKiroRetryRecovery(tabId, { - ...options, - timeoutBudget, - }), - logMessage: options.readyLogMessage || '正在等待 Kiro 页面进入下一状态...', }); - if (result?.error) { - throw new Error(result.error); - } - return result || { state: '', url: '' }; } async function waitForKiroPageChange(tabId, options = {}) { @@ -730,7 +791,7 @@ return { state: '', url: '' }; } const stateWaitTimeoutMs = timeoutBudget.getRemainingMs(1000); - const result = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { + const message = { type: 'ENSURE_KIRO_STATE_CHANGE', step: options.step || 0, source: 'background', @@ -741,19 +802,13 @@ returnOnCodeInvalid: Boolean(options.returnOnCodeInvalid), timeoutMessage: options.timeoutMessage || '', }, - }, { + }; + return sendKiroStateDriverMessage(tabId, message, { + ...options, + timeoutBudget, timeoutMs: stateWaitTimeoutMs, - retryDelayMs: 700, - onRetryableError: buildKiroRetryRecovery(tabId, { - ...options, - timeoutBudget, - }), - logMessage: options.readyLogMessage || '正在等待 Kiro 页面完成跳转...', + readyLogMessage: options.readyLogMessage || '正在等待 Kiro 页面完成跳转...', }); - if (result?.error) { - throw new Error(result.error); - } - return result || { state: '', url: '' }; } async function readKiroRegisterPageState(tabId, options = {}) { diff --git a/tests/background-kiro-register-runner-module.test.js b/tests/background-kiro-register-runner-module.test.js index 9707e73..2ce58d1 100644 --- a/tests/background-kiro-register-runner-module.test.js +++ b/tests/background-kiro-register-runner-module.test.js @@ -364,6 +364,110 @@ test('kiro verification step can adopt the active AWS verify-otp page without st assert.equal(getKiroRuntime(completedPayload).register?.email, 'tmp3x58ft2ivc@edu.email.qlhazycoder.tech'); }); +test('kiro verification step reinjects the register driver when only the generic content script responds', async () => { + const api = loadRegisterRunnerApi(); + const currentState = { + email: 'tmp3x58ft2ivc@edu.email.qlhazycoder.tech', + registrationEmailState: { + current: 'tmp3x58ft2ivc@edu.email.qlhazycoder.tech', + previous: 'tmp3x58ft2ivc@edu.email.qlhazycoder.tech', + source: 'manual', + updatedAt: Date.now(), + }, + runtimeState: { + flowState: { + kiro: { + session: { + registerTabId: 302, + }, + }, + }, + }, + }; + const sentMessages = []; + const injectedScripts = []; + const pollPayloads = []; + let completedPayload = null; + const runner = api.createKiroRegisterRunner({ + addLog: async () => {}, + chrome: { + scripting: { + executeScript: async (payload) => { + injectedScripts.push(payload); + }, + }, + tabs: { + get: async (tabId) => ({ + id: tabId, + url: 'https://profile.aws.amazon.com/?workflowID=b4e8f9ff-3d60-40ce-90ec-d2113d951b08#/signup/verify-otp', + }), + update: async () => {}, + }, + }, + completeNodeFromBackground: async (_nodeId, payload) => { + completedPayload = payload; + }, + getMailConfig: () => ({ + provider: 'cloudflare-temp-email', + source: 'cloudflare-temp-email', + label: 'Cloudflare Temp Email', + }), + getState: async () => currentState, + getTabId: async () => 302, + isTabAlive: async () => true, + KIRO_REGISTER_INJECT_FILES: [ + 'content/utils.js', + 'flows/kiro/content/register-page.js', + ], + pollCloudflareTempEmailVerificationCode: async (_step, _state, payload) => { + pollPayloads.push(payload); + return { code: '248680', emailTimestamp: 2000, mailId: 'mail-reinject' }; + }, + sendToContentScriptResilient: async (_sourceId, message) => { + sentMessages.push(message); + const ensureCount = sentMessages.filter((entry) => entry.type === 'ENSURE_KIRO_PAGE_STATE').length; + if (message.type === 'ENSURE_KIRO_PAGE_STATE' && ensureCount === 1) { + return undefined; + } + if (message.type === 'ENSURE_KIRO_PAGE_STATE') { + return { + state: 'register_otp_page', + url: 'https://profile.aws.amazon.com/?workflowID=b4e8f9ff-3d60-40ce-90ec-d2113d951b08#/signup/verify-otp', + email: 'tmp3x58ft2ivc@edu.email.qlhazycoder.tech', + }; + } + if (message.type === 'EXECUTE_NODE') { + return { submitted: true, state: 'verification_submitted' }; + } + if (message.type === 'ENSURE_KIRO_STATE_CHANGE') { + return { + state: 'create_password_page', + url: 'https://profile.aws.amazon.com/?workflowID=b4e8f9ff-3d60-40ce-90ec-d2113d951b08#/signup/create-password', + email: 'tmp3x58ft2ivc@edu.email.qlhazycoder.tech', + }; + } + return {}; + }, + setState: async () => {}, + sleepWithStop: async () => {}, + }); + + await runner.executeKiroSubmitVerificationCode({ + nodeId: 'kiro-submit-verification-code', + ...currentState, + }); + + assert.equal(sentMessages.filter((message) => message.type === 'ENSURE_KIRO_PAGE_STATE').length, 2); + assert.equal(injectedScripts.length, 2); + assert.deepEqual(injectedScripts[0].args, ['kiro-register-page']); + assert.deepEqual(injectedScripts[1].files, [ + 'content/utils.js', + 'flows/kiro/content/register-page.js', + ]); + assert.equal(pollPayloads[0].targetEmail, 'tmp3x58ft2ivc@edu.email.qlhazycoder.tech'); + assert.equal(getKiroRuntime(completedPayload).register?.email, 'tmp3x58ft2ivc@edu.email.qlhazycoder.tech'); +}); + test('kiro submit-email reuses the step 1 register tab even when the source registry was reset', async () => { const api = loadRegisterRunnerApi(); const currentState = {