diff --git a/background.js b/background.js index 43638b4..130c69b 100644 --- a/background.js +++ b/background.js @@ -5,6 +5,7 @@ importScripts( 'shared/settings-schema.js', 'shared/source-registry.js', 'shared/flow-capabilities.js', + 'shared/kiro-timeouts.js', 'managed-alias-utils.js', 'mail2925-utils.js', 'paypal-utils.js', @@ -12744,8 +12745,8 @@ async function resumeAutoRun() { const SIGNUP_ENTRY_URL = 'https://chatgpt.com/'; const SIGNUP_PAGE_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/auth-page-recovery.js', 'content/phone-country-utils.js', 'content/phone-auth.js', 'content/signup-page.js']; -const KIRO_REGISTER_INJECT_FILES = ['shared/source-registry.js', 'content/utils.js', 'content/kiro/register-page.js']; -const KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = ['shared/source-registry.js', 'content/utils.js', 'content/kiro/desktop-authorize-page.js']; +const KIRO_REGISTER_INJECT_FILES = ['shared/source-registry.js', 'shared/kiro-timeouts.js', 'content/utils.js', 'content/kiro/register-page.js']; +const KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = ['shared/source-registry.js', 'shared/kiro-timeouts.js', 'content/utils.js', 'content/kiro/desktop-authorize-page.js']; const panelBridge = self.MultiPageBackgroundPanelBridge?.createPanelBridge({ chrome, addLog, diff --git a/background/kiro/desktop-authorize-runner.js b/background/kiro/desktop-authorize-runner.js index 7ad6780..68c61bf 100644 --- a/background/kiro/desktop-authorize-runner.js +++ b/background/kiro/desktop-authorize-runner.js @@ -3,7 +3,9 @@ })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroDesktopAuthorizeRunnerModule(root) { const kiroStateApi = root.MultiPageBackgroundKiroState || null; const desktopClientApi = root.MultiPageBackgroundKiroDesktopClient || null; + const kiroTimeoutApi = root.MultiPageKiroTimeouts || null; 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_DESKTOP_SOURCE_ID = 'kiro-desktop-authorize'; const KIRO_AWS_VERIFICATION_CODE_PATTERNS = Object.freeze([ @@ -107,6 +109,36 @@ return fallback; } + function normalizeKiroPageLoadTimeoutMs(value, fallback = DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS) { + if (typeof kiroTimeoutApi?.normalizeKiroPageLoadTimeoutMs === 'function') { + return kiroTimeoutApi.normalizeKiroPageLoadTimeoutMs(value, fallback); + } + return normalizePositiveInteger(value, normalizePositiveInteger(fallback, DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS)); + } + + function createTimeoutBudget(timeoutMs = DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS) { + const totalTimeoutMs = normalizeKiroPageLoadTimeoutMs(timeoutMs, DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS); + const startedAt = Date.now(); + return { + totalTimeoutMs, + getRemainingMs(minimumMs = 1) { + const normalizedMinimumMs = normalizePositiveInteger(minimumMs, 1); + return Math.max(normalizedMinimumMs, totalTimeoutMs - (Date.now() - startedAt)); + }, + }; + } + + function resolveTimeoutBudget(options = {}, fallbackTimeoutMs = DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS) { + if (options?.timeoutBudget && typeof options.timeoutBudget.getRemainingMs === 'function') { + return options.timeoutBudget; + } + return createTimeoutBudget( + options?.pageTimeoutMs + ?? options?.timeoutMs + ?? fallbackTimeoutMs + ); + } + function getErrorMessage(error) { return error instanceof Error ? error.message : String(error ?? '未知错误'); } @@ -418,9 +450,10 @@ if (!Number.isInteger(tabId)) { throw new Error('缺少 Kiro 桌面授权页标签页,无法重新连接内容脚本。'); } + const timeoutBudget = resolveTimeoutBudget(options); if (typeof waitForTabStableComplete === 'function') { await waitForTabStableComplete(tabId, { - timeoutMs: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 300, stableMs: Number(options.stableMs) || 1200, initialDelayMs: Number(options.initialDelayMs) || 120, @@ -430,7 +463,7 @@ await ensureContentScriptReadyOnTab(KIRO_DESKTOP_SOURCE_ID, tabId, { inject: Array.isArray(KIRO_DESKTOP_AUTHORIZE_INJECT_FILES) ? KIRO_DESKTOP_AUTHORIZE_INJECT_FILES : null, injectSource: KIRO_DESKTOP_SOURCE_ID, - timeoutMs: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, logMessage: options.injectLogMessage || 'Kiro 桌面授权页已跳转,正在重新连接内容脚本...', }); @@ -438,8 +471,15 @@ } function buildDesktopRetryRecovery(tabId, options = {}) { - return async () => { + return async (_error, context = {}) => { + const remainingTimeoutMs = normalizeKiroPageLoadTimeoutMs( + options?.timeoutBudget?.getRemainingMs?.(1000) + ?? context?.remainingTimeoutMs, + DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS + ); await reattachDesktopAuthorizePage(tabId, { + timeoutMs: remainingTimeoutMs, + timeoutBudget: createTimeoutBudget(remainingTimeoutMs), stableMs: Number(options.recoveryStableMs) || Number(options.stableMs) || 1200, initialDelayMs: Number(options.recoveryInitialDelayMs) || 120, injectLogMessage: options.recoveryInjectLogMessage || options.injectLogMessage || 'Kiro 桌面授权页已跳转,正在重新连接内容脚本...', @@ -451,9 +491,14 @@ if (!Number.isInteger(tabId)) { throw new Error('缺少 Kiro 桌面授权页标签页,无法继续执行。'); } + const pageLoadTimeoutMs = normalizeKiroPageLoadTimeoutMs( + options.pageTimeoutMs, + DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS + ); + const timeoutBudget = resolveTimeoutBudget(options, pageLoadTimeoutMs); if (typeof waitForTabStableComplete === 'function') { await waitForTabStableComplete(tabId, { - timeoutMs: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 300, stableMs: Number(options.stableMs) || 1200, initialDelayMs: Number(options.initialDelayMs) || 120, @@ -463,19 +508,23 @@ await ensureContentScriptReadyOnTab(KIRO_DESKTOP_SOURCE_ID, tabId, { inject: Array.isArray(KIRO_DESKTOP_AUTHORIZE_INJECT_FILES) ? KIRO_DESKTOP_AUTHORIZE_INJECT_FILES : null, injectSource: KIRO_DESKTOP_SOURCE_ID, - timeoutMs: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, logMessage: options.injectLogMessage || 'Kiro 桌面授权页内容脚本未就绪,正在等待页面恢复...', }); } + const stateWaitTimeoutMs = timeoutBudget.getRemainingMs(1000); const result = await sendToContentScriptResilient(KIRO_DESKTOP_SOURCE_ID, { type: 'GET_KIRO_DESKTOP_AUTHORIZE_STATE', step: options.step || 0, source: 'background', }, { - timeoutMs: 30000, + timeoutMs: stateWaitTimeoutMs, retryDelayMs: 700, - onRetryableError: buildDesktopRetryRecovery(tabId, options), + onRetryableError: buildDesktopRetryRecovery(tabId, { + ...options, + timeoutBudget, + }), logMessage: options.readyLogMessage || '正在读取 Kiro 桌面授权页状态...', }); if (result?.error) { diff --git a/background/kiro/register-runner.js b/background/kiro/register-runner.js index 812ab88..0ef1d2e 100644 --- a/background/kiro/register-runner.js +++ b/background/kiro/register-runner.js @@ -2,8 +2,10 @@ root.MultiPageBackgroundKiroRegisterRunner = factory(root); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroRegisterRunnerModule(root) { const kiroStateApi = root.MultiPageBackgroundKiroState || null; + const kiroTimeoutApi = root.MultiPageKiroTimeouts || null; const DEFAULT_REGION = kiroStateApi?.DEFAULT_REGION || 'us-east-1'; const DEFAULT_TARGET_ID = kiroStateApi?.DEFAULT_TARGET_ID || 'kiro-rs'; + const DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS = kiroTimeoutApi?.DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS || (3 * 60 * 1000); const DEVICE_LOGIN_START_URL = 'https://view.awsapps.com/start'; const DEFAULT_SCOPES = Object.freeze([ 'codewhisperer:completions', @@ -120,6 +122,36 @@ return fallback; } + function normalizeKiroPageLoadTimeoutMs(value, fallback = DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS) { + if (typeof kiroTimeoutApi?.normalizeKiroPageLoadTimeoutMs === 'function') { + return kiroTimeoutApi.normalizeKiroPageLoadTimeoutMs(value, fallback); + } + return normalizePositiveInteger(value, normalizePositiveInteger(fallback, DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS)); + } + + function createTimeoutBudget(timeoutMs = DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS) { + const totalTimeoutMs = normalizeKiroPageLoadTimeoutMs(timeoutMs, DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS); + const startedAt = Date.now(); + return { + totalTimeoutMs, + getRemainingMs(minimumMs = 1) { + const normalizedMinimumMs = normalizePositiveInteger(minimumMs, 1); + return Math.max(normalizedMinimumMs, totalTimeoutMs - (Date.now() - startedAt)); + }, + }; + } + + function resolveTimeoutBudget(options = {}, fallbackTimeoutMs = DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS) { + if (options?.timeoutBudget && typeof options.timeoutBudget.getRemainingMs === 'function') { + return options.timeoutBudget; + } + return createTimeoutBudget( + options?.pageTimeoutMs + ?? options?.timeoutMs + ?? fallbackTimeoutMs + ); + } + function buildOidcBaseUrl(region = DEFAULT_REGION) { return `https://oidc.${normalizeRegion(region)}.amazonaws.com`; } @@ -485,9 +517,10 @@ if (!Number.isInteger(tabId)) { throw new Error('缺少 Kiro 注册页标签页,无法重新连接内容脚本。'); } + const timeoutBudget = resolveTimeoutBudget(options); if (typeof waitForTabStableComplete === 'function') { await waitForTabStableComplete(tabId, { - timeoutMs: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 300, stableMs: Number(options.stableMs) || 1200, initialDelayMs: Number(options.initialDelayMs) || 120, @@ -497,7 +530,7 @@ 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: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, logMessage: options.injectLogMessage || 'Kiro 注册页已跳转,正在重新连接内容脚本...', }); @@ -505,8 +538,15 @@ } function buildKiroRetryRecovery(tabId, options = {}) { - return async () => { + return async (_error, context = {}) => { + const remainingTimeoutMs = normalizeKiroPageLoadTimeoutMs( + options?.timeoutBudget?.getRemainingMs?.(1000) + ?? context?.remainingTimeoutMs, + DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS + ); await reattachKiroRegisterPage(tabId, { + timeoutMs: remainingTimeoutMs, + timeoutBudget: createTimeoutBudget(remainingTimeoutMs), stableMs: Number(options.recoveryStableMs) || Number(options.stableMs) || 1200, initialDelayMs: Number(options.recoveryInitialDelayMs) || 120, injectLogMessage: options.recoveryInjectLogMessage || options.injectLogMessage || 'Kiro 注册页已跳转,正在重新连接内容脚本...', @@ -518,9 +558,14 @@ if (!Number.isInteger(tabId)) { throw new Error('缺少 Kiro 注册页标签页,无法继续执行。'); } + const pageLoadTimeoutMs = normalizeKiroPageLoadTimeoutMs( + options.pageTimeoutMs, + DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS + ); + const timeoutBudget = resolveTimeoutBudget(options, pageLoadTimeoutMs); if (typeof waitForTabStableComplete === 'function') { await waitForTabStableComplete(tabId, { - timeoutMs: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 300, stableMs: Number(options.stableMs) || 1500, initialDelayMs: Number(options.initialDelayMs) || 150, @@ -530,7 +575,7 @@ 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: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, logMessage: options.injectLogMessage || 'Kiro 注册页内容脚本未就绪,正在等待页面恢复...', }); @@ -541,20 +586,24 @@ url: '', }; } + const stateWaitTimeoutMs = timeoutBudget.getRemainingMs(1000); const result = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { type: 'ENSURE_KIRO_PAGE_STATE', step: options.step || 0, source: 'background', payload: { targetStates: Array.isArray(options.targetStates) ? options.targetStates : [], - timeoutMs: Number(options.pageTimeoutMs) || 30000, + timeoutMs: stateWaitTimeoutMs, retryDelayMs: Number(options.pageRetryDelayMs) || 250, timeoutMessage: options.timeoutMessage || '', }, }, { - timeoutMs: Math.max(30000, Number(options.pageTimeoutMs) || 30000), + timeoutMs: stateWaitTimeoutMs, retryDelayMs: 700, - onRetryableError: buildKiroRetryRecovery(tabId, options), + onRetryableError: buildKiroRetryRecovery(tabId, { + ...options, + timeoutBudget, + }), logMessage: options.readyLogMessage || '正在等待 Kiro 页面进入下一状态...', }); if (result?.error) { @@ -567,9 +616,14 @@ if (!Number.isInteger(tabId)) { throw new Error('缺少 Kiro 注册页标签页,无法继续执行。'); } + const pageLoadTimeoutMs = normalizeKiroPageLoadTimeoutMs( + options.pageTimeoutMs, + DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS + ); + const timeoutBudget = resolveTimeoutBudget(options, pageLoadTimeoutMs); if (typeof waitForTabStableComplete === 'function') { await waitForTabStableComplete(tabId, { - timeoutMs: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 300, stableMs: Number(options.stableMs) || 1200, initialDelayMs: Number(options.initialDelayMs) || 120, @@ -579,7 +633,7 @@ 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: 45000, + timeoutMs: timeoutBudget.getRemainingMs(1000), retryDelayMs: 800, logMessage: options.injectLogMessage || 'Kiro 注册页切换中,正在等待页面恢复...', }); @@ -587,20 +641,24 @@ if (typeof sendToContentScriptResilient !== 'function') { return { state: '', url: '' }; } + const stateWaitTimeoutMs = timeoutBudget.getRemainingMs(1000); const result = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, { type: 'ENSURE_KIRO_STATE_CHANGE', step: options.step || 0, source: 'background', payload: { fromStates: Array.isArray(options.fromStates) ? options.fromStates : [], - timeoutMs: Number(options.pageTimeoutMs) || 30000, + timeoutMs: stateWaitTimeoutMs, retryDelayMs: Number(options.pageRetryDelayMs) || 250, timeoutMessage: options.timeoutMessage || '', }, }, { - timeoutMs: Math.max(30000, Number(options.pageTimeoutMs) || 30000), + timeoutMs: stateWaitTimeoutMs, retryDelayMs: 700, - onRetryableError: buildKiroRetryRecovery(tabId, options), + onRetryableError: buildKiroRetryRecovery(tabId, { + ...options, + timeoutBudget, + }), logMessage: options.readyLogMessage || '正在等待 Kiro 页面完成跳转...', }); if (result?.error) { diff --git a/content/kiro/register-page.js b/content/kiro/register-page.js index 36cac37..02e06f2 100644 --- a/content/kiro/register-page.js +++ b/content/kiro/register-page.js @@ -1,6 +1,7 @@ console.log('[MultiPage:kiro-register-page] Content script loaded on', location.href); const KIRO_REGISTER_PAGE_LISTENER_SENTINEL = 'data-multipage-kiro-register-page-listener'; +const DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS = globalThis.MultiPageKiroTimeouts?.DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS || (3 * 60 * 1000); const KIRO_CONTINUE_TEXT_PATTERN = /continue|继续/i; const KIRO_CONFIRM_CONTINUE_TEXT_PATTERN = /confirm and continue|确认并继续/i; const KIRO_ALLOW_ACCESS_TEXT_PATTERN = /allow access|允许访问/i; @@ -312,7 +313,7 @@ function detectKiroRegisterPageState() { } async function waitForKiroState(predicate, options = {}) { - const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || 30000)); + const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS)); const retryDelayMs = Math.max(100, Math.floor(Number(options.retryDelayMs) || 250)); const start = Date.now(); @@ -405,7 +406,7 @@ async function submitKiroEmail(payload = {}) { const readyState = await ensureKiroRegisterPageState({ targetStates: ['email_entry'], - timeoutMs: payload?.timeoutMs || 30000, + timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, retryDelayMs: payload?.retryDelayMs || 250, }); if (!readyState.emailInput || !readyState.continueButton) { @@ -430,7 +431,7 @@ async function submitKiroName(payload = {}) { const readyState = await ensureKiroRegisterPageState({ targetStates: ['name_entry'], - timeoutMs: payload?.timeoutMs || 30000, + timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, retryDelayMs: payload?.retryDelayMs || 250, }); if (!readyState.nameInput || !readyState.continueButton) { @@ -455,7 +456,7 @@ async function submitKiroVerificationCode(payload = {}) { const readyState = await ensureKiroRegisterPageState({ targetStates: ['otp_page'], - timeoutMs: payload?.timeoutMs || 30000, + timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, retryDelayMs: payload?.retryDelayMs || 250, }); if (!readyState.otpInput || !readyState.verifyButton) { @@ -480,7 +481,7 @@ async function submitKiroPassword(payload = {}) { const readyState = await ensureKiroRegisterPageState({ targetStates: ['password_page'], - timeoutMs: payload?.timeoutMs || 30000, + timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, retryDelayMs: payload?.retryDelayMs || 250, }); if (!readyState.passwordInput || !readyState.continueButton) { @@ -511,7 +512,7 @@ async function submitKiroPassword(payload = {}) { async function confirmKiroRegisterConsent(payload = {}) { let currentState = await ensureKiroRegisterPageState({ targetStates: ['authorization_page', 'success_page'], - timeoutMs: payload?.timeoutMs || 45000, + timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, retryDelayMs: payload?.retryDelayMs || 250, }); @@ -528,7 +529,7 @@ async function confirmKiroRegisterConsent(payload = {}) { }); simulateClick(currentState.actionButton); currentState = await waitForKiroAuthorizationAdvance(currentState, { - timeoutMs: payload?.timeoutMs || 45000, + timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, retryDelayMs: payload?.retryDelayMs || 250, timeoutMessage: 'Kiro 授权按钮点击后页面未继续,请检查当前授权页状态。', }); diff --git a/shared/kiro-timeouts.js b/shared/kiro-timeouts.js new file mode 100644 index 0000000..2c1cbc3 --- /dev/null +++ b/shared/kiro-timeouts.js @@ -0,0 +1,25 @@ +(function attachKiroTimeouts(root, factory) { + root.MultiPageKiroTimeouts = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createKiroTimeoutsModule() { + const DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS = 3 * 60 * 1000; + + function normalizePositiveInteger(value, fallback) { + const numeric = Math.floor(Number(value)); + if (Number.isInteger(numeric) && numeric > 0) { + return numeric; + } + return fallback; + } + + function normalizeKiroPageLoadTimeoutMs(value, fallback = DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS) { + return normalizePositiveInteger( + value, + normalizePositiveInteger(fallback, DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS) + ); + } + + return { + DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS, + normalizeKiroPageLoadTimeoutMs, + }; +}); diff --git a/tests/background-kiro-desktop-authorize-runner-module.test.js b/tests/background-kiro-desktop-authorize-runner-module.test.js index e8f957f..d5a11f2 100644 --- a/tests/background-kiro-desktop-authorize-runner-module.test.js +++ b/tests/background-kiro-desktop-authorize-runner-module.test.js @@ -46,3 +46,12 @@ test('parseDesktopCallbackUrl validates state and redirect port', () => { ); assert.equal(badPort, null); }); + +test('kiro desktop authorize runner uses a shared 3-minute page-load timeout budget', () => { + const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8'); + assert.match(source, /DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS/); + assert.match(source, /createTimeoutBudget/); + assert.match(source, /resolveTimeoutBudget/); + assert.match(source, /timeoutBudget\.getRemainingMs\(1000\)/); + assert.match(source, /onRetryableError: buildDesktopRetryRecovery\(tabId, \{\s*\.\.\.options,\s*timeoutBudget,/); +}); diff --git a/tests/background-kiro-register-runner-module.test.js b/tests/background-kiro-register-runner-module.test.js index 34ccfc2..2129642 100644 --- a/tests/background-kiro-register-runner-module.test.js +++ b/tests/background-kiro-register-runner-module.test.js @@ -57,3 +57,12 @@ test('startBuilderIdDeviceLogin registers Builder ID client and returns login bo assert.equal(result.interval, 7); assert.equal(result.region, 'us-east-1'); }); + +test('kiro register runner uses a shared 3-minute page-load timeout budget', () => { + const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8'); + assert.match(source, /DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS/); + assert.match(source, /createTimeoutBudget/); + assert.match(source, /resolveTimeoutBudget/); + assert.match(source, /timeoutBudget\.getRemainingMs\(1000\)/); + assert.match(source, /onRetryableError: buildKiroRetryRecovery\(tabId, \{\s*\.\.\.options,\s*timeoutBudget,/); +}); diff --git a/tests/background-step-modules.test.js b/tests/background-step-modules.test.js index 96b2086..1651099 100644 --- a/tests/background-step-modules.test.js +++ b/tests/background-step-modules.test.js @@ -16,6 +16,7 @@ test('background imports workflow step modules including rebuilt Kiro modules', 'background/steps/fetch-login-code.js', 'background/steps/confirm-oauth.js', 'background/steps/platform-verify.js', + 'shared/kiro-timeouts.js', 'background/kiro/state.js', 'background/kiro/register-runner.js', 'background/kiro/desktop-client.js', diff --git a/tests/source-registry-module.test.js b/tests/source-registry-module.test.js index 861dca2..3178232 100644 --- a/tests/source-registry-module.test.js +++ b/tests/source-registry-module.test.js @@ -15,6 +15,7 @@ test('background imports shared source registry module', () => { assert.match(source, /shared\/flow-registry\.js/); assert.match(source, /shared\/settings-schema\.js/); assert.match(source, /shared\/source-registry\.js/); + assert.match(source, /shared\/kiro-timeouts\.js/); }); test('manifest loads shared source registry before content utils in static bundles', () => { @@ -41,6 +42,18 @@ test('manifest no longer ships a static Kiro content bundle', () => { assert.equal(hasStaticKiroBundle, false); }); +test('background injects shared Kiro timeout module before Kiro content scripts', () => { + const source = fs.readFileSync('background.js', 'utf8'); + assert.match( + source, + /const KIRO_REGISTER_INJECT_FILES = \['shared\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'content\/kiro\/register-page\.js'\];/ + ); + assert.match( + source, + /const KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = \['shared\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'content\/kiro\/desktop-authorize-page\.js'\];/ + ); +}); + test('shared source registry exposes canonical Kiro sources and drivers', () => { const registry = loadSourceRegistry();