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
@@ -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');