feat: rebuild kiro flow as independent desktop auth pipeline
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
console.log('[MultiPage:kiro-desktop-authorize-page] Content script loaded on', location.href);
|
||||
|
||||
const KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL = 'data-multipage-kiro-desktop-authorize-page-listener';
|
||||
const KIRO_DESKTOP_ALLOW_TEXT_PATTERN = /allow access|authorize|continue|allow|允许访问|授权|继续/i;
|
||||
const KIRO_DESKTOP_LOADING_TEXT_PATTERN = /loading|redirecting|please wait|加载中|跳转中|请稍候/i;
|
||||
const KIRO_DESKTOP_CLOUDFRONT_403_TEXT_PATTERN = /403 error|the request could not be satisfied|generated by cloudfront/i;
|
||||
|
||||
const KIRO_DESKTOP_EMAIL_INPUT_SELECTOR = [
|
||||
'input[placeholder="username@example.com"]',
|
||||
'input[type="email"]',
|
||||
'input[name="email"]',
|
||||
'input[autocomplete="username"]',
|
||||
].join(', ');
|
||||
|
||||
const KIRO_DESKTOP_PASSWORD_INPUT_SELECTOR = [
|
||||
'input[type="password"]',
|
||||
'input[name="password"]',
|
||||
'input[id*="password" i]',
|
||||
'input[autocomplete="current-password"]',
|
||||
].join(', ');
|
||||
|
||||
const KIRO_DESKTOP_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(', ');
|
||||
|
||||
function isVisibleDesktopElement(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 collectVisibleDesktopElements(selector) {
|
||||
return Array.from(document.querySelectorAll(selector))
|
||||
.filter((element) => isVisibleDesktopElement(element));
|
||||
}
|
||||
|
||||
function findFirstVisibleDesktopElement(selector) {
|
||||
return collectVisibleDesktopElements(selector)[0] || null;
|
||||
}
|
||||
|
||||
function getDesktopPageText() {
|
||||
return String(document.body?.textContent || '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function getDesktopActionText(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 findDesktopActionButton(options = {}) {
|
||||
const {
|
||||
preferredSelectors = [],
|
||||
textPattern = null,
|
||||
formOwner = null,
|
||||
} = options;
|
||||
|
||||
for (const selector of preferredSelectors) {
|
||||
const preferred = findFirstVisibleDesktopElement(selector);
|
||||
if (preferred && !preferred.disabled && preferred.getAttribute('aria-disabled') !== 'true') {
|
||||
return preferred;
|
||||
}
|
||||
}
|
||||
|
||||
const candidates = collectVisibleDesktopElements('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(getDesktopActionText(element))) || null;
|
||||
}
|
||||
return pool[0] || null;
|
||||
}
|
||||
|
||||
function detectDesktopFatalState(pageText = '', currentUrl = '', pageTitle = '') {
|
||||
const combinedText = `${pageTitle} ${pageText}`.replace(/\s+/g, ' ').trim();
|
||||
if (KIRO_DESKTOP_CLOUDFRONT_403_TEXT_PATTERN.test(combinedText)) {
|
||||
return {
|
||||
state: 'cloudfront_403_page',
|
||||
url: currentUrl,
|
||||
fatalMessage: 'Kiro 桌面授权页返回 403(CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectKiroDesktopAuthorizeState() {
|
||||
const currentUrl = location.href;
|
||||
const pageText = getDesktopPageText();
|
||||
const fatalState = detectDesktopFatalState(pageText, currentUrl, document.title || '');
|
||||
if (fatalState) {
|
||||
return fatalState;
|
||||
}
|
||||
|
||||
if (/^https?:\/\/(127\.0\.0\.1|localhost):/i.test(currentUrl)) {
|
||||
const parsed = new URL(currentUrl);
|
||||
if (parsed.searchParams.get('error')) {
|
||||
return {
|
||||
state: 'callback_error',
|
||||
url: currentUrl,
|
||||
error: parsed.searchParams.get('error_description') || parsed.searchParams.get('error') || 'unknown_error',
|
||||
};
|
||||
}
|
||||
return {
|
||||
state: 'callback_page',
|
||||
url: currentUrl,
|
||||
code: parsed.searchParams.get('code') || '',
|
||||
stateValue: parsed.searchParams.get('state') || '',
|
||||
};
|
||||
}
|
||||
|
||||
const otpInput = findFirstVisibleDesktopElement(KIRO_DESKTOP_OTP_INPUT_SELECTOR);
|
||||
if (otpInput) {
|
||||
return {
|
||||
state: 'otp_page',
|
||||
url: currentUrl,
|
||||
otpInput,
|
||||
continueButton: findDesktopActionButton({
|
||||
preferredSelectors: ['button[data-testid="email-verification-verify-button"]'],
|
||||
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
|
||||
formOwner: otpInput.form || otpInput.closest?.('form') || null,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const emailInput = findFirstVisibleDesktopElement(KIRO_DESKTOP_EMAIL_INPUT_SELECTOR);
|
||||
if (emailInput) {
|
||||
return {
|
||||
state: 'relogin_email',
|
||||
url: currentUrl,
|
||||
emailInput,
|
||||
continueButton: findDesktopActionButton({
|
||||
preferredSelectors: ['button[data-testid="test-primary-button"]'],
|
||||
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
|
||||
formOwner: emailInput.form || emailInput.closest?.('form') || null,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const passwordInput = findFirstVisibleDesktopElement(KIRO_DESKTOP_PASSWORD_INPUT_SELECTOR);
|
||||
if (passwordInput) {
|
||||
return {
|
||||
state: 'relogin_password',
|
||||
url: currentUrl,
|
||||
passwordInput,
|
||||
continueButton: findDesktopActionButton({
|
||||
preferredSelectors: ['button[data-testid="test-primary-button"]'],
|
||||
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
|
||||
formOwner: passwordInput.form || passwordInput.closest?.('form') || null,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const consentButton = findDesktopActionButton({
|
||||
preferredSelectors: [
|
||||
'button[data-testid="confirm-button"]',
|
||||
'button[data-testid="allow-access-button"]',
|
||||
],
|
||||
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
|
||||
});
|
||||
if (consentButton) {
|
||||
return {
|
||||
state: 'consent_page',
|
||||
url: currentUrl,
|
||||
actionButton: consentButton,
|
||||
actionText: getDesktopActionText(consentButton),
|
||||
};
|
||||
}
|
||||
|
||||
if (KIRO_DESKTOP_LOADING_TEXT_PATTERN.test(pageText)) {
|
||||
return {
|
||||
state: 'redirecting',
|
||||
url: currentUrl,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
state: 'loading',
|
||||
url: currentUrl,
|
||||
};
|
||||
}
|
||||
|
||||
async function getCurrentDesktopAuthorizeState() {
|
||||
const detected = detectKiroDesktopAuthorizeState();
|
||||
if (detected.state === 'cloudfront_403_page') {
|
||||
throw new Error(detected.fatalMessage || 'Kiro 桌面授权页出现 403。');
|
||||
}
|
||||
if (detected.state === 'callback_error') {
|
||||
throw new Error(`Kiro 桌面授权回调失败:${detected.error || 'unknown_error'}`);
|
||||
}
|
||||
return detected;
|
||||
}
|
||||
|
||||
async function submitDesktopEmail(payload = {}) {
|
||||
const email = String(payload?.email || '').trim();
|
||||
if (!email) {
|
||||
throw new Error('缺少桌面授权邮箱,无法继续。');
|
||||
}
|
||||
const currentState = await getCurrentDesktopAuthorizeState();
|
||||
if (currentState.state !== 'relogin_email' || !currentState.emailInput || !currentState.continueButton) {
|
||||
throw new Error(`当前桌面授权页不是邮箱重登状态:${currentState.state}`);
|
||||
}
|
||||
fillInput(currentState.emailInput, email);
|
||||
await sleep(150);
|
||||
simulateClick(currentState.continueButton);
|
||||
return {
|
||||
submitted: true,
|
||||
state: 'email_submitted',
|
||||
url: location.href,
|
||||
};
|
||||
}
|
||||
|
||||
async function submitDesktopPassword(payload = {}) {
|
||||
const password = String(payload?.password || '');
|
||||
if (!password) {
|
||||
throw new Error('缺少桌面授权密码,无法继续。');
|
||||
}
|
||||
const currentState = await getCurrentDesktopAuthorizeState();
|
||||
if (currentState.state !== 'relogin_password' || !currentState.passwordInput || !currentState.continueButton) {
|
||||
throw new Error(`当前桌面授权页不是密码重登状态:${currentState.state}`);
|
||||
}
|
||||
fillInput(currentState.passwordInput, password);
|
||||
await sleep(150);
|
||||
simulateClick(currentState.continueButton);
|
||||
return {
|
||||
submitted: true,
|
||||
state: 'password_submitted',
|
||||
url: location.href,
|
||||
};
|
||||
}
|
||||
|
||||
async function submitDesktopOtp(payload = {}) {
|
||||
const code = String(payload?.code || '').trim();
|
||||
if (!code) {
|
||||
throw new Error('缺少桌面授权验证码,无法继续。');
|
||||
}
|
||||
const currentState = await getCurrentDesktopAuthorizeState();
|
||||
if (currentState.state !== 'otp_page' || !currentState.otpInput || !currentState.continueButton) {
|
||||
throw new Error(`当前桌面授权页不是验证码状态:${currentState.state}`);
|
||||
}
|
||||
fillInput(currentState.otpInput, code);
|
||||
await sleep(150);
|
||||
simulateClick(currentState.continueButton);
|
||||
return {
|
||||
submitted: true,
|
||||
state: 'otp_submitted',
|
||||
url: location.href,
|
||||
};
|
||||
}
|
||||
|
||||
async function confirmDesktopConsent() {
|
||||
const currentState = await getCurrentDesktopAuthorizeState();
|
||||
if (currentState.state !== 'consent_page' || !currentState.actionButton) {
|
||||
throw new Error(`当前桌面授权页不是授权确认状态:${currentState.state}`);
|
||||
}
|
||||
simulateClick(currentState.actionButton);
|
||||
return {
|
||||
submitted: true,
|
||||
state: 'consent_submitted',
|
||||
url: location.href,
|
||||
actionText: currentState.actionText || '',
|
||||
};
|
||||
}
|
||||
|
||||
async function handleKiroDesktopAuthorizeCommand(message) {
|
||||
switch (message.type) {
|
||||
case 'GET_KIRO_DESKTOP_AUTHORIZE_STATE':
|
||||
return getCurrentDesktopAuthorizeState();
|
||||
case 'EXECUTE_KIRO_DESKTOP_AUTHORIZE_ACTION': {
|
||||
const action = String(message.payload?.action || '').trim();
|
||||
if (action === 'submit-email') {
|
||||
return submitDesktopEmail(message.payload || {});
|
||||
}
|
||||
if (action === 'submit-password') {
|
||||
return submitDesktopPassword(message.payload || {});
|
||||
}
|
||||
if (action === 'submit-otp') {
|
||||
return submitDesktopOtp(message.payload || {});
|
||||
}
|
||||
if (action === 'confirm-consent') {
|
||||
return confirmDesktopConsent();
|
||||
}
|
||||
throw new Error(`desktop-authorize-page.js 不处理动作:${action}`);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (document.documentElement.getAttribute(KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL) !== '1') {
|
||||
document.documentElement.setAttribute(KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL, '1');
|
||||
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
if (
|
||||
message.type === 'GET_KIRO_DESKTOP_AUTHORIZE_STATE'
|
||||
|| message.type === 'EXECUTE_KIRO_DESKTOP_AUTHORIZE_ACTION'
|
||||
) {
|
||||
resetStopState();
|
||||
handleKiroDesktopAuthorizeCommand(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;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
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 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_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 detectKiroFatalPageState(pageText = '', currentUrl = '', pageTitle = '') {
|
||||
const combinedText = `${pageTitle} ${pageText}`.replace(/\s+/g, ' ').trim();
|
||||
const normalizedHost = String(location.hostname || '').trim().toLowerCase();
|
||||
const isAwsProfileHost = normalizedHost === 'profile.aws.amazon.com'
|
||||
|| normalizedHost === 'profile.aws'
|
||||
|| normalizedHost.endsWith('.profile.aws.amazon.com')
|
||||
|| normalizedHost.endsWith('.profile.aws');
|
||||
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 (matchedSignals >= 2 || (isAwsProfileHost && matchedSignals >= 1 && KIRO_CLOUDFRONT_403_TEXT_PATTERN.test(combinedText))) {
|
||||
return {
|
||||
state: 'cloudfront_403_page',
|
||||
url: currentUrl,
|
||||
fatalMessage: 'Kiro 注册页返回 403(CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isKiroFatalState(state = '') {
|
||||
return state === 'cloudfront_403_page';
|
||||
}
|
||||
|
||||
function getKiroFatalStateMessage(snapshot = {}) {
|
||||
if (snapshot?.fatalMessage) {
|
||||
return snapshot.fatalMessage;
|
||||
}
|
||||
switch (snapshot?.state) {
|
||||
case 'cloudfront_403_page':
|
||||
return 'Kiro 注册页返回 403(CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。';
|
||||
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) || 30000));
|
||||
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 || 30000,
|
||||
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 || 30000,
|
||||
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 || 30000,
|
||||
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 || 30000,
|
||||
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 || 45000,
|
||||
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 || 45000,
|
||||
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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user