feat: 添加 Kiro 授权流程的 cookie 清理和重试机制,优化跨页面恢复

This commit is contained in:
QLHazyCoder
2026-05-18 15:10:18 +08:00
parent 12314e446a
commit 253f15cee6
14 changed files with 471 additions and 15 deletions
+1
View File
@@ -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,
+189
View File
@@ -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(`步骤 1browsingData 补扫 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) {
+24
View File
@@ -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)} 重新就绪超时。`);
}
+19
View File
@@ -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": {
+6
View File
@@ -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 配置',
+31 -6
View File
@@ -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';
}
+5 -6
View File
@@ -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;
}
+7 -2
View File
@@ -592,9 +592,9 @@
<button id="btn-fetch-email" class="btn btn-outline btn-sm data-inline-btn" type="button">获取</button>
</div>
</div>
<div class="data-row module-divider-start" id="row-auto-delay-settings">
<div class="data-row" id="row-step6-cookie-settings">
<span class="data-label">第六步</span>
<div class="data-inline setting-pair auto-delay-setting-pair">
<div class="data-inline">
<div class="setting-group setting-group-primary step6-cookie-cleanup-setting">
<span class="setting-caption setting-caption-left">清 Cookies</span>
<label class="toggle-switch" for="input-step6-cookie-cleanup-enabled">
@@ -604,6 +604,11 @@
</span>
</label>
</div>
</div>
</div>
<div class="data-row module-divider-start" id="row-auto-delay-settings">
<span class="data-label">启动前</span>
<div class="data-inline setting-pair auto-delay-setting-pair">
<div class="setting-group setting-group-secondary auto-run-delay-setting">
<span class="setting-caption">延迟</span>
<label class="toggle-switch" for="input-auto-delay-enabled">
@@ -54,7 +54,28 @@ test('kiro start device login opens the auth tab and waits for the email entry p
const contentReadyCalls = [];
const contentMessages = [];
const stableWaitCalls = [];
const removedCookies = [];
const browsingDataCalls = [];
const { chrome, updates: tabUpdates } = createChromeRecorder();
chrome.cookies = {
getAllCookieStores: async () => [{ id: 'store-a' }],
getAll: async () => [
{ domain: '.view.awsapps.com', path: '/start', name: 'awsapps', storeId: 'store-a' },
{ domain: '.oidc.us-east-1.amazonaws.com', path: '/', name: 'oidc', storeId: 'store-a' },
{ domain: '.signin.aws', path: '/', name: 'signin', storeId: 'store-a' },
{ domain: '.profile.aws.amazon.com', path: '/', name: 'profile-amazon', storeId: 'store-a' },
{ domain: '.example.com', path: '/', name: 'keep', storeId: 'store-a' },
],
remove: async (details) => {
removedCookies.push(details);
return details;
},
};
chrome.browsingData = {
removeCookies: async (details) => {
browsingDataCalls.push(details);
},
};
const executor = api.createKiroDeviceAuthExecutor({
addLog: async () => {},
@@ -132,6 +153,40 @@ test('kiro start device login opens the auth tab and waits for the email entry p
nodeId: 'kiro-start-device-login',
});
assert.deepEqual(removedCookies, [
{
url: 'https://view.awsapps.com/start',
name: 'awsapps',
storeId: 'store-a',
},
{
url: 'https://oidc.us-east-1.amazonaws.com/',
name: 'oidc',
storeId: 'store-a',
},
{
url: 'https://signin.aws/',
name: 'signin',
storeId: 'store-a',
},
{
url: 'https://profile.aws.amazon.com/',
name: 'profile-amazon',
storeId: 'store-a',
},
]);
assert.deepEqual(browsingDataCalls, [{
since: 0,
origins: [
'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',
],
}]);
assert.equal(fetchCalls.length, 2);
assert.equal(fetchCalls[0].url, 'https://oidc.us-east-1.amazonaws.com/client/register');
assert.equal(fetchCalls[1].url, 'https://oidc.us-east-1.amazonaws.com/device_authorization');
@@ -152,6 +152,60 @@ test('tab runtime gives step 5 profile submit enough response time for slow page
);
});
test('tab runtime replays retryable transport recovery hook and surfaces a localized timeout error', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
let recoveryCalls = 0;
const runtime = api.createTabRuntime({
LOG_PREFIX: '[test]',
addLog: async () => {},
chrome: {
tabs: {
get: async () => ({
id: 9,
windowId: 1,
url: 'https://profile.aws.amazon.com/complete',
status: 'complete',
}),
query: async () => [],
sendMessage: async () => {
throw new Error('Could not establish connection. Receiving end does not exist.');
},
},
},
getSourceLabel: () => 'Kiro 授权页',
getState: async () => ({
tabRegistry: {
'kiro-device-auth': { tabId: 9, ready: true },
},
sourceLastUrls: {},
}),
isRetryableContentScriptTransportError: (error) => /Receiving end does not exist/i.test(String(error?.message || error || '')),
matchesSourceUrlFamily: () => false,
setState: async () => {},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
await assert.rejects(
runtime.sendToContentScriptResilient('kiro-device-auth', {
type: 'ENSURE_KIRO_PAGE_STATE',
payload: {},
}, {
timeoutMs: 5,
retryDelayMs: 0,
onRetryableError: async () => {
recoveryCalls += 1;
},
}),
/页面刚完成跳转或刷新,内容脚本还没有重新接回/
);
assert.equal(recoveryCalls > 0, true);
});
test('tab runtime waitForTabComplete waits until tab status becomes complete', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const globalScope = {};
@@ -21,6 +21,10 @@ test('flow registry exposes openai and kiro with canonical source metadata', ()
assert.equal(flowRegistry.normalizeFlowId('codex'), 'openai');
assert.equal(flowRegistry.normalizeSourceId('openai', 'sub2api'), 'sub2api');
assert.equal(flowRegistry.normalizeSourceId('kiro', 'anything-else'), 'kiro-rs');
assert.deepEqual(
flowRegistry.getVisibleGroupIds('openai', 'cpa'),
['openai-plus', 'openai-phone', 'openai-oauth', 'openai-step6', 'openai-source-cpa', 'service-account', 'service-email', 'service-proxy']
);
assert.deepEqual(
flowRegistry.getVisibleGroupIds('kiro', 'kiro-rs'),
['kiro-runtime-status', 'kiro-source-kiro-rs', 'service-account', 'service-email', 'service-proxy']
@@ -33,6 +37,10 @@ test('flow registry exposes openai and kiro with canonical source metadata', ()
flowRegistry.getSettingsGroupDefinition('openai-phone')?.rowIds || [],
[]
);
assert.deepEqual(
flowRegistry.getSettingsGroupDefinition('openai-step6')?.rowIds || [],
['row-step6-cookie-settings']
);
});
test('settings schema normalizes flat input into canonical flow and service namespaces', () => {
@@ -37,6 +37,7 @@ test('sidepanel html exposes flow selector and kiro source fields', () => {
[
'id="select-flow"',
'id="label-source-selector"',
'id="row-step6-cookie-settings"',
'id="row-kiro-rs-url"',
'id="row-kiro-rs-key"',
'id="row-kiro-device-code"',
+4 -1
View File
@@ -42,16 +42,19 @@ test('sidepanel no longer exposes operation delay switch and places step executi
assert.doesNotMatch(html, /id="row-operation-delay-settings"/);
assert.doesNotMatch(html, /id="input-operation-delay-enabled"/);
const step6CookieIndex = html.indexOf('id="row-step6-cookie-settings"');
const autoDelayIndex = html.indexOf('id="row-auto-delay-settings"');
const oauthTimeoutIndex = html.indexOf('id="row-oauth-flow-timeout"');
const stepRangeIndex = html.indexOf('id="row-step-execution-range"');
const oauthDisplayIndex = html.indexOf('id="row-oauth-display"');
assert.notEqual(step6CookieIndex, -1);
assert.notEqual(autoDelayIndex, -1);
assert.notEqual(oauthTimeoutIndex, -1);
assert.notEqual(stepRangeIndex, -1);
assert.notEqual(oauthDisplayIndex, -1);
assert.ok(stepRangeIndex > autoDelayIndex, 'step execution range should still remain below the step6 toggle row');
assert.ok(autoDelayIndex > step6CookieIndex, 'startup delay row should render below the openai step6 cookie row');
assert.ok(stepRangeIndex > autoDelayIndex, 'step execution range should still remain below the startup delay row');
assert.ok(stepRangeIndex > oauthTimeoutIndex, 'step execution range should render below oauth timeout');
assert.ok(stepRangeIndex < oauthDisplayIndex, 'step execution range should stay above oauth runtime display');
});
+67
View File
@@ -22,6 +22,25 @@ test('manifest loads shared source registry before content utils in static bundl
}
});
test('manifest ships a static Kiro auth bundle for cross-page recovery', () => {
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
const kiroEntry = (manifest.content_scripts || []).find((entry) => {
const matches = Array.isArray(entry.matches) ? entry.matches : [];
return matches.includes('https://view.awsapps.com/*')
&& matches.includes('https://signin.aws/*')
&& matches.includes('https://signin.aws.amazon.com/*')
&& matches.includes('https://profile.aws/*')
&& matches.includes('https://profile.aws.amazon.com/*');
});
assert.ok(kiroEntry, 'missing static Kiro auth content script entry');
assert.deepEqual(kiroEntry.js, [
'shared/source-registry.js',
'content/utils.js',
'content/kiro-device-auth-page.js',
]);
});
test('shared source registry exposes canonical source, alias, detection, and ready policies', () => {
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const source = fs.readFileSync('shared/source-registry.js', 'utf8');
@@ -57,6 +76,54 @@ test('shared source registry exposes canonical source, alias, detection, and rea
url: 'https://view.awsapps.com/start',
hostname: 'view.awsapps.com',
}), 'kiro-device-auth');
assert.equal(registry.detectSourceFromLocation({
url: 'https://signin.aws/register',
hostname: 'signin.aws',
}), 'kiro-device-auth');
assert.equal(registry.detectSourceFromLocation({
url: 'https://profile.aws/complete',
hostname: 'profile.aws',
}), 'kiro-device-auth');
assert.equal(registry.detectSourceFromLocation({
url: 'https://signin.aws.amazon.com/register',
hostname: 'signin.aws.amazon.com',
}), 'kiro-device-auth');
assert.equal(registry.detectSourceFromLocation({
url: 'https://profile.aws.amazon.com/complete',
hostname: 'profile.aws.amazon.com',
}), 'kiro-device-auth');
assert.equal(
registry.matchesSourceUrlFamily(
'kiro-device-auth',
'https://signin.aws/register',
'https://view.awsapps.com/start'
),
true
);
assert.equal(
registry.matchesSourceUrlFamily(
'kiro-device-auth',
'https://profile.aws/complete',
'https://signin.aws/register'
),
true
);
assert.equal(
registry.matchesSourceUrlFamily(
'kiro-device-auth',
'https://profile.aws.amazon.com/complete',
'https://signin.aws.amazon.com/register'
),
true
);
assert.equal(
registry.matchesSourceUrlFamily(
'kiro-device-auth',
'https://oidc.us-east-1.amazonaws.com/authorize',
'https://view.awsapps.com/start'
),
true
);
assert.equal(registry.shouldReportReadyForFrame('mail-163', true), false);
assert.equal(registry.shouldReportReadyForFrame('unknown-source', false), false);
assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth');