359 lines
12 KiB
JavaScript
359 lines
12 KiB
JavaScript
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_AWS_REQUEST_ERROR_TEXT_PATTERN = /sorry,\s*there was an error processing your request\.?\s*please try again\.?|抱歉,处理您的请求时出错。请重试。?/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 isKiroDesktopAuthHost(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 detectDesktopFatalState(pageText = '', currentUrl = '', pageTitle = '') {
|
||
const combinedText = `${pageTitle} ${pageText}`.replace(/\s+/g, ' ').trim();
|
||
const isKiroAuthHost = isKiroDesktopAuthHost(location.hostname || '');
|
||
if (isKiroAuthHost && KIRO_DESKTOP_AWS_REQUEST_ERROR_TEXT_PATTERN.test(combinedText)) {
|
||
return {
|
||
state: 'proxy_error_page',
|
||
url: currentUrl,
|
||
fatalMessage: 'Kiro 桌面授权页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。',
|
||
};
|
||
}
|
||
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' || detected.state === 'proxy_error_page') {
|
||
throw new Error(detected.fatalMessage || 'Kiro 桌面授权页出现代理异常。');
|
||
}
|
||
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;
|
||
});
|
||
}
|