Fix Kiro existing web session desktop auth
This commit is contained in:
+9
-3
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 也已同时跳过。'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user