Files
FlowPilot/content/kiro/register-page.js
T
2026-05-19 02:04:32 +08:00

628 lines
20 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_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_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[name*="otp" i]',
'input[name*="code" i]',
].join(', ');
const KIRO_PASSWORD_INPUT_SELECTOR = [
'input[placeholder="Enter password"]',
'input[placeholder*="Create password" i]',
'input[placeholder*="Password" i]',
'input[name="password"]',
'input[id*="password" i]',
'input[type="password"]',
].join(', ');
const KIRO_CONFIRM_PASSWORD_SELECTOR = [
'input[placeholder*="Re-enter password" i]',
'input[placeholder*="Confirm password" i]',
'input[placeholder*="Confirm Password" i]',
'input[name="confirmPassword"]',
'input[id*="confirm" i]',
].join(', ');
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 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 findVisiblePasswordInputs() {
return collectVisibleElements(KIRO_PASSWORD_INPUT_SELECTOR);
}
function findVisibleConfirmPasswordInput() {
return findFirstVisible(KIRO_CONFIRM_PASSWORD_SELECTOR);
}
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 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 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 fatalState = detectKiroFatalPageState(pageText, currentUrl, document.title || '');
if (fatalState) {
return fatalState;
}
const passwordInputs = findVisiblePasswordInputs();
const confirmPasswordInput = findVisibleConfirmPasswordInput();
if (passwordInputs.length) {
const passwordInput = passwordInputs[0];
return {
state: 'password_page',
url: currentUrl,
passwordInput,
confirmPasswordInput,
continueButton: findPasswordContinueButton(passwordInput),
};
}
const authorizationButton = findAuthorizationActionButton();
if (authorizationButton) {
const authorizationActionText = getElementActionText(authorizationButton);
return {
state: 'authorization_page',
url: currentUrl,
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,
};
}
const otpInput = findVisibleOtpInput();
if (otpInput) {
return {
state: 'otp_page',
url: currentUrl,
otpInput,
verifyButton: findOtpVerifyButton(otpInput),
};
}
const nameInput = findVisibleNameInput();
if (nameInput) {
return {
state: 'name_entry',
url: currentUrl,
nameInput,
continueButton: findNameContinueButton(nameInput),
};
}
const emailInput = findVisibleEmailInput();
if (emailInput) {
return {
state: 'email_entry',
url: currentUrl,
emailInput,
continueButton: findEmailContinueButton(emailInput),
};
}
return {
state: 'loading',
url: currentUrl,
};
}
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) => detected.state !== 'loading' && !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') {
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 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: ['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: ['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);
const confirmPasswordInput = readyState.confirmPasswordInput
|| (() => {
const passwordInputs = findVisiblePasswordInputs();
return passwordInputs.length > 1 ? passwordInputs[1] : null;
})();
if (confirmPasswordInput && confirmPasswordInput !== readyState.passwordInput) {
fillInput(confirmPasswordInput, password);
await sleep(150);
}
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'],
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') {
throw new Error('Kiro 授权页未完成确认访问流程。');
}
return {
submitted: true,
state: currentState.state,
url: currentState.url || location.href,
actions,
};
}
async function handleKiroRegisterCommand(message) {
switch (message.type) {
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-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 === '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;
});
}