Files
FlowPilot/flows/kiro/content/register-page.js
T
2026-05-22 12:40:37 +08:00

1027 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
console.log('[MultiPage:kiro-register-page] Content script loaded on', location.href);
const KIRO_REGISTER_PAGE_LISTENER_SENTINEL = 'data-multipage-kiro-register-page-listener';
const DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS = globalThis.MultiPageKiroTimeouts?.DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS || (3 * 60 * 1000);
const KIRO_CONTINUE_TEXT_PATTERN = /continue|继续/i;
const KIRO_BUILDER_ID_TEXT_PATTERN = /aws\s*builder\s*id|builder\s*id/i;
const KIRO_CONFIRM_CONTINUE_TEXT_PATTERN = /confirm and continue|确认并继续/i;
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"]',
'input[type="email"]',
'input[name="email"]',
'input[autocomplete="username"]',
'input[placeholder*="example.com" i]',
].join(', ');
const KIRO_NAME_INPUT_SELECTOR = [
'input[placeholder*="Silva" i]',
'input[autocomplete="name"]',
'input[name="name"]',
'input[name="fullName"]',
].join(', ');
const KIRO_OTP_INPUT_SELECTOR = [
'input[autocomplete="one-time-code"]',
'input[inputmode="numeric"]',
'input[placeholder*="6-digit" i]',
'input[placeholder*="6 位" i]',
'input[placeholder*="verification" i]',
'input[placeholder*="code" i]',
'input[name*="otp" i]',
'input[name*="code" i]',
'input[id*="otp" i]',
'input[id*="code" i]',
'input[data-testid*="otp" i]',
'input[data-testid*="code" i]',
'input[aria-label*="otp" i]',
'input[aria-label*="code" i]',
'input[aria-label*="verification" i]',
'input[aria-label*="one-time" i]',
].join(', ');
const KIRO_PASSWORD_FIELD_SELECTOR = 'input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="file"])';
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;
const style = window.getComputedStyle(element);
const rect = element.getBoundingClientRect();
return style.display !== 'none'
&& style.visibility !== 'hidden'
&& rect.width > 0
&& rect.height > 0;
}
function getKiroPageText() {
return String(document.body?.textContent || '')
.replace(/\s+/g, ' ')
.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 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));
}
function findFirstVisible(selector) {
return collectVisibleElements(selector)[0] || null;
}
function getElementActionText(element) {
return [
element?.textContent,
element?.value,
element?.getAttribute?.('aria-label'),
element?.getAttribute?.('title'),
element?.getAttribute?.('data-testid'),
]
.filter(Boolean)
.join(' ')
.replace(/\s+/g, ' ')
.trim();
}
function findActionButton(options = {}) {
const {
preferredSelectors = [],
textPattern = null,
formOwner = null,
} = options;
for (const selector of preferredSelectors) {
const preferred = findFirstVisible(selector);
if (preferred && !preferred.disabled && preferred.getAttribute('aria-disabled') !== 'true') {
return preferred;
}
}
const candidates = collectVisibleElements('button, [role="button"], input[type="submit"], input[type="button"]')
.filter((element) => !element.disabled && element.getAttribute('aria-disabled') !== 'true');
const prioritized = formOwner
? candidates.filter((element) => (element.form || element.closest?.('form') || null) === formOwner)
: [];
const pool = prioritized.length ? prioritized : candidates;
if (textPattern instanceof RegExp) {
return pool.find((element) => textPattern.test(getElementActionText(element))) || null;
}
return pool[0] || null;
}
function findVisibleEmailInput() {
return findFirstVisible(KIRO_EMAIL_INPUT_SELECTOR);
}
function findVisibleNameInput() {
return findFirstVisible(KIRO_NAME_INPUT_SELECTOR);
}
function findVisibleOtpInput() {
return findFirstVisible(KIRO_OTP_INPUT_SELECTOR);
}
function getKiroElementText(element) {
return String(element?.textContent || '')
.replace(/\s+/g, ' ')
.trim();
}
function cssEscapeKiroValue(value = '') {
if (globalThis.CSS?.escape) {
return globalThis.CSS.escape(String(value));
}
return String(value).replace(/["\\]/g, '\\$&');
}
function getKiroInputLabelText(input) {
const labels = [];
for (const label of Array.from(input?.labels || [])) {
const text = getKiroElementText(label);
if (text) labels.push(text);
}
const id = String(input?.id || '').trim();
if (id) {
for (const label of Array.from(document.querySelectorAll(`label[for="${cssEscapeKiroValue(id)}"]`))) {
const text = getKiroElementText(label);
if (text) labels.push(text);
}
}
const labelledBy = String(input?.getAttribute?.('aria-labelledby') || '').trim();
if (labelledBy) {
for (const labelId of labelledBy.split(/\s+/).filter(Boolean)) {
const label = document.getElementById?.(labelId);
const text = getKiroElementText(label);
if (text) labels.push(text);
}
}
const parentLabel = input?.closest?.('label');
const parentLabelText = getKiroElementText(parentLabel);
if (parentLabelText) labels.push(parentLabelText);
return Array.from(new Set(labels)).join(' ');
}
function getKiroPasswordInputContext(input) {
const describedBy = String(input?.getAttribute?.('aria-describedby') || '').trim();
const describedByText = describedBy
? describedBy
.split(/\s+/)
.map((id) => getKiroElementText(document.getElementById?.(id)))
.filter(Boolean)
.join(' ')
: '';
return [
input?.getAttribute?.('placeholder'),
input?.getAttribute?.('name'),
input?.id,
input?.getAttribute?.('aria-label'),
input?.getAttribute?.('data-testid'),
input?.getAttribute?.('autocomplete'),
getKiroInputLabelText(input),
describedByText,
]
.filter(Boolean)
.join(' ')
.replace(/\s+/g, ' ')
.trim();
}
function createKiroPasswordCandidate(input, index) {
const context = getKiroPasswordInputContext(input);
const type = String(input?.getAttribute?.('type') || input?.type || '').trim().toLowerCase();
const isConfirm = KIRO_CONFIRM_PASSWORD_TEXT_PATTERN.test(context);
const hasPasswordHint = KIRO_PASSWORD_TEXT_PATTERN.test(context) || type === 'password';
if (!hasPasswordHint && !isConfirm) {
return null;
}
let primaryScore = 0;
let confirmScore = 0;
if (type === 'password') primaryScore += 5;
if (KIRO_PASSWORD_TEXT_PATTERN.test(context)) primaryScore += 20;
if (KIRO_PRIMARY_PASSWORD_HINT_PATTERN.test(context)) primaryScore += 40;
if (isConfirm) {
primaryScore -= 100;
confirmScore += 100;
}
if (/^password$/i.test(String(input?.getAttribute?.('name') || ''))) primaryScore += 40;
if (/^password$/i.test(String(input?.id || ''))) primaryScore += 20;
return {
input,
index,
context,
isConfirm,
primaryScore,
confirmScore,
};
}
function findKiroPasswordFields() {
const candidates = collectVisibleElements(KIRO_PASSWORD_FIELD_SELECTOR)
.map((input, index) => createKiroPasswordCandidate(input, index))
.filter(Boolean);
if (!candidates.length) {
return {
passwordInput: null,
confirmPasswordInput: null,
passwordInputs: [],
};
}
const confirmCandidate = candidates
.filter((candidate) => candidate.isConfirm)
.sort((left, right) => right.confirmScore - left.confirmScore || left.index - right.index)[0] || null;
let passwordCandidate = candidates
.filter((candidate) => !candidate.isConfirm)
.sort((left, right) => right.primaryScore - left.primaryScore || left.index - right.index)[0] || null;
if (!passwordCandidate) {
passwordCandidate = candidates
.filter((candidate) => !confirmCandidate || candidate.input !== confirmCandidate.input)
.sort((left, right) => left.index - right.index)[0] || null;
}
let resolvedConfirmCandidate = confirmCandidate;
if (
(!resolvedConfirmCandidate || resolvedConfirmCandidate.input === passwordCandidate?.input)
&& passwordCandidate
) {
resolvedConfirmCandidate = candidates
.filter((candidate) => candidate.input !== passwordCandidate.input)
.sort((left, right) => left.index - right.index)[0] || null;
}
return {
passwordInput: passwordCandidate?.input || null,
confirmPasswordInput: resolvedConfirmCandidate?.input || null,
passwordInputs: candidates.map((candidate) => candidate.input),
};
}
function findVisiblePasswordInputs() {
return findKiroPasswordFields().passwordInputs;
}
function findVisibleConfirmPasswordInput() {
return findKiroPasswordFields().confirmPasswordInput;
}
function findEmailContinueButton(emailInput = null) {
const form = emailInput?.form || emailInput?.closest?.('form') || null;
return findActionButton({
preferredSelectors: ['button[data-testid="test-primary-button"]'],
textPattern: KIRO_CONTINUE_TEXT_PATTERN,
formOwner: form,
});
}
function findNameContinueButton(nameInput = null) {
const form = nameInput?.form || nameInput?.closest?.('form') || null;
return findActionButton({
preferredSelectors: ['button[data-testid="signup-next-button"]'],
textPattern: KIRO_CONTINUE_TEXT_PATTERN,
formOwner: form,
});
}
function findOtpVerifyButton(otpInput = null) {
const form = otpInput?.form || otpInput?.closest?.('form') || null;
return findActionButton({
preferredSelectors: ['button[data-testid="email-verification-verify-button"]'],
textPattern: KIRO_CONTINUE_TEXT_PATTERN,
formOwner: form,
});
}
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({
preferredSelectors: ['button[data-testid="test-primary-button"]'],
textPattern: KIRO_CONTINUE_TEXT_PATTERN,
formOwner: form,
});
}
function isKiroWebHost(hostname = '') {
const normalized = String(hostname || '').trim().toLowerCase();
return normalized === 'app.kiro.dev'
|| normalized === 'kiro.dev';
}
function parseCurrentUrl() {
try {
return new URL(location.href);
} catch (_error) {
return null;
}
}
function getKiroAuthRouteText() {
const parsed = parseCurrentUrl();
return [
parsed?.pathname || '',
parsed?.hash || '',
].map((entry) => String(entry || '').toLowerCase()).join(' ');
}
function getKiroAuthRouteKind() {
const routeText = getKiroAuthRouteText();
if (/(^|\/)login(\/|\s|$)/.test(routeText)) {
return 'login';
}
if (/(^|\/)signup(\/|\s|$)/.test(routeText)) {
return 'signup';
}
return '';
}
function getKiroOtpRouteState(authRouteKind = '') {
if (!/(^|\/)verify-otp(\/|\s|$)/.test(getKiroAuthRouteText())) {
return '';
}
return authRouteKind === 'login' ? 'login_otp_page' : 'register_otp_page';
}
function findBuilderIdButton() {
return findActionButton({
textPattern: KIRO_BUILDER_ID_TEXT_PATTERN,
});
}
function isKiroWebSignedInPage(pageText = '') {
const parsedUrl = parseCurrentUrl();
const pathname = String(parsedUrl?.pathname || '').replace(/\/+$/, '') || '/';
const authStatus = String(parsedUrl?.searchParams?.get('auth_status') || '').trim().toLowerCase();
if (authStatus === 'success') {
return true;
}
if (pathname !== '/signin') {
return true;
}
return /signed\s*in|authorization\s*successful|登录成功|已登录|授权成功/i.test(pageText);
}
function getAuthorizationActionKind(text = '') {
if (KIRO_CONFIRM_CONTINUE_TEXT_PATTERN.test(text)) {
return 'confirm_continue';
}
if (KIRO_ALLOW_ACCESS_TEXT_PATTERN.test(text)) {
return 'allow_access';
}
return 'unknown';
}
function findAuthorizationActionButton() {
return findActionButton({
preferredSelectors: [
'button[data-testid="confirm-button"]',
'button[data-testid="allow-access-button"]',
],
textPattern: /confirm and continue|allow access|确认并继续|允许访问/i,
});
}
function isKiroAwsAuthHost(hostname = '') {
const normalized = String(hostname || '').trim().toLowerCase();
return normalized === 'view.awsapps.com'
|| normalized === 'login.awsapps.com'
|| normalized === 'profile.aws.amazon.com'
|| normalized === 'profile.aws'
|| normalized.endsWith('.profile.aws.amazon.com')
|| normalized.endsWith('.profile.aws')
|| normalized === 'signin.aws.amazon.com'
|| normalized === 'signin.aws'
|| normalized.endsWith('.signin.aws.amazon.com')
|| normalized.endsWith('.signin.aws');
}
function detectKiroFatalPageState(pageText = '', currentUrl = '', pageTitle = '') {
const combinedText = `${pageTitle} ${pageText}`.replace(/\s+/g, ' ').trim();
const normalizedHost = String(location.hostname || '').trim().toLowerCase();
const isKiroAuthHost = isKiroAwsAuthHost(normalizedHost);
const matchedSignals = [
/403 error/i.test(combinedText),
/the request could not be satisfied/i.test(combinedText),
/generated by cloudfront/i.test(combinedText),
].filter(Boolean).length;
if (isKiroAuthHost && KIRO_AWS_REQUEST_ERROR_TEXT_PATTERN.test(combinedText)) {
return {
state: 'proxy_error_page',
url: currentUrl,
fatalMessage: 'Kiro 注册页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。',
};
}
if (matchedSignals >= 2 || (isKiroAuthHost && matchedSignals >= 1 && KIRO_CLOUDFRONT_403_TEXT_PATTERN.test(combinedText))) {
return {
state: 'cloudfront_403_page',
url: currentUrl,
fatalMessage: 'Kiro 注册页返回 403CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。',
};
}
return null;
}
function isKiroFatalState(state = '') {
return state === 'cloudfront_403_page'
|| state === 'proxy_error_page';
}
function getKiroFatalStateMessage(snapshot = {}) {
if (snapshot?.fatalMessage) {
return snapshot.fatalMessage;
}
switch (snapshot?.state) {
case 'cloudfront_403_page':
return 'Kiro 注册页返回 403CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。';
case 'proxy_error_page':
return 'Kiro 注册页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。';
default:
return `Kiro 页面出现异常状态:${snapshot?.state || 'unknown'}`;
}
}
function detectKiroRegisterPageState() {
const pageText = getKiroPageText();
const currentUrl = location.href;
const authRouteKind = getKiroAuthRouteKind();
const pageEmail = findKiroPageAccountEmail(pageText);
const fatalState = detectKiroFatalPageState(pageText, currentUrl, document.title || '');
if (fatalState) {
return fatalState;
}
if (isKiroWebHost(location.hostname || '')) {
const builderIdButton = findBuilderIdButton();
if (builderIdButton) {
return {
state: 'kiro_signin_page',
url: currentUrl,
email: pageEmail,
accountEmail: pageEmail,
actionButton: builderIdButton,
actionText: getElementActionText(builderIdButton),
};
}
if (isKiroWebSignedInPage(pageText)) {
const accountEmail = findKiroSignedInAccountEmail(pageText);
return {
state: 'kiro_web_signed_in',
url: currentUrl,
accountEmail,
email: accountEmail,
};
}
}
const passwordFields = findKiroPasswordFields();
if (passwordFields.passwordInput) {
const passwordInput = passwordFields.passwordInput;
const isLoginPasswordPage = authRouteKind === 'login'
|| (!passwordFields.confirmPasswordInput && KIRO_SIGN_IN_TEXT_PATTERN.test(pageText));
return {
state: isLoginPasswordPage ? 'login_password_page' : 'create_password_page',
url: currentUrl,
email: pageEmail,
accountEmail: pageEmail,
passwordInput,
confirmPasswordInput: passwordFields.confirmPasswordInput,
continueButton: findPasswordContinueButton(passwordInput),
};
}
const authorizationButton = findAuthorizationActionButton();
if (authorizationButton) {
const authorizationActionText = getElementActionText(authorizationButton);
return {
state: 'authorization_page',
url: currentUrl,
email: pageEmail,
accountEmail: pageEmail,
actionButton: authorizationButton,
authorizationActionKind: getAuthorizationActionKind(authorizationActionText),
authorizationActionText,
};
}
if (
KIRO_SUCCESS_TEXT_PATTERN.test(pageText)
|| /request approved|access your data|请求已批准|访问你的数据/i.test(pageText)
) {
return {
state: 'success_page',
url: currentUrl,
email: pageEmail,
accountEmail: pageEmail,
};
}
const otpInput = findVisibleOtpInput();
const otpRouteState = getKiroOtpRouteState(authRouteKind);
if (otpInput || otpRouteState) {
return {
state: otpRouteState || (authRouteKind === 'login' ? 'login_otp_page' : 'register_otp_page'),
url: currentUrl,
email: pageEmail,
accountEmail: pageEmail,
otpInput,
verifyButton: otpInput ? findOtpVerifyButton(otpInput) : null,
resendButton: otpInput ? findOtpResendButton(otpInput) : null,
codeInvalid: KIRO_CODE_INVALID_TEXT_PATTERN.test(pageText),
};
}
const nameInput = findVisibleNameInput();
if (nameInput) {
return {
state: 'name_entry',
url: currentUrl,
email: pageEmail,
accountEmail: pageEmail,
nameInput,
continueButton: findNameContinueButton(nameInput),
};
}
const emailInput = findVisibleEmailInput();
if (emailInput) {
return {
state: 'email_entry',
url: currentUrl,
email: pageEmail,
accountEmail: pageEmail,
emailInput,
continueButton: findEmailContinueButton(emailInput),
};
}
return {
state: 'loading',
url: currentUrl,
email: pageEmail,
accountEmail: pageEmail,
};
}
async function waitForKiroState(predicate, options = {}) {
const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS));
const retryDelayMs = Math.max(100, Math.floor(Number(options.retryDelayMs) || 250));
const start = Date.now();
while (Date.now() - start < timeoutMs) {
throwIfStopped();
const detected = detectKiroRegisterPageState();
if (isKiroFatalState(detected.state)) {
throw new Error(getKiroFatalStateMessage(detected));
}
if (predicate(detected)) {
return detected;
}
await sleep(retryDelayMs);
}
const finalState = detectKiroRegisterPageState();
if (isKiroFatalState(finalState.state)) {
throw new Error(getKiroFatalStateMessage(finalState));
}
throw new Error(options.timeoutMessage || `等待 Kiro 页面状态超时:${finalState.state}`);
}
async function ensureKiroRegisterPageState(payload = {}) {
const targetStates = Array.isArray(payload?.targetStates)
? payload.targetStates.map((entry) => String(entry || '').trim()).filter(Boolean)
: [];
if (!targetStates.length) {
throw new Error('缺少 Kiro 目标页面状态。');
}
return waitForKiroState(
(detected) => targetStates.includes(detected.state),
{
timeoutMs: payload?.timeoutMs,
retryDelayMs: payload?.retryDelayMs,
timeoutMessage: payload?.timeoutMessage || `等待 Kiro 页面进入 ${targetStates.join(' / ')} 超时,当前页面:${location.href}`,
}
);
}
async function waitForKiroRegisterStateChange(payload = {}) {
const fromStates = Array.isArray(payload?.fromStates)
? payload.fromStates.map((entry) => String(entry || '').trim()).filter(Boolean)
: [];
if (!fromStates.length) {
throw new Error('缺少 Kiro 原始页面状态。');
}
return waitForKiroState(
(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,
timeoutMessage: payload?.timeoutMessage || `等待 Kiro 页面离开 ${fromStates.join(' / ')} 超时,当前页面:${location.href}`,
}
);
}
async function waitForKiroAuthorizationAdvance(previousState = {}, options = {}) {
return waitForKiroState(
(detected) => {
if (detected.state === 'success_page' || detected.state === 'kiro_web_signed_in') {
return true;
}
if (detected.state !== 'authorization_page') {
return false;
}
const previousKind = String(previousState?.authorizationActionKind || '').trim();
const previousText = String(previousState?.authorizationActionText || '').trim();
const previousUrl = String(previousState?.url || '').trim();
const nextKind = String(detected.authorizationActionKind || '').trim();
const nextText = String(detected.authorizationActionText || '').trim();
return nextKind !== previousKind
|| nextText !== previousText
|| String(detected.url || '').trim() !== previousUrl;
},
{
timeoutMs: options.timeoutMs,
retryDelayMs: options.retryDelayMs,
timeoutMessage: options.timeoutMessage || `等待 Kiro 授权页进入下一步超时:${location.href}`,
}
);
}
async function selectKiroBuilderId() {
const readyState = await ensureKiroRegisterPageState({
targetStates: ['kiro_signin_page'],
timeoutMs: DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
retryDelayMs: 250,
});
if (!readyState.actionButton) {
throw new Error('Kiro 官方登录页未找到 Builder ID 登录按钮。');
}
simulateClick(readyState.actionButton);
return {
submitted: true,
state: 'builder_id_selected',
url: location.href,
actionText: readyState.actionText || '',
};
}
async function submitKiroEmail(payload = {}) {
const email = String(payload?.email || '').trim();
if (!email) {
throw new Error('缺少 Kiro 注册邮箱,无法继续提交。');
}
const readyState = await ensureKiroRegisterPageState({
targetStates: ['email_entry'],
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
retryDelayMs: payload?.retryDelayMs || 250,
});
if (!readyState.emailInput || !readyState.continueButton) {
throw new Error('Kiro 邮箱页未找到可用的输入框或继续按钮。');
}
fillInput(readyState.emailInput, email);
await sleep(200);
simulateClick(readyState.continueButton);
return {
submitted: true,
state: 'email_submitted',
url: location.href,
};
}
async function submitKiroName(payload = {}) {
const fullName = String(payload?.fullName || '').trim();
if (!fullName) {
throw new Error('缺少 Kiro 注册姓名,无法继续提交。');
}
const readyState = await ensureKiroRegisterPageState({
targetStates: ['name_entry'],
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
retryDelayMs: payload?.retryDelayMs || 250,
});
if (!readyState.nameInput || !readyState.continueButton) {
throw new Error('Kiro 姓名页未找到可用的输入框或继续按钮。');
}
fillInput(readyState.nameInput, fullName);
await sleep(200);
simulateClick(readyState.continueButton);
return {
submitted: true,
state: 'name_submitted',
url: location.href,
};
}
async function submitKiroVerificationCode(payload = {}) {
const code = String(payload?.code || '').trim();
if (!code) {
throw new Error('缺少 Kiro 邮箱验证码,无法继续提交。');
}
const readyState = await ensureKiroRegisterPageState({
targetStates: ['register_otp_page'],
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
retryDelayMs: payload?.retryDelayMs || 250,
});
if (!readyState.otpInput || !readyState.verifyButton) {
throw new Error('Kiro 验证码页未找到可用的输入框或继续按钮。');
}
fillInput(readyState.otpInput, code);
await sleep(200);
simulateClick(readyState.verifyButton);
return {
submitted: true,
state: 'verification_submitted',
url: location.href,
};
}
async function submitKiroPassword(payload = {}) {
const password = String(payload?.password || '');
if (!password) {
throw new Error('缺少 Kiro 账户密码,无法继续提交。');
}
const readyState = await ensureKiroRegisterPageState({
targetStates: ['create_password_page'],
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
retryDelayMs: payload?.retryDelayMs || 250,
});
if (!readyState.passwordInput || !readyState.continueButton) {
throw new Error('Kiro 密码页未找到可用的密码框或继续按钮。');
}
fillInput(readyState.passwordInput, password);
await sleep(150);
if (String(readyState.passwordInput.value || '') !== password) {
throw new Error('Kiro \u5bc6\u7801\u9875\u4e3b\u5bc6\u7801\u6846\u586b\u5165\u5931\u8d25\uff0c\u5df2\u505c\u6b62\u63d0\u4ea4\uff0c\u8bf7\u68c0\u67e5\u9875\u9762\u5b57\u6bb5\u7ed3\u6784\u3002');
}
const confirmPasswordInput = readyState.confirmPasswordInput
|| (() => {
const passwordInputs = findVisiblePasswordInputs();
return passwordInputs.find((input) => input !== readyState.passwordInput) || null;
})();
if (confirmPasswordInput && confirmPasswordInput !== readyState.passwordInput) {
fillInput(confirmPasswordInput, password);
await sleep(150);
if (String(confirmPasswordInput.value || '') !== password) {
throw new Error('Kiro \u5bc6\u7801\u9875\u786e\u8ba4\u5bc6\u7801\u6846\u586b\u5165\u5931\u8d25\uff0c\u5df2\u505c\u6b62\u63d0\u4ea4\uff0c\u8bf7\u68c0\u67e5\u9875\u9762\u5b57\u6bb5\u7ed3\u6784\u3002');
}
}
if (
confirmPasswordInput
&& confirmPasswordInput !== readyState.passwordInput
&& String(confirmPasswordInput.value || '') !== String(readyState.passwordInput.value || '')
) {
throw new Error('Kiro \u5bc6\u7801\u9875\u4e24\u4e2a\u5bc6\u7801\u6846\u5185\u5bb9\u4e0d\u4e00\u81f4\uff0c\u5df2\u505c\u6b62\u63d0\u4ea4\u3002');
}
simulateClick(readyState.continueButton);
return {
submitted: true,
state: 'password_submitted',
url: location.href,
};
}
async function confirmKiroRegisterConsent(payload = {}) {
let currentState = await ensureKiroRegisterPageState({
targetStates: ['authorization_page', 'success_page', 'kiro_web_signed_in'],
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
retryDelayMs: payload?.retryDelayMs || 250,
});
const maxActions = Math.max(1, Math.min(4, Number(payload?.maxActions) || 3));
const actions = [];
while (currentState.state === 'authorization_page' && actions.length < maxActions) {
if (!currentState.actionButton) {
throw new Error('Kiro 授权页未找到可用的授权按钮。');
}
actions.push({
kind: currentState.authorizationActionKind || 'unknown',
text: currentState.authorizationActionText || getElementActionText(currentState.actionButton),
});
simulateClick(currentState.actionButton);
currentState = await waitForKiroAuthorizationAdvance(currentState, {
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
retryDelayMs: payload?.retryDelayMs || 250,
timeoutMessage: 'Kiro 授权按钮点击后页面未继续,请检查当前授权页状态。',
});
}
if (currentState.state !== 'success_page' && currentState.state !== 'kiro_web_signed_in') {
throw new Error('Kiro 授权页未完成确认访问流程。');
}
return {
submitted: true,
state: currentState.state,
url: currentState.url || location.href,
actions,
};
}
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 || '',
codeInvalid: Boolean(detected?.codeInvalid),
};
}
case 'ENSURE_KIRO_PAGE_STATE':
return ensureKiroRegisterPageState(message.payload || {});
case 'ENSURE_KIRO_STATE_CHANGE':
return waitForKiroRegisterStateChange(message.payload || {});
case 'EXECUTE_NODE': {
const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim();
if (nodeId === 'kiro-open-register-page') {
return selectKiroBuilderId();
}
if (nodeId === 'kiro-submit-email') {
return submitKiroEmail(message.payload || {});
}
if (nodeId === 'kiro-submit-name') {
return submitKiroName(message.payload || {});
}
if (nodeId === 'kiro-submit-verification-code') {
return submitKiroVerificationCode(message.payload || {});
}
if (nodeId === 'kiro-submit-password') {
return submitKiroPassword(message.payload || {});
}
if (nodeId === 'kiro-complete-register-consent') {
return confirmKiroRegisterConsent(message.payload || {});
}
throw new Error(`register-page.js 不处理节点:${nodeId}`);
}
default:
return null;
}
}
if (document.documentElement.getAttribute(KIRO_REGISTER_PAGE_LISTENER_SENTINEL) !== '1') {
document.documentElement.setAttribute(KIRO_REGISTER_PAGE_LISTENER_SENTINEL, '1');
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
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();
handleKiroRegisterCommand(message)
.then((result) => {
sendResponse({ ok: true, ...(result || {}) });
})
.catch((error) => {
if (isStopError(error)) {
sendResponse({ stopped: true, error: error.message });
return;
}
sendResponse({ error: error?.message || String(error || '未知错误') });
});
return true;
}
return false;
});
}