重构 Kiro 注册页面状态收养
This commit is contained in:
@@ -68,6 +68,48 @@
|
||||
'code',
|
||||
'aws',
|
||||
]);
|
||||
const KIRO_REGISTER_PAGE_STATES = Object.freeze([
|
||||
'kiro_signin_page',
|
||||
'email_entry',
|
||||
'name_entry',
|
||||
'register_otp_page',
|
||||
'create_password_page',
|
||||
'authorization_page',
|
||||
'success_page',
|
||||
'kiro_web_signed_in',
|
||||
'login_password_page',
|
||||
'login_otp_page',
|
||||
]);
|
||||
const KIRO_REGISTER_EXISTING_ACCOUNT_STATES = Object.freeze([
|
||||
'login_password_page',
|
||||
'login_otp_page',
|
||||
]);
|
||||
const KIRO_REGISTER_AFTER_EMAIL_STATES = Object.freeze([
|
||||
'name_entry',
|
||||
'register_otp_page',
|
||||
'create_password_page',
|
||||
'authorization_page',
|
||||
'success_page',
|
||||
'kiro_web_signed_in',
|
||||
]);
|
||||
const KIRO_REGISTER_AFTER_NAME_STATES = Object.freeze([
|
||||
'register_otp_page',
|
||||
'create_password_page',
|
||||
'authorization_page',
|
||||
'success_page',
|
||||
'kiro_web_signed_in',
|
||||
]);
|
||||
const KIRO_REGISTER_AFTER_OTP_STATES = Object.freeze([
|
||||
'create_password_page',
|
||||
'authorization_page',
|
||||
'success_page',
|
||||
'kiro_web_signed_in',
|
||||
]);
|
||||
const KIRO_REGISTER_AFTER_PASSWORD_STATES = Object.freeze([
|
||||
'authorization_page',
|
||||
'success_page',
|
||||
'kiro_web_signed_in',
|
||||
]);
|
||||
|
||||
function isPlainObject(value) {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||
@@ -607,6 +649,7 @@
|
||||
fromStates: Array.isArray(options.fromStates) ? options.fromStates : [],
|
||||
timeoutMs: stateWaitTimeoutMs,
|
||||
retryDelayMs: Number(options.pageRetryDelayMs) || 250,
|
||||
returnOnCodeInvalid: Boolean(options.returnOnCodeInvalid),
|
||||
timeoutMessage: options.timeoutMessage || '',
|
||||
},
|
||||
}, {
|
||||
@@ -624,6 +667,109 @@
|
||||
return result || { state: '', url: '' };
|
||||
}
|
||||
|
||||
async function readKiroRegisterPageState(tabId, options = {}) {
|
||||
return ensureKiroPageState(tabId, {
|
||||
...options,
|
||||
targetStates: KIRO_REGISTER_PAGE_STATES,
|
||||
});
|
||||
}
|
||||
|
||||
function isKiroExistingAccountState(pageState = '') {
|
||||
return KIRO_REGISTER_EXISTING_ACCOUNT_STATES.includes(cleanString(pageState));
|
||||
}
|
||||
|
||||
function resolveKiroRegisterEmail(currentState = {}, pageState = {}, fallbackEmail = '') {
|
||||
const runtimeState = readKiroRuntime(currentState);
|
||||
return cleanString(
|
||||
fallbackEmail
|
||||
|| pageState?.email
|
||||
|| pageState?.accountEmail
|
||||
|| runtimeState.register?.email
|
||||
|| currentState?.email
|
||||
).toLowerCase();
|
||||
}
|
||||
|
||||
function createKiroExistingAccountError(pageState = {}, currentState = {}, step = 0, fallbackEmail = '') {
|
||||
const email = resolveKiroRegisterEmail(currentState, pageState, fallbackEmail);
|
||||
const emailText = email ? ` ${email}` : '';
|
||||
return new Error(
|
||||
`步骤 ${step}:邮箱${emailText} 已进入 AWS Builder ID 登录页,说明该邮箱已存在或被 AWS 判定为已有账号;Kiro 注册流程只处理新账号注册,已停止,请换新邮箱重试。`
|
||||
);
|
||||
}
|
||||
|
||||
function assertKiroRegistrationOnlyState(pageState = {}, currentState = {}, step = 0, fallbackEmail = '') {
|
||||
if (isKiroExistingAccountState(pageState?.state)) {
|
||||
throw createKiroExistingAccountError(pageState, currentState, step, fallbackEmail);
|
||||
}
|
||||
}
|
||||
|
||||
function getKiroRegisterStatusForPageState(pageState = '') {
|
||||
switch (cleanString(pageState)) {
|
||||
case 'email_entry':
|
||||
return 'waiting_email';
|
||||
case 'name_entry':
|
||||
return 'waiting_name';
|
||||
case 'register_otp_page':
|
||||
return 'waiting_otp';
|
||||
case 'create_password_page':
|
||||
return 'waiting_password';
|
||||
case 'authorization_page':
|
||||
return 'waiting_consent';
|
||||
case 'success_page':
|
||||
case 'kiro_web_signed_in':
|
||||
return 'completed';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function buildKiroRegisterStatePatch(currentState = {}, pageState = {}, options = {}) {
|
||||
const resolvedEmail = resolveKiroRegisterEmail(currentState, pageState, options.email);
|
||||
const nextStatus = cleanString(options.status) || getKiroRegisterStatusForPageState(pageState?.state);
|
||||
const registerPatch = {};
|
||||
if (resolvedEmail) {
|
||||
registerPatch.email = resolvedEmail;
|
||||
}
|
||||
if (options.fullName !== undefined) {
|
||||
registerPatch.fullName = cleanString(options.fullName);
|
||||
}
|
||||
if (options.verificationRequestedAt !== undefined) {
|
||||
registerPatch.verificationRequestedAt = Math.max(0, Number(options.verificationRequestedAt) || 0);
|
||||
}
|
||||
if (nextStatus) {
|
||||
registerPatch.status = nextStatus;
|
||||
registerPatch.completedAt = nextStatus === 'completed' ? Date.now() : 0;
|
||||
}
|
||||
|
||||
return {
|
||||
session: {
|
||||
currentStage: nextStatus === 'completed' ? 'desktop-authorize' : 'register',
|
||||
pageState: pageState?.state || '',
|
||||
pageUrl: pageState?.url || '',
|
||||
lastError: '',
|
||||
},
|
||||
register: registerPatch,
|
||||
upload: {
|
||||
status: nextStatus === 'completed' ? 'waiting_desktop_authorize' : 'waiting_register',
|
||||
error: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function adoptKiroRegisterPageState(currentState = {}, pageState = {}, nodeId = '', options = {}) {
|
||||
const payload = await applyRuntimeState(
|
||||
currentState,
|
||||
buildKiroRegisterStatePatch(currentState, pageState, options)
|
||||
);
|
||||
await completeNodeFromBackground(nodeId, {
|
||||
...payload,
|
||||
email: resolveKiroRegisterEmail(currentState, pageState, options.email),
|
||||
accountIdentifierType: 'email',
|
||||
accountIdentifier: resolveKiroRegisterEmail(currentState, pageState, options.email),
|
||||
});
|
||||
return payload;
|
||||
}
|
||||
|
||||
function resolveKiroFullName(state = {}) {
|
||||
const runtimeState = readKiroRuntime(state);
|
||||
const cachedName = cleanString(runtimeState.register?.fullName);
|
||||
@@ -753,7 +899,8 @@
|
||||
}
|
||||
|
||||
const runtimeState = readKiroRuntime(state);
|
||||
const requestedAt = Math.max(0, Number(runtimeState.register?.verificationRequestedAt) || Date.now());
|
||||
const recordedRequestedAt = Math.max(0, Number(runtimeState.register?.verificationRequestedAt) || 0);
|
||||
const requestedAt = recordedRequestedAt || Math.max(0, Date.now() - MAIL_2925_FILTER_LOOKBACK_MS);
|
||||
const filterAfterTimestamp = mail.provider === '2925'
|
||||
? Math.max(0, requestedAt - MAIL_2925_FILTER_LOOKBACK_MS)
|
||||
: requestedAt;
|
||||
@@ -952,22 +1099,44 @@
|
||||
const nodeId = String(state?.nodeId || 'kiro-submit-email').trim();
|
||||
const currentState = await getExecutionState(state);
|
||||
try {
|
||||
if (typeof resolveSignupEmailForFlow !== 'function') {
|
||||
throw new Error('Kiro 邮箱步骤缺少公共邮箱解析能力,无法继续执行。');
|
||||
}
|
||||
|
||||
const tabId = await activateKiroRegisterTab(currentState, {
|
||||
missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。',
|
||||
openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。',
|
||||
});
|
||||
await ensureKiroPageState(tabId, {
|
||||
const currentPageState = await readKiroRegisterPageState(tabId, {
|
||||
step: 2,
|
||||
targetStates: ['email_entry'],
|
||||
stableMs: 2500,
|
||||
initialDelayMs: 300,
|
||||
injectLogMessage: '步骤 2:Kiro 注册页内容脚本未就绪,正在等待页面恢复...',
|
||||
readyLogMessage: '步骤 2:正在等待 Kiro 注册页邮箱输入框加载完成...',
|
||||
readyLogMessage: '步骤 2:正在读取 Kiro 注册页当前状态...',
|
||||
});
|
||||
assertKiroRegistrationOnlyState(currentPageState, currentState, 2);
|
||||
|
||||
if (KIRO_REGISTER_AFTER_EMAIL_STATES.includes(currentPageState?.state)) {
|
||||
const runtimeState = readKiroRuntime(currentState);
|
||||
const adoptedEmail = resolveKiroRegisterEmail(currentState, currentPageState);
|
||||
if (!adoptedEmail) {
|
||||
throw new Error('步骤 2:当前已不在邮箱页,但无法识别注册邮箱,请回到邮箱页重新提交或在配置中填入注册邮箱。');
|
||||
}
|
||||
const status = getKiroRegisterStatusForPageState(currentPageState.state);
|
||||
await adoptKiroRegisterPageState(currentState, currentPageState, nodeId, {
|
||||
email: adoptedEmail,
|
||||
status,
|
||||
verificationRequestedAt: currentPageState.state === 'register_otp_page'
|
||||
? runtimeState.register?.verificationRequestedAt || 0
|
||||
: undefined,
|
||||
});
|
||||
await log(`步骤 2:检测到当前已进入 ${currentPageState.state},已收养注册进度并继续。`, 'ok', nodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPageState?.state !== 'email_entry') {
|
||||
throw new Error(`步骤 2:当前页面状态为 ${currentPageState?.state || 'unknown'},不是 Kiro 注册邮箱页,请先执行步骤 1 或回到邮箱输入页。`);
|
||||
}
|
||||
|
||||
if (typeof resolveSignupEmailForFlow !== 'function') {
|
||||
throw new Error('Kiro 邮箱步骤缺少公共邮箱解析能力,无法继续执行。');
|
||||
}
|
||||
|
||||
const resolvedEmail = await resolveSignupEmailForFlow(currentState, {
|
||||
preserveAccountIdentity: true,
|
||||
@@ -993,19 +1162,27 @@
|
||||
throw new Error(submitResult.error);
|
||||
}
|
||||
|
||||
const landingResult = await ensureKiroPageState(tabId, {
|
||||
const landingResult = await waitForKiroPageChange(tabId, {
|
||||
step: 2,
|
||||
targetStates: ['name_entry'],
|
||||
fromStates: ['email_entry'],
|
||||
stableMs: 1500,
|
||||
initialDelayMs: 150,
|
||||
injectLogMessage: '步骤 2:邮箱提交后页面切换中,正在等待 Kiro 注册页恢复...',
|
||||
readyLogMessage: '步骤 2:邮箱已提交,正在等待 Kiro 姓名页加载完成...',
|
||||
timeoutMessage: '邮箱提交后未进入姓名页,请检查当前邮箱是否已注册或页面是否异常。',
|
||||
readyLogMessage: '步骤 2:邮箱已提交,正在等待 Kiro 注册链路进入下一页...',
|
||||
timeoutMessage: '邮箱提交后页面没有离开邮箱页,请检查邮箱是否被拒绝、页面是否异常或代理是否卡住。',
|
||||
});
|
||||
assertKiroRegistrationOnlyState(landingResult, currentState, 2, resolvedEmail);
|
||||
if (!KIRO_REGISTER_AFTER_EMAIL_STATES.includes(landingResult?.state)) {
|
||||
throw new Error(`步骤 2:邮箱提交后进入了无法继续注册的页面状态:${landingResult?.state || 'unknown'}。`);
|
||||
}
|
||||
|
||||
const landedStatus = getKiroRegisterStatusForPageState(landingResult.state);
|
||||
const requestedAt = landingResult.state === 'register_otp_page' ? Date.now() : 0;
|
||||
const isCompleted = landedStatus === 'completed';
|
||||
|
||||
const payload = await applyRuntimeState(currentState, {
|
||||
session: {
|
||||
currentStage: 'register',
|
||||
currentStage: isCompleted ? 'desktop-authorize' : 'register',
|
||||
pageState: landingResult?.state || '',
|
||||
pageUrl: landingResult?.url || '',
|
||||
lastError: '',
|
||||
@@ -1013,15 +1190,16 @@
|
||||
register: {
|
||||
email: resolvedEmail,
|
||||
fullName: '',
|
||||
verificationRequestedAt: 0,
|
||||
status: 'waiting_name',
|
||||
verificationRequestedAt: requestedAt,
|
||||
status: landedStatus,
|
||||
completedAt: isCompleted ? Date.now() : 0,
|
||||
},
|
||||
upload: {
|
||||
status: 'waiting_register',
|
||||
status: isCompleted ? 'waiting_desktop_authorize' : 'waiting_register',
|
||||
error: '',
|
||||
},
|
||||
});
|
||||
await log(`步骤 2:邮箱 ${resolvedEmail} 已提交,当前已进入姓名页。`, 'ok', nodeId);
|
||||
await log(`步骤 2:邮箱 ${resolvedEmail} 已提交,当前页面状态:${landingResult?.state || 'unknown'}。`, 'ok', nodeId);
|
||||
await completeNodeFromBackground(nodeId, {
|
||||
...payload,
|
||||
email: resolvedEmail,
|
||||
@@ -1040,22 +1218,41 @@
|
||||
const currentState = await getExecutionState(state);
|
||||
try {
|
||||
const runtimeState = readKiroRuntime(currentState);
|
||||
if (!cleanString(runtimeState.register?.email || currentState?.email)) {
|
||||
throw new Error('缺少 Kiro 注册邮箱,请先完成步骤 2。');
|
||||
}
|
||||
|
||||
const tabId = await activateKiroRegisterTab(currentState, {
|
||||
missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。',
|
||||
openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。',
|
||||
});
|
||||
await ensureKiroPageState(tabId, {
|
||||
const currentPageState = await readKiroRegisterPageState(tabId, {
|
||||
step: 3,
|
||||
targetStates: ['name_entry'],
|
||||
stableMs: 1500,
|
||||
initialDelayMs: 150,
|
||||
injectLogMessage: '步骤 3:Kiro 姓名页内容脚本未就绪,正在等待页面恢复...',
|
||||
readyLogMessage: '步骤 3:正在等待 Kiro 姓名页加载完成...',
|
||||
readyLogMessage: '步骤 3:正在读取 Kiro 注册页当前状态...',
|
||||
});
|
||||
assertKiroRegistrationOnlyState(currentPageState, currentState, 3);
|
||||
|
||||
const currentEmail = resolveKiroRegisterEmail(currentState, currentPageState);
|
||||
if (!currentEmail) {
|
||||
throw new Error('步骤 3:缺少 Kiro 注册邮箱,请先完成步骤 2。');
|
||||
}
|
||||
|
||||
if (KIRO_REGISTER_AFTER_NAME_STATES.includes(currentPageState?.state)) {
|
||||
const status = getKiroRegisterStatusForPageState(currentPageState.state);
|
||||
await adoptKiroRegisterPageState(currentState, currentPageState, nodeId, {
|
||||
email: currentEmail,
|
||||
status,
|
||||
verificationRequestedAt: currentPageState.state === 'register_otp_page'
|
||||
? (runtimeState.register?.verificationRequestedAt || 0)
|
||||
: undefined,
|
||||
});
|
||||
await log(`步骤 3:检测到当前已进入 ${currentPageState.state},已收养注册进度并继续。`, 'ok', nodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPageState?.state !== 'name_entry') {
|
||||
throw new Error(`步骤 3:当前页面状态为 ${currentPageState?.state || 'unknown'},不是 Kiro 注册姓名页,请先完成步骤 2。`);
|
||||
}
|
||||
|
||||
const fullName = resolveKiroFullName(currentState);
|
||||
const verificationRequestedAt = Date.now();
|
||||
@@ -1079,33 +1276,44 @@
|
||||
throw new Error(submitResult.error);
|
||||
}
|
||||
|
||||
const landingResult = await ensureKiroPageState(tabId, {
|
||||
const landingResult = await waitForKiroPageChange(tabId, {
|
||||
step: 3,
|
||||
targetStates: ['otp_page'],
|
||||
fromStates: ['name_entry'],
|
||||
stableMs: 1500,
|
||||
initialDelayMs: 150,
|
||||
injectLogMessage: '步骤 3:姓名提交后页面切换中,正在等待 Kiro 注册页恢复...',
|
||||
readyLogMessage: '步骤 3:姓名已提交,正在等待 Kiro 验证码页加载完成...',
|
||||
readyLogMessage: '步骤 3:姓名已提交,正在等待 Kiro 注册链路进入下一页...',
|
||||
timeoutMessage: '姓名提交后未进入验证码页,请检查当前页面状态。',
|
||||
});
|
||||
assertKiroRegistrationOnlyState(landingResult, currentState, 3, currentEmail);
|
||||
if (!KIRO_REGISTER_AFTER_NAME_STATES.includes(landingResult?.state)) {
|
||||
throw new Error(`步骤 3:姓名提交后进入了无法继续注册的页面状态:${landingResult?.state || 'unknown'}。`);
|
||||
}
|
||||
|
||||
const landedStatus = getKiroRegisterStatusForPageState(landingResult.state);
|
||||
const isCompleted = landedStatus === 'completed';
|
||||
const payload = await applyRuntimeState(currentState, {
|
||||
session: {
|
||||
currentStage: 'register',
|
||||
currentStage: isCompleted ? 'desktop-authorize' : 'register',
|
||||
pageState: landingResult?.state || '',
|
||||
pageUrl: landingResult?.url || '',
|
||||
lastError: '',
|
||||
},
|
||||
register: {
|
||||
email: currentEmail,
|
||||
fullName,
|
||||
verificationRequestedAt,
|
||||
status: 'waiting_otp',
|
||||
verificationRequestedAt: landingResult.state === 'register_otp_page'
|
||||
? verificationRequestedAt
|
||||
: runtimeState.register?.verificationRequestedAt || 0,
|
||||
status: landedStatus,
|
||||
completedAt: isCompleted ? Date.now() : 0,
|
||||
},
|
||||
upload: {
|
||||
status: 'waiting_register',
|
||||
status: isCompleted ? 'waiting_desktop_authorize' : 'waiting_register',
|
||||
error: '',
|
||||
},
|
||||
});
|
||||
await log('步骤 3:姓名已提交,当前已进入验证码页。', 'ok', nodeId);
|
||||
await log(`步骤 3:姓名已提交,当前页面状态:${landingResult?.state || 'unknown'}。`, 'ok', nodeId);
|
||||
await completeNodeFromBackground(nodeId, payload);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
@@ -1118,25 +1326,48 @@
|
||||
const nodeId = String(state?.nodeId || 'kiro-submit-verification-code').trim();
|
||||
const currentState = await getExecutionState(state);
|
||||
try {
|
||||
const runtimeState = readKiroRuntime(currentState);
|
||||
if (!cleanString(runtimeState.register?.email || currentState?.email)) {
|
||||
throw new Error('缺少 Kiro 注册邮箱,请先完成步骤 2。');
|
||||
}
|
||||
|
||||
const tabId = await activateKiroRegisterTab(currentState, {
|
||||
missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。',
|
||||
openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。',
|
||||
});
|
||||
await ensureKiroPageState(tabId, {
|
||||
const currentPageState = await readKiroRegisterPageState(tabId, {
|
||||
step: 4,
|
||||
targetStates: ['otp_page'],
|
||||
stableMs: 1500,
|
||||
initialDelayMs: 150,
|
||||
injectLogMessage: '步骤 4:Kiro 验证码页内容脚本未就绪,正在等待页面恢复...',
|
||||
readyLogMessage: '步骤 4:正在等待 Kiro 验证码页加载完成...',
|
||||
readyLogMessage: '步骤 4:正在读取 Kiro 注册页当前状态...',
|
||||
});
|
||||
assertKiroRegistrationOnlyState(currentPageState, currentState, 4);
|
||||
|
||||
const codeResult = await pollKiroVerificationCode(4, currentState, nodeId);
|
||||
const currentEmail = resolveKiroRegisterEmail(currentState, currentPageState);
|
||||
if (!currentEmail) {
|
||||
throw new Error('步骤 4:缺少 Kiro 注册邮箱,请先完成步骤 2,或在当前验证码页显示注册邮箱后重试。');
|
||||
}
|
||||
|
||||
if (KIRO_REGISTER_AFTER_OTP_STATES.includes(currentPageState?.state)) {
|
||||
const status = getKiroRegisterStatusForPageState(currentPageState.state);
|
||||
await adoptKiroRegisterPageState(currentState, currentPageState, nodeId, {
|
||||
email: currentEmail,
|
||||
status,
|
||||
});
|
||||
await log(`步骤 4:检测到当前已进入 ${currentPageState.state},已收养注册进度并继续。`, 'ok', nodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPageState?.state !== 'register_otp_page') {
|
||||
throw new Error(`步骤 4:当前页面状态为 ${currentPageState?.state || 'unknown'},不是 Kiro 注册验证码页,请先完成前置注册步骤。`);
|
||||
}
|
||||
|
||||
const pollingState = {
|
||||
...currentState,
|
||||
email: currentEmail,
|
||||
kiroRuntime: deepMerge(readKiroRuntime(currentState), {
|
||||
register: {
|
||||
email: currentEmail,
|
||||
},
|
||||
}),
|
||||
};
|
||||
const codeResult = await pollKiroVerificationCode(4, pollingState, nodeId);
|
||||
const code = cleanString(codeResult?.code);
|
||||
if (!code) {
|
||||
throw new Error('未能获取到 Kiro 邮箱验证码。');
|
||||
@@ -1162,31 +1393,44 @@
|
||||
throw new Error(submitResult.error);
|
||||
}
|
||||
|
||||
const landingResult = await ensureKiroPageState(tabId, {
|
||||
const landingResult = await waitForKiroPageChange(tabId, {
|
||||
step: 4,
|
||||
targetStates: ['password_page'],
|
||||
fromStates: ['register_otp_page'],
|
||||
stableMs: 1500,
|
||||
initialDelayMs: 150,
|
||||
injectLogMessage: '步骤 4:验证码提交后页面切换中,正在等待 Kiro 注册页恢复...',
|
||||
readyLogMessage: '步骤 4:验证码已提交,正在等待 Kiro 密码页加载完成...',
|
||||
returnOnCodeInvalid: true,
|
||||
timeoutMessage: '验证码提交后未进入密码页,请检查验证码是否失效或页面是否异常。',
|
||||
});
|
||||
assertKiroRegistrationOnlyState(landingResult, currentState, 4, currentEmail);
|
||||
if (landingResult?.state === 'register_otp_page' && landingResult?.codeInvalid) {
|
||||
throw new Error('步骤 4:Kiro 提示验证码无效或已过期,已停止当前注册;请重新获取验证码或换邮箱重试。');
|
||||
}
|
||||
if (!KIRO_REGISTER_AFTER_OTP_STATES.includes(landingResult?.state)) {
|
||||
throw new Error(`步骤 4:验证码提交后进入了无法继续注册的页面状态:${landingResult?.state || 'unknown'}。`);
|
||||
}
|
||||
|
||||
const landedStatus = getKiroRegisterStatusForPageState(landingResult.state);
|
||||
const isCompleted = landedStatus === 'completed';
|
||||
const payload = await applyRuntimeState(currentState, {
|
||||
session: {
|
||||
currentStage: 'register',
|
||||
currentStage: isCompleted ? 'desktop-authorize' : 'register',
|
||||
pageState: landingResult?.state || '',
|
||||
pageUrl: landingResult?.url || '',
|
||||
lastError: '',
|
||||
},
|
||||
register: {
|
||||
status: 'waiting_password',
|
||||
email: currentEmail,
|
||||
status: landedStatus,
|
||||
completedAt: isCompleted ? Date.now() : 0,
|
||||
},
|
||||
upload: {
|
||||
status: 'waiting_register',
|
||||
status: isCompleted ? 'waiting_desktop_authorize' : 'waiting_register',
|
||||
error: '',
|
||||
},
|
||||
});
|
||||
await log('步骤 4:验证码已提交,当前已进入密码页。', 'ok', nodeId);
|
||||
await log(`步骤 4:验证码已提交,当前页面状态:${landingResult?.state || 'unknown'}。`, 'ok', nodeId);
|
||||
await completeNodeFromBackground(nodeId, {
|
||||
...payload,
|
||||
code,
|
||||
@@ -1208,14 +1452,29 @@
|
||||
missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。',
|
||||
openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。',
|
||||
});
|
||||
await ensureKiroPageState(tabId, {
|
||||
const currentPageState = await readKiroRegisterPageState(tabId, {
|
||||
step: 5,
|
||||
targetStates: ['password_page'],
|
||||
stableMs: 1500,
|
||||
initialDelayMs: 150,
|
||||
injectLogMessage: '步骤 5:Kiro 密码页内容脚本未就绪,正在等待页面恢复...',
|
||||
readyLogMessage: '步骤 5:正在等待 Kiro 密码页加载完成...',
|
||||
readyLogMessage: '步骤 5:正在读取 Kiro 注册页当前状态...',
|
||||
});
|
||||
assertKiroRegistrationOnlyState(currentPageState, currentState, 5);
|
||||
|
||||
const currentEmail = resolveKiroRegisterEmail(currentState, currentPageState);
|
||||
if (KIRO_REGISTER_AFTER_PASSWORD_STATES.includes(currentPageState?.state)) {
|
||||
const status = getKiroRegisterStatusForPageState(currentPageState.state);
|
||||
await adoptKiroRegisterPageState(currentState, currentPageState, nodeId, {
|
||||
email: currentEmail,
|
||||
status,
|
||||
});
|
||||
await log(`步骤 5:检测到当前已进入 ${currentPageState.state},已收养注册进度并继续。`, 'ok', nodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPageState?.state !== 'create_password_page') {
|
||||
throw new Error(`步骤 5:当前页面状态为 ${currentPageState?.state || 'unknown'},不是 Kiro 注册密码页,请先完成前置注册步骤。`);
|
||||
}
|
||||
|
||||
const passwordResolution = resolveKiroPassword(currentState);
|
||||
const password = passwordResolution.password;
|
||||
@@ -1253,28 +1512,33 @@
|
||||
|
||||
const landingResult = await waitForKiroPageChange(tabId, {
|
||||
step: 5,
|
||||
fromStates: ['password_page'],
|
||||
fromStates: ['create_password_page'],
|
||||
stableMs: 1200,
|
||||
initialDelayMs: 120,
|
||||
injectLogMessage: '步骤 5:密码提交后页面切换中,正在等待 Kiro 注册页恢复...',
|
||||
readyLogMessage: '步骤 5:密码已提交,正在等待 Kiro 注册页完成跳转...',
|
||||
timeoutMessage: '密码提交后页面未离开密码页,请检查密码规则或当前页面提示。',
|
||||
});
|
||||
const nextRegisterStatus = landingResult?.state === 'success_page'
|
||||
? 'completed'
|
||||
: 'waiting_consent';
|
||||
assertKiroRegistrationOnlyState(landingResult, currentState, 5, currentEmail);
|
||||
if (!KIRO_REGISTER_AFTER_PASSWORD_STATES.includes(landingResult?.state)) {
|
||||
throw new Error(`步骤 5:密码提交后进入了无法继续注册的页面状态:${landingResult?.state || 'unknown'}。`);
|
||||
}
|
||||
|
||||
const nextRegisterStatus = getKiroRegisterStatusForPageState(landingResult?.state);
|
||||
const payload = await applyRuntimeState(currentState, {
|
||||
session: {
|
||||
currentStage: 'register',
|
||||
currentStage: nextRegisterStatus === 'completed' ? 'desktop-authorize' : 'register',
|
||||
pageState: landingResult?.state || '',
|
||||
pageUrl: landingResult?.url || '',
|
||||
lastError: '',
|
||||
},
|
||||
register: {
|
||||
email: currentEmail || undefined,
|
||||
status: nextRegisterStatus,
|
||||
completedAt: nextRegisterStatus === 'completed' ? Date.now() : 0,
|
||||
},
|
||||
upload: {
|
||||
status: 'waiting_register',
|
||||
status: nextRegisterStatus === 'completed' ? 'waiting_desktop_authorize' : 'waiting_register',
|
||||
error: '',
|
||||
},
|
||||
});
|
||||
@@ -1295,17 +1559,21 @@
|
||||
missingUrlMessage: '缺少 Kiro 注册页地址,请先执行步骤 1。',
|
||||
openFailedMessage: '无法恢复 Kiro 注册页,请重新执行步骤 1。',
|
||||
});
|
||||
let landingResult = await ensureKiroPageState(tabId, {
|
||||
let landingResult = await readKiroRegisterPageState(tabId, {
|
||||
step: 6,
|
||||
targetStates: ['authorization_page', 'kiro_web_signed_in'],
|
||||
stableMs: 1500,
|
||||
initialDelayMs: 150,
|
||||
injectLogMessage: '步骤 6:Kiro 授权确认页内容脚本未就绪,正在等待页面恢复...',
|
||||
readyLogMessage: '步骤 6:正在等待 Kiro 授权确认页加载完成...',
|
||||
readyLogMessage: '步骤 6:正在读取 Kiro 注册页当前状态...',
|
||||
timeoutMessage: '未进入 Kiro 授权确认页,请检查当前页面状态。',
|
||||
});
|
||||
assertKiroRegistrationOnlyState(landingResult, currentState, 6);
|
||||
|
||||
if (landingResult?.state !== 'kiro_web_signed_in') {
|
||||
if (!['authorization_page', 'success_page', 'kiro_web_signed_in'].includes(landingResult?.state)) {
|
||||
throw new Error(`步骤 6:当前页面状态为 ${landingResult?.state || 'unknown'},不是 Kiro 注册授权确认页,请先完成前置注册步骤。`);
|
||||
}
|
||||
|
||||
if (landingResult?.state === 'authorization_page') {
|
||||
await log('步骤 6:正在确认访问并完成 Kiro 注册授权...', 'info', nodeId);
|
||||
const submitResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, {
|
||||
type: 'EXECUTE_NODE',
|
||||
@@ -1326,13 +1594,14 @@
|
||||
}
|
||||
landingResult = await ensureKiroPageState(tabId, {
|
||||
step: 6,
|
||||
targetStates: ['kiro_web_signed_in'],
|
||||
targetStates: ['success_page', 'kiro_web_signed_in'],
|
||||
stableMs: 2000,
|
||||
initialDelayMs: 300,
|
||||
injectLogMessage: '步骤 6:授权确认后页面跳转中,正在等待 Kiro Web 登录态恢复...',
|
||||
readyLogMessage: '步骤 6:授权确认已提交,正在等待回到 Kiro Web...',
|
||||
timeoutMessage: '授权确认后未回到 Kiro Web 登录完成页,请检查当前页面或代理状态。',
|
||||
});
|
||||
assertKiroRegistrationOnlyState(landingResult, currentState, 6);
|
||||
}
|
||||
|
||||
const webAuthSummary = await captureKiroWebAuthSummary();
|
||||
|
||||
@@ -45,6 +45,9 @@ const KIRO_PASSWORD_FIELD_SELECTOR = 'input:not([type="hidden"]):not([type="chec
|
||||
const KIRO_PASSWORD_TEXT_PATTERN = /password|\u5bc6\u7801/i;
|
||||
const KIRO_CONFIRM_PASSWORD_TEXT_PATTERN = /confirm\s*password|re[-\s]*enter\s*password|repeat\s*password|verify\s*password|\u786e\u8ba4\s*\u5bc6\u7801|\u518d\u6b21.*\u5bc6\u7801|\u91cd\u590d.*\u5bc6\u7801/i;
|
||||
const KIRO_PRIMARY_PASSWORD_HINT_PATTERN = /enter\s*password|create\s*password|new\s*password|\u8f93\u5165.*\u5bc6\u7801|\u521b\u5efa.*\u5bc6\u7801|^\s*\u5bc6\u7801\s*$/i;
|
||||
const KIRO_SIGN_IN_TEXT_PATTERN = /sign\s*in\s*with\s*your\s*aws\s*builder\s*id|aws\s*builder\s*id\s*sign\s*in/i;
|
||||
const KIRO_CODE_INVALID_TEXT_PATTERN = /code\s*(?:is\s*)?invalid|invalid\s*code|\u4ee3\u7801\u65e0\u6548|\u9a8c\u8bc1\u7801\u65e0\u6548/i;
|
||||
const KIRO_RESEND_CODE_TEXT_PATTERN = /resend|send\s*again|\u91cd\u65b0\u53d1\u9001/i;
|
||||
|
||||
function isVisibleKiroElement(element) {
|
||||
if (!element) return false;
|
||||
@@ -114,6 +117,32 @@ function findKiroSignedInAccountEmail(pageText = '') {
|
||||
return extractFirstKiroAccountEmailFromText(pageText);
|
||||
}
|
||||
|
||||
function findKiroPageAccountEmail(pageText = '') {
|
||||
const selector = [
|
||||
'input[type="email"]',
|
||||
'input[name*="email" i]',
|
||||
'[data-testid*="email" i]',
|
||||
'[aria-label*="email" i]',
|
||||
'[title*="email" i]',
|
||||
'dd',
|
||||
'span',
|
||||
'div',
|
||||
'p',
|
||||
].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));
|
||||
@@ -357,6 +386,14 @@ function findOtpVerifyButton(otpInput = null) {
|
||||
});
|
||||
}
|
||||
|
||||
function findOtpResendButton(otpInput = null) {
|
||||
const form = otpInput?.form || otpInput?.closest?.('form') || null;
|
||||
return findActionButton({
|
||||
textPattern: KIRO_RESEND_CODE_TEXT_PATTERN,
|
||||
formOwner: form,
|
||||
});
|
||||
}
|
||||
|
||||
function findPasswordContinueButton(passwordInput = null) {
|
||||
const form = passwordInput?.form || passwordInput?.closest?.('form') || null;
|
||||
return findActionButton({
|
||||
@@ -380,6 +417,18 @@ function parseCurrentUrl() {
|
||||
}
|
||||
}
|
||||
|
||||
function getKiroAuthPathKind() {
|
||||
const parsed = parseCurrentUrl();
|
||||
const pathname = String(parsed?.pathname || '').toLowerCase();
|
||||
if (/(^|\/)login(\/|$)/.test(pathname)) {
|
||||
return 'login';
|
||||
}
|
||||
if (/(^|\/)signup(\/|$)/.test(pathname)) {
|
||||
return 'signup';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function findBuilderIdButton() {
|
||||
return findActionButton({
|
||||
textPattern: KIRO_BUILDER_ID_TEXT_PATTERN,
|
||||
@@ -484,6 +533,8 @@ function getKiroFatalStateMessage(snapshot = {}) {
|
||||
function detectKiroRegisterPageState() {
|
||||
const pageText = getKiroPageText();
|
||||
const currentUrl = location.href;
|
||||
const authPathKind = getKiroAuthPathKind();
|
||||
const pageEmail = findKiroPageAccountEmail(pageText);
|
||||
const fatalState = detectKiroFatalPageState(pageText, currentUrl, document.title || '');
|
||||
if (fatalState) {
|
||||
return fatalState;
|
||||
@@ -495,6 +546,8 @@ function detectKiroRegisterPageState() {
|
||||
return {
|
||||
state: 'kiro_signin_page',
|
||||
url: currentUrl,
|
||||
email: pageEmail,
|
||||
accountEmail: pageEmail,
|
||||
actionButton: builderIdButton,
|
||||
actionText: getElementActionText(builderIdButton),
|
||||
};
|
||||
@@ -513,9 +566,13 @@ function detectKiroRegisterPageState() {
|
||||
const passwordFields = findKiroPasswordFields();
|
||||
if (passwordFields.passwordInput) {
|
||||
const passwordInput = passwordFields.passwordInput;
|
||||
const isLoginPasswordPage = authPathKind === 'login'
|
||||
|| (!passwordFields.confirmPasswordInput && KIRO_SIGN_IN_TEXT_PATTERN.test(pageText));
|
||||
return {
|
||||
state: 'password_page',
|
||||
state: isLoginPasswordPage ? 'login_password_page' : 'create_password_page',
|
||||
url: currentUrl,
|
||||
email: pageEmail,
|
||||
accountEmail: pageEmail,
|
||||
passwordInput,
|
||||
confirmPasswordInput: passwordFields.confirmPasswordInput,
|
||||
continueButton: findPasswordContinueButton(passwordInput),
|
||||
@@ -528,6 +585,8 @@ function detectKiroRegisterPageState() {
|
||||
return {
|
||||
state: 'authorization_page',
|
||||
url: currentUrl,
|
||||
email: pageEmail,
|
||||
accountEmail: pageEmail,
|
||||
actionButton: authorizationButton,
|
||||
authorizationActionKind: getAuthorizationActionKind(authorizationActionText),
|
||||
authorizationActionText,
|
||||
@@ -541,16 +600,22 @@ function detectKiroRegisterPageState() {
|
||||
return {
|
||||
state: 'success_page',
|
||||
url: currentUrl,
|
||||
email: pageEmail,
|
||||
accountEmail: pageEmail,
|
||||
};
|
||||
}
|
||||
|
||||
const otpInput = findVisibleOtpInput();
|
||||
if (otpInput) {
|
||||
return {
|
||||
state: 'otp_page',
|
||||
state: authPathKind === 'login' ? 'login_otp_page' : 'register_otp_page',
|
||||
url: currentUrl,
|
||||
email: pageEmail,
|
||||
accountEmail: pageEmail,
|
||||
otpInput,
|
||||
verifyButton: findOtpVerifyButton(otpInput),
|
||||
resendButton: findOtpResendButton(otpInput),
|
||||
codeInvalid: KIRO_CODE_INVALID_TEXT_PATTERN.test(pageText),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -559,6 +624,8 @@ function detectKiroRegisterPageState() {
|
||||
return {
|
||||
state: 'name_entry',
|
||||
url: currentUrl,
|
||||
email: pageEmail,
|
||||
accountEmail: pageEmail,
|
||||
nameInput,
|
||||
continueButton: findNameContinueButton(nameInput),
|
||||
};
|
||||
@@ -569,6 +636,8 @@ function detectKiroRegisterPageState() {
|
||||
return {
|
||||
state: 'email_entry',
|
||||
url: currentUrl,
|
||||
email: pageEmail,
|
||||
accountEmail: pageEmail,
|
||||
emailInput,
|
||||
continueButton: findEmailContinueButton(emailInput),
|
||||
};
|
||||
@@ -577,6 +646,8 @@ function detectKiroRegisterPageState() {
|
||||
return {
|
||||
state: 'loading',
|
||||
url: currentUrl,
|
||||
email: pageEmail,
|
||||
accountEmail: pageEmail,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -631,7 +702,15 @@ async function waitForKiroRegisterStateChange(payload = {}) {
|
||||
}
|
||||
|
||||
return waitForKiroState(
|
||||
(detected) => detected.state !== 'loading' && !fromStates.includes(detected.state),
|
||||
(detected) => {
|
||||
if (detected.state === 'loading') {
|
||||
return false;
|
||||
}
|
||||
if (payload?.returnOnCodeInvalid && detected.state === 'register_otp_page' && detected.codeInvalid) {
|
||||
return true;
|
||||
}
|
||||
return !fromStates.includes(detected.state);
|
||||
},
|
||||
{
|
||||
timeoutMs: payload?.timeoutMs,
|
||||
retryDelayMs: payload?.retryDelayMs,
|
||||
@@ -741,7 +820,7 @@ async function submitKiroVerificationCode(payload = {}) {
|
||||
}
|
||||
|
||||
const readyState = await ensureKiroRegisterPageState({
|
||||
targetStates: ['otp_page'],
|
||||
targetStates: ['register_otp_page'],
|
||||
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
|
||||
retryDelayMs: payload?.retryDelayMs || 250,
|
||||
});
|
||||
@@ -766,7 +845,7 @@ async function submitKiroPassword(payload = {}) {
|
||||
}
|
||||
|
||||
const readyState = await ensureKiroRegisterPageState({
|
||||
targetStates: ['password_page'],
|
||||
targetStates: ['create_password_page'],
|
||||
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
|
||||
retryDelayMs: payload?.retryDelayMs || 250,
|
||||
});
|
||||
@@ -860,6 +939,7 @@ async function handleKiroRegisterCommand(message) {
|
||||
authorizationActionKind: detected?.authorizationActionKind || '',
|
||||
authorizationActionText: detected?.authorizationActionText || '',
|
||||
actionText: detected?.actionText || '',
|
||||
codeInvalid: Boolean(detected?.codeInvalid),
|
||||
};
|
||||
}
|
||||
case 'ENSURE_KIRO_PAGE_STATE':
|
||||
|
||||
@@ -35,7 +35,136 @@ test('kiro register runner uses a shared 3-minute page-load timeout budget', ()
|
||||
|
||||
test('kiro register consent step treats Kiro Web signed-in page as completion', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
assert.match(source, /targetStates: \['authorization_page', 'kiro_web_signed_in'\]/);
|
||||
assert.match(source, /landingResult\?\.state !== 'kiro_web_signed_in'/);
|
||||
assert.match(source, /readKiroRegisterPageState\(tabId, \{/);
|
||||
assert.match(source, /\['authorization_page', 'success_page', 'kiro_web_signed_in'\]\.includes\(landingResult\?\.state\)/);
|
||||
assert.match(source, /landingResult\?\.state === 'authorization_page'/);
|
||||
assert.doesNotMatch(source, /landingResult\?\.state !== 'success_page'/);
|
||||
});
|
||||
|
||||
test('kiro register runner uses registration-only page states instead of shared OpenAI names', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
assert.match(source, /KIRO_REGISTER_PAGE_STATES/);
|
||||
assert.match(source, /'register_otp_page'/);
|
||||
assert.match(source, /'create_password_page'/);
|
||||
assert.match(source, /'login_password_page'/);
|
||||
assert.match(source, /'login_otp_page'/);
|
||||
assert.doesNotMatch(source, /targetStates: \['otp_page'\]/);
|
||||
assert.doesNotMatch(source, /targetStates: \['password_page'\]/);
|
||||
assert.doesNotMatch(source, /fromStates: \['password_page'\]/);
|
||||
});
|
||||
|
||||
test('kiro register runner fails existing-account login branches during registration', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
assert.match(source, /KIRO_REGISTER_EXISTING_ACCOUNT_STATES/);
|
||||
assert.match(source, /assertKiroRegistrationOnlyState\(landingResult, currentState, 2, resolvedEmail\)/);
|
||||
assert.match(source, /邮箱.*已进入 AWS Builder ID 登录页/);
|
||||
assert.match(source, /Kiro 注册流程只处理新账号注册/);
|
||||
});
|
||||
|
||||
test('kiro submit-email stops immediately when AWS routes the email to login', async () => {
|
||||
const api = loadRegisterRunnerApi();
|
||||
const currentState = {
|
||||
kiroRuntime: {
|
||||
session: {
|
||||
registerTabId: 101,
|
||||
},
|
||||
register: {
|
||||
loginUrl: 'https://app.kiro.dev/signin',
|
||||
},
|
||||
},
|
||||
};
|
||||
const sentMessages = [];
|
||||
const statePatches = [];
|
||||
let completed = false;
|
||||
const runner = api.createKiroRegisterRunner({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async () => {
|
||||
completed = true;
|
||||
},
|
||||
getState: async () => currentState,
|
||||
getTabId: async () => 101,
|
||||
isTabAlive: async () => true,
|
||||
resolveSignupEmailForFlow: async () => 'existing-user@duck.com',
|
||||
sendToContentScriptResilient: async (_sourceId, message) => {
|
||||
sentMessages.push(message);
|
||||
if (message.type === 'ENSURE_KIRO_PAGE_STATE') {
|
||||
return { state: 'email_entry', url: 'https://us-east-1.signin.aws/platform/d/signup' };
|
||||
}
|
||||
if (message.type === 'EXECUTE_NODE') {
|
||||
return { submitted: true, state: 'email_submitted' };
|
||||
}
|
||||
if (message.type === 'ENSURE_KIRO_STATE_CHANGE') {
|
||||
return {
|
||||
state: 'login_password_page',
|
||||
url: 'https://us-east-1.signin.aws/platform/d/login',
|
||||
email: 'existing-user@duck.com',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
setState: async (patch) => {
|
||||
statePatches.push(patch);
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => runner.executeKiroSubmitEmail({ nodeId: 'kiro-submit-email', ...currentState }),
|
||||
/existing-user@duck\.com.*已进入 AWS Builder ID 登录页/
|
||||
);
|
||||
|
||||
assert.equal(completed, false);
|
||||
assert.equal(sentMessages.some((message) => message.type === 'EXECUTE_NODE'), true);
|
||||
assert.equal(statePatches.some((patch) => /已进入 AWS Builder ID 登录页/.test(patch.kiroRuntime?.session?.lastError || '')), true);
|
||||
});
|
||||
|
||||
test('kiro submit-email can adopt an already-open registration OTP page without allocating a new mailbox', async () => {
|
||||
const api = loadRegisterRunnerApi();
|
||||
const currentState = {
|
||||
kiroRuntime: {
|
||||
session: {
|
||||
registerTabId: 102,
|
||||
},
|
||||
register: {
|
||||
loginUrl: 'https://app.kiro.dev/signin',
|
||||
},
|
||||
},
|
||||
};
|
||||
const sentMessages = [];
|
||||
let completedPayload = null;
|
||||
const runner = api.createKiroRegisterRunner({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (_nodeId, payload) => {
|
||||
completedPayload = payload;
|
||||
},
|
||||
getState: async () => currentState,
|
||||
getTabId: async () => 102,
|
||||
isTabAlive: async () => true,
|
||||
sendToContentScriptResilient: async (_sourceId, message) => {
|
||||
sentMessages.push(message);
|
||||
assert.equal(message.type, 'ENSURE_KIRO_PAGE_STATE');
|
||||
return {
|
||||
state: 'register_otp_page',
|
||||
url: 'https://us-east-1.signin.aws/platform/d/signup',
|
||||
email: 'manual-user@duck.com',
|
||||
};
|
||||
},
|
||||
setState: async () => {},
|
||||
});
|
||||
|
||||
await runner.executeKiroSubmitEmail({ nodeId: 'kiro-submit-email', ...currentState });
|
||||
|
||||
assert.equal(completedPayload?.kiroRuntime?.register?.email, 'manual-user@duck.com');
|
||||
assert.equal(completedPayload?.kiroRuntime?.register?.status, 'waiting_otp');
|
||||
assert.equal(completedPayload?.kiroRuntime?.register?.verificationRequestedAt, 0);
|
||||
assert.equal(sentMessages.some((message) => message.type === 'EXECUTE_NODE'), false);
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ test('kiro state session patch accepts canonical nested runtime updates', () =>
|
||||
kiroRuntime: {
|
||||
session: {
|
||||
currentStage: 'register',
|
||||
pageState: 'otp_page',
|
||||
pageState: 'register_otp_page',
|
||||
pageUrl: 'https://signin.aws/register',
|
||||
},
|
||||
register: {
|
||||
@@ -92,7 +92,7 @@ test('kiro state session patch accepts canonical nested runtime updates', () =>
|
||||
});
|
||||
|
||||
assert.equal(patch.kiroRuntime.session.currentStage, 'register');
|
||||
assert.equal(patch.kiroRuntime.session.pageState, 'otp_page');
|
||||
assert.equal(patch.kiroRuntime.session.pageState, 'register_otp_page');
|
||||
assert.equal(patch.kiroRuntime.session.pageUrl, 'https://signin.aws/register');
|
||||
assert.equal(patch.kiroRuntime.register.email, 'aws-user@example.com');
|
||||
assert.equal(patch.kiroRuntime.register.fullName, 'Ada Lovelace');
|
||||
|
||||
@@ -87,6 +87,7 @@ function createDomHarness({
|
||||
}
|
||||
const context = {
|
||||
console: { log() {}, warn() {}, error() {}, info() {} },
|
||||
URL,
|
||||
location: { href, hostname },
|
||||
document: {
|
||||
title,
|
||||
@@ -109,6 +110,10 @@ function createDomHarness({
|
||||
if (String(selector).startsWith('label[for=')) {
|
||||
return [];
|
||||
}
|
||||
const testIdMatch = String(selector).match(/\[data-testid="([^"]+)"\]/);
|
||||
if (testIdMatch) {
|
||||
return [...inputs, ...buttons].filter((element) => element.getAttribute?.('data-testid') === testIdMatch[1]);
|
||||
}
|
||||
if (String(selector).trim().startsWith('input')) {
|
||||
return inputs;
|
||||
}
|
||||
@@ -234,7 +239,7 @@ test('kiro register content fills primary and confirm password fields separately
|
||||
harness.simulateClick = (element) => clicks.push(element);
|
||||
|
||||
const detected = harness.detectKiroRegisterPageState();
|
||||
assert.equal(detected.state, 'password_page');
|
||||
assert.equal(detected.state, 'create_password_page');
|
||||
assert.equal(detected.passwordInput, passwordInput);
|
||||
assert.equal(detected.confirmPasswordInput, confirmPasswordInput);
|
||||
|
||||
@@ -245,3 +250,68 @@ test('kiro register content fills primary and confirm password fields separately
|
||||
assert.equal(confirmPasswordInput.value, 'mdy8U9_rzqhw6D');
|
||||
assert.deepEqual(clicks, [continueButton]);
|
||||
});
|
||||
|
||||
test('kiro register content classifies AWS login password as an existing-account branch', () => {
|
||||
const passwordInput = createInputElement({
|
||||
id: 'formField15-1779237828927-4809',
|
||||
type: 'text',
|
||||
placeholder: 'Enter password',
|
||||
label: '\u5bc6\u7801',
|
||||
});
|
||||
const continueButton = createButtonElement({ text: '\u7ee7\u7eed' });
|
||||
|
||||
const harness = createDomHarness({
|
||||
href: 'https://us-east-1.signin.aws/platform/d-9067642ac7/login?workflowStateHandle=abc',
|
||||
hostname: 'us-east-1.signin.aws',
|
||||
title: 'Amazon Web Services',
|
||||
bodyText: 'Sign in with your AWS Builder ID Email much-glance-avert@duck.com Change',
|
||||
inputs: [passwordInput],
|
||||
buttons: [continueButton],
|
||||
});
|
||||
|
||||
const detected = harness.detectKiroRegisterPageState();
|
||||
|
||||
assert.equal(detected.state, 'login_password_page');
|
||||
assert.equal(detected.email, 'much-glance-avert@duck.com');
|
||||
assert.equal(detected.passwordInput, passwordInput);
|
||||
});
|
||||
|
||||
test('kiro register content classifies signup verification separately from login verification', () => {
|
||||
const registerOtpInput = createInputElement({
|
||||
id: 'formField38-1779237828927-4809',
|
||||
type: 'text',
|
||||
placeholder: '6-digit',
|
||||
label: '\u9a8c\u8bc1\u7801',
|
||||
});
|
||||
const loginOtpInput = createInputElement({
|
||||
id: 'formField38-1779237828927-4810',
|
||||
type: 'text',
|
||||
placeholder: '6-digit',
|
||||
label: '\u9a8c\u8bc1\u7801',
|
||||
});
|
||||
|
||||
const registerHarness = createDomHarness({
|
||||
href: 'https://us-east-1.signin.aws/platform/d-9067642ac7/signup?state=abc',
|
||||
hostname: 'us-east-1.signin.aws',
|
||||
title: 'Amazon Web Services',
|
||||
bodyText: 'Verify your identity Email new-user@duck.com',
|
||||
inputs: [registerOtpInput],
|
||||
buttons: [createButtonElement()],
|
||||
});
|
||||
const loginHarness = createDomHarness({
|
||||
href: 'https://us-east-1.signin.aws/platform/d-9067642ac7/login?workflowStateHandle=abc',
|
||||
hostname: 'us-east-1.signin.aws',
|
||||
title: 'Amazon Web Services',
|
||||
bodyText: 'Verify your identity Email existing-user@duck.com',
|
||||
inputs: [loginOtpInput],
|
||||
buttons: [createButtonElement()],
|
||||
});
|
||||
|
||||
const registerDetected = registerHarness.detectKiroRegisterPageState();
|
||||
const loginDetected = loginHarness.detectKiroRegisterPageState();
|
||||
|
||||
assert.equal(registerDetected.state, 'register_otp_page');
|
||||
assert.equal(registerDetected.email, 'new-user@duck.com');
|
||||
assert.equal(loginDetected.state, 'login_otp_page');
|
||||
assert.equal(loginDetected.email, 'existing-user@duck.com');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user