Files
FlowPilot/background/steps/kiro-device-auth.js
T

1496 lines
55 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.
(function attachBackgroundKiroDeviceAuth(root, factory) {
root.MultiPageBackgroundKiroDeviceAuth = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroDeviceAuthModule() {
const DEFAULT_REGION = 'us-east-1';
const DEVICE_LOGIN_START_URL = 'https://view.awsapps.com/start';
const DEFAULT_SCOPES = Object.freeze([
'codewhisperer:completions',
'codewhisperer:analysis',
'codewhisperer:conversations',
'codewhisperer:transformations',
'codewhisperer:taskassist',
]);
const KIRO_STEP1_COOKIE_CLEAR_DOMAINS = Object.freeze([
'awsapps.com',
'view.awsapps.com',
'login.awsapps.com',
'amazonaws.com',
'signin.aws',
'signin.aws.amazon.com',
'profile.aws',
'profile.aws.amazon.com',
]);
const KIRO_STEP1_COOKIE_CLEAR_ORIGINS = Object.freeze([
'https://view.awsapps.com',
'https://login.awsapps.com',
'https://oidc.us-east-1.amazonaws.com',
'https://signin.aws',
'https://signin.aws.amazon.com',
'https://profile.aws',
'https://profile.aws.amazon.com',
]);
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
const KIRO_AWS_VERIFICATION_CODE_PATTERNS = Object.freeze([
Object.freeze({
source: '(?:verification\\s*code|验证码|Your code is|code is)[:\\s]*(\\d{6})',
flags: 'gi',
}),
Object.freeze({
source: '^\\s*(\\d{6})\\s*$',
flags: 'gm',
}),
Object.freeze({
source: '>\\s*(\\d{6})\\s*<',
flags: 'g',
}),
]);
const KIRO_AWS_SENDER_FILTERS = Object.freeze([
'no-reply@signin.aws',
'no-reply@login.awsapps.com',
'noreply@amazon.com',
'account-update@amazon.com',
'no-reply@aws.amazon.com',
'noreply@aws.amazon.com',
'aws',
]);
const KIRO_AWS_SUBJECT_FILTERS = Object.freeze([
'aws builder id',
'verification',
'验证码',
'code',
'aws',
]);
const KIRO_AWS_REQUIRED_KEYWORDS = Object.freeze([
'verification',
'验证码',
'code',
'aws',
]);
function cleanString(value = '') {
return String(value ?? '').trim();
}
function normalizeRegion(value = '', fallback = DEFAULT_REGION) {
return cleanString(value) || fallback;
}
function buildOidcBaseUrl(region = DEFAULT_REGION) {
return `https://oidc.${normalizeRegion(region)}.amazonaws.com`;
}
function normalizeKiroRsBaseUrl(value = '') {
const normalized = cleanString(value).replace(/\/+$/, '');
if (!normalized) {
throw new Error('缺少 kiro.rs 管理后台地址。');
}
return normalized.endsWith('/admin')
? normalized.slice(0, -'/admin'.length)
: normalized;
}
async function readResponse(response) {
const text = await response.text();
let json = null;
try {
json = text ? JSON.parse(text) : null;
} catch (_error) {
json = null;
}
return { text, json };
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : String(error ?? '未知错误');
}
function normalizeKiroUploadMessage(value = '') {
const rawValue = cleanString(value);
if (!rawValue) {
return '上传成功';
}
const normalizedValue = rawValue.toLowerCase();
if (normalizedValue === 'uploaded' || normalizedValue === 'credential uploaded.') {
return '上传成功';
}
return rawValue;
}
function normalizePositiveInteger(value, fallback) {
const numeric = Math.floor(Number(value));
if (Number.isInteger(numeric) && numeric > 0) {
return numeric;
}
return fallback;
}
function normalizeKiroCookieDomain(domain = '') {
return String(domain || '').trim().replace(/^\.+/, '').toLowerCase();
}
function matchesKiroNamedHostFamily(domain = '', family = '') {
const normalizedDomain = normalizeKiroCookieDomain(domain);
const normalizedFamily = normalizeKiroCookieDomain(family);
if (!normalizedDomain || !normalizedFamily) {
return false;
}
return normalizedDomain === normalizedFamily
|| normalizedDomain.endsWith(`.${normalizedFamily}`)
|| normalizedDomain.startsWith(`${normalizedFamily}.`)
|| normalizedDomain.includes(`.${normalizedFamily}.`);
}
function shouldClearKiroStep1Cookie(cookie) {
const domain = normalizeKiroCookieDomain(cookie?.domain);
if (!domain) {
return false;
}
return KIRO_STEP1_COOKIE_CLEAR_DOMAINS.some((target) => (
domain === target
|| domain.endsWith(`.${target}`)
|| matchesKiroNamedHostFamily(domain, target)
));
}
function buildKiroStep1CookieRemovalUrl(cookie) {
const host = normalizeKiroCookieDomain(cookie?.domain);
const rawPath = String(cookie?.path || '/');
const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
return `https://${host}${path}`;
}
async function collectKiroStep1Cookies(chromeApi) {
if (!chromeApi?.cookies?.getAll) {
return [];
}
const stores = chromeApi.cookies.getAllCookieStores
? await chromeApi.cookies.getAllCookieStores()
: [{ id: undefined }];
const cookies = [];
const seen = new Set();
for (const store of stores) {
const storeId = store?.id;
const batch = await chromeApi.cookies.getAll(storeId ? { storeId } : {});
for (const cookie of batch || []) {
if (!shouldClearKiroStep1Cookie(cookie)) {
continue;
}
const key = [
cookie.storeId || storeId || '',
cookie.domain || '',
cookie.path || '',
cookie.name || '',
cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
].join('|');
if (seen.has(key)) {
continue;
}
seen.add(key);
cookies.push(cookie);
}
}
return cookies;
}
async function removeKiroStep1Cookie(chromeApi, cookie) {
const details = {
url: buildKiroStep1CookieRemovalUrl(cookie),
name: cookie.name,
};
if (cookie.storeId) {
details.storeId = cookie.storeId;
}
if (cookie.partitionKey) {
details.partitionKey = cookie.partitionKey;
}
try {
const result = await chromeApi.cookies.remove(details);
return Boolean(result);
} catch (error) {
console.warn('[MultiPage:kiro-step1] remove cookie failed', {
domain: cookie?.domain,
name: cookie?.name,
message: getErrorMessage(error),
});
return false;
}
}
function buildCredentialUploadOptions(state = {}) {
const next = {
priority: Math.max(0, Math.floor(Number(state?.kiroRsPriority) || 0)),
authMethod: 'IdC',
provider: 'BuilderId',
};
const endpoint = cleanString(state?.kiroRsEndpoint);
const authRegion = cleanString(state?.kiroRsAuthRegion);
const apiRegion = cleanString(state?.kiroRsApiRegion);
if (endpoint) {
next.endpoint = endpoint;
}
if (authRegion) {
next.authRegion = authRegion;
}
if (apiRegion) {
next.apiRegion = apiRegion;
}
if (state?.ipProxyEnabled) {
const proxyUrl = cleanString(state?.ipProxyApiUrl)
|| (() => {
const host = cleanString(state?.ipProxyHost);
const port = cleanString(state?.ipProxyPort);
if (!host || !port) {
return '';
}
const protocol = cleanString(state?.ipProxyProtocol) || 'http';
return `${protocol}://${host}:${port}`;
})();
if (proxyUrl) {
next.proxyUrl = proxyUrl;
}
const proxyUsername = cleanString(state?.ipProxyUsername);
const proxyPassword = String(state?.ipProxyPassword || '');
if (proxyUsername) {
next.proxyUsername = proxyUsername;
}
if (proxyPassword) {
next.proxyPassword = proxyPassword;
}
}
return next;
}
async function startBuilderIdDeviceLogin(region, fetchImpl) {
const normalizedRegion = normalizeRegion(region);
const oidcBaseUrl = buildOidcBaseUrl(normalizedRegion);
const registerResponse = await fetchImpl(`${oidcBaseUrl}/client/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientName: 'Codex Registration Extension',
clientType: 'public',
scopes: DEFAULT_SCOPES,
grantTypes: ['urn:ietf:params:oauth:grant-type:device_code', 'refresh_token'],
issuerUrl: DEVICE_LOGIN_START_URL,
}),
});
const registerBody = await readResponse(registerResponse);
if (!registerResponse.ok) {
throw new Error(`Builder ID 客户端注册失败:${cleanString(registerBody.text || registerResponse.statusText) || registerResponse.status}`);
}
const clientId = cleanString(registerBody.json?.clientId);
const clientSecret = String(registerBody.json?.clientSecret || '');
if (!clientId || !clientSecret) {
throw new Error('Builder ID 客户端注册响应缺少凭据。');
}
const authorizationResponse = await fetchImpl(`${oidcBaseUrl}/device_authorization`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientId,
clientSecret,
startUrl: DEVICE_LOGIN_START_URL,
}),
});
const authorizationBody = await readResponse(authorizationResponse);
if (!authorizationResponse.ok) {
throw new Error(`Builder ID 设备授权失败:${cleanString(authorizationBody.text || authorizationResponse.statusText) || authorizationResponse.status}`);
}
const deviceCode = String(authorizationBody.json?.deviceCode || '');
const userCode = cleanString(authorizationBody.json?.userCode);
const verificationUri = cleanString(authorizationBody.json?.verificationUri);
const verificationUriComplete = cleanString(
authorizationBody.json?.verificationUriComplete || verificationUri
);
const interval = normalizePositiveInteger(authorizationBody.json?.interval, 5);
const expiresIn = normalizePositiveInteger(authorizationBody.json?.expiresIn, 600);
if (!deviceCode || !userCode || !verificationUriComplete) {
throw new Error('Builder ID 设备授权响应缺少必要字段。');
}
return {
clientId,
clientSecret,
deviceCode,
expiresAt: Date.now() + expiresIn * 1000,
expiresIn,
interval,
region: normalizedRegion,
userCode,
verificationUri,
verificationUriComplete,
};
}
async function pollBuilderIdDeviceAuth(params = {}, fetchImpl) {
const oidcBaseUrl = buildOidcBaseUrl(params.region);
const response = await fetchImpl(`${oidcBaseUrl}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientId: params.clientId,
clientSecret: params.clientSecret,
grantType: 'urn:ietf:params:oauth:grant-type:device_code',
deviceCode: params.deviceCode,
}),
});
const body = await readResponse(response);
if (response.status === 200) {
return {
completed: true,
accessToken: String(body.json?.accessToken || ''),
refreshToken: String(body.json?.refreshToken || ''),
expiresIn: normalizePositiveInteger(body.json?.expiresIn, 3600),
region: normalizeRegion(params.region),
};
}
if (response.status === 400) {
const errorCode = cleanString(body.json?.error);
if (errorCode === 'authorization_pending') {
return { completed: false, status: 'pending' };
}
if (errorCode === 'slow_down') {
return { completed: false, status: 'slow_down' };
}
if (errorCode === 'expired_token') {
throw new Error('Kiro 设备登录已过期。');
}
if (errorCode === 'access_denied') {
throw new Error('用户拒绝了 Builder ID 设备登录授权请求。');
}
throw new Error(`Builder ID 授权失败:${errorCode || cleanString(body.text || response.statusText) || response.status}`);
}
throw new Error(`Builder ID 令牌请求失败:HTTP ${response.status}`);
}
async function checkKiroRsConnection(baseUrl, apiKey, fetchImpl) {
const normalizedBaseUrl = normalizeKiroRsBaseUrl(baseUrl);
const response = await fetchImpl(`${normalizedBaseUrl}/api/admin/credentials`, {
method: 'GET',
headers: {
Accept: 'application/json',
'x-api-key': String(apiKey || ''),
},
});
const body = await readResponse(response);
if (response.ok) {
return {
ok: true,
message: `kiro.rs 连接正常(HTTP ${response.status}`,
};
}
if (response.status === 405) {
return {
ok: true,
message: 'kiro.rs 上传接口可访问。',
};
}
if (response.status === 401 || response.status === 403) {
return {
ok: false,
message: `kiro.rs API Key 被拒绝(HTTP ${response.status}`,
};
}
if (response.status === 404) {
return {
ok: false,
message: '未找到 kiro.rs 管理接口。',
};
}
return {
ok: false,
message: cleanString(body.json?.error?.message || body.json?.message || body.text || response.statusText)
|| `kiro.rs 连接失败(HTTP ${response.status}`,
};
}
async function uploadBuilderIdCredential(baseUrl, apiKey, payload, fetchImpl) {
const normalizedBaseUrl = normalizeKiroRsBaseUrl(baseUrl);
const response = await fetchImpl(`${normalizedBaseUrl}/api/admin/credentials`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'x-api-key': String(apiKey || ''),
},
body: JSON.stringify(payload),
});
const body = await readResponse(response);
if (!response.ok) {
const message = cleanString(body.json?.error?.message || body.json?.message || body.text || response.statusText)
|| `HTTP ${response.status}`;
throw new Error(`kiro.rs 凭据上传失败:${message}`);
}
return {
credentialId: Number(body.json?.credentialId || body.json?.credential_id || 0) || null,
email: cleanString(body.json?.email),
message: normalizeKiroUploadMessage(body.json?.message),
raw: body.json,
};
}
function createKiroDeviceAuthExecutor(deps = {}) {
const {
addLog = async () => {},
chrome = (typeof globalThis !== 'undefined' ? globalThis.chrome : null),
completeNodeFromBackground,
ensureContentScriptReadyOnTab = null,
ensureIcloudMailSession = null,
ensureMail2925MailboxSession = null,
fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null,
generatePassword = null,
generateRandomName = null,
getMailConfig = null,
getState = async () => ({}),
getTabId = async () => null,
HOTMAIL_PROVIDER = 'hotmail-api',
LUCKMAIL_PROVIDER = 'luckmail-api',
CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email',
CLOUD_MAIL_PROVIDER = 'cloudmail',
YYDS_MAIL_PROVIDER = 'yyds-mail',
MAIL_2925_VERIFICATION_INTERVAL_MS = 15000,
MAIL_2925_VERIFICATION_MAX_ATTEMPTS = 15,
isRetryableContentScriptTransportError = () => false,
isTabAlive = async () => false,
KIRO_DEVICE_AUTH_INJECT_FILES = null,
pollCloudflareTempEmailVerificationCode = null,
pollCloudMailVerificationCode = null,
pollHotmailVerificationCode = null,
pollLuckmailVerificationCode = null,
pollYydsMailVerificationCode = null,
registerTab = async () => {},
resolveSignupEmailForFlow = null,
reuseOrCreateTab = async () => null,
sendToContentScriptResilient = null,
sendToMailContentScriptResilient = null,
setPasswordState = async () => {},
setState = async () => {},
sleepWithStop = async (ms) => {
await new Promise((resolve) => setTimeout(resolve, ms));
},
throwIfStopped = () => {},
waitForTabStableComplete = null,
} = deps;
if (typeof completeNodeFromBackground !== 'function') {
throw new Error('Kiro device auth executor requires completeNodeFromBackground.');
}
if (typeof fetchImpl !== 'function') {
throw new Error('Kiro device auth executor requires fetch support.');
}
async function log(message, level = 'info', nodeId = '') {
await addLog(message, level, nodeId ? { nodeId } : {});
}
async function activateTab(tabId) {
if (!Number.isInteger(tabId) || !chrome?.tabs?.update) {
return;
}
await chrome.tabs.update(tabId, { active: true });
}
async function getExecutionState(state = {}) {
if (state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length) {
return state;
}
return getState();
}
async function persistFailure(updates = {}) {
if (updates && Object.keys(updates).length) {
await setState(updates);
}
}
async function clearKiroCookiesBeforeStep1() {
if (!chrome?.cookies?.getAll || !chrome.cookies?.remove) {
await log('步骤 1:当前浏览器不支持 cookies API,跳过打开 Kiro 授权页前 cookie 清理。', 'warn');
return;
}
await log('步骤 1:打开 Kiro 授权页前清理 AWS Builder ID 相关 cookies...', 'info');
const cookies = await collectKiroStep1Cookies(chrome);
let removedCount = 0;
for (const cookie of cookies) {
if (await removeKiroStep1Cookie(chrome, cookie)) {
removedCount += 1;
}
}
if (chrome.browsingData?.removeCookies) {
try {
await chrome.browsingData.removeCookies({
since: 0,
origins: KIRO_STEP1_COOKIE_CLEAR_ORIGINS,
});
} catch (error) {
await log(`步骤 1browsingData 补扫 cookies 失败:${getErrorMessage(error)}`, 'warn');
}
}
await log(`步骤 1:已清理 ${removedCount} 个 AWS Builder ID 相关 cookies。`, 'ok');
}
async function ensureKiroAuthTab(state = {}, options = {}) {
let tabId = Number.isInteger(state?.kiroAuthTabId)
? state.kiroAuthTabId
: await getTabId('kiro-device-auth');
const loginUrl = cleanString(state?.kiroLoginUrl || state?.kiroVerificationUriComplete || state?.kiroVerificationUri);
if (Number.isInteger(tabId) && await isTabAlive('kiro-device-auth')) {
return tabId;
}
if (!loginUrl) {
throw new Error(options.missingUrlMessage || '缺少 Kiro 授权页地址,请先执行步骤 1。');
}
tabId = await reuseOrCreateTab('kiro-device-auth', loginUrl);
if (!Number.isInteger(tabId)) {
throw new Error(options.openFailedMessage || '无法打开 Kiro 授权页,请重试步骤 1。');
}
await registerTab('kiro-device-auth', tabId);
await setState({ kiroAuthTabId: tabId });
return tabId;
}
async function activateKiroAuthTab(state = {}, options = {}) {
const tabId = await ensureKiroAuthTab(state, options);
await activateTab(tabId);
return tabId;
}
async function reattachKiroContentScript(tabId, options = {}) {
if (!Number.isInteger(tabId)) {
throw new Error('缺少 Kiro 授权页标签页,无法重新连接内容脚本。');
}
if (typeof waitForTabStableComplete === 'function') {
await waitForTabStableComplete(tabId, {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: Number(options.stableMs) || 1500,
initialDelayMs: Number(options.initialDelayMs) || 150,
});
}
if (typeof ensureContentScriptReadyOnTab === 'function') {
await ensureContentScriptReadyOnTab('kiro-device-auth', tabId, {
inject: Array.isArray(KIRO_DEVICE_AUTH_INJECT_FILES) ? KIRO_DEVICE_AUTH_INJECT_FILES : null,
injectSource: 'kiro-device-auth',
timeoutMs: 45000,
retryDelayMs: 800,
logMessage: options.injectLogMessage || 'Kiro 授权页内容脚本未就绪,正在等待页面恢复...',
});
}
}
function buildKiroRetryRecovery(tabId, options = {}) {
return async (error) => {
if (!isRetryableContentScriptTransportError(error)) {
return;
}
await reattachKiroContentScript(tabId, {
stableMs: Number(options.recoveryStableMs) || Number(options.stableMs) || 1200,
initialDelayMs: Number(options.recoveryInitialDelayMs) || 120,
injectLogMessage: options.recoveryInjectLogMessage || options.injectLogMessage || 'Kiro 授权页已跳转,正在重新连接内容脚本...',
});
};
}
async function ensureKiroPageState(tabId, options = {}) {
if (!Number.isInteger(tabId)) {
throw new Error('缺少 Kiro 授权页标签页,无法继续执行。');
}
if (typeof waitForTabStableComplete === 'function') {
await waitForTabStableComplete(tabId, {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: Number(options.stableMs) || 1500,
initialDelayMs: Number(options.initialDelayMs) || 150,
});
}
if (typeof ensureContentScriptReadyOnTab === 'function') {
await ensureContentScriptReadyOnTab('kiro-device-auth', tabId, {
inject: Array.isArray(KIRO_DEVICE_AUTH_INJECT_FILES) ? KIRO_DEVICE_AUTH_INJECT_FILES : null,
injectSource: 'kiro-device-auth',
timeoutMs: 45000,
retryDelayMs: 800,
logMessage: options.injectLogMessage || 'Kiro 授权页内容脚本未就绪,正在等待页面恢复...',
});
}
if (typeof sendToContentScriptResilient !== 'function') {
return {
state: Array.isArray(options.targetStates) ? options.targetStates[0] || '' : '',
url: '',
};
}
const result = await sendToContentScriptResilient('kiro-device-auth', {
type: 'ENSURE_KIRO_PAGE_STATE',
step: options.step || 0,
source: 'background',
payload: {
targetStates: Array.isArray(options.targetStates) ? options.targetStates : [],
timeoutMs: Number(options.pageTimeoutMs) || 30000,
retryDelayMs: Number(options.pageRetryDelayMs) || 250,
timeoutMessage: options.timeoutMessage || '',
},
}, {
timeoutMs: Math.max(30000, Number(options.pageTimeoutMs) || 30000),
retryDelayMs: 700,
onRetryableError: buildKiroRetryRecovery(tabId, options),
logMessage: options.readyLogMessage || '正在等待 Kiro 页面进入下一状态...',
});
if (result?.error) {
throw new Error(result.error);
}
return result || { state: '', url: '' };
}
async function waitForKiroPageChange(tabId, options = {}) {
if (!Number.isInteger(tabId)) {
throw new Error('缺少 Kiro 授权页标签页,无法继续执行。');
}
if (typeof waitForTabStableComplete === 'function') {
await waitForTabStableComplete(tabId, {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: Number(options.stableMs) || 1200,
initialDelayMs: Number(options.initialDelayMs) || 120,
});
}
if (typeof ensureContentScriptReadyOnTab === 'function') {
await ensureContentScriptReadyOnTab('kiro-device-auth', tabId, {
inject: Array.isArray(KIRO_DEVICE_AUTH_INJECT_FILES) ? KIRO_DEVICE_AUTH_INJECT_FILES : null,
injectSource: 'kiro-device-auth',
timeoutMs: 45000,
retryDelayMs: 800,
logMessage: options.injectLogMessage || 'Kiro 授权页切换中,正在等待页面恢复...',
});
}
if (typeof sendToContentScriptResilient !== 'function') {
return { state: '', url: '' };
}
const result = await sendToContentScriptResilient('kiro-device-auth', {
type: 'ENSURE_KIRO_STATE_CHANGE',
step: options.step || 0,
source: 'background',
payload: {
fromStates: Array.isArray(options.fromStates) ? options.fromStates : [],
timeoutMs: Number(options.pageTimeoutMs) || 30000,
retryDelayMs: Number(options.pageRetryDelayMs) || 250,
timeoutMessage: options.timeoutMessage || '',
},
}, {
timeoutMs: Math.max(30000, Number(options.pageTimeoutMs) || 30000),
retryDelayMs: 700,
onRetryableError: buildKiroRetryRecovery(tabId, options),
logMessage: options.readyLogMessage || '正在等待 Kiro 页面完成跳转...',
});
if (result?.error) {
throw new Error(result.error);
}
return result || { state: '', url: '' };
}
function resolveKiroFullName(state = {}) {
const cachedName = cleanString(state?.kiroFullName);
if (cachedName) {
return cachedName;
}
if (typeof generateRandomName !== 'function') {
throw new Error('Kiro 姓名步骤缺少随机姓名能力,无法继续执行。');
}
const generated = generateRandomName();
if (typeof generated === 'string') {
const normalized = cleanString(generated);
if (normalized) {
return normalized;
}
}
const firstName = cleanString(generated?.firstName);
const lastName = cleanString(generated?.lastName);
const fullName = cleanString(`${firstName} ${lastName}`);
if (!fullName) {
throw new Error('Kiro 姓名步骤未生成有效姓名。');
}
return fullName;
}
function resolveKiroPassword(state = {}) {
const existingPassword = String(state?.customPassword || state?.password || '');
if (existingPassword) {
return {
password: existingPassword,
mode: state?.customPassword ? 'custom' : 'reused',
};
}
if (typeof generatePassword !== 'function') {
throw new Error('Kiro 密码步骤缺少公共密码生成能力,无法继续执行。');
}
return {
password: String(generatePassword() || ''),
mode: 'generated',
};
}
function getExpectedMail2925MailboxEmail(state = {}) {
if (Boolean(state?.mail2925UseAccountPool)) {
const currentAccountId = String(state?.currentMail2925AccountId || '').trim();
const accounts = Array.isArray(state?.mail2925Accounts) ? state.mail2925Accounts : [];
const currentAccount = accounts.find((account) => String(account?.id || '') === currentAccountId) || null;
const accountEmail = String(currentAccount?.email || '').trim().toLowerCase();
if (accountEmail) {
return accountEmail;
}
}
return String(state?.mail2925BaseEmail || '').trim().toLowerCase();
}
async function focusOrOpenMailTab(mail) {
if (!mail?.source) {
return;
}
const alive = await isTabAlive(mail.source);
if (alive) {
if (mail.navigateOnReuse) {
await reuseOrCreateTab(mail.source, mail.url, {
inject: mail.inject,
injectSource: mail.injectSource,
});
return;
}
const tabId = await getTabId(mail.source);
if (Number.isInteger(tabId)) {
await activateTab(tabId);
}
return;
}
await reuseOrCreateTab(mail.source, mail.url, {
inject: mail.inject,
injectSource: mail.injectSource,
});
}
function buildKiroVerificationPollPayload(step, state = {}, mail = {}, filterAfterTimestamp = 0) {
const targetEmail = cleanString(state?.kiroAuthorizedEmail || state?.email).toLowerCase();
const targetEmailHints = targetEmail ? [targetEmail] : [];
const isMail2925Provider = String(mail?.provider || '').trim().toLowerCase() === '2925';
const normalizedProvider = String(mail?.provider || '').trim().toLowerCase();
const maxAttempts = normalizedProvider === String(LUCKMAIL_PROVIDER || '').trim().toLowerCase()
? 3
: (isMail2925Provider ? MAIL_2925_VERIFICATION_MAX_ATTEMPTS : 5);
const intervalMs = normalizedProvider === String(LUCKMAIL_PROVIDER || '').trim().toLowerCase()
? 15000
: (isMail2925Provider ? MAIL_2925_VERIFICATION_INTERVAL_MS : 3000);
return {
flowId: 'kiro',
step,
targetEmail,
targetEmailHints,
filterAfterTimestamp,
senderFilters: [...KIRO_AWS_SENDER_FILTERS],
subjectFilters: [...KIRO_AWS_SUBJECT_FILTERS],
requiredKeywords: [...KIRO_AWS_REQUIRED_KEYWORDS],
codePatterns: [...KIRO_AWS_VERIFICATION_CODE_PATTERNS],
mail2925MatchTargetEmail: isMail2925Provider
&& String(state?.mail2925Mode || '').trim().toLowerCase() === 'receive',
maxAttempts,
intervalMs,
};
}
function getMailPollingResponseTimeoutMs(payload = {}) {
const maxAttempts = Math.max(1, Math.floor(Number(payload?.maxAttempts) || 1));
const intervalMs = Math.max(1, Number(payload?.intervalMs) || 3000);
return Math.max(45000, maxAttempts * intervalMs + 25000);
}
async function pollKiroVerificationCode(step, state = {}, nodeId = '') {
if (typeof getMailConfig !== 'function') {
throw new Error('Kiro 验证码步骤缺少邮箱配置能力,无法继续执行。');
}
const mail = getMailConfig(state);
if (mail?.error) {
throw new Error(mail.error);
}
const requestedAt = Math.max(0, Number(state?.kiroVerificationRequestedAt) || Date.now());
const filterAfterTimestamp = mail.provider === '2925'
? Math.max(0, requestedAt - MAIL_2925_FILTER_LOOKBACK_MS)
: requestedAt;
const pollPayload = buildKiroVerificationPollPayload(step, state, mail, filterAfterTimestamp);
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
await log(`步骤 ${step}:正在确认 ${mail.label || 'iCloud 邮箱'} 登录状态...`, 'info', nodeId);
await ensureIcloudMailSession({
state,
step,
actionLabel: `步骤 ${step}:确认 iCloud 邮箱登录状态`,
});
}
throwIfStopped();
if (mail.provider === HOTMAIL_PROVIDER) {
await log(`步骤 ${step}:正在通过 ${mail.label || 'Hotmail'} 轮询验证码...`, 'info', nodeId);
return pollHotmailVerificationCode(step, state, pollPayload);
}
if (mail.provider === LUCKMAIL_PROVIDER) {
await log(`步骤 ${step}:正在通过 ${mail.label || 'LuckMail'} 轮询验证码...`, 'info', nodeId);
return pollLuckmailVerificationCode(step, state, pollPayload);
}
if (mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
await log(`步骤 ${step}:正在通过 ${mail.label || 'Cloudflare Temp Email'} 轮询验证码...`, 'info', nodeId);
return pollCloudflareTempEmailVerificationCode(step, state, pollPayload);
}
if (mail.provider === CLOUD_MAIL_PROVIDER) {
await log(`步骤 ${step}:正在通过 ${mail.label || 'Cloud Mail'} 轮询验证码...`, 'info', nodeId);
return pollCloudMailVerificationCode(step, state, pollPayload);
}
if (mail.provider === YYDS_MAIL_PROVIDER) {
await log(`步骤 ${step}:正在通过 ${mail.label || 'YYDS Mail'} 轮询验证码...`, 'info', nodeId);
return pollYydsMailVerificationCode(step, state, pollPayload);
}
if (mail.provider === '2925' && typeof ensureMail2925MailboxSession === 'function') {
await log(`步骤 ${step}:正在确认 ${mail.label || '2925 邮箱'} 登录状态...`, 'info', nodeId);
await ensureMail2925MailboxSession({
accountId: state.currentMail2925AccountId || null,
forceRelogin: false,
allowLoginWhenOnLoginPage: Boolean(state?.mail2925UseAccountPool),
expectedMailboxEmail: getExpectedMail2925MailboxEmail(state),
actionLabel: `步骤 ${step}:确认 2925 邮箱登录状态`,
});
} else {
await log(`步骤 ${step}:正在打开 ${mail.label || '邮箱'}...`, 'info', nodeId);
await focusOrOpenMailTab(mail);
}
if (typeof sendToMailContentScriptResilient !== 'function') {
throw new Error('Kiro 验证码步骤缺少邮箱内容脚本通信能力,无法继续执行。');
}
const responseTimeoutMs = getMailPollingResponseTimeoutMs(pollPayload);
const result = await sendToMailContentScriptResilient(
mail,
{
type: 'POLL_EMAIL',
step,
source: 'background',
payload: pollPayload,
},
{
timeoutMs: responseTimeoutMs,
responseTimeoutMs,
maxRecoveryAttempts: 2,
logStep: step,
logStepKey: 'kiro-submit-verification-code',
}
);
if (result?.error) {
throw new Error(result.error);
}
if (!result?.code) {
throw new Error(`步骤 ${step}:邮箱轮询结束,但未获取到验证码。`);
}
return result;
}
async function executeKiroStartDeviceLogin(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-start-device-login').trim();
try {
await clearKiroCookiesBeforeStep1();
const auth = await startBuilderIdDeviceLogin(DEFAULT_REGION, fetchImpl);
const loginUrl = cleanString(auth.verificationUriComplete || auth.verificationUri);
const tabId = loginUrl ? await reuseOrCreateTab('kiro-device-auth', loginUrl) : null;
if (!Number.isInteger(tabId)) {
throw new Error('无法打开 Kiro 授权页,请重试步骤 1。');
}
await registerTab('kiro-device-auth', tabId);
const updates = {
kiroAccessToken: '',
kiroAuthError: '',
kiroAuthExpiresAt: auth.expiresAt,
kiroAuthIntervalSeconds: auth.interval,
kiroAuthRegion: auth.region,
kiroAuthStatus: 'waiting_user',
kiroAuthTabId: Number.isInteger(tabId) ? tabId : null,
kiroAuthorizedEmail: '',
kiroClientId: auth.clientId,
kiroClientSecret: auth.clientSecret,
kiroCredentialId: null,
kiroDeviceAuthorizationCode: auth.deviceCode,
kiroDeviceCode: auth.userCode,
kiroFullName: '',
kiroLastConnectionMessage: '',
kiroLastUploadAt: 0,
kiroLoginUrl: loginUrl,
kiroRefreshToken: '',
kiroUploadError: '',
kiroUploadStatus: 'waiting_login',
kiroUserCode: auth.userCode,
kiroVerificationRequestedAt: 0,
kiroVerificationUri: auth.verificationUri,
kiroVerificationUriComplete: loginUrl,
};
await setState(updates);
await activateTab(tabId);
await ensureKiroPageState(tabId, {
step: 1,
targetStates: ['email_entry'],
stableMs: 2500,
initialDelayMs: 300,
injectLogMessage: '步骤 1:Kiro 授权页内容脚本未就绪,正在等待页面恢复...',
readyLogMessage: '步骤 1:正在等待 Kiro 授权页邮箱输入框加载完成...',
});
await log(`Kiro 授权页已就绪,请在下一步中获取邮箱并继续。当前授权码:${auth.userCode}`, 'ok', nodeId);
await completeNodeFromBackground(nodeId, updates);
} catch (error) {
const message = getErrorMessage(error);
await persistFailure({
kiroAuthError: message,
kiroAuthStatus: 'error',
});
throw error;
}
}
async function executeKiroSubmitEmail(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-submit-email').trim();
try {
const latestState = await getExecutionState(state);
if (typeof resolveSignupEmailForFlow !== 'function') {
throw new Error('Kiro 邮箱步骤缺少公共邮箱解析能力,无法继续执行。');
}
const tabId = await activateKiroAuthTab(latestState, {
missingUrlMessage: '缺少 Kiro 授权页地址,请先执行步骤 1。',
openFailedMessage: '无法恢复 Kiro 授权页,请重新执行步骤 1。',
});
await ensureKiroPageState(tabId, {
step: 2,
targetStates: ['email_entry'],
stableMs: 2500,
initialDelayMs: 300,
injectLogMessage: '步骤 2:Kiro 授权页内容脚本未就绪,正在等待页面恢复...',
readyLogMessage: '步骤 2:正在等待 Kiro 授权页邮箱输入框加载完成...',
});
const resolvedEmail = await resolveSignupEmailForFlow(latestState, {
preserveAccountIdentity: true,
});
await log(`步骤 2:已获取邮箱 ${resolvedEmail},正在提交到 Kiro 授权页...`, 'info', nodeId);
await activateTab(tabId);
const submitResult = await sendToContentScriptResilient('kiro-device-auth', {
type: 'EXECUTE_NODE',
nodeId: 'kiro-submit-email',
step: 2,
source: 'background',
payload: {
email: resolvedEmail,
},
}, {
timeoutMs: 30000,
retryDelayMs: 700,
onRetryableError: buildKiroRetryRecovery(tabId, {}),
logMessage: '步骤 2:正在向 Kiro 授权页提交邮箱...',
});
if (submitResult?.error) {
throw new Error(submitResult.error);
}
const landingResult = await ensureKiroPageState(tabId, {
step: 2,
targetStates: ['name_entry'],
stableMs: 1500,
initialDelayMs: 150,
injectLogMessage: '步骤 2:邮箱提交后页面切换中,正在等待 Kiro 授权页恢复...',
readyLogMessage: '步骤 2:邮箱已提交,正在等待 Kiro 姓名页加载完成...',
timeoutMessage: '邮箱提交后未进入姓名页,请检查当前邮箱是否已注册或页面是否异常。',
});
const updates = {
kiroAuthorizedEmail: resolvedEmail,
kiroAuthError: '',
kiroAuthStatus: 'waiting_user',
kiroFullName: '',
kiroUploadError: '',
kiroUploadStatus: 'waiting_login',
kiroVerificationRequestedAt: 0,
};
await setState(updates);
await log(`步骤 2:邮箱 ${resolvedEmail} 已提交,当前已进入姓名页。`, 'ok', nodeId);
await completeNodeFromBackground(nodeId, {
...updates,
email: resolvedEmail,
accountIdentifierType: 'email',
accountIdentifier: resolvedEmail,
kiroNextState: landingResult?.state || '',
kiroNextUrl: landingResult?.url || '',
});
} catch (error) {
const message = getErrorMessage(error);
await persistFailure({
kiroAuthError: message,
});
throw error;
}
}
async function executeKiroSubmitName(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-submit-name').trim();
try {
const latestState = await getExecutionState(state);
if (!cleanString(latestState?.kiroAuthorizedEmail || latestState?.email)) {
throw new Error('缺少 Kiro 授权邮箱,请先完成步骤 2。');
}
const tabId = await activateKiroAuthTab(latestState, {
missingUrlMessage: '缺少 Kiro 授权页地址,请先执行步骤 1。',
openFailedMessage: '无法恢复 Kiro 授权页,请重新执行步骤 1。',
});
await ensureKiroPageState(tabId, {
step: 3,
targetStates: ['name_entry'],
stableMs: 1500,
initialDelayMs: 150,
injectLogMessage: '步骤 3:Kiro 姓名页内容脚本未就绪,正在等待页面恢复...',
readyLogMessage: '步骤 3:正在等待 Kiro 姓名页加载完成...',
});
const fullName = resolveKiroFullName(latestState);
const verificationRequestedAt = Date.now();
await log(`步骤 3:正在填写姓名 ${fullName} 并继续...`, 'info', nodeId);
const submitResult = await sendToContentScriptResilient('kiro-device-auth', {
type: 'EXECUTE_NODE',
nodeId: 'kiro-submit-name',
step: 3,
source: 'background',
payload: {
fullName,
},
}, {
timeoutMs: 30000,
retryDelayMs: 700,
onRetryableError: buildKiroRetryRecovery(tabId, {}),
logMessage: '步骤 3:正在向 Kiro 姓名页提交姓名...',
});
if (submitResult?.error) {
throw new Error(submitResult.error);
}
const landingResult = await ensureKiroPageState(tabId, {
step: 3,
targetStates: ['otp_page'],
stableMs: 1500,
initialDelayMs: 150,
injectLogMessage: '步骤 3:姓名提交后页面切换中,正在等待 Kiro 授权页恢复...',
readyLogMessage: '步骤 3:姓名已提交,正在等待 Kiro 验证码页加载完成...',
timeoutMessage: '姓名提交后未进入验证码页,请检查当前页面状态。',
});
const updates = {
kiroAuthError: '',
kiroFullName: fullName,
kiroUploadError: '',
kiroVerificationRequestedAt: verificationRequestedAt,
};
await setState(updates);
await log('步骤 3:姓名已提交,当前已进入验证码页。', 'ok', nodeId);
await completeNodeFromBackground(nodeId, {
...updates,
kiroNextState: landingResult?.state || '',
kiroNextUrl: landingResult?.url || '',
});
} catch (error) {
const message = getErrorMessage(error);
await persistFailure({
kiroAuthError: message,
});
throw error;
}
}
async function executeKiroSubmitVerificationCode(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-submit-verification-code').trim();
try {
const latestState = await getExecutionState(state);
if (!cleanString(latestState?.kiroAuthorizedEmail || latestState?.email)) {
throw new Error('缺少 Kiro 授权邮箱,请先完成步骤 2。');
}
const tabId = await activateKiroAuthTab(latestState, {
missingUrlMessage: '缺少 Kiro 授权页地址,请先执行步骤 1。',
openFailedMessage: '无法恢复 Kiro 授权页,请重新执行步骤 1。',
});
await ensureKiroPageState(tabId, {
step: 4,
targetStates: ['otp_page'],
stableMs: 1500,
initialDelayMs: 150,
injectLogMessage: '步骤 4:Kiro 验证码页内容脚本未就绪,正在等待页面恢复...',
readyLogMessage: '步骤 4:正在等待 Kiro 验证码页加载完成...',
});
const codeResult = await pollKiroVerificationCode(4, latestState, nodeId);
const code = cleanString(codeResult?.code);
if (!code) {
throw new Error('未能获取到 Kiro 邮箱验证码。');
}
await log(`步骤 4:已获取验证码 ${code},正在返回 Kiro 授权页提交...`, 'info', nodeId);
await activateTab(tabId);
const submitResult = await sendToContentScriptResilient('kiro-device-auth', {
type: 'EXECUTE_NODE',
nodeId: 'kiro-submit-verification-code',
step: 4,
source: 'background',
payload: {
code,
},
}, {
timeoutMs: 30000,
retryDelayMs: 700,
onRetryableError: buildKiroRetryRecovery(tabId, {}),
logMessage: '步骤 4:正在向 Kiro 验证码页提交验证码...',
});
if (submitResult?.error) {
throw new Error(submitResult.error);
}
const landingResult = await ensureKiroPageState(tabId, {
step: 4,
targetStates: ['password_page'],
stableMs: 1500,
initialDelayMs: 150,
injectLogMessage: '步骤 4:验证码提交后页面切换中,正在等待 Kiro 授权页恢复...',
readyLogMessage: '步骤 4:验证码已提交,正在等待 Kiro 密码页加载完成...',
timeoutMessage: '验证码提交后未进入密码页,请检查验证码是否失效或页面是否异常。',
});
const updates = {
kiroAuthError: '',
kiroUploadError: '',
};
await setState(updates);
await log('步骤 4:验证码已提交,当前已进入密码页。', 'ok', nodeId);
await completeNodeFromBackground(nodeId, {
...updates,
code,
emailTimestamp: Number(codeResult?.emailTimestamp || 0) || 0,
mailId: String(codeResult?.mailId || ''),
kiroNextState: landingResult?.state || '',
kiroNextUrl: landingResult?.url || '',
});
} catch (error) {
const message = getErrorMessage(error);
await persistFailure({
kiroAuthError: message,
});
throw error;
}
}
async function executeKiroFillPassword(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-fill-password').trim();
try {
const latestState = await getExecutionState(state);
const tabId = await activateKiroAuthTab(latestState, {
missingUrlMessage: '缺少 Kiro 授权页地址,请先执行步骤 1。',
openFailedMessage: '无法恢复 Kiro 授权页,请重新执行步骤 1。',
});
await ensureKiroPageState(tabId, {
step: 5,
targetStates: ['password_page'],
stableMs: 1500,
initialDelayMs: 150,
injectLogMessage: '步骤 5:Kiro 密码页内容脚本未就绪,正在等待页面恢复...',
readyLogMessage: '步骤 5:正在等待 Kiro 密码页加载完成...',
});
const passwordResolution = resolveKiroPassword(latestState);
const password = passwordResolution.password;
if (!password) {
throw new Error('未生成有效的 Kiro 账户密码。');
}
if (typeof setPasswordState === 'function') {
await setPasswordState(password);
} else {
await setState({ password });
}
const passwordModeLabel = passwordResolution.mode === 'custom'
? '自定义密码'
: (passwordResolution.mode === 'reused' ? '复用现有密码' : '自动生成密码');
await log(`步骤 5:正在填写 Kiro 账户密码(${passwordModeLabel}${password.length} 位)...`, 'info', nodeId);
const submitResult = await sendToContentScriptResilient('kiro-device-auth', {
type: 'EXECUTE_NODE',
nodeId: 'kiro-fill-password',
step: 5,
source: 'background',
payload: {
password,
},
}, {
timeoutMs: 30000,
retryDelayMs: 700,
onRetryableError: buildKiroRetryRecovery(tabId, {}),
logMessage: '步骤 5:正在向 Kiro 密码页提交密码...',
});
if (submitResult?.error) {
throw new Error(submitResult.error);
}
const landingResult = await waitForKiroPageChange(tabId, {
step: 5,
fromStates: ['password_page'],
stableMs: 1200,
initialDelayMs: 120,
injectLogMessage: '步骤 5:密码提交后页面切换中,正在等待 Kiro 授权页恢复...',
readyLogMessage: '步骤 5:密码已提交,正在等待 Kiro 授权页完成跳转...',
timeoutMessage: '密码提交后页面未离开密码页,请检查密码规则或当前页面提示。',
});
const updates = {
kiroAuthError: '',
kiroUploadError: '',
};
await setState(updates);
await log(`步骤 5:密码已提交,当前页面状态:${landingResult?.state || 'unknown'}`, 'ok', nodeId);
await completeNodeFromBackground(nodeId, {
...updates,
kiroNextState: landingResult?.state || '',
kiroNextUrl: landingResult?.url || '',
});
} catch (error) {
const message = getErrorMessage(error);
await persistFailure({
kiroAuthError: message,
});
throw error;
}
}
async function executeKiroConfirmAccess(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-confirm-access').trim();
try {
const latestState = await getExecutionState(state);
const clientId = cleanString(latestState.kiroClientId);
const clientSecret = String(latestState.kiroClientSecret || '');
const deviceCode = String(latestState.kiroDeviceAuthorizationCode || '');
const region = normalizeRegion(latestState.kiroAuthRegion, DEFAULT_REGION);
const expiresAt = Math.max(0, Number(latestState.kiroAuthExpiresAt) || 0);
if (!clientId || !clientSecret || !deviceCode) {
throw new Error('尚未启动 Kiro 设备登录,请先执行步骤 1。');
}
if (!expiresAt || expiresAt <= Date.now()) {
throw new Error('Kiro 设备登录已过期,请重新执行步骤 1。');
}
const tabId = await activateKiroAuthTab(latestState, {
missingUrlMessage: '缺少 Kiro 授权页地址,请先执行步骤 1。',
openFailedMessage: '无法恢复 Kiro 授权页,请重新执行步骤 1。',
});
await setState({
kiroAuthError: '',
kiroAuthStatus: 'waiting_user',
kiroUploadStatus: 'waiting_login',
});
let landingResult = await ensureKiroPageState(tabId, {
step: 6,
targetStates: ['authorization_page', 'success_page'],
stableMs: 1500,
initialDelayMs: 150,
injectLogMessage: '步骤 6:Kiro 授权确认页内容脚本未就绪,正在等待页面恢复...',
readyLogMessage: '步骤 6:正在等待 Kiro 授权确认页加载完成...',
timeoutMessage: '未进入 Kiro 授权确认页,请检查当前页面状态。',
});
if (landingResult?.state !== 'success_page') {
await log('步骤 6:正在确认访问并完成 Kiro 授权...', 'info', nodeId);
const submitResult = await sendToContentScriptResilient('kiro-device-auth', {
type: 'EXECUTE_NODE',
nodeId: 'kiro-confirm-access',
step: 6,
source: 'background',
payload: {
maxActions: 3,
},
}, {
timeoutMs: 60000,
retryDelayMs: 700,
onRetryableError: buildKiroRetryRecovery(tabId, {}),
logMessage: '步骤 6:正在处理 Kiro 授权确认页...',
});
if (submitResult?.error) {
throw new Error(submitResult.error);
}
landingResult = {
state: String(submitResult?.state || ''),
url: String(submitResult?.url || ''),
};
}
await log('步骤 6:授权页已完成,正在同步 Builder ID 凭据...', 'info', nodeId);
let intervalSeconds = normalizePositiveInteger(latestState.kiroAuthIntervalSeconds, 5);
while (Date.now() < expiresAt) {
throwIfStopped();
const result = await pollBuilderIdDeviceAuth({
clientId,
clientSecret,
deviceCode,
region,
}, fetchImpl);
if (result.completed) {
const updates = {
kiroAccessToken: result.accessToken,
kiroAuthError: '',
kiroAuthStatus: 'authorized',
kiroRefreshToken: result.refreshToken,
kiroUploadError: '',
kiroUploadStatus: 'ready_to_upload',
};
await setState(updates);
await log('步骤 6:确认访问已完成,已获取 Refresh Token。', 'ok', nodeId);
await completeNodeFromBackground(nodeId, {
...updates,
kiroNextState: landingResult?.state || '',
kiroNextUrl: landingResult?.url || '',
});
return;
}
if (result.status === 'slow_down') {
intervalSeconds = Math.max(intervalSeconds + 5, 10);
}
await sleepWithStop(intervalSeconds * 1000);
}
throw new Error('Kiro 设备登录已过期,请重新执行步骤 1。');
} catch (error) {
const message = getErrorMessage(error);
await persistFailure({
kiroAuthError: message,
kiroAuthStatus: /(expired|过期)/i.test(message) ? 'expired' : 'error',
});
throw error;
}
}
async function executeKiroUploadCredential(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-upload-credential').trim();
try {
const latestState = await getExecutionState(state);
const refreshToken = String(latestState.kiroRefreshToken || '');
const clientId = cleanString(latestState.kiroClientId);
const clientSecret = String(latestState.kiroClientSecret || '');
const region = normalizeRegion(latestState.kiroAuthRegion, DEFAULT_REGION);
const kiroRsUrl = String(latestState.kiroRsUrl || '');
const kiroRsKey = String(latestState.kiroRsKey || '');
if (!refreshToken || !clientId || !clientSecret) {
throw new Error('缺少 Kiro Refresh Token,请先完成步骤 6。');
}
if (!cleanString(kiroRsUrl)) {
throw new Error('缺少 kiro.rs 管理后台地址。');
}
if (!cleanString(kiroRsKey)) {
throw new Error('缺少 kiro.rs API Key。');
}
await setState({
kiroUploadError: '',
kiroUploadStatus: 'uploading',
});
await log('步骤 7:正在上传 Builder ID 凭据到 kiro.rs...', 'info', nodeId);
const connection = await checkKiroRsConnection(kiroRsUrl, kiroRsKey, fetchImpl);
await setState({
kiroLastConnectionMessage: connection.message,
});
if (!connection.ok) {
throw new Error(connection.message);
}
const uploadOptions = buildCredentialUploadOptions(latestState);
const uploadPayload = {
refreshToken,
clientId,
clientSecret,
region,
...(cleanString(latestState.kiroAuthorizedEmail)
? { email: cleanString(latestState.kiroAuthorizedEmail) }
: {}),
...uploadOptions,
};
const uploadResult = await uploadBuilderIdCredential(
kiroRsUrl,
kiroRsKey,
uploadPayload,
fetchImpl
);
const updates = {
kiroAuthorizedEmail: uploadResult.email || cleanString(latestState.kiroAuthorizedEmail),
kiroCredentialId: uploadResult.credentialId,
kiroLastUploadAt: Date.now(),
kiroUploadError: '',
kiroUploadStatus: normalizeKiroUploadMessage(uploadResult.message),
};
await setState(updates);
await log(`步骤 7:kiro.rs 上传完成,状态:${updates.kiroUploadStatus}`, 'ok', nodeId);
await completeNodeFromBackground(nodeId, updates);
} catch (error) {
const message = getErrorMessage(error);
await persistFailure({
kiroUploadError: message,
kiroUploadStatus: 'error',
});
throw error;
}
}
return {
executeKiroConfirmAccess,
executeKiroFillPassword,
executeKiroStartDeviceLogin,
executeKiroSubmitEmail,
executeKiroSubmitName,
executeKiroSubmitVerificationCode,
executeKiroUploadCredential,
};
}
return {
buildCredentialUploadOptions,
checkKiroRsConnection,
createKiroDeviceAuthExecutor,
normalizeKiroRsBaseUrl,
pollBuilderIdDeviceAuth,
startBuilderIdDeviceLogin,
uploadBuilderIdCredential,
};
});