feat: rebuild kiro flow as independent desktop auth pipeline

This commit is contained in:
QLHazyCoder
2026-05-18 22:50:43 +08:00
parent 4aa459e599
commit 995982d7be
42 changed files with 5029 additions and 2293 deletions
+1 -1
View File
@@ -106,7 +106,7 @@
activeFlowId: state.activeFlowId,
flowId: state.flowId || state.activeFlowId,
panelMode: state.panelMode,
kiroSourceId: state.kiroSourceId,
kiroTargetId: state.kiroTargetId,
vpsUrl: state.vpsUrl,
vpsPassword: state.vpsPassword,
customPassword: state.customPassword,
+989
View File
@@ -0,0 +1,989 @@
(function attachBackgroundKiroDesktopAuthorizeRunner(root, factory) {
root.MultiPageBackgroundKiroDesktopAuthorizeRunner = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroDesktopAuthorizeRunnerModule(root) {
const kiroStateApi = root.MultiPageBackgroundKiroState || null;
const desktopClientApi = root.MultiPageBackgroundKiroDesktopClient || null;
const DEFAULT_REGION = kiroStateApi?.DEFAULT_REGION || desktopClientApi?.DEFAULT_REGION || 'us-east-1';
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
const KIRO_DESKTOP_SOURCE_ID = 'kiro-desktop-authorize';
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 isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cloneValue(value) {
if (Array.isArray(value)) {
return value.map((entry) => cloneValue(entry));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [key, cloneValue(entryValue)])
);
}
return value;
}
function deepMerge(baseValue, patchValue) {
if (Array.isArray(patchValue)) {
return patchValue.map((entry) => cloneValue(entry));
}
if (!isPlainObject(patchValue)) {
return patchValue === undefined ? cloneValue(baseValue) : patchValue;
}
const baseObject = isPlainObject(baseValue) ? baseValue : {};
const next = {
...cloneValue(baseObject),
};
Object.entries(patchValue).forEach(([key, value]) => {
next[key] = deepMerge(baseObject[key], value);
});
return next;
}
function cleanString(value = '') {
return String(value ?? '').trim();
}
function readKiroRuntime(state = {}) {
if (typeof kiroStateApi?.ensureRuntimeState === 'function') {
return kiroStateApi.ensureRuntimeState(state);
}
return deepMerge(
typeof kiroStateApi?.buildDefaultRuntimeState === 'function'
? kiroStateApi.buildDefaultRuntimeState()
: {},
state?.kiroRuntime || {}
);
}
function mergeRuntimePatch(currentState = {}, patch = {}) {
return {
kiroRuntime: deepMerge(readKiroRuntime(currentState), patch),
};
}
function normalizePositiveInteger(value, fallback) {
const numeric = Math.floor(Number(value));
if (Number.isInteger(numeric) && numeric > 0) {
return numeric;
}
return fallback;
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : String(error ?? '未知错误');
}
function parseDesktopCallbackUrl(rawUrl, expectedState = '', expectedPort = 0) {
const normalizedUrl = cleanString(rawUrl);
if (!normalizedUrl) {
return null;
}
let parsed = null;
try {
parsed = new URL(normalizedUrl);
} catch (_error) {
return null;
}
if (!/^https?:$/.test(parsed.protocol)) {
return null;
}
if (!['127.0.0.1', 'localhost'].includes(parsed.hostname)) {
return null;
}
if (expectedPort && Number(parsed.port || 0) !== Number(expectedPort)) {
return null;
}
if (parsed.pathname !== '/oauth/callback') {
return null;
}
const stateValue = cleanString(parsed.searchParams.get('state'));
if (expectedState && stateValue && stateValue !== cleanString(expectedState)) {
return {
url: normalizedUrl,
state: stateValue,
error: `回调 state 不匹配:expected=${cleanString(expectedState)} actual=${stateValue}`,
};
}
const error = cleanString(parsed.searchParams.get('error_description') || parsed.searchParams.get('error'));
const code = cleanString(parsed.searchParams.get('code'));
if (error) {
return {
url: normalizedUrl,
state: stateValue,
error,
};
}
if (!code) {
return null;
}
return {
url: normalizedUrl,
state: stateValue,
code,
};
}
function createDesktopCallbackTracker(chromeApi) {
const pendingSessions = new Map();
const resolvedSessions = new Map();
let listenersInstalled = false;
function installListeners() {
if (listenersInstalled || !chromeApi) {
return;
}
listenersInstalled = true;
const handleNavigation = (details = {}) => {
const url = cleanString(details.url);
if (!url) {
return;
}
for (const [stateKey, session] of pendingSessions.entries()) {
const parsed = parseDesktopCallbackUrl(url, session.expectedState, session.redirectPort);
if (!parsed) {
continue;
}
const result = {
...parsed,
tabId: Number.isInteger(details.tabId) ? details.tabId : (Number.isInteger(session.tabId) ? session.tabId : null),
};
resolvedSessions.set(stateKey, result);
const waiters = Array.isArray(session.waiters) ? session.waiters.splice(0, session.waiters.length) : [];
pendingSessions.set(stateKey, {
...session,
resolved: result,
waiters: [],
});
waiters.forEach(({ resolve }) => resolve(result));
const targetTabId = Number.isInteger(result.tabId) ? result.tabId : (Number.isInteger(session.tabId) ? session.tabId : null);
if (Number.isInteger(targetTabId) && chromeApi.tabs?.remove) {
chromeApi.tabs.remove(targetTabId).catch(() => {});
}
break;
}
};
chromeApi.webNavigation?.onBeforeNavigate?.addListener?.(handleNavigation);
chromeApi.webNavigation?.onCommitted?.addListener?.(handleNavigation);
chromeApi.webRequest?.onBeforeRequest?.addListener?.(
handleNavigation,
{ urls: ['http://127.0.0.1/*', 'http://localhost/*'] }
);
}
function registerPending(params = {}) {
installListeners();
const expectedState = cleanString(params.expectedState);
if (!expectedState) {
throw new Error('缺少桌面授权 state,无法注册回调监听。');
}
const existingResolved = resolvedSessions.get(expectedState);
const existingPending = pendingSessions.get(expectedState);
pendingSessions.set(expectedState, {
expectedState,
redirectPort: Number(params.redirectPort || 0) || 0,
tabId: Number.isInteger(params.tabId) ? params.tabId : (existingPending?.tabId ?? null),
waiters: existingPending?.waiters || [],
resolved: existingResolved || existingPending?.resolved || null,
});
return existingResolved || null;
}
function consumeResolved(expectedState = '') {
const stateKey = cleanString(expectedState);
if (!stateKey || !resolvedSessions.has(stateKey)) {
return null;
}
const result = resolvedSessions.get(stateKey) || null;
resolvedSessions.delete(stateKey);
pendingSessions.delete(stateKey);
return result;
}
function waitForResolved(expectedState = '', timeoutMs = 120000) {
const stateKey = cleanString(expectedState);
const immediate = consumeResolved(stateKey);
if (immediate) {
return Promise.resolve(immediate);
}
const session = pendingSessions.get(stateKey);
if (!session) {
return Promise.reject(new Error(`未注册桌面授权回调监听:${stateKey}`));
}
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
const nextSession = pendingSessions.get(stateKey);
if (nextSession) {
nextSession.waiters = (nextSession.waiters || []).filter((entry) => entry.reject !== reject);
pendingSessions.set(stateKey, nextSession);
}
reject(new Error('等待桌面授权回调超时。'));
}, Math.max(1000, Math.floor(Number(timeoutMs) || 120000)));
session.waiters.push({
resolve: (result) => {
clearTimeout(timer);
resolve(result);
},
reject: (error) => {
clearTimeout(timer);
reject(error);
},
});
pendingSessions.set(stateKey, session);
});
}
function clear(expectedState = '') {
const stateKey = cleanString(expectedState);
if (!stateKey) {
return;
}
const session = pendingSessions.get(stateKey);
if (session && Array.isArray(session.waiters)) {
session.waiters.forEach(({ reject }) => reject(new Error('桌面授权回调监听已清理。')));
}
pendingSessions.delete(stateKey);
resolvedSessions.delete(stateKey);
}
return {
clear,
consumeResolved,
registerPending,
waitForResolved,
};
}
function createKiroDesktopAuthorizeRunner(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,
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,
isTabAlive = async () => false,
KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = null,
pollCloudflareTempEmailVerificationCode = null,
pollCloudMailVerificationCode = null,
pollHotmailVerificationCode = null,
pollLuckmailVerificationCode = null,
pollYydsMailVerificationCode = null,
registerTab = async () => {},
reuseOrCreateTab = async () => null,
sendToContentScriptResilient = null,
sendToMailContentScriptResilient = null,
setState = async () => {},
sleepWithStop = async (ms) => {
await new Promise((resolve) => setTimeout(resolve, ms));
},
throwIfStopped = () => {},
waitForTabStableComplete = null,
} = deps;
if (typeof completeNodeFromBackground !== 'function') {
throw new Error('Kiro desktop authorize runner requires completeNodeFromBackground.');
}
if (!desktopClientApi) {
throw new Error('Kiro desktop authorize runner requires desktop client module.');
}
if (typeof fetchImpl !== 'function') {
throw new Error('Kiro desktop authorize runner requires fetch support.');
}
const callbackTracker = createDesktopCallbackTracker(chrome);
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 applyRuntimeState(currentState = {}, patch = {}, extraState = {}) {
const runtimePatch = mergeRuntimePatch(currentState, patch);
const nextPatch = {
...runtimePatch,
...extraState,
};
await setState(nextPatch);
return nextPatch;
}
async function persistFailure(currentState = {}, message = '') {
await setState(mergeRuntimePatch(currentState, {
session: {
lastError: message,
},
desktopAuth: {
status: 'error',
},
}));
}
async function ensureDesktopAuthorizeTab(state = {}, options = {}) {
const runtimeState = readKiroRuntime(state);
let tabId = Number.isInteger(runtimeState.session?.desktopTabId)
? runtimeState.session.desktopTabId
: await getTabId(KIRO_DESKTOP_SOURCE_ID);
const authorizeUrl = cleanString(runtimeState.desktopAuth?.authorizeUrl);
if (Number.isInteger(tabId) && await isTabAlive(KIRO_DESKTOP_SOURCE_ID)) {
return tabId;
}
if (!authorizeUrl) {
throw new Error(options.missingUrlMessage || '缺少桌面授权地址,请先执行步骤 7。');
}
tabId = await reuseOrCreateTab(KIRO_DESKTOP_SOURCE_ID, authorizeUrl);
if (!Number.isInteger(tabId)) {
throw new Error(options.openFailedMessage || '无法打开桌面授权页,请重试步骤 7。');
}
await registerTab(KIRO_DESKTOP_SOURCE_ID, tabId);
await setState(mergeRuntimePatch(state, {
session: {
desktopTabId: tabId,
},
}));
return tabId;
}
async function activateDesktopAuthorizeTab(state = {}, options = {}) {
const tabId = await ensureDesktopAuthorizeTab(state, options);
await activateTab(tabId);
return tabId;
}
async function reattachDesktopAuthorizePage(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_DESKTOP_SOURCE_ID, tabId, {
inject: Array.isArray(KIRO_DESKTOP_AUTHORIZE_INJECT_FILES) ? KIRO_DESKTOP_AUTHORIZE_INJECT_FILES : null,
injectSource: KIRO_DESKTOP_SOURCE_ID,
timeoutMs: 45000,
retryDelayMs: 800,
logMessage: options.injectLogMessage || 'Kiro 桌面授权页已跳转,正在重新连接内容脚本...',
});
}
}
function buildDesktopRetryRecovery(tabId, options = {}) {
return async () => {
await reattachDesktopAuthorizePage(tabId, {
stableMs: Number(options.recoveryStableMs) || Number(options.stableMs) || 1200,
initialDelayMs: Number(options.recoveryInitialDelayMs) || 120,
injectLogMessage: options.recoveryInjectLogMessage || options.injectLogMessage || 'Kiro 桌面授权页已跳转,正在重新连接内容脚本...',
});
};
}
async function getDesktopAuthorizePageState(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_DESKTOP_SOURCE_ID, tabId, {
inject: Array.isArray(KIRO_DESKTOP_AUTHORIZE_INJECT_FILES) ? KIRO_DESKTOP_AUTHORIZE_INJECT_FILES : null,
injectSource: KIRO_DESKTOP_SOURCE_ID,
timeoutMs: 45000,
retryDelayMs: 800,
logMessage: options.injectLogMessage || 'Kiro 桌面授权页内容脚本未就绪,正在等待页面恢复...',
});
}
const result = await sendToContentScriptResilient(KIRO_DESKTOP_SOURCE_ID, {
type: 'GET_KIRO_DESKTOP_AUTHORIZE_STATE',
step: options.step || 0,
source: 'background',
}, {
timeoutMs: 30000,
retryDelayMs: 700,
onRetryableError: buildDesktopRetryRecovery(tabId, options),
logMessage: options.readyLogMessage || '正在读取 Kiro 桌面授权页状态...',
});
if (result?.error) {
throw new Error(result.error);
}
return result || { state: '', url: '' };
}
async function executeDesktopAction(tabId, action, payload = {}, options = {}) {
const result = await sendToContentScriptResilient(KIRO_DESKTOP_SOURCE_ID, {
type: 'EXECUTE_KIRO_DESKTOP_AUTHORIZE_ACTION',
step: options.step || 0,
source: 'background',
payload: {
action,
...payload,
},
}, {
timeoutMs: 30000,
retryDelayMs: 700,
onRetryableError: buildDesktopRetryRecovery(tabId, options),
logMessage: options.logMessage || '正在执行 Kiro 桌面授权动作...',
});
if (result?.error) {
throw new Error(result.error);
}
return result || { state: '', url: '' };
}
function resolveDesktopLoginPassword(state = {}) {
const password = String(state?.customPassword || state?.password || '');
if (!password) {
throw new Error('缺少已注册账号密码,无法完成桌面授权重登。');
}
return password;
}
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 buildDesktopOtpPollPayload(step, state = {}, mail = {}, filterAfterTimestamp = 0) {
const runtimeState = readKiroRuntime(state);
const targetEmail = cleanString(runtimeState.register?.email || 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 pollDesktopOtpCode(step, state = {}, nodeId = '') {
if (typeof getMailConfig !== 'function') {
throw new Error('Kiro 桌面授权验证码步骤缺少邮箱配置能力,无法继续执行。');
}
const mail = getMailConfig(state);
if (mail?.error) {
throw new Error(mail.error);
}
const runtimeState = readKiroRuntime(state);
const requestedAt = Math.max(0, Number(runtimeState.desktopAuth?.otpRequestedAt) || Date.now());
const filterAfterTimestamp = mail.provider === '2925'
? Math.max(0, requestedAt - MAIL_2925_FILTER_LOOKBACK_MS)
: requestedAt;
const pollPayload = buildDesktopOtpPollPayload(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 邮箱登录状态`,
});
}
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-complete-desktop-authorize',
}
);
if (result?.error) {
throw new Error(result.error);
}
if (!result?.code) {
throw new Error(`步骤 ${step}:邮箱轮询结束,但未获取到桌面授权验证码。`);
}
return result;
}
async function executeKiroStartDesktopAuthorize(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-start-desktop-authorize').trim();
const currentState = await getExecutionState(state);
try {
const runtimeState = readKiroRuntime(currentState);
if (!cleanString(runtimeState.register?.email || currentState?.email)) {
throw new Error('缺少已注册邮箱,请先完成注册页步骤。');
}
const client = await desktopClientApi.registerDesktopClient({
region: DEFAULT_REGION,
clientName: 'Kiro IDE',
}, fetchImpl);
const pkce = await desktopClientApi.generatePkcePair();
const stateToken = cleanString(globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`);
const redirectPort = desktopClientApi.chooseRedirectPort();
const redirectUri = desktopClientApi.buildRedirectUri(redirectPort);
const authorizeUrl = desktopClientApi.buildAuthorizeUrl({
region: client.region,
clientId: client.clientId,
redirectUri,
state: stateToken,
codeChallenge: pkce.codeChallenge,
});
callbackTracker.registerPending({
expectedState: stateToken,
redirectPort,
});
const tabId = await reuseOrCreateTab(KIRO_DESKTOP_SOURCE_ID, authorizeUrl);
if (!Number.isInteger(tabId)) {
throw new Error('无法打开 Kiro 桌面授权页,请重试步骤 7。');
}
await registerTab(KIRO_DESKTOP_SOURCE_ID, tabId);
callbackTracker.registerPending({
expectedState: stateToken,
redirectPort,
tabId,
});
const payload = await applyRuntimeState(currentState, {
session: {
currentStage: 'desktop-authorize',
desktopTabId: tabId,
pageState: '',
pageUrl: authorizeUrl,
lastError: '',
lastWarning: '',
},
desktopAuth: {
region: client.region,
clientId: client.clientId,
clientSecret: client.clientSecret,
clientIdHash: client.clientIdHash,
state: stateToken,
codeVerifier: pkce.codeVerifier,
codeChallenge: pkce.codeChallenge,
redirectUri,
redirectPort,
authorizeUrl,
authorizationCode: '',
accessToken: '',
refreshToken: '',
status: 'waiting_callback',
authorizedAt: 0,
otpRequestedAt: 0,
tokenSource: 'desktop_authorization_code_pkce',
},
upload: {
status: 'waiting_desktop_authorize',
error: '',
credentialId: null,
lastMessage: '',
lastUploadedAt: 0,
},
});
await activateTab(tabId);
await log('步骤 7:Kiro 桌面授权页已打开,下一步将继续完成授权并抓取回调。', 'ok', nodeId);
await completeNodeFromBackground(nodeId, payload);
} catch (error) {
const message = getErrorMessage(error);
await persistFailure(currentState, message);
throw error;
}
}
async function executeKiroCompleteDesktopAuthorize(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-complete-desktop-authorize').trim();
const currentState = await getExecutionState(state);
let runtimeState = readKiroRuntime(currentState);
const desktopState = cleanString(runtimeState.desktopAuth?.state);
try {
if (!desktopState) {
throw new Error('缺少桌面授权 state,请先执行步骤 7。');
}
if (!cleanString(runtimeState.desktopAuth?.clientId) || !cleanString(runtimeState.desktopAuth?.clientSecret)) {
throw new Error('缺少桌面授权客户端凭据,请先执行步骤 7。');
}
if (!cleanString(runtimeState.desktopAuth?.redirectUri) || !runtimeState.desktopAuth?.redirectPort) {
throw new Error('缺少桌面授权回调地址,请先执行步骤 7。');
}
if (!cleanString(runtimeState.desktopAuth?.codeVerifier)) {
throw new Error('缺少桌面授权 PKCE verifier,请先执行步骤 7。');
}
callbackTracker.registerPending({
expectedState: desktopState,
redirectPort: runtimeState.desktopAuth.redirectPort,
tabId: runtimeState.session?.desktopTabId,
});
const deadline = Date.now() + 120000;
while (Date.now() < deadline) {
throwIfStopped();
const resolvedCallback = callbackTracker.consumeResolved(desktopState);
if (resolvedCallback) {
if (resolvedCallback.error) {
throw new Error(`桌面授权回调失败:${resolvedCallback.error}`);
}
const tokenResult = await desktopClientApi.exchangeDesktopAuthorizationCode({
region: runtimeState.desktopAuth.region || DEFAULT_REGION,
clientId: runtimeState.desktopAuth.clientId,
clientSecret: runtimeState.desktopAuth.clientSecret,
redirectUri: runtimeState.desktopAuth.redirectUri,
code: resolvedCallback.code,
codeVerifier: runtimeState.desktopAuth.codeVerifier,
}, fetchImpl);
const payload = await applyRuntimeState(currentState, {
session: {
currentStage: 'upload',
pageState: 'callback_captured',
pageUrl: resolvedCallback.url,
lastError: '',
},
desktopAuth: {
authorizationCode: resolvedCallback.code,
accessToken: tokenResult.accessToken,
refreshToken: tokenResult.refreshToken,
status: 'authorized',
authorizedAt: Date.now(),
},
upload: {
status: 'ready_to_upload',
error: '',
},
});
await log('步骤 8:桌面授权回调已捕获,Token 换取成功。', 'ok', nodeId);
await completeNodeFromBackground(nodeId, payload);
return;
}
const tabId = await activateDesktopAuthorizeTab(currentState, {
missingUrlMessage: '缺少桌面授权地址,请先执行步骤 7。',
openFailedMessage: '无法恢复桌面授权页,请重新执行步骤 7。',
});
const pageState = await getDesktopAuthorizePageState(tabId, {
step: 8,
injectLogMessage: '步骤 8:Kiro 桌面授权页内容脚本未就绪,正在等待页面恢复...',
readyLogMessage: '步骤 8:正在读取 Kiro 桌面授权页当前状态...',
});
await setState(mergeRuntimePatch(currentState, {
session: {
pageState: pageState?.state || '',
pageUrl: pageState?.url || '',
lastError: '',
},
}));
if (pageState.state === 'relogin_email') {
const email = cleanString(runtimeState.register?.email || currentState?.email);
await log(`步骤 8:桌面授权页要求重新输入邮箱,正在填写 ${email}...`, 'info', nodeId);
await executeDesktopAction(tabId, 'submit-email', { email }, {
step: 8,
logMessage: '步骤 8:正在向桌面授权页提交邮箱...',
});
await sleepWithStop(1200);
continue;
}
if (pageState.state === 'relogin_password') {
const password = resolveDesktopLoginPassword(currentState);
await log('步骤 8:桌面授权页要求重新输入密码,正在填写密码...', 'info', nodeId);
await executeDesktopAction(tabId, 'submit-password', { password }, {
step: 8,
logMessage: '步骤 8:正在向桌面授权页提交密码...',
});
await sleepWithStop(1200);
continue;
}
if (pageState.state === 'otp_page') {
runtimeState = readKiroRuntime(currentState);
if (!runtimeState.desktopAuth?.otpRequestedAt) {
await setState(mergeRuntimePatch(currentState, {
desktopAuth: {
otpRequestedAt: Date.now(),
status: 'waiting_otp',
},
}));
}
const codeResult = await pollDesktopOtpCode(8, currentState, nodeId);
const code = cleanString(codeResult?.code);
if (!code) {
throw new Error('未获取到桌面授权验证码。');
}
await log(`步骤 8:已获取桌面授权验证码 ${code},正在提交...`, 'info', nodeId);
await executeDesktopAction(tabId, 'submit-otp', { code }, {
step: 8,
logMessage: '步骤 8:正在向桌面授权页提交验证码...',
});
await sleepWithStop(1200);
continue;
}
if (pageState.state === 'consent_page') {
await log('步骤 8:正在确认 Kiro 桌面授权访问...', 'info', nodeId);
await executeDesktopAction(tabId, 'confirm-consent', {}, {
step: 8,
logMessage: '步骤 8:正在确认桌面授权访问...',
});
await sleepWithStop(1200);
continue;
}
if (pageState.state === 'callback_page') {
const parsedCallback = parseDesktopCallbackUrl(pageState.url, desktopState, runtimeState.desktopAuth?.redirectPort);
if (parsedCallback) {
callbackTracker.registerPending({
expectedState: desktopState,
redirectPort: runtimeState.desktopAuth?.redirectPort,
tabId,
});
if (parsedCallback.code) {
await setState(mergeRuntimePatch(currentState, {
session: {
pageState: 'callback_page',
pageUrl: parsedCallback.url,
},
}));
}
}
}
await sleepWithStop(1000);
}
const lastResult = await callbackTracker.waitForResolved(desktopState, 2000).catch(() => null);
if (lastResult?.error) {
throw new Error(`桌面授权回调失败:${lastResult.error}`);
}
if (lastResult?.code) {
const tokenResult = await desktopClientApi.exchangeDesktopAuthorizationCode({
region: runtimeState.desktopAuth.region || DEFAULT_REGION,
clientId: runtimeState.desktopAuth.clientId,
clientSecret: runtimeState.desktopAuth.clientSecret,
redirectUri: runtimeState.desktopAuth.redirectUri,
code: lastResult.code,
codeVerifier: runtimeState.desktopAuth.codeVerifier,
}, fetchImpl);
const payload = await applyRuntimeState(currentState, {
session: {
currentStage: 'upload',
pageState: 'callback_captured',
pageUrl: lastResult.url,
lastError: '',
},
desktopAuth: {
authorizationCode: lastResult.code,
accessToken: tokenResult.accessToken,
refreshToken: tokenResult.refreshToken,
status: 'authorized',
authorizedAt: Date.now(),
},
upload: {
status: 'ready_to_upload',
error: '',
},
});
await log('步骤 8:桌面授权回调已捕获,Token 换取成功。', 'ok', nodeId);
await completeNodeFromBackground(nodeId, payload);
return;
}
throw new Error('等待桌面授权回调超时。');
} catch (error) {
callbackTracker.clear(desktopState);
const message = getErrorMessage(error);
await persistFailure(currentState, message);
throw error;
}
}
return {
executeKiroCompleteDesktopAuthorize,
executeKiroStartDesktopAuthorize,
parseDesktopCallbackUrl,
};
}
return {
createDesktopCallbackTracker,
createKiroDesktopAuthorizeRunner,
parseDesktopCallbackUrl,
};
});
+196
View File
@@ -0,0 +1,196 @@
(function attachBackgroundKiroDesktopClient(root, factory) {
root.MultiPageBackgroundKiroDesktopClient = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroDesktopClientModule() {
const DEFAULT_REGION = 'us-east-1';
const DEFAULT_START_URL = 'https://view.awsapps.com/start';
const DEFAULT_SCOPES = Object.freeze([
'codewhisperer:completions',
'codewhisperer:analysis',
'codewhisperer:conversations',
'codewhisperer:transformations',
'codewhisperer:taskassist',
]);
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`;
}
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 };
}
async function sha256Bytes(input) {
const encoder = new TextEncoder();
return new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(String(input || ''))));
}
function base64UrlEncode(bytes) {
let binary = '';
for (const byte of bytes) {
binary += String.fromCharCode(byte);
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
async function sha256Hex(input) {
const bytes = await sha256Bytes(input);
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, '0')).join('');
}
function randomUrlSafeString(length = 64) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
const size = Math.max(32, Math.floor(Number(length) || 64));
const bytes = new Uint8Array(size);
crypto.getRandomValues(bytes);
let output = '';
for (let index = 0; index < size; index += 1) {
output += alphabet[bytes[index] % alphabet.length];
}
return output;
}
async function generatePkcePair() {
const codeVerifier = randomUrlSafeString(64);
const codeChallenge = base64UrlEncode(await sha256Bytes(codeVerifier));
return {
codeVerifier,
codeChallenge,
};
}
function chooseRedirectPort() {
return 49152 + Math.floor(Math.random() * 16384);
}
function buildRedirectUri(port) {
const normalizedPort = Math.max(1, Math.floor(Number(port) || 0));
if (!normalizedPort) {
throw new Error('缺少桌面授权回调端口。');
}
return `http://127.0.0.1:${normalizedPort}/oauth/callback`;
}
async function registerDesktopClient(params = {}, fetchImpl) {
if (typeof fetchImpl !== 'function') {
throw new Error('registerDesktopClient requires fetch support.');
}
const region = normalizeRegion(params.region);
const oidcBaseUrl = buildOidcBaseUrl(region);
const response = await fetchImpl(`${oidcBaseUrl}/client/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientName: cleanString(params.clientName) || 'Kiro IDE',
clientType: 'public',
scopes: Array.isArray(params.scopes) && params.scopes.length ? params.scopes : DEFAULT_SCOPES,
grantTypes: ['authorization_code', 'refresh_token'],
redirectUris: ['http://127.0.0.1/oauth/callback'],
issuerUrl: cleanString(params.issuerUrl) || DEFAULT_START_URL,
}),
});
const body = await readResponse(response);
if (!response.ok) {
throw new Error(`Kiro 桌面客户端注册失败:${cleanString(body.text || response.statusText) || response.status}`);
}
const clientId = cleanString(body.json?.clientId);
const clientSecret = String(body.json?.clientSecret || '');
if (!clientId || !clientSecret) {
throw new Error('Kiro 桌面客户端注册响应缺少 clientId 或 clientSecret。');
}
return {
region,
clientId,
clientSecret,
clientSecretExpiresAt: Number(body.json?.clientSecretExpiresAt || 0) || 0,
clientIdHash: await sha256Hex(JSON.stringify({
startUrl: cleanString(params.issuerUrl) || DEFAULT_START_URL,
clientId,
})),
};
}
function buildAuthorizeUrl(params = {}) {
const region = normalizeRegion(params.region);
const search = new URLSearchParams();
search.set('response_type', 'code');
search.set('client_id', cleanString(params.clientId));
search.set('redirect_uri', cleanString(params.redirectUri));
search.set('scopes', Array.isArray(params.scopes) && params.scopes.length
? params.scopes.join(',')
: DEFAULT_SCOPES.join(','));
search.set('state', cleanString(params.state));
search.set('code_challenge', cleanString(params.codeChallenge));
search.set('code_challenge_method', 'S256');
return `${buildOidcBaseUrl(region)}/authorize?${search.toString()}`;
}
async function exchangeDesktopAuthorizationCode(params = {}, fetchImpl) {
if (typeof fetchImpl !== 'function') {
throw new Error('exchangeDesktopAuthorizationCode requires fetch support.');
}
const region = normalizeRegion(params.region);
const response = await fetchImpl(`${buildOidcBaseUrl(region)}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientId: cleanString(params.clientId),
clientSecret: String(params.clientSecret || ''),
grantType: 'authorization_code',
code: cleanString(params.code),
redirectUri: cleanString(params.redirectUri),
codeVerifier: cleanString(params.codeVerifier),
}),
});
const body = await readResponse(response);
if (!response.ok) {
throw new Error(`Kiro 桌面授权换取 Token 失败:${cleanString(body.text || response.statusText) || response.status}`);
}
const accessToken = String(body.json?.accessToken || '');
const refreshToken = String(body.json?.refreshToken || '');
if (!accessToken || !refreshToken) {
throw new Error('Kiro 桌面授权换取 Token 响应缺少 accessToken 或 refreshToken。');
}
return {
accessToken,
refreshToken,
expiresIn: Number(body.json?.expiresIn || 0) || 0,
tokenType: cleanString(body.json?.tokenType),
region,
};
}
return {
DEFAULT_REGION,
DEFAULT_SCOPES,
DEFAULT_START_URL,
buildAuthorizeUrl,
buildRedirectUri,
chooseRedirectPort,
exchangeDesktopAuthorizationCode,
generatePkcePair,
registerDesktopClient,
};
});
+402
View File
@@ -0,0 +1,402 @@
(function attachBackgroundKiroPublisherKiroRs(root, factory) {
root.MultiPageBackgroundKiroPublisherKiroRs = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroPublisherKiroRsModule(root) {
const kiroStateApi = root?.MultiPageBackgroundKiroState || null;
const DEFAULT_REGION = kiroStateApi?.DEFAULT_REGION || 'us-east-1';
const DEFAULT_TARGET_ID = kiroStateApi?.DEFAULT_TARGET_ID || 'kiro-rs';
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cloneValue(value) {
if (Array.isArray(value)) {
return value.map((entry) => cloneValue(entry));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [key, cloneValue(entryValue)])
);
}
return value;
}
function deepMerge(baseValue, patchValue) {
if (Array.isArray(patchValue)) {
return patchValue.map((entry) => cloneValue(entry));
}
if (!isPlainObject(patchValue)) {
return patchValue === undefined ? cloneValue(baseValue) : patchValue;
}
const baseObject = isPlainObject(baseValue) ? baseValue : {};
const next = {
...cloneValue(baseObject),
};
Object.entries(patchValue).forEach(([key, value]) => {
next[key] = deepMerge(baseObject[key], value);
});
return next;
}
function cleanString(value = '') {
return String(value ?? '').trim();
}
function normalizeRegion(value = '', fallback = DEFAULT_REGION) {
return cleanString(value) || fallback;
}
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;
}
function normalizeKiroUploadMessage(value = '') {
const rawValue = cleanString(value);
if (!rawValue) {
return '上传成功';
}
const normalizedValue = rawValue.toLowerCase();
if (normalizedValue === 'uploaded' || normalizedValue === 'credential uploaded.') {
return '上传成功';
}
return rawValue;
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : String(error ?? '未知错误');
}
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 readKiroRuntime(state = {}) {
return kiroStateApi?.ensureRuntimeState
? kiroStateApi.ensureRuntimeState(state)
: (isPlainObject(state?.kiroRuntime) ? state.kiroRuntime : {});
}
function mergeRuntimePatch(currentState = {}, patch = {}) {
return {
kiroRuntime: deepMerge(readKiroRuntime(currentState), patch),
};
}
function resolveKiroTargetId(state = {}) {
return cleanString(
state?.settingsState?.flows?.kiro?.targetId
|| state?.flows?.kiro?.targetId
|| state?.kiroTargetId
|| readKiroRuntime(state).upload?.targetId
|| DEFAULT_TARGET_ID
) || DEFAULT_TARGET_ID;
}
function resolveKiroTargetConfig(state = {}, targetId = DEFAULT_TARGET_ID) {
if (targetId !== DEFAULT_TARGET_ID) {
throw new Error(`暂不支持 Kiro 发布目标:${targetId}`);
}
const nestedConfig = state?.settingsState?.flows?.kiro?.targets?.[targetId]
|| state?.flows?.kiro?.targets?.[targetId]
|| {};
return {
baseUrl: cleanString(nestedConfig.baseUrl || state?.kiroRsUrl),
apiKey: String(nestedConfig.apiKey ?? state?.kiroRsKey ?? ''),
};
}
function buildProxyPayload(state = {}) {
if (!state?.ipProxyEnabled) {
return {};
}
const apiProxyUrl = cleanString(state?.ipProxyApiUrl);
const host = cleanString(state?.ipProxyHost);
const port = cleanString(state?.ipProxyPort);
const protocol = cleanString(state?.ipProxyProtocol) || 'http';
const proxyUrl = apiProxyUrl || (host && port ? `${protocol}://${host}:${port}` : '');
const proxyUsername = cleanString(state?.ipProxyUsername);
const proxyPassword = String(state?.ipProxyPassword || '');
return {
...(proxyUrl ? { proxyUrl } : {}),
...(proxyUsername ? { proxyUsername } : {}),
...(proxyPassword ? { proxyPassword } : {}),
};
}
async function sha256Hex(input = '') {
const encoder = new TextEncoder();
const bytes = encoder.encode(String(input ?? ''));
const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes);
return Array.from(new Uint8Array(digest))
.map((value) => value.toString(16).padStart(2, '0'))
.join('');
}
async function buildMachineId(refreshToken = '') {
const normalizedRefreshToken = cleanString(refreshToken);
if (!normalizedRefreshToken) {
throw new Error('缺少 refreshToken,无法生成 machineId。');
}
return sha256Hex(`KotlinNativeAPI/${normalizedRefreshToken}`);
}
function buildUploadPayload(state = {}) {
const runtimeState = readKiroRuntime(state);
const targetId = resolveKiroTargetId(state);
const desktopAuth = runtimeState.desktopAuth || {};
const register = runtimeState.register || {};
const refreshToken = String(desktopAuth.refreshToken || '');
const clientId = cleanString(desktopAuth.clientId);
const clientSecret = String(desktopAuth.clientSecret || '');
const region = normalizeRegion(
desktopAuth.region
|| state?.settingsState?.flows?.kiro?.targets?.[targetId]?.region
|| state?.flows?.kiro?.targets?.[targetId]?.region
|| DEFAULT_REGION
);
const email = cleanString(register.email || state?.email);
if (!refreshToken) {
throw new Error('缺少桌面授权 refreshToken,请先完成步骤 8。');
}
if (!clientId || !clientSecret) {
throw new Error('缺少桌面授权 clientId 或 clientSecret,请先完成步骤 7-8。');
}
if (!email) {
throw new Error('缺少注册邮箱,无法上传到 kiro.rs。');
}
return {
targetId,
region,
email,
refreshToken,
clientId,
clientSecret,
authMethod: 'idc',
authRegion: region,
apiRegion: region,
...buildProxyPayload(state),
};
}
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 createKiroRsPublisher(deps = {}) {
const {
addLog = async () => {},
completeNodeFromBackground,
fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null,
getState = async () => ({}),
setState = async () => {},
} = deps;
if (typeof completeNodeFromBackground !== 'function') {
throw new Error('Kiro kiro.rs publisher requires completeNodeFromBackground.');
}
if (typeof fetchImpl !== 'function') {
throw new Error('Kiro kiro.rs publisher requires fetch support.');
}
async function log(message, level = 'info', nodeId = '') {
await addLog(message, level, nodeId ? { nodeId } : {});
}
async function getExecutionState(state = {}) {
if (state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length) {
return state;
}
return getState();
}
async function applyRuntimeState(currentState = {}, patch = {}) {
const nextPatch = mergeRuntimePatch(currentState, patch);
await setState(nextPatch);
return nextPatch;
}
async function persistFailure(currentState = {}, message = '') {
const nextPatch = mergeRuntimePatch(currentState, {
session: {
currentStage: 'upload',
lastError: message,
},
upload: {
status: 'error',
error: message,
},
});
await setState(nextPatch);
}
async function executeKiroUploadCredential(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-upload-credential').trim();
const currentState = await getExecutionState(state);
try {
const targetId = resolveKiroTargetId(currentState);
const targetConfig = resolveKiroTargetConfig(currentState, targetId);
const baseUrl = normalizeKiroRsBaseUrl(targetConfig.baseUrl);
const apiKey = String(targetConfig.apiKey || '');
if (!apiKey) {
throw new Error('缺少 kiro.rs API Key。');
}
const uploadInput = buildUploadPayload(currentState);
const machineId = await buildMachineId(uploadInput.refreshToken);
await applyRuntimeState(currentState, {
session: {
currentStage: 'upload',
lastError: '',
lastWarning: '',
},
upload: {
targetId,
status: 'uploading',
error: '',
},
});
await log('步骤 9:正在上传 Builder ID 凭据到 kiro.rs...', 'info', nodeId);
const connection = await checkKiroRsConnection(baseUrl, apiKey, fetchImpl);
if (!connection.ok) {
throw new Error(connection.message);
}
const uploadResult = await uploadBuilderIdCredential(baseUrl, apiKey, {
refreshToken: uploadInput.refreshToken,
authMethod: uploadInput.authMethod,
clientId: uploadInput.clientId,
clientSecret: uploadInput.clientSecret,
region: uploadInput.region,
authRegion: uploadInput.authRegion,
apiRegion: uploadInput.apiRegion,
machineId,
email: uploadInput.email,
...(uploadInput.proxyUrl ? { proxyUrl: uploadInput.proxyUrl } : {}),
...(uploadInput.proxyUsername ? { proxyUsername: uploadInput.proxyUsername } : {}),
...(uploadInput.proxyPassword ? { proxyPassword: uploadInput.proxyPassword } : {}),
}, fetchImpl);
const uploadedAt = Date.now();
const payload = await applyRuntimeState(currentState, {
session: {
currentStage: 'upload',
lastError: '',
},
upload: {
targetId,
status: 'uploaded',
error: '',
credentialId: uploadResult.credentialId,
lastMessage: uploadResult.message || '上传成功',
lastUploadedAt: uploadedAt,
},
});
await log(`步骤 9:kiro.rs 上传完成,状态:${uploadResult.message || '上传成功'}`, 'ok', nodeId);
await completeNodeFromBackground(nodeId, payload);
} catch (error) {
const message = getErrorMessage(error);
await persistFailure(currentState, message);
throw error;
}
}
return {
executeKiroUploadCredential,
};
}
return {
buildKiroRsPayload: buildUploadPayload,
buildMachineId,
checkKiroRsConnection,
createKiroRsPublisher,
normalizeKiroRsBaseUrl,
normalizeKiroUploadMessage,
uploadBuilderIdCredential,
};
});
File diff suppressed because it is too large Load Diff
+353
View File
@@ -0,0 +1,353 @@
(function attachBackgroundKiroState(root, factory) {
root.MultiPageBackgroundKiroState = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroStateModule() {
const DEFAULT_TARGET_ID = 'kiro-rs';
const DEFAULT_REGION = 'us-east-1';
const FLAT_FIELD_DEFINITIONS = Object.freeze([]);
const FLAT_FIELD_KEYS = Object.freeze([]);
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cloneValue(value) {
if (Array.isArray(value)) {
return value.map((entry) => cloneValue(entry));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [key, cloneValue(entryValue)])
);
}
return value;
}
function deepMerge(baseValue, patchValue) {
if (Array.isArray(patchValue)) {
return patchValue.map((entry) => cloneValue(entry));
}
if (!isPlainObject(patchValue)) {
return patchValue === undefined ? cloneValue(baseValue) : patchValue;
}
const baseObject = isPlainObject(baseValue) ? baseValue : {};
const next = {
...cloneValue(baseObject),
};
Object.entries(patchValue).forEach(([key, value]) => {
next[key] = deepMerge(baseObject[key], value);
});
return next;
}
function normalizeString(value = '', fallback = '') {
const normalized = String(value ?? '').trim();
return normalized || fallback;
}
function normalizeInteger(value, fallback = 0) {
const numeric = Math.floor(Number(value));
return Number.isInteger(numeric) ? numeric : fallback;
}
function normalizeNullableInteger(value, fallback = null) {
if (value === null || value === undefined || value === '') {
return fallback;
}
const numeric = Math.floor(Number(value));
return Number.isInteger(numeric) ? numeric : fallback;
}
function buildDefaultRuntimeState() {
return {
session: {
currentStage: '',
registerTabId: null,
desktopTabId: null,
startedAt: 0,
pageState: '',
pageUrl: '',
lastError: '',
lastWarning: '',
},
register: {
email: '',
fullName: '',
verificationRequestedAt: 0,
entryClientId: '',
entryClientSecret: '',
entryDeviceCode: '',
userCode: '',
loginUrl: '',
verificationUri: '',
verificationUriComplete: '',
entryExpiresAt: 0,
entryIntervalSeconds: 0,
status: '',
completedAt: 0,
},
desktopAuth: {
region: DEFAULT_REGION,
clientId: '',
clientSecret: '',
clientIdHash: '',
state: '',
codeVerifier: '',
codeChallenge: '',
redirectUri: '',
redirectPort: 0,
authorizeUrl: '',
authorizationCode: '',
accessToken: '',
refreshToken: '',
status: '',
authorizedAt: 0,
otpRequestedAt: 0,
tokenSource: 'desktop_authorization_code_pkce',
},
upload: {
targetId: DEFAULT_TARGET_ID,
status: '',
error: '',
credentialId: null,
lastMessage: '',
lastUploadedAt: 0,
},
};
}
function normalizeRuntimeState(runtimeState = {}) {
const merged = deepMerge(buildDefaultRuntimeState(), runtimeState);
return {
session: {
currentStage: normalizeString(merged.session?.currentStage),
registerTabId: normalizeNullableInteger(merged.session?.registerTabId),
desktopTabId: normalizeNullableInteger(merged.session?.desktopTabId),
startedAt: Math.max(0, normalizeInteger(merged.session?.startedAt)),
pageState: normalizeString(merged.session?.pageState),
pageUrl: normalizeString(merged.session?.pageUrl),
lastError: normalizeString(merged.session?.lastError),
lastWarning: normalizeString(merged.session?.lastWarning),
},
register: {
email: normalizeString(merged.register?.email),
fullName: normalizeString(merged.register?.fullName),
verificationRequestedAt: Math.max(0, normalizeInteger(merged.register?.verificationRequestedAt)),
entryClientId: normalizeString(merged.register?.entryClientId),
entryClientSecret: normalizeString(merged.register?.entryClientSecret),
entryDeviceCode: normalizeString(merged.register?.entryDeviceCode),
userCode: normalizeString(merged.register?.userCode),
loginUrl: normalizeString(merged.register?.loginUrl),
verificationUri: normalizeString(merged.register?.verificationUri),
verificationUriComplete: normalizeString(merged.register?.verificationUriComplete),
entryExpiresAt: Math.max(0, normalizeInteger(merged.register?.entryExpiresAt)),
entryIntervalSeconds: Math.max(0, normalizeInteger(merged.register?.entryIntervalSeconds)),
status: normalizeString(merged.register?.status),
completedAt: Math.max(0, normalizeInteger(merged.register?.completedAt)),
},
desktopAuth: {
region: normalizeString(merged.desktopAuth?.region, DEFAULT_REGION),
clientId: normalizeString(merged.desktopAuth?.clientId),
clientSecret: normalizeString(merged.desktopAuth?.clientSecret),
clientIdHash: normalizeString(merged.desktopAuth?.clientIdHash),
state: normalizeString(merged.desktopAuth?.state),
codeVerifier: normalizeString(merged.desktopAuth?.codeVerifier),
codeChallenge: normalizeString(merged.desktopAuth?.codeChallenge),
redirectUri: normalizeString(merged.desktopAuth?.redirectUri),
redirectPort: Math.max(0, normalizeInteger(merged.desktopAuth?.redirectPort)),
authorizeUrl: normalizeString(merged.desktopAuth?.authorizeUrl),
authorizationCode: normalizeString(merged.desktopAuth?.authorizationCode),
accessToken: normalizeString(merged.desktopAuth?.accessToken),
refreshToken: normalizeString(merged.desktopAuth?.refreshToken),
status: normalizeString(merged.desktopAuth?.status),
authorizedAt: Math.max(0, normalizeInteger(merged.desktopAuth?.authorizedAt)),
otpRequestedAt: Math.max(0, normalizeInteger(merged.desktopAuth?.otpRequestedAt)),
tokenSource: normalizeString(
merged.desktopAuth?.tokenSource,
'desktop_authorization_code_pkce'
),
},
upload: {
targetId: normalizeString(merged.upload?.targetId, DEFAULT_TARGET_ID),
status: normalizeString(merged.upload?.status),
error: normalizeString(merged.upload?.error),
credentialId: normalizeNullableInteger(merged.upload?.credentialId),
lastMessage: normalizeString(merged.upload?.lastMessage),
lastUploadedAt: Math.max(0, normalizeInteger(merged.upload?.lastUploadedAt)),
},
};
}
function ensureRuntimeState(state = {}) {
return normalizeRuntimeState(isPlainObject(state?.kiroRuntime) ? state.kiroRuntime : {});
}
function projectRuntimeFields() {
return {};
}
function buildStateView(state = {}) {
return {
...state,
kiroRuntime: ensureRuntimeState(state),
};
}
function buildSessionStatePatch(currentState = {}, updates = {}) {
if (!isPlainObject(updates?.kiroRuntime)) {
return {};
}
const nextRuntimeState = normalizeRuntimeState(
deepMerge(ensureRuntimeState(currentState), updates.kiroRuntime)
);
return {
kiroRuntime: nextRuntimeState,
};
}
function buildRuntimeResetPatch(currentState = {}, patch = {}) {
return {
kiroRuntime: normalizeRuntimeState(
deepMerge(ensureRuntimeState(currentState), patch)
),
};
}
function buildStartRegisterResetPatch(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
const nextRuntimeState = buildDefaultRuntimeState();
nextRuntimeState.upload.targetId = currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID;
return {
kiroRuntime: nextRuntimeState,
};
}
function buildRegisterOnlyResetPatch(currentState = {}, registerPatch = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
const nextRuntimeState = normalizeRuntimeState({
...buildDefaultRuntimeState(),
session: {
...currentRuntimeState.session,
currentStage: 'register',
desktopTabId: null,
pageState: '',
pageUrl: '',
lastError: '',
lastWarning: '',
},
register: {
...currentRuntimeState.register,
completedAt: 0,
status: '',
...registerPatch,
},
upload: {
...buildDefaultRuntimeState().upload,
targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID,
},
});
return {
kiroRuntime: nextRuntimeState,
};
}
function buildDesktopResetPatch(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
return {
kiroRuntime: normalizeRuntimeState({
...currentRuntimeState,
session: {
...currentRuntimeState.session,
currentStage: 'desktop-authorize',
desktopTabId: null,
pageState: '',
pageUrl: '',
lastError: '',
lastWarning: '',
},
desktopAuth: buildDefaultRuntimeState().desktopAuth,
upload: {
...buildDefaultRuntimeState().upload,
targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID,
},
}),
};
}
function buildUploadResetPatch(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
return {
kiroRuntime: normalizeRuntimeState({
...currentRuntimeState,
upload: {
...buildDefaultRuntimeState().upload,
targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID,
},
}),
};
}
function buildDownstreamResetPatch(stepKey = '', currentState = {}) {
switch (normalizeString(stepKey)) {
case 'kiro-open-register-page':
return buildStartRegisterResetPatch(currentState);
case 'kiro-submit-email':
return buildRegisterOnlyResetPatch(currentState, {
email: '',
fullName: '',
verificationRequestedAt: 0,
});
case 'kiro-submit-name':
return buildRegisterOnlyResetPatch(currentState, {
fullName: '',
verificationRequestedAt: 0,
});
case 'kiro-submit-verification-code':
return buildRegisterOnlyResetPatch(currentState, {});
case 'kiro-submit-password':
return buildRegisterOnlyResetPatch(currentState, {});
case 'kiro-complete-register-consent':
return buildDesktopResetPatch(currentState);
case 'kiro-start-desktop-authorize':
return buildDesktopResetPatch(currentState);
case 'kiro-complete-desktop-authorize':
return buildUploadResetPatch(currentState);
case 'kiro-upload-credential':
return buildUploadResetPatch(currentState);
default:
return {};
}
}
function applyNodeCompletionPayload(currentState = {}, payload = {}) {
return buildSessionStatePatch(currentState, payload);
}
function buildFreshKeepState(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
const nextRuntimeState = buildDefaultRuntimeState();
nextRuntimeState.upload.targetId = currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID;
return {
kiroRuntime: nextRuntimeState,
...(Object.prototype.hasOwnProperty.call(currentState, 'kiroTargetId')
? { kiroTargetId: normalizeString(currentState.kiroTargetId, DEFAULT_TARGET_ID).toLowerCase() }
: {}),
};
}
return {
DEFAULT_REGION,
DEFAULT_TARGET_ID,
FLAT_FIELD_DEFINITIONS,
FLAT_FIELD_KEYS,
applyNodeCompletionPayload,
buildDefaultRuntimeState,
buildDownstreamResetPatch,
buildFreshKeepState,
buildSessionStatePatch,
buildStateView,
ensureRuntimeState,
projectRuntimeFields,
};
});
+11 -15
View File
@@ -204,29 +204,25 @@
return normalized;
}
function normalizeMessageSourceId(flowId, sourceId = '', fallback = '') {
function normalizeMessageTargetId(flowId, targetId = '', fallback = '') {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (typeof rootScope.MultiPageFlowRegistry?.normalizeSourceId === 'function') {
return rootScope.MultiPageFlowRegistry.normalizeSourceId(flowId, sourceId, fallback);
if (typeof rootScope.MultiPageFlowRegistry?.normalizeTargetId === 'function') {
return rootScope.MultiPageFlowRegistry.normalizeTargetId(flowId, targetId, fallback);
}
const fallbackSourceId = String(
fallback || (normalizeMessageFlowId(flowId) === 'kiro' ? 'kiro-rs' : 'cpa')
).trim().toLowerCase();
return String(sourceId || fallbackSourceId).trim().toLowerCase() || fallbackSourceId;
return String(targetId || fallbackSourceId).trim().toLowerCase() || fallbackSourceId;
}
function mapAutoRunSourceIdToPanelMode(sourceId = '', fallback = 'cpa') {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (typeof rootScope.MultiPageFlowRegistry?.mapSourceIdToPanelMode === 'function') {
return rootScope.MultiPageFlowRegistry.mapSourceIdToPanelMode('openai', sourceId, fallback);
}
return String(sourceId || fallback || 'cpa').trim().toLowerCase() || 'cpa';
function mapAutoRunTargetIdToPanelMode(targetId = '', fallback = 'cpa') {
return String(targetId || fallback || 'cpa').trim().toLowerCase() || 'cpa';
}
function buildAutoRunFlowStateUpdates(payload = {}) {
const hasActiveFlowId = Object.prototype.hasOwnProperty.call(payload, 'activeFlowId');
const hasSourceId = Object.prototype.hasOwnProperty.call(payload, 'sourceId');
if (!hasActiveFlowId && !hasSourceId) {
const hasTargetId = Object.prototype.hasOwnProperty.call(payload, 'targetId');
if (!hasActiveFlowId && !hasTargetId) {
return {};
}
const activeFlowId = normalizeMessageFlowId(payload.activeFlowId, 'openai');
@@ -234,11 +230,11 @@
activeFlowId,
flowId: activeFlowId,
};
if (hasSourceId) {
if (hasTargetId) {
if (activeFlowId === 'kiro') {
updates.kiroSourceId = normalizeMessageSourceId('kiro', payload.sourceId, 'kiro-rs');
updates.kiroTargetId = normalizeMessageTargetId('kiro', payload.targetId, 'kiro-rs');
} else {
updates.panelMode = mapAutoRunSourceIdToPanelMode(payload.sourceId, 'cpa');
updates.panelMode = mapAutoRunTargetIdToPanelMode(payload.targetId, 'cpa');
}
}
return updates;
-38
View File
@@ -120,39 +120,8 @@
'step8VerificationTargetEmail',
]),
});
const KIRO_FLOW_FIELD_GROUPS = Object.freeze({
auth: Object.freeze([
'kiroDeviceCode',
'kiroUserCode',
'kiroDeviceAuthorizationCode',
'kiroLoginUrl',
'kiroVerificationUri',
'kiroVerificationUriComplete',
'kiroClientId',
'kiroClientSecret',
'kiroAuthRegion',
'kiroAuthExpiresAt',
'kiroAuthIntervalSeconds',
'kiroAuthTabId',
'kiroAuthStatus',
'kiroAuthError',
'kiroAccessToken',
'kiroRefreshToken',
]),
upload: Object.freeze([
'kiroUploadStatus',
'kiroUploadError',
'kiroCredentialId',
'kiroLastUploadAt',
'kiroLastConnectionMessage',
]),
identity: Object.freeze([
'kiroAuthorizedEmail',
]),
});
const FLOW_FIELD_GROUPS = Object.freeze({
openai: OPENAI_FLOW_FIELD_GROUPS,
kiro: KIRO_FLOW_FIELD_GROUPS,
});
function isPlainObject(value) {
@@ -266,7 +235,6 @@
return {
...baseFlowState,
openai: buildScopedFlowState(baseFlowState, state, 'openai'),
kiro: buildScopedFlowState(baseFlowState, state, 'kiro'),
};
}
@@ -411,11 +379,6 @@
luckmail: {},
identity: {},
},
kiro: {
auth: {},
upload: {},
identity: {},
},
},
};
}
@@ -530,7 +493,6 @@
return {
DEFAULT_ACTIVE_FLOW_ID,
FLOW_FIELD_GROUPS,
KIRO_FLOW_FIELD_GROUPS,
OPENAI_FLOW_FIELD_GROUPS,
RUNTIME_PROXY_FIELDS,
RUNTIME_SHARED_FIELDS,