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
+107 -165
View File
@@ -24,6 +24,11 @@ importScripts(
'background/registration-email-state.js', 'background/registration-email-state.js',
'background/workflow-engine.js', 'background/workflow-engine.js',
'background/runtime-state.js', 'background/runtime-state.js',
'background/kiro/state.js',
'background/kiro/register-runner.js',
'background/kiro/desktop-client.js',
'background/kiro/desktop-authorize-runner.js',
'background/kiro/publisher-kiro-rs.js',
'background/generated-email-helpers.js', 'background/generated-email-helpers.js',
'background/signup-flow-helpers.js', 'background/signup-flow-helpers.js',
'background/mail-rule-registry.js', 'background/mail-rule-registry.js',
@@ -53,7 +58,6 @@ importScripts(
'background/steps/fetch-login-code.js', 'background/steps/fetch-login-code.js',
'background/steps/confirm-oauth.js', 'background/steps/confirm-oauth.js',
'background/steps/platform-verify.js', 'background/steps/platform-verify.js',
'background/steps/kiro-device-auth.js',
'data/names.js', 'data/names.js',
'hotmail-utils.js', 'hotmail-utils.js',
'microsoft-email.js', 'microsoft-email.js',
@@ -321,6 +325,7 @@ const runtimeStateHelpers = self.MultiPageBackgroundRuntimeState?.createRuntimeS
DEFAULT_ACTIVE_FLOW_ID, DEFAULT_ACTIVE_FLOW_ID,
defaultNodeStatuses: DEFAULT_NODE_STATUSES, defaultNodeStatuses: DEFAULT_NODE_STATUSES,
}) || null; }) || null;
const kiroStateHelpers = self.MultiPageBackgroundKiroState || null;
const DEFAULT_REGISTRATION_EMAIL_STATE = registrationEmailStateHelpers?.DEFAULT_REGISTRATION_EMAIL_STATE || { const DEFAULT_REGISTRATION_EMAIL_STATE = registrationEmailStateHelpers?.DEFAULT_REGISTRATION_EMAIL_STATE || {
current: '', current: '',
previous: '', previous: '',
@@ -386,17 +391,31 @@ function getPreservedPhoneIdentity(state = {}) {
} }
function buildStateViewWithRuntimeState(state = {}) { function buildStateViewWithRuntimeState(state = {}) {
let nextState = state;
if (runtimeStateHelpers?.buildStateView) { if (runtimeStateHelpers?.buildStateView) {
return runtimeStateHelpers.buildStateView(state); nextState = runtimeStateHelpers.buildStateView(nextState);
} }
return state; if (kiroStateHelpers?.buildStateView) {
nextState = kiroStateHelpers.buildStateView(nextState);
}
return nextState;
} }
function buildStatePatchWithRuntimeState(currentState = {}, updates = {}) { function buildStatePatchWithRuntimeState(currentState = {}, updates = {}) {
let nextPatch = updates;
if (runtimeStateHelpers?.buildSessionStatePatch) { if (runtimeStateHelpers?.buildSessionStatePatch) {
return runtimeStateHelpers.buildSessionStatePatch(currentState, updates); nextPatch = runtimeStateHelpers.buildSessionStatePatch(currentState, nextPatch);
} }
return updates; if (kiroStateHelpers?.buildSessionStatePatch) {
const kiroPatch = kiroStateHelpers.buildSessionStatePatch(currentState, updates);
if (kiroPatch && Object.keys(kiroPatch).length > 0) {
nextPatch = {
...nextPatch,
...kiroPatch,
};
}
}
return nextPatch;
} }
function statePatchHasChanges(state = {}, patch = {}) { function statePatchHasChanges(state = {}, patch = {}) {
@@ -892,7 +911,7 @@ function setupDeclarativeNetRequestRules() {
const PERSISTED_SETTING_DEFAULTS = { const PERSISTED_SETTING_DEFAULTS = {
panelMode: 'cpa', panelMode: 'cpa',
activeFlowId: DEFAULT_ACTIVE_FLOW_ID, activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
kiroSourceId: 'kiro-rs', kiroTargetId: 'kiro-rs',
kiroRsUrl: self.MultiPageFlowRegistry?.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin', kiroRsUrl: self.MultiPageFlowRegistry?.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin',
kiroRsKey: '', kiroRsKey: '',
vpsUrl: '', vpsUrl: '',
@@ -1080,9 +1099,8 @@ const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState'
const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([ const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
'activeFlowId', 'activeFlowId',
'openaiIntegrationTargetId', 'openaiIntegrationTargetId',
'kiroIntegrationTargetId',
'panelMode', 'panelMode',
'kiroSourceId', 'kiroTargetId',
'vpsUrl', 'vpsUrl',
'vpsPassword', 'vpsPassword',
'localCpaStep9Mode', 'localCpaStep9Mode',
@@ -1122,6 +1140,7 @@ const DEFAULT_STATE = {
currentNodeId: '', currentNodeId: '',
nodeStatuses: { ...DEFAULT_NODE_STATUSES }, nodeStatuses: { ...DEFAULT_NODE_STATUSES },
runtimeState: runtimeStateHelpers?.buildDefaultRuntimeState?.() || null, runtimeState: runtimeStateHelpers?.buildDefaultRuntimeState?.() || null,
kiroRuntime: kiroStateHelpers?.buildDefaultRuntimeState?.() || null,
...CONTRIBUTION_RUNTIME_DEFAULTS, ...CONTRIBUTION_RUNTIME_DEFAULTS,
accounts: [], // 已生成账号记录:{ email, password, createdAt }。 accounts: [], // 已生成账号记录:{ email, password, createdAt }。
accountRunHistory: [], // 账号运行历史快照,实际持久化在 chrome.storage.local。 accountRunHistory: [], // 账号运行历史快照,实际持久化在 chrome.storage.local。
@@ -2743,9 +2762,9 @@ function normalizePersistentSettingValue(key, value) {
return self.MultiPageFlowRegistry.normalizeFlowId(value, DEFAULT_ACTIVE_FLOW_ID); return self.MultiPageFlowRegistry.normalizeFlowId(value, DEFAULT_ACTIVE_FLOW_ID);
} }
return String(value || '').trim().toLowerCase() === 'kiro' ? 'kiro' : DEFAULT_ACTIVE_FLOW_ID; return String(value || '').trim().toLowerCase() === 'kiro' ? 'kiro' : DEFAULT_ACTIVE_FLOW_ID;
case 'kiroSourceId': case 'kiroTargetId':
if (typeof self.MultiPageFlowRegistry?.normalizeSourceId === 'function') { if (typeof self.MultiPageFlowRegistry?.normalizeTargetId === 'function') {
return self.MultiPageFlowRegistry.normalizeSourceId('kiro', value, 'kiro-rs'); return self.MultiPageFlowRegistry.normalizeTargetId('kiro', value, 'kiro-rs');
} }
return String(value || '').trim().toLowerCase() === 'kiro-rs' ? 'kiro-rs' : 'kiro-rs'; return String(value || '').trim().toLowerCase() === 'kiro-rs' ? 'kiro-rs' : 'kiro-rs';
case 'kiroRsUrl': case 'kiroRsUrl':
@@ -3419,6 +3438,7 @@ function collectAutoRunFreshResetRuntimeSettingKeys() {
const sharedRuntimeFieldGroups = [ const sharedRuntimeFieldGroups = [
runtimeStateHelpers?.RUNTIME_SHARED_FIELDS, runtimeStateHelpers?.RUNTIME_SHARED_FIELDS,
runtimeStateHelpers?.RUNTIME_PROXY_FIELDS, runtimeStateHelpers?.RUNTIME_PROXY_FIELDS,
kiroStateHelpers?.FLAT_FIELD_KEYS,
]; ];
for (const fields of sharedRuntimeFieldGroups) { for (const fields of sharedRuntimeFieldGroups) {
if (!Array.isArray(fields)) { if (!Array.isArray(fields)) {
@@ -3465,7 +3485,7 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA
: undefined, : undefined,
}, },
kiro: { kiro: {
integrationTargetId: prevState?.kiroIntegrationTargetId || prevState?.kiroSourceId, targetId: prevState?.kiroTargetId,
autoRun: normalizedStepExecutionRangeByFlow.kiro autoRun: normalizedStepExecutionRangeByFlow.kiro
? { ? {
stepExecutionRange: normalizedStepExecutionRangeByFlow.kiro, stepExecutionRange: normalizedStepExecutionRangeByFlow.kiro,
@@ -3510,10 +3530,12 @@ function buildFreshAutoRunKeepState(prevState = {}) {
if (Object.prototype.hasOwnProperty.call(sourceState, 'panelMode')) { if (Object.prototype.hasOwnProperty.call(sourceState, 'panelMode')) {
keepState.panelMode = normalizePanelMode(sourceState.panelMode); keepState.panelMode = normalizePanelMode(sourceState.panelMode);
} }
if (Object.prototype.hasOwnProperty.call(sourceState, 'kiroSourceId')) { if (typeof kiroStateHelpers?.buildFreshKeepState === 'function') {
keepState.kiroSourceId = self.MultiPageFlowRegistry?.normalizeSourceId Object.assign(keepState, kiroStateHelpers.buildFreshKeepState(sourceState));
? self.MultiPageFlowRegistry.normalizeSourceId('kiro', sourceState.kiroSourceId, 'kiro-rs') } else if (Object.prototype.hasOwnProperty.call(sourceState, 'kiroTargetId')) {
: String(sourceState.kiroSourceId || 'kiro-rs').trim().toLowerCase(); keepState.kiroTargetId = self.MultiPageFlowRegistry?.normalizeTargetId
? self.MultiPageFlowRegistry.normalizeTargetId('kiro', sourceState.kiroTargetId, 'kiro-rs')
: String(sourceState.kiroTargetId || 'kiro-rs').trim().toLowerCase();
} }
if (Object.prototype.hasOwnProperty.call(sourceState, 'settingsSchemaVersion')) { if (Object.prototype.hasOwnProperty.call(sourceState, 'settingsSchemaVersion')) {
keepState.settingsSchemaVersion = Number(sourceState.settingsSchemaVersion) || 0; keepState.settingsSchemaVersion = Number(sourceState.settingsSchemaVersion) || 0;
@@ -8995,113 +9017,16 @@ function hasSavedProgress(statuses = {}, stateOverride = null) {
function getDownstreamStateResets(step, state = {}) { function getDownstreamStateResets(step, state = {}) {
const stepKey = getStepExecutionKeyForState(step, state); const stepKey = getStepExecutionKeyForState(step, state);
if (stepKey === 'kiro-start-device-login') { if (String(stepKey || '').trim().toLowerCase().startsWith('kiro-')) {
const kiroResets = typeof kiroStateHelpers?.buildDownstreamResetPatch === 'function'
? kiroStateHelpers.buildDownstreamResetPatch(stepKey, state)
: {};
if (Object.keys(kiroResets).length > 0) {
return { return {
flowStartTime: null, ...(stepKey === 'kiro-open-register-page' ? { flowStartTime: null } : {}),
kiroAccessToken: '', ...kiroResets,
kiroAuthError: '',
kiroAuthExpiresAt: 0,
kiroAuthIntervalSeconds: 0,
kiroAuthRegion: '',
kiroAuthStatus: '',
kiroAuthTabId: null,
kiroAuthorizedEmail: '',
kiroClientId: '',
kiroClientSecret: '',
kiroCredentialId: null,
kiroDeviceAuthorizationCode: '',
kiroDeviceCode: '',
kiroFullName: '',
kiroLastConnectionMessage: '',
kiroLastUploadAt: 0,
kiroLoginUrl: '',
kiroRefreshToken: '',
kiroUploadError: '',
kiroUploadStatus: '',
kiroUserCode: '',
kiroVerificationRequestedAt: 0,
kiroVerificationUri: '',
kiroVerificationUriComplete: '',
}; };
} }
if (stepKey === 'kiro-submit-email') {
return {
kiroAccessToken: '',
kiroAuthError: '',
kiroAuthStatus: 'waiting_user',
kiroAuthorizedEmail: '',
kiroCredentialId: null,
kiroFullName: '',
kiroLastConnectionMessage: '',
kiroLastUploadAt: 0,
kiroRefreshToken: '',
kiroUploadError: '',
kiroUploadStatus: 'waiting_login',
kiroVerificationRequestedAt: 0,
};
}
if (stepKey === 'kiro-submit-name') {
return {
kiroAccessToken: '',
kiroAuthError: '',
kiroAuthStatus: 'waiting_user',
kiroCredentialId: null,
kiroFullName: '',
kiroLastConnectionMessage: '',
kiroLastUploadAt: 0,
kiroRefreshToken: '',
kiroUploadError: '',
kiroUploadStatus: 'waiting_login',
kiroVerificationRequestedAt: 0,
};
}
if (stepKey === 'kiro-submit-verification-code') {
return {
kiroAccessToken: '',
kiroAuthError: '',
kiroAuthStatus: 'waiting_user',
kiroCredentialId: null,
kiroLastConnectionMessage: '',
kiroLastUploadAt: 0,
kiroRefreshToken: '',
kiroUploadError: '',
kiroUploadStatus: 'waiting_login',
};
}
if (stepKey === 'kiro-fill-password') {
return {
kiroAccessToken: '',
kiroAuthError: '',
kiroAuthStatus: 'waiting_user',
kiroCredentialId: null,
kiroLastConnectionMessage: '',
kiroLastUploadAt: 0,
kiroRefreshToken: '',
kiroUploadError: '',
kiroUploadStatus: 'waiting_login',
};
}
if (stepKey === 'kiro-confirm-access') {
return {
kiroAccessToken: '',
kiroAuthError: '',
kiroAuthStatus: 'waiting_user',
kiroCredentialId: null,
kiroLastConnectionMessage: '',
kiroLastUploadAt: 0,
kiroRefreshToken: '',
kiroUploadError: '',
kiroUploadStatus: 'waiting_login',
};
}
if (stepKey === 'kiro-upload-credential') {
return {
kiroCredentialId: null,
kiroLastConnectionMessage: '',
kiroLastUploadAt: 0,
kiroUploadError: '',
kiroUploadStatus: 'ready_to_upload',
};
} }
const plusRuntimeResets = { const plusRuntimeResets = {
plusCheckoutTabId: null, plusCheckoutTabId: null,
@@ -10270,38 +10195,9 @@ async function handleNodeData(nodeId, payload) {
const state = await getState(); const state = await getState();
const nodeDefinition = getNodeDefinitionForState(nodeId, state); const nodeDefinition = getNodeDefinitionForState(nodeId, state);
if (String(nodeDefinition?.flowId || '').trim().toLowerCase() === 'kiro') { if (String(nodeDefinition?.flowId || '').trim().toLowerCase() === 'kiro') {
const kiroFieldKeys = [ const updates = typeof kiroStateHelpers?.applyNodeCompletionPayload === 'function'
'kiroAccessToken', ? kiroStateHelpers.applyNodeCompletionPayload(state, payload || {})
'kiroAuthError', : {};
'kiroAuthExpiresAt',
'kiroAuthIntervalSeconds',
'kiroAuthRegion',
'kiroAuthStatus',
'kiroAuthTabId',
'kiroAuthorizedEmail',
'kiroClientId',
'kiroClientSecret',
'kiroCredentialId',
'kiroDeviceAuthorizationCode',
'kiroDeviceCode',
'kiroFullName',
'kiroLastConnectionMessage',
'kiroLastUploadAt',
'kiroLoginUrl',
'kiroRefreshToken',
'kiroUploadError',
'kiroUploadStatus',
'kiroUserCode',
'kiroVerificationRequestedAt',
'kiroVerificationUri',
'kiroVerificationUriComplete',
];
const updates = {};
for (const field of kiroFieldKeys) {
if (Object.prototype.hasOwnProperty.call(payload || {}, field)) {
updates[field] = payload[field];
}
}
if (Object.keys(updates).length > 0) { if (Object.keys(updates).length > 0) {
await setState(updates); await setState(updates);
broadcastDataUpdate(updates); broadcastDataUpdate(updates);
@@ -10349,12 +10245,14 @@ const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([
'fetch-bound-email-login-code', 'fetch-bound-email-login-code',
'post-bound-email-phone-verification', 'post-bound-email-phone-verification',
'confirm-oauth', 'confirm-oauth',
'kiro-start-device-login', 'kiro-open-register-page',
'kiro-submit-email', 'kiro-submit-email',
'kiro-submit-name', 'kiro-submit-name',
'kiro-submit-verification-code', 'kiro-submit-verification-code',
'kiro-fill-password', 'kiro-submit-password',
'kiro-confirm-access', 'kiro-complete-register-consent',
'kiro-start-desktop-authorize',
'kiro-complete-desktop-authorize',
'kiro-upload-credential', 'kiro-upload-credential',
]); ]);
const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([ const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([
@@ -12846,7 +12744,8 @@ async function resumeAutoRun() {
const SIGNUP_ENTRY_URL = 'https://chatgpt.com/'; const SIGNUP_ENTRY_URL = 'https://chatgpt.com/';
const SIGNUP_PAGE_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/auth-page-recovery.js', 'content/phone-country-utils.js', 'content/phone-auth.js', 'content/signup-page.js']; const SIGNUP_PAGE_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/auth-page-recovery.js', 'content/phone-country-utils.js', 'content/phone-auth.js', 'content/signup-page.js'];
const KIRO_DEVICE_AUTH_INJECT_FILES = ['shared/source-registry.js', 'content/utils.js', 'content/kiro-device-auth-page.js']; const KIRO_REGISTER_INJECT_FILES = ['shared/source-registry.js', 'content/utils.js', 'content/kiro/register-page.js'];
const KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = ['shared/source-registry.js', 'content/utils.js', 'content/kiro/desktop-authorize-page.js'];
const panelBridge = self.MultiPageBackgroundPanelBridge?.createPanelBridge({ const panelBridge = self.MultiPageBackgroundPanelBridge?.createPanelBridge({
chrome, chrome,
addLog, addLog,
@@ -13238,7 +13137,7 @@ const plusReturnConfirmExecutor = self.MultiPageBackgroundPlusReturnConfirm?.cre
sleepWithStop, sleepWithStop,
waitForTabUrlMatchUntilStopped, waitForTabUrlMatchUntilStopped,
}); });
const kiroDeviceAuthExecutor = self.MultiPageBackgroundKiroDeviceAuth?.createKiroDeviceAuthExecutor({ const kiroRegisterRunner = self.MultiPageBackgroundKiroRegisterRunner?.createKiroRegisterRunner({
addLog, addLog,
chrome, chrome,
ensureContentScriptReadyOnTab, ensureContentScriptReadyOnTab,
@@ -13275,7 +13174,48 @@ const kiroDeviceAuthExecutor = self.MultiPageBackgroundKiroDeviceAuth?.createKir
sleepWithStop, sleepWithStop,
throwIfStopped, throwIfStopped,
waitForTabStableComplete, waitForTabStableComplete,
KIRO_DEVICE_AUTH_INJECT_FILES, KIRO_REGISTER_INJECT_FILES,
});
const kiroDesktopAuthorizeRunner = self.MultiPageBackgroundKiroDesktopAuthorizeRunner?.createKiroDesktopAuthorizeRunner({
addLog,
chrome,
completeNodeFromBackground,
ensureContentScriptReadyOnTab,
ensureIcloudMailSession: ensureIcloudMailSessionForVerification,
ensureMail2925MailboxSession,
fetchImpl: typeof fetch === 'function' ? fetch.bind(globalThis) : null,
getMailConfig,
getTabId,
getState,
HOTMAIL_PROVIDER,
isTabAlive,
LUCKMAIL_PROVIDER,
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
CLOUD_MAIL_PROVIDER,
YYDS_MAIL_PROVIDER,
MAIL_2925_VERIFICATION_INTERVAL_MS,
MAIL_2925_VERIFICATION_MAX_ATTEMPTS,
pollCloudflareTempEmailVerificationCode,
pollCloudMailVerificationCode,
pollHotmailVerificationCode,
pollLuckmailVerificationCode,
pollYydsMailVerificationCode,
registerTab,
reuseOrCreateTab,
sendToContentScriptResilient,
sendToMailContentScriptResilient,
setState,
sleepWithStop,
throwIfStopped,
waitForTabStableComplete,
KIRO_DESKTOP_AUTHORIZE_INJECT_FILES,
});
const kiroPublisher = self.MultiPageBackgroundKiroPublisherKiroRs?.createKiroRsPublisher({
addLog,
completeNodeFromBackground,
fetchImpl: typeof fetch === 'function' ? fetch.bind(globalThis) : null,
getState,
setState,
}); });
const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({ const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({
addLog, addLog,
@@ -13354,13 +13294,15 @@ const stepExecutorsByKey = {
'post-bound-email-phone-verification': (state) => step8Executor.executeBoundEmailPostLoginPhoneVerification(state), 'post-bound-email-phone-verification': (state) => step8Executor.executeBoundEmailPostLoginPhoneVerification(state),
'confirm-oauth': (state) => step9Executor.executeStep9(state), 'confirm-oauth': (state) => step9Executor.executeStep9(state),
'platform-verify': (state) => executeStep10(state), 'platform-verify': (state) => executeStep10(state),
'kiro-start-device-login': (state) => kiroDeviceAuthExecutor.executeKiroStartDeviceLogin(state), 'kiro-open-register-page': (state) => kiroRegisterRunner.executeKiroOpenRegisterPage(state),
'kiro-submit-email': (state) => kiroDeviceAuthExecutor.executeKiroSubmitEmail(state), 'kiro-submit-email': (state) => kiroRegisterRunner.executeKiroSubmitEmail(state),
'kiro-submit-name': (state) => kiroDeviceAuthExecutor.executeKiroSubmitName(state), 'kiro-submit-name': (state) => kiroRegisterRunner.executeKiroSubmitName(state),
'kiro-submit-verification-code': (state) => kiroDeviceAuthExecutor.executeKiroSubmitVerificationCode(state), 'kiro-submit-verification-code': (state) => kiroRegisterRunner.executeKiroSubmitVerificationCode(state),
'kiro-fill-password': (state) => kiroDeviceAuthExecutor.executeKiroFillPassword(state), 'kiro-submit-password': (state) => kiroRegisterRunner.executeKiroSubmitPassword(state),
'kiro-confirm-access': (state) => kiroDeviceAuthExecutor.executeKiroConfirmAccess(state), 'kiro-complete-register-consent': (state) => kiroRegisterRunner.executeKiroCompleteRegisterConsent(state),
'kiro-upload-credential': (state) => kiroDeviceAuthExecutor.executeKiroUploadCredential(state), 'kiro-start-desktop-authorize': (state) => kiroDesktopAuthorizeRunner.executeKiroStartDesktopAuthorize(state),
'kiro-complete-desktop-authorize': (state) => kiroDesktopAuthorizeRunner.executeKiroCompleteDesktopAuthorize(state),
'kiro-upload-credential': (state) => kiroPublisher.executeKiroUploadCredential(state),
}; };
const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter({ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter({
addLog, addLog,
+1 -1
View File
@@ -106,7 +106,7 @@
activeFlowId: state.activeFlowId, activeFlowId: state.activeFlowId,
flowId: state.flowId || state.activeFlowId, flowId: state.flowId || state.activeFlowId,
panelMode: state.panelMode, panelMode: state.panelMode,
kiroSourceId: state.kiroSourceId, kiroTargetId: state.kiroTargetId,
vpsUrl: state.vpsUrl, vpsUrl: state.vpsUrl,
vpsPassword: state.vpsPassword, vpsPassword: state.vpsPassword,
customPassword: state.customPassword, 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; return normalized;
} }
function normalizeMessageSourceId(flowId, sourceId = '', fallback = '') { function normalizeMessageTargetId(flowId, targetId = '', fallback = '') {
const rootScope = typeof self !== 'undefined' ? self : globalThis; const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (typeof rootScope.MultiPageFlowRegistry?.normalizeSourceId === 'function') { if (typeof rootScope.MultiPageFlowRegistry?.normalizeTargetId === 'function') {
return rootScope.MultiPageFlowRegistry.normalizeSourceId(flowId, sourceId, fallback); return rootScope.MultiPageFlowRegistry.normalizeTargetId(flowId, targetId, fallback);
} }
const fallbackSourceId = String( const fallbackSourceId = String(
fallback || (normalizeMessageFlowId(flowId) === 'kiro' ? 'kiro-rs' : 'cpa') fallback || (normalizeMessageFlowId(flowId) === 'kiro' ? 'kiro-rs' : 'cpa')
).trim().toLowerCase(); ).trim().toLowerCase();
return String(sourceId || fallbackSourceId).trim().toLowerCase() || fallbackSourceId; return String(targetId || fallbackSourceId).trim().toLowerCase() || fallbackSourceId;
} }
function mapAutoRunSourceIdToPanelMode(sourceId = '', fallback = 'cpa') { function mapAutoRunTargetIdToPanelMode(targetId = '', fallback = 'cpa') {
const rootScope = typeof self !== 'undefined' ? self : globalThis; return String(targetId || fallback || 'cpa').trim().toLowerCase() || 'cpa';
if (typeof rootScope.MultiPageFlowRegistry?.mapSourceIdToPanelMode === 'function') {
return rootScope.MultiPageFlowRegistry.mapSourceIdToPanelMode('openai', sourceId, fallback);
}
return String(sourceId || fallback || 'cpa').trim().toLowerCase() || 'cpa';
} }
function buildAutoRunFlowStateUpdates(payload = {}) { function buildAutoRunFlowStateUpdates(payload = {}) {
const hasActiveFlowId = Object.prototype.hasOwnProperty.call(payload, 'activeFlowId'); const hasActiveFlowId = Object.prototype.hasOwnProperty.call(payload, 'activeFlowId');
const hasSourceId = Object.prototype.hasOwnProperty.call(payload, 'sourceId'); const hasTargetId = Object.prototype.hasOwnProperty.call(payload, 'targetId');
if (!hasActiveFlowId && !hasSourceId) { if (!hasActiveFlowId && !hasTargetId) {
return {}; return {};
} }
const activeFlowId = normalizeMessageFlowId(payload.activeFlowId, 'openai'); const activeFlowId = normalizeMessageFlowId(payload.activeFlowId, 'openai');
@@ -234,11 +230,11 @@
activeFlowId, activeFlowId,
flowId: activeFlowId, flowId: activeFlowId,
}; };
if (hasSourceId) { if (hasTargetId) {
if (activeFlowId === 'kiro') { if (activeFlowId === 'kiro') {
updates.kiroSourceId = normalizeMessageSourceId('kiro', payload.sourceId, 'kiro-rs'); updates.kiroTargetId = normalizeMessageTargetId('kiro', payload.targetId, 'kiro-rs');
} else { } else {
updates.panelMode = mapAutoRunSourceIdToPanelMode(payload.sourceId, 'cpa'); updates.panelMode = mapAutoRunTargetIdToPanelMode(payload.targetId, 'cpa');
} }
} }
return updates; return updates;
-38
View File
@@ -120,39 +120,8 @@
'step8VerificationTargetEmail', '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({ const FLOW_FIELD_GROUPS = Object.freeze({
openai: OPENAI_FLOW_FIELD_GROUPS, openai: OPENAI_FLOW_FIELD_GROUPS,
kiro: KIRO_FLOW_FIELD_GROUPS,
}); });
function isPlainObject(value) { function isPlainObject(value) {
@@ -266,7 +235,6 @@
return { return {
...baseFlowState, ...baseFlowState,
openai: buildScopedFlowState(baseFlowState, state, 'openai'), openai: buildScopedFlowState(baseFlowState, state, 'openai'),
kiro: buildScopedFlowState(baseFlowState, state, 'kiro'),
}; };
} }
@@ -411,11 +379,6 @@
luckmail: {}, luckmail: {},
identity: {}, identity: {},
}, },
kiro: {
auth: {},
upload: {},
identity: {},
},
}, },
}; };
} }
@@ -530,7 +493,6 @@
return { return {
DEFAULT_ACTIVE_FLOW_ID, DEFAULT_ACTIVE_FLOW_ID,
FLOW_FIELD_GROUPS, FLOW_FIELD_GROUPS,
KIRO_FLOW_FIELD_GROUPS,
OPENAI_FLOW_FIELD_GROUPS, OPENAI_FLOW_FIELD_GROUPS,
RUNTIME_PROXY_FIELDS, RUNTIME_PROXY_FIELDS,
RUNTIME_SHARED_FIELDS, RUNTIME_SHARED_FIELDS,
+335
View File
@@ -0,0 +1,335 @@
console.log('[MultiPage:kiro-desktop-authorize-page] Content script loaded on', location.href);
const KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL = 'data-multipage-kiro-desktop-authorize-page-listener';
const KIRO_DESKTOP_ALLOW_TEXT_PATTERN = /allow access|authorize|continue|allow|允许访问|授权|继续/i;
const KIRO_DESKTOP_LOADING_TEXT_PATTERN = /loading|redirecting|please wait|加载中|跳转中|请稍候/i;
const KIRO_DESKTOP_CLOUDFRONT_403_TEXT_PATTERN = /403 error|the request could not be satisfied|generated by cloudfront/i;
const KIRO_DESKTOP_EMAIL_INPUT_SELECTOR = [
'input[placeholder="username@example.com"]',
'input[type="email"]',
'input[name="email"]',
'input[autocomplete="username"]',
].join(', ');
const KIRO_DESKTOP_PASSWORD_INPUT_SELECTOR = [
'input[type="password"]',
'input[name="password"]',
'input[id*="password" i]',
'input[autocomplete="current-password"]',
].join(', ');
const KIRO_DESKTOP_OTP_INPUT_SELECTOR = [
'input[autocomplete="one-time-code"]',
'input[inputmode="numeric"]',
'input[placeholder*="6-digit" i]',
'input[placeholder*="6 位" i]',
'input[name*="otp" i]',
'input[name*="code" i]',
].join(', ');
function isVisibleDesktopElement(element) {
if (!element) return false;
const style = window.getComputedStyle(element);
const rect = element.getBoundingClientRect();
return style.display !== 'none'
&& style.visibility !== 'hidden'
&& rect.width > 0
&& rect.height > 0;
}
function collectVisibleDesktopElements(selector) {
return Array.from(document.querySelectorAll(selector))
.filter((element) => isVisibleDesktopElement(element));
}
function findFirstVisibleDesktopElement(selector) {
return collectVisibleDesktopElements(selector)[0] || null;
}
function getDesktopPageText() {
return String(document.body?.textContent || '')
.replace(/\s+/g, ' ')
.trim();
}
function getDesktopActionText(element) {
return [
element?.textContent,
element?.value,
element?.getAttribute?.('aria-label'),
element?.getAttribute?.('title'),
element?.getAttribute?.('data-testid'),
]
.filter(Boolean)
.join(' ')
.replace(/\s+/g, ' ')
.trim();
}
function findDesktopActionButton(options = {}) {
const {
preferredSelectors = [],
textPattern = null,
formOwner = null,
} = options;
for (const selector of preferredSelectors) {
const preferred = findFirstVisibleDesktopElement(selector);
if (preferred && !preferred.disabled && preferred.getAttribute('aria-disabled') !== 'true') {
return preferred;
}
}
const candidates = collectVisibleDesktopElements('button, [role="button"], input[type="submit"], input[type="button"]')
.filter((element) => !element.disabled && element.getAttribute('aria-disabled') !== 'true');
const prioritized = formOwner
? candidates.filter((element) => (element.form || element.closest?.('form') || null) === formOwner)
: [];
const pool = prioritized.length ? prioritized : candidates;
if (textPattern instanceof RegExp) {
return pool.find((element) => textPattern.test(getDesktopActionText(element))) || null;
}
return pool[0] || null;
}
function detectDesktopFatalState(pageText = '', currentUrl = '', pageTitle = '') {
const combinedText = `${pageTitle} ${pageText}`.replace(/\s+/g, ' ').trim();
if (KIRO_DESKTOP_CLOUDFRONT_403_TEXT_PATTERN.test(combinedText)) {
return {
state: 'cloudfront_403_page',
url: currentUrl,
fatalMessage: 'Kiro 桌面授权页返回 403CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。',
};
}
return null;
}
function detectKiroDesktopAuthorizeState() {
const currentUrl = location.href;
const pageText = getDesktopPageText();
const fatalState = detectDesktopFatalState(pageText, currentUrl, document.title || '');
if (fatalState) {
return fatalState;
}
if (/^https?:\/\/(127\.0\.0\.1|localhost):/i.test(currentUrl)) {
const parsed = new URL(currentUrl);
if (parsed.searchParams.get('error')) {
return {
state: 'callback_error',
url: currentUrl,
error: parsed.searchParams.get('error_description') || parsed.searchParams.get('error') || 'unknown_error',
};
}
return {
state: 'callback_page',
url: currentUrl,
code: parsed.searchParams.get('code') || '',
stateValue: parsed.searchParams.get('state') || '',
};
}
const otpInput = findFirstVisibleDesktopElement(KIRO_DESKTOP_OTP_INPUT_SELECTOR);
if (otpInput) {
return {
state: 'otp_page',
url: currentUrl,
otpInput,
continueButton: findDesktopActionButton({
preferredSelectors: ['button[data-testid="email-verification-verify-button"]'],
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
formOwner: otpInput.form || otpInput.closest?.('form') || null,
}),
};
}
const emailInput = findFirstVisibleDesktopElement(KIRO_DESKTOP_EMAIL_INPUT_SELECTOR);
if (emailInput) {
return {
state: 'relogin_email',
url: currentUrl,
emailInput,
continueButton: findDesktopActionButton({
preferredSelectors: ['button[data-testid="test-primary-button"]'],
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
formOwner: emailInput.form || emailInput.closest?.('form') || null,
}),
};
}
const passwordInput = findFirstVisibleDesktopElement(KIRO_DESKTOP_PASSWORD_INPUT_SELECTOR);
if (passwordInput) {
return {
state: 'relogin_password',
url: currentUrl,
passwordInput,
continueButton: findDesktopActionButton({
preferredSelectors: ['button[data-testid="test-primary-button"]'],
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
formOwner: passwordInput.form || passwordInput.closest?.('form') || null,
}),
};
}
const consentButton = findDesktopActionButton({
preferredSelectors: [
'button[data-testid="confirm-button"]',
'button[data-testid="allow-access-button"]',
],
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
});
if (consentButton) {
return {
state: 'consent_page',
url: currentUrl,
actionButton: consentButton,
actionText: getDesktopActionText(consentButton),
};
}
if (KIRO_DESKTOP_LOADING_TEXT_PATTERN.test(pageText)) {
return {
state: 'redirecting',
url: currentUrl,
};
}
return {
state: 'loading',
url: currentUrl,
};
}
async function getCurrentDesktopAuthorizeState() {
const detected = detectKiroDesktopAuthorizeState();
if (detected.state === 'cloudfront_403_page') {
throw new Error(detected.fatalMessage || 'Kiro 桌面授权页出现 403。');
}
if (detected.state === 'callback_error') {
throw new Error(`Kiro 桌面授权回调失败:${detected.error || 'unknown_error'}`);
}
return detected;
}
async function submitDesktopEmail(payload = {}) {
const email = String(payload?.email || '').trim();
if (!email) {
throw new Error('缺少桌面授权邮箱,无法继续。');
}
const currentState = await getCurrentDesktopAuthorizeState();
if (currentState.state !== 'relogin_email' || !currentState.emailInput || !currentState.continueButton) {
throw new Error(`当前桌面授权页不是邮箱重登状态:${currentState.state}`);
}
fillInput(currentState.emailInput, email);
await sleep(150);
simulateClick(currentState.continueButton);
return {
submitted: true,
state: 'email_submitted',
url: location.href,
};
}
async function submitDesktopPassword(payload = {}) {
const password = String(payload?.password || '');
if (!password) {
throw new Error('缺少桌面授权密码,无法继续。');
}
const currentState = await getCurrentDesktopAuthorizeState();
if (currentState.state !== 'relogin_password' || !currentState.passwordInput || !currentState.continueButton) {
throw new Error(`当前桌面授权页不是密码重登状态:${currentState.state}`);
}
fillInput(currentState.passwordInput, password);
await sleep(150);
simulateClick(currentState.continueButton);
return {
submitted: true,
state: 'password_submitted',
url: location.href,
};
}
async function submitDesktopOtp(payload = {}) {
const code = String(payload?.code || '').trim();
if (!code) {
throw new Error('缺少桌面授权验证码,无法继续。');
}
const currentState = await getCurrentDesktopAuthorizeState();
if (currentState.state !== 'otp_page' || !currentState.otpInput || !currentState.continueButton) {
throw new Error(`当前桌面授权页不是验证码状态:${currentState.state}`);
}
fillInput(currentState.otpInput, code);
await sleep(150);
simulateClick(currentState.continueButton);
return {
submitted: true,
state: 'otp_submitted',
url: location.href,
};
}
async function confirmDesktopConsent() {
const currentState = await getCurrentDesktopAuthorizeState();
if (currentState.state !== 'consent_page' || !currentState.actionButton) {
throw new Error(`当前桌面授权页不是授权确认状态:${currentState.state}`);
}
simulateClick(currentState.actionButton);
return {
submitted: true,
state: 'consent_submitted',
url: location.href,
actionText: currentState.actionText || '',
};
}
async function handleKiroDesktopAuthorizeCommand(message) {
switch (message.type) {
case 'GET_KIRO_DESKTOP_AUTHORIZE_STATE':
return getCurrentDesktopAuthorizeState();
case 'EXECUTE_KIRO_DESKTOP_AUTHORIZE_ACTION': {
const action = String(message.payload?.action || '').trim();
if (action === 'submit-email') {
return submitDesktopEmail(message.payload || {});
}
if (action === 'submit-password') {
return submitDesktopPassword(message.payload || {});
}
if (action === 'submit-otp') {
return submitDesktopOtp(message.payload || {});
}
if (action === 'confirm-consent') {
return confirmDesktopConsent();
}
throw new Error(`desktop-authorize-page.js 不处理动作:${action}`);
}
default:
return null;
}
}
if (document.documentElement.getAttribute(KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL) !== '1') {
document.documentElement.setAttribute(KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL, '1');
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (
message.type === 'GET_KIRO_DESKTOP_AUTHORIZE_STATE'
|| message.type === 'EXECUTE_KIRO_DESKTOP_AUTHORIZE_ACTION'
) {
resetStopState();
handleKiroDesktopAuthorizeCommand(message)
.then((result) => {
sendResponse({ ok: true, ...(result || {}) });
})
.catch((error) => {
if (isStopError(error)) {
sendResponse({ stopped: true, error: error.message });
return;
}
sendResponse({ error: error?.message || String(error || '未知错误') });
});
return true;
}
return false;
});
}
@@ -1,6 +1,6 @@
console.log('[MultiPage:kiro-device-auth] Content script loaded on', location.href); console.log('[MultiPage:kiro-register-page] Content script loaded on', location.href);
const KIRO_DEVICE_AUTH_LISTENER_SENTINEL = 'data-multipage-kiro-device-auth-listener'; const KIRO_REGISTER_PAGE_LISTENER_SENTINEL = 'data-multipage-kiro-register-page-listener';
const KIRO_CONTINUE_TEXT_PATTERN = /continue|继续/i; const KIRO_CONTINUE_TEXT_PATTERN = /continue|继续/i;
const KIRO_CONFIRM_CONTINUE_TEXT_PATTERN = /confirm and continue|确认并继续/i; const KIRO_CONFIRM_CONTINUE_TEXT_PATTERN = /confirm and continue|确认并继续/i;
const KIRO_ALLOW_ACCESS_TEXT_PATTERN = /allow access|允许访问/i; const KIRO_ALLOW_ACCESS_TEXT_PATTERN = /allow access|允许访问/i;
@@ -48,10 +48,10 @@ const KIRO_CONFIRM_PASSWORD_SELECTOR = [
'input[id*="confirm" i]', 'input[id*="confirm" i]',
].join(', '); ].join(', ');
function isVisibleKiroElement(el) { function isVisibleKiroElement(element) {
if (!el) return false; if (!element) return false;
const style = window.getComputedStyle(el); const style = window.getComputedStyle(element);
const rect = el.getBoundingClientRect(); const rect = element.getBoundingClientRect();
return style.display !== 'none' return style.display !== 'none'
&& style.visibility !== 'hidden' && style.visibility !== 'hidden'
&& rect.width > 0 && rect.width > 0
@@ -66,20 +66,20 @@ function getKiroPageText() {
function collectVisibleElements(selector) { function collectVisibleElements(selector) {
return Array.from(document.querySelectorAll(selector)) return Array.from(document.querySelectorAll(selector))
.filter((el) => isVisibleKiroElement(el)); .filter((element) => isVisibleKiroElement(element));
} }
function findFirstVisible(selector) { function findFirstVisible(selector) {
return collectVisibleElements(selector)[0] || null; return collectVisibleElements(selector)[0] || null;
} }
function getElementActionText(el) { function getElementActionText(element) {
return [ return [
el?.textContent, element?.textContent,
el?.value, element?.value,
el?.getAttribute?.('aria-label'), element?.getAttribute?.('aria-label'),
el?.getAttribute?.('title'), element?.getAttribute?.('title'),
el?.getAttribute?.('data-testid'), element?.getAttribute?.('data-testid'),
] ]
.filter(Boolean) .filter(Boolean)
.join(' ') .join(' ')
@@ -102,15 +102,15 @@ function findActionButton(options = {}) {
} }
const candidates = collectVisibleElements('button, [role="button"], input[type="submit"], input[type="button"]') const candidates = collectVisibleElements('button, [role="button"], input[type="submit"], input[type="button"]')
.filter((el) => !el.disabled && el.getAttribute('aria-disabled') !== 'true'); .filter((element) => !element.disabled && element.getAttribute('aria-disabled') !== 'true');
const prioritized = formOwner const prioritized = formOwner
? candidates.filter((el) => (el.form || el.closest?.('form') || null) === formOwner) ? candidates.filter((element) => (element.form || element.closest?.('form') || null) === formOwner)
: []; : [];
const pool = prioritized.length ? prioritized : candidates; const pool = prioritized.length ? prioritized : candidates;
if (textPattern instanceof RegExp) { if (textPattern instanceof RegExp) {
return pool.find((el) => textPattern.test(getElementActionText(el))) || null; return pool.find((element) => textPattern.test(getElementActionText(element))) || null;
} }
return pool[0] || null; return pool[0] || null;
@@ -209,7 +209,7 @@ function detectKiroFatalPageState(pageText = '', currentUrl = '', pageTitle = ''
return { return {
state: 'cloudfront_403_page', state: 'cloudfront_403_page',
url: currentUrl, url: currentUrl,
fatalMessage: 'Kiro 注册页返回 403CloudFront 拒绝请求),通常是当前代理/IP/区域触发了 AWS 风控,请更换代理后重试。', fatalMessage: 'Kiro 注册页返回 403CloudFront 拒绝请求),通常是当前代理 IP区域触发了 AWS 风控,请更换代理后重试。',
}; };
} }
@@ -226,13 +226,13 @@ function getKiroFatalStateMessage(snapshot = {}) {
} }
switch (snapshot?.state) { switch (snapshot?.state) {
case 'cloudfront_403_page': case 'cloudfront_403_page':
return 'Kiro 注册页返回 403CloudFront 拒绝请求),通常是当前代理/IP/区域触发了 AWS 风控,请更换代理后重试。'; return 'Kiro 注册页返回 403CloudFront 拒绝请求),通常是当前代理 IP区域触发了 AWS 风控,请更换代理后重试。';
default: default:
return `Kiro 页面出现异常状态:${snapshot?.state || 'unknown'}`; return `Kiro 页面出现异常状态:${snapshot?.state || 'unknown'}`;
} }
} }
function detectKiroPageState() { function detectKiroRegisterPageState() {
const pageText = getKiroPageText(); const pageText = getKiroPageText();
const currentUrl = location.href; const currentUrl = location.href;
const fatalState = detectKiroFatalPageState(pageText, currentUrl, document.title || ''); const fatalState = detectKiroFatalPageState(pageText, currentUrl, document.title || '');
@@ -267,7 +267,7 @@ function detectKiroPageState() {
if ( if (
KIRO_SUCCESS_TEXT_PATTERN.test(pageText) KIRO_SUCCESS_TEXT_PATTERN.test(pageText)
|| /request approved|access your data|请求已批准|访问的数据/i.test(pageText) || /request approved|access your data|请求已批准|访问的数据/i.test(pageText)
) { ) {
return { return {
state: 'success_page', state: 'success_page',
@@ -318,7 +318,7 @@ async function waitForKiroState(predicate, options = {}) {
while (Date.now() - start < timeoutMs) { while (Date.now() - start < timeoutMs) {
throwIfStopped(); throwIfStopped();
const detected = detectKiroPageState(); const detected = detectKiroRegisterPageState();
if (isKiroFatalState(detected.state)) { if (isKiroFatalState(detected.state)) {
throw new Error(getKiroFatalStateMessage(detected)); throw new Error(getKiroFatalStateMessage(detected));
} }
@@ -328,14 +328,14 @@ async function waitForKiroState(predicate, options = {}) {
await sleep(retryDelayMs); await sleep(retryDelayMs);
} }
const finalState = detectKiroPageState(); const finalState = detectKiroRegisterPageState();
if (isKiroFatalState(finalState.state)) { if (isKiroFatalState(finalState.state)) {
throw new Error(getKiroFatalStateMessage(finalState)); throw new Error(getKiroFatalStateMessage(finalState));
} }
throw new Error(options.timeoutMessage || `等待 Kiro 页面状态超时:${finalState.state}`); throw new Error(options.timeoutMessage || `等待 Kiro 页面状态超时:${finalState.state}`);
} }
async function ensureKiroPageState(payload = {}) { async function ensureKiroRegisterPageState(payload = {}) {
const targetStates = Array.isArray(payload?.targetStates) const targetStates = Array.isArray(payload?.targetStates)
? payload.targetStates.map((entry) => String(entry || '').trim()).filter(Boolean) ? payload.targetStates.map((entry) => String(entry || '').trim()).filter(Boolean)
: []; : [];
@@ -353,7 +353,7 @@ async function ensureKiroPageState(payload = {}) {
); );
} }
async function waitForKiroStateChange(payload = {}) { async function waitForKiroRegisterStateChange(payload = {}) {
const fromStates = Array.isArray(payload?.fromStates) const fromStates = Array.isArray(payload?.fromStates)
? payload.fromStates.map((entry) => String(entry || '').trim()).filter(Boolean) ? payload.fromStates.map((entry) => String(entry || '').trim()).filter(Boolean)
: []; : [];
@@ -400,10 +400,10 @@ async function waitForKiroAuthorizationAdvance(previousState = {}, options = {})
async function submitKiroEmail(payload = {}) { async function submitKiroEmail(payload = {}) {
const email = String(payload?.email || '').trim(); const email = String(payload?.email || '').trim();
if (!email) { if (!email) {
throw new Error('缺少 Kiro 授权邮箱,无法继续提交。'); throw new Error('缺少 Kiro 注册邮箱,无法继续提交。');
} }
const readyState = await ensureKiroPageState({ const readyState = await ensureKiroRegisterPageState({
targetStates: ['email_entry'], targetStates: ['email_entry'],
timeoutMs: payload?.timeoutMs || 30000, timeoutMs: payload?.timeoutMs || 30000,
retryDelayMs: payload?.retryDelayMs || 250, retryDelayMs: payload?.retryDelayMs || 250,
@@ -428,7 +428,7 @@ async function submitKiroName(payload = {}) {
throw new Error('缺少 Kiro 注册姓名,无法继续提交。'); throw new Error('缺少 Kiro 注册姓名,无法继续提交。');
} }
const readyState = await ensureKiroPageState({ const readyState = await ensureKiroRegisterPageState({
targetStates: ['name_entry'], targetStates: ['name_entry'],
timeoutMs: payload?.timeoutMs || 30000, timeoutMs: payload?.timeoutMs || 30000,
retryDelayMs: payload?.retryDelayMs || 250, retryDelayMs: payload?.retryDelayMs || 250,
@@ -453,7 +453,7 @@ async function submitKiroVerificationCode(payload = {}) {
throw new Error('缺少 Kiro 邮箱验证码,无法继续提交。'); throw new Error('缺少 Kiro 邮箱验证码,无法继续提交。');
} }
const readyState = await ensureKiroPageState({ const readyState = await ensureKiroRegisterPageState({
targetStates: ['otp_page'], targetStates: ['otp_page'],
timeoutMs: payload?.timeoutMs || 30000, timeoutMs: payload?.timeoutMs || 30000,
retryDelayMs: payload?.retryDelayMs || 250, retryDelayMs: payload?.retryDelayMs || 250,
@@ -478,7 +478,7 @@ async function submitKiroPassword(payload = {}) {
throw new Error('缺少 Kiro 账户密码,无法继续提交。'); throw new Error('缺少 Kiro 账户密码,无法继续提交。');
} }
const readyState = await ensureKiroPageState({ const readyState = await ensureKiroRegisterPageState({
targetStates: ['password_page'], targetStates: ['password_page'],
timeoutMs: payload?.timeoutMs || 30000, timeoutMs: payload?.timeoutMs || 30000,
retryDelayMs: payload?.retryDelayMs || 250, retryDelayMs: payload?.retryDelayMs || 250,
@@ -508,8 +508,8 @@ async function submitKiroPassword(payload = {}) {
}; };
} }
async function confirmKiroAccess(payload = {}) { async function confirmKiroRegisterConsent(payload = {}) {
let currentState = await ensureKiroPageState({ let currentState = await ensureKiroRegisterPageState({
targetStates: ['authorization_page', 'success_page'], targetStates: ['authorization_page', 'success_page'],
timeoutMs: payload?.timeoutMs || 45000, timeoutMs: payload?.timeoutMs || 45000,
retryDelayMs: payload?.retryDelayMs || 250, retryDelayMs: payload?.retryDelayMs || 250,
@@ -546,12 +546,12 @@ async function confirmKiroAccess(payload = {}) {
}; };
} }
async function handleKiroDeviceAuthCommand(message) { async function handleKiroRegisterCommand(message) {
switch (message.type) { switch (message.type) {
case 'ENSURE_KIRO_PAGE_STATE': case 'ENSURE_KIRO_PAGE_STATE':
return ensureKiroPageState(message.payload || {}); return ensureKiroRegisterPageState(message.payload || {});
case 'ENSURE_KIRO_STATE_CHANGE': case 'ENSURE_KIRO_STATE_CHANGE':
return waitForKiroStateChange(message.payload || {}); return waitForKiroRegisterStateChange(message.payload || {});
case 'EXECUTE_NODE': { case 'EXECUTE_NODE': {
const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim(); const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim();
if (nodeId === 'kiro-submit-email') { if (nodeId === 'kiro-submit-email') {
@@ -563,21 +563,21 @@ async function handleKiroDeviceAuthCommand(message) {
if (nodeId === 'kiro-submit-verification-code') { if (nodeId === 'kiro-submit-verification-code') {
return submitKiroVerificationCode(message.payload || {}); return submitKiroVerificationCode(message.payload || {});
} }
if (nodeId === 'kiro-fill-password') { if (nodeId === 'kiro-submit-password') {
return submitKiroPassword(message.payload || {}); return submitKiroPassword(message.payload || {});
} }
if (nodeId === 'kiro-confirm-access') { if (nodeId === 'kiro-complete-register-consent') {
return confirmKiroAccess(message.payload || {}); return confirmKiroRegisterConsent(message.payload || {});
} }
throw new Error(`kiro-device-auth-page.js 不处理节点:${nodeId}`); throw new Error(`register-page.js 不处理节点:${nodeId}`);
} }
default: default:
return null; return null;
} }
} }
if (document.documentElement.getAttribute(KIRO_DEVICE_AUTH_LISTENER_SENTINEL) !== '1') { if (document.documentElement.getAttribute(KIRO_REGISTER_PAGE_LISTENER_SENTINEL) !== '1') {
document.documentElement.setAttribute(KIRO_DEVICE_AUTH_LISTENER_SENTINEL, '1'); document.documentElement.setAttribute(KIRO_REGISTER_PAGE_LISTENER_SENTINEL, '1');
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if ( if (
message.type === 'ENSURE_KIRO_PAGE_STATE' message.type === 'ENSURE_KIRO_PAGE_STATE'
@@ -585,7 +585,7 @@ if (document.documentElement.getAttribute(KIRO_DEVICE_AUTH_LISTENER_SENTINEL) !=
|| message.type === 'EXECUTE_NODE' || message.type === 'EXECUTE_NODE'
) { ) {
resetStopState(); resetStopState();
handleKiroDeviceAuthCommand(message) handleKiroRegisterCommand(message)
.then((result) => { .then((result) => {
sendResponse({ ok: true, ...(result || {}) }); sendResponse({ ok: true, ...(result || {}) });
}) })
+39 -21
View File
@@ -115,19 +115,19 @@
{ {
id: 1, id: 1,
order: 10, order: 10,
key: 'kiro-start-device-login', key: 'kiro-open-register-page',
title: '启动设备登录', title: '打开注册页',
sourceId: 'kiro-device-auth', sourceId: 'kiro-register-page',
driverId: 'background/kiro-device-auth', driverId: 'background/kiro-register',
command: 'kiro-start-device-login', command: 'kiro-open-register-page',
}, },
{ {
id: 2, id: 2,
order: 20, order: 20,
key: 'kiro-submit-email', key: 'kiro-submit-email',
title: '获取邮箱并继续', title: '获取邮箱并继续',
sourceId: 'kiro-device-auth', sourceId: 'kiro-register-page',
driverId: 'background/kiro-device-auth', driverId: 'background/kiro-register',
command: 'kiro-submit-email', command: 'kiro-submit-email',
}, },
{ {
@@ -135,8 +135,8 @@
order: 30, order: 30,
key: 'kiro-submit-name', key: 'kiro-submit-name',
title: '填写姓名并继续', title: '填写姓名并继续',
sourceId: 'kiro-device-auth', sourceId: 'kiro-register-page',
driverId: 'background/kiro-device-auth', driverId: 'background/kiro-register',
command: 'kiro-submit-name', command: 'kiro-submit-name',
}, },
{ {
@@ -144,35 +144,53 @@
order: 40, order: 40,
key: 'kiro-submit-verification-code', key: 'kiro-submit-verification-code',
title: '获取验证码并继续', title: '获取验证码并继续',
sourceId: 'kiro-device-auth', sourceId: 'kiro-register-page',
driverId: 'background/kiro-device-auth', driverId: 'background/kiro-register',
command: 'kiro-submit-verification-code', command: 'kiro-submit-verification-code',
}, },
{ {
id: 5, id: 5,
order: 50, order: 50,
key: 'kiro-fill-password', key: 'kiro-submit-password',
title: '设置密码并继续', title: '设置密码并继续',
sourceId: 'kiro-device-auth', sourceId: 'kiro-register-page',
driverId: 'background/kiro-device-auth', driverId: 'background/kiro-register',
command: 'kiro-fill-password', command: 'kiro-submit-password',
}, },
{ {
id: 6, id: 6,
order: 60, order: 60,
key: 'kiro-confirm-access', key: 'kiro-complete-register-consent',
title: '确认访问并授权', title: '完成注册授权',
sourceId: 'kiro-device-auth', sourceId: 'kiro-register-page',
driverId: 'background/kiro-device-auth', driverId: 'background/kiro-register',
command: 'kiro-confirm-access', command: 'kiro-complete-register-consent',
}, },
{ {
id: 7, id: 7,
order: 70, order: 70,
key: 'kiro-start-desktop-authorize',
title: '启动桌面授权',
sourceId: 'kiro-desktop-authorize',
driverId: 'background/kiro-desktop-authorize',
command: 'kiro-start-desktop-authorize',
},
{
id: 8,
order: 80,
key: 'kiro-complete-desktop-authorize',
title: '完成桌面授权',
sourceId: 'kiro-desktop-authorize',
driverId: 'background/kiro-desktop-authorize',
command: 'kiro-complete-desktop-authorize',
},
{
id: 9,
order: 90,
key: 'kiro-upload-credential', key: 'kiro-upload-credential',
title: '上传凭据到 kiro.rs', title: '上传凭据到 kiro.rs',
sourceId: 'kiro-rs-admin', sourceId: 'kiro-rs-admin',
driverId: 'background/kiro-device-auth', driverId: 'background/kiro-publisher-kiro-rs',
command: 'kiro-upload-credential', command: 'kiro-upload-credential',
}, },
]; ];
-19
View File
@@ -116,25 +116,6 @@
"content/duck-mail.js" "content/duck-mail.js"
], ],
"run_at": "document_idle" "run_at": "document_idle"
},
{
"matches": [
"https://view.awsapps.com/*",
"https://login.awsapps.com/*",
"https://*.awsapps.com/*",
"https://signin.aws/*",
"https://signin.aws.amazon.com/*",
"https://*.signin.aws/*",
"https://profile.aws/*",
"https://profile.aws.amazon.com/*",
"https://*.profile.aws/*"
],
"js": [
"shared/source-registry.js",
"content/utils.js",
"content/kiro-device-auth-page.js"
],
"run_at": "document_idle"
} }
], ],
"action": { "action": {
File diff suppressed because it is too large Load Diff
+117 -120
View File
@@ -5,11 +5,11 @@
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {}; const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {}; const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {};
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai'; const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
const DEFAULT_OPENAI_INTEGRATION_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_INTEGRATION_TARGET_ID || 'cpa'; const DEFAULT_OPENAI_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_TARGET_ID || 'cpa';
const SIGNUP_METHOD_EMAIL = 'email'; const SIGNUP_METHOD_EMAIL = 'email';
const SIGNUP_METHOD_PHONE = 'phone'; const SIGNUP_METHOD_PHONE = 'phone';
const VALID_OPENAI_INTEGRATION_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_INTEGRATION_TARGET_IDS) const VALID_OPENAI_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_TARGET_IDS)
? flowRegistryApi.OPENAI_INTEGRATION_TARGET_IDS.slice() ? flowRegistryApi.OPENAI_TARGET_IDS.slice()
: ['cpa', 'sub2api', 'codex2api']; : ['cpa', 'sub2api', 'codex2api'];
const REGISTERED_FLOW_IDS = Array.isArray(flowRegistryApi.getRegisteredFlowIds?.()) const REGISTERED_FLOW_IDS = Array.isArray(flowRegistryApi.getRegisteredFlowIds?.())
? flowRegistryApi.getRegisteredFlowIds().map((flowId) => String(flowId || '').trim().toLowerCase()).filter(Boolean) ? flowRegistryApi.getRegisteredFlowIds().map((flowId) => String(flowId || '').trim().toLowerCase()).filter(Boolean)
@@ -22,12 +22,12 @@
supportsPhoneVerificationSettings: false, supportsPhoneVerificationSettings: false,
supportsPlusMode: false, supportsPlusMode: false,
supportsContributionMode: false, supportsContributionMode: false,
supportedIntegrationTargets: [], supportedTargetIds: [],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false, supportsOauthTimeoutBudget: false,
canSwitchFlow: true, canSwitchFlow: true,
stepDefinitionMode: 'default', stepDefinitionMode: 'default',
sourceSelectorLabel: '来源', targetSelectorLabel: '来源',
}); });
const FLOW_CAPABILITIES = Object.freeze( const FLOW_CAPABILITIES = Object.freeze(
@@ -47,7 +47,7 @@
) )
); );
const DEFAULT_INTEGRATION_TARGET_CAPABILITIES = Object.freeze({ const DEFAULT_TARGET_CAPABILITIES = Object.freeze({
supportsPhoneSignup: true, supportsPhoneSignup: true,
requiresPhoneSignupWarning: false, requiresPhoneSignupWarning: false,
}); });
@@ -59,12 +59,11 @@
'phoneVerificationEnabled', 'phoneVerificationEnabled',
'plusModeEnabled', 'plusModeEnabled',
'signupMethod', 'signupMethod',
'kiroSourceId',
'openaiIntegrationTargetId', 'openaiIntegrationTargetId',
'kiroIntegrationTargetId', 'kiroTargetId',
]); ]);
const OPENAI_INTEGRATION_TARGET_CAPABILITIES = Object.freeze({ const OPENAI_TARGET_CAPABILITIES = Object.freeze({
cpa: Object.freeze({ cpa: Object.freeze({
supportsPhoneSignup: true, supportsPhoneSignup: true,
requiresPhoneSignupWarning: true, requiresPhoneSignupWarning: true,
@@ -100,15 +99,15 @@
return Boolean(normalized) && REGISTERED_FLOW_ID_SET.has(normalized); return Boolean(normalized) && REGISTERED_FLOW_ID_SET.has(normalized);
} }
function normalizeOpenAiIntegrationTargetId(value = '', fallback = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) { function normalizeOpenAiTargetId(value = '', fallback = DEFAULT_OPENAI_TARGET_ID) {
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
if (VALID_OPENAI_INTEGRATION_TARGET_IDS.includes(normalized)) { if (VALID_OPENAI_TARGET_IDS.includes(normalized)) {
return normalized; return normalized;
} }
const fallbackValue = String(fallback || '').trim().toLowerCase(); const fallbackValue = String(fallback || '').trim().toLowerCase();
return VALID_OPENAI_INTEGRATION_TARGET_IDS.includes(fallbackValue) return VALID_OPENAI_TARGET_IDS.includes(fallbackValue)
? fallbackValue ? fallbackValue
: DEFAULT_OPENAI_INTEGRATION_TARGET_ID; : DEFAULT_OPENAI_TARGET_ID;
} }
function normalizeSignupMethod(value = '') { function normalizeSignupMethod(value = '') {
@@ -117,31 +116,31 @@
: SIGNUP_METHOD_EMAIL; : SIGNUP_METHOD_EMAIL;
} }
function normalizeOpenAiIntegrationTargetList(values = []) { function normalizeOpenAiTargetList(values = []) {
if (!Array.isArray(values)) { if (!Array.isArray(values)) {
return []; return [];
} }
const seen = new Set(); const seen = new Set();
const normalized = []; const normalized = [];
values.forEach((value) => { values.forEach((value) => {
const integrationTargetId = normalizeOpenAiIntegrationTargetId(value, ''); const targetId = normalizeOpenAiTargetId(value, '');
if (!integrationTargetId || seen.has(integrationTargetId)) { if (!targetId || seen.has(targetId)) {
return; return;
} }
seen.add(integrationTargetId); seen.add(targetId);
normalized.push(integrationTargetId); normalized.push(targetId);
}); });
return normalized; return normalized;
} }
function getIntegrationTargetLabel(flowId = DEFAULT_FLOW_ID, integrationTargetId = '') { function getTargetLabel(flowId = DEFAULT_FLOW_ID, targetId = '') {
if ( if (
isRegisteredFlowId(flowId) isRegisteredFlowId(flowId)
&& typeof flowRegistryApi.getIntegrationTargetLabel === 'function' && typeof flowRegistryApi.getTargetLabel === 'function'
) { ) {
return flowRegistryApi.getIntegrationTargetLabel(flowId, integrationTargetId); return flowRegistryApi.getTargetLabel(flowId, targetId);
} }
const normalized = String(integrationTargetId || '').trim().toLowerCase(); const normalized = String(targetId || '').trim().toLowerCase();
if (normalized === 'sub2api') { if (normalized === 'sub2api') {
return 'SUB2API'; return 'SUB2API';
} }
@@ -151,16 +150,16 @@
if (normalized === 'cpa') { if (normalized === 'cpa') {
return 'CPA'; return 'CPA';
} }
return normalized || String(integrationTargetId || '').trim(); return normalized || String(targetId || '').trim();
} }
function createFlowCapabilityRegistry(deps = {}) { function createFlowCapabilityRegistry(deps = {}) {
const { const {
defaultFlowCapabilities = DEFAULT_FLOW_CAPABILITIES, defaultFlowCapabilities = DEFAULT_FLOW_CAPABILITIES,
defaultFlowId = DEFAULT_FLOW_ID, defaultFlowId = DEFAULT_FLOW_ID,
defaultIntegrationTargetCapabilities = DEFAULT_INTEGRATION_TARGET_CAPABILITIES, defaultTargetCapabilities = DEFAULT_TARGET_CAPABILITIES,
flowCapabilities = FLOW_CAPABILITIES, flowCapabilities = FLOW_CAPABILITIES,
integrationTargetCapabilities = OPENAI_INTEGRATION_TARGET_CAPABILITIES, targetCapabilities = OPENAI_TARGET_CAPABILITIES,
} = deps; } = deps;
const settingsSchema = settingsSchemaApi.createSettingsSchema const settingsSchema = settingsSchemaApi.createSettingsSchema
? settingsSchemaApi.createSettingsSchema({ ? settingsSchemaApi.createSettingsSchema({
@@ -171,70 +170,69 @@
function getFlowCapabilities(flowId) { function getFlowCapabilities(flowId) {
const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId); const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId);
const entry = flowCapabilities[normalizedFlowId] || null; const entry = flowCapabilities[normalizedFlowId] || null;
const supportedIntegrationTargets = normalizedFlowId === 'openai' const supportedTargetIds = normalizedFlowId === 'openai'
? normalizeOpenAiIntegrationTargetList( ? normalizeOpenAiTargetList(
entry?.supportedIntegrationTargets || defaultFlowCapabilities.supportedIntegrationTargets entry?.supportedTargetIds || defaultFlowCapabilities.supportedTargetIds
) )
: (Array.isArray(entry?.supportedIntegrationTargets) : (Array.isArray(entry?.supportedTargetIds)
? entry.supportedIntegrationTargets.map((value) => String(value || '').trim().toLowerCase()).filter(Boolean) ? entry.supportedTargetIds.map((value) => String(value || '').trim().toLowerCase()).filter(Boolean)
: []); : []);
return { return {
...defaultFlowCapabilities, ...defaultFlowCapabilities,
...(entry || {}), ...(entry || {}),
supportedIntegrationTargets, supportedTargetIds,
}; };
} }
function getOpenAiIntegrationTargetCapabilities(integrationTargetId) { function getOpenAiTargetCapabilities(targetId) {
const normalizedIntegrationTargetId = normalizeOpenAiIntegrationTargetId(integrationTargetId); const normalizedTargetId = normalizeOpenAiTargetId(targetId);
return { return {
...defaultIntegrationTargetCapabilities, ...defaultTargetCapabilities,
...(integrationTargetCapabilities[normalizedIntegrationTargetId] || {}), ...(targetCapabilities[normalizedTargetId] || {}),
}; };
} }
function normalizeRequestedIntegrationTargetId(activeFlowId, state = {}, options = {}) { function normalizeRequestedTargetId(activeFlowId, state = {}, options = {}) {
if (activeFlowId === 'openai') { if (activeFlowId === 'openai') {
return normalizeOpenAiIntegrationTargetId( return normalizeOpenAiTargetId(
options?.integrationTargetId options?.targetId
?? options?.integrationTargetId
?? options?.panelMode ?? options?.panelMode
?? state?.openaiIntegrationTargetId ?? state?.openaiIntegrationTargetId
?? state?.panelMode, ?? state?.panelMode,
DEFAULT_OPENAI_INTEGRATION_TARGET_ID DEFAULT_OPENAI_TARGET_ID
); );
} }
const rawIntegrationTargetId = activeFlowId === 'kiro' const rawTargetId = activeFlowId === 'kiro'
? ( ? (
options?.integrationTargetId options?.targetId
?? state?.kiroIntegrationTargetId ?? state?.kiroTargetId
?? state?.kiroSourceId ?? flowRegistryApi.getDefaultTargetId?.(activeFlowId)
?? flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId)
?? '' ?? ''
) )
: ( : (
options?.integrationTargetId options?.targetId
?? state?.integrationTargetId ?? state?.targetId
?? state?.openaiIntegrationTargetId ?? state?.openaiIntegrationTargetId
?? state?.panelMode ?? state?.panelMode
?? state?.kiroIntegrationTargetId ?? state?.kiroTargetId
?? state?.kiroSourceId ?? flowRegistryApi.getDefaultTargetId?.(activeFlowId)
?? flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId)
?? '' ?? ''
); );
if ( if (
isRegisteredFlowId(activeFlowId) isRegisteredFlowId(activeFlowId)
&& typeof flowRegistryApi.normalizeIntegrationTargetId === 'function' && typeof flowRegistryApi.normalizeTargetId === 'function'
) { ) {
return flowRegistryApi.normalizeIntegrationTargetId( return flowRegistryApi.normalizeTargetId(
activeFlowId, activeFlowId,
rawIntegrationTargetId, rawTargetId,
flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId) flowRegistryApi.getDefaultTargetId?.(activeFlowId)
); );
} }
return String(rawIntegrationTargetId || '').trim().toLowerCase(); return String(rawTargetId || '').trim().toLowerCase();
} }
function normalizeChangedKeys(values = []) { function normalizeChangedKeys(values = []) {
@@ -252,33 +250,33 @@
return normalized; return normalized;
} }
function resolveEffectiveIntegrationTargetId(activeFlowId, state = {}, requestedIntegrationTargetId = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) { function resolveEffectiveTargetId(activeFlowId, state = {}, requestedTargetId = DEFAULT_OPENAI_TARGET_ID) {
if (!isRegisteredFlowId(activeFlowId)) { if (!isRegisteredFlowId(activeFlowId)) {
return normalizeRequestedIntegrationTargetId(activeFlowId, state, { return normalizeRequestedTargetId(activeFlowId, state, {
integrationTargetId: requestedIntegrationTargetId, targetId: requestedTargetId,
}); });
} }
if (settingsSchema?.getSelectedIntegrationTargetId) { if (settingsSchema?.getSelectedTargetId) {
const integrationTargetId = settingsSchema.getSelectedIntegrationTargetId({ const targetId = settingsSchema.getSelectedTargetId({
...state, ...state,
activeFlowId, activeFlowId,
}, activeFlowId); }, activeFlowId);
if (integrationTargetId) { if (targetId) {
return integrationTargetId; return targetId;
} }
} }
if (typeof flowRegistryApi.normalizeIntegrationTargetId === 'function') { if (typeof flowRegistryApi.normalizeTargetId === 'function') {
return flowRegistryApi.normalizeIntegrationTargetId( return flowRegistryApi.normalizeTargetId(
activeFlowId, activeFlowId,
activeFlowId === 'openai' activeFlowId === 'openai'
? (state?.openaiIntegrationTargetId || state?.panelMode || requestedIntegrationTargetId) ? (state?.openaiIntegrationTargetId || state?.panelMode || requestedTargetId)
: (state?.kiroIntegrationTargetId || state?.kiroSourceId || requestedIntegrationTargetId), : (state?.kiroTargetId || requestedTargetId),
flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId) flowRegistryApi.getDefaultTargetId?.(activeFlowId)
); );
} }
return activeFlowId === 'openai' return activeFlowId === 'openai'
? normalizeOpenAiIntegrationTargetId(requestedIntegrationTargetId) ? normalizeOpenAiTargetId(requestedTargetId)
: String(requestedIntegrationTargetId || '').trim().toLowerCase(); : String(requestedTargetId || '').trim().toLowerCase();
} }
function resolveSidepanelCapabilities(options = {}) { function resolveSidepanelCapabilities(options = {}) {
@@ -288,25 +286,25 @@
defaultFlowId defaultFlowId
); );
const flowState = getFlowCapabilities(activeFlowId); const flowState = getFlowCapabilities(activeFlowId);
const requestedIntegrationTargetId = normalizeRequestedIntegrationTargetId( const requestedTargetId = normalizeRequestedTargetId(
activeFlowId, activeFlowId,
state, state,
options options
); );
const supportedIntegrationTargets = activeFlowId === 'openai' const supportedTargetIds = activeFlowId === 'openai'
? normalizeOpenAiIntegrationTargetList(flowState.supportedIntegrationTargets) ? normalizeOpenAiTargetList(flowState.supportedTargetIds)
: (Array.isArray(flowState.supportedIntegrationTargets) : (Array.isArray(flowState.supportedTargetIds)
? flowState.supportedIntegrationTargets.slice() ? flowState.supportedTargetIds.slice()
: []); : []);
const integrationTargetSupported = supportedIntegrationTargets.length === 0 const targetSupported = supportedTargetIds.length === 0
? true ? true
: supportedIntegrationTargets.includes(requestedIntegrationTargetId); : supportedTargetIds.includes(requestedTargetId);
const effectiveIntegrationTargetId = integrationTargetSupported const effectiveTargetId = targetSupported
? requestedIntegrationTargetId ? requestedTargetId
: (supportedIntegrationTargets[0] || requestedIntegrationTargetId); : (supportedTargetIds[0] || requestedTargetId);
const integrationTargetState = activeFlowId === 'openai' const targetState = activeFlowId === 'openai'
? getOpenAiIntegrationTargetCapabilities(effectiveIntegrationTargetId) ? getOpenAiTargetCapabilities(effectiveTargetId)
: defaultIntegrationTargetCapabilities; : defaultTargetCapabilities;
const runtimeLocks = { const runtimeLocks = {
autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked), autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked),
contributionMode: activeFlowId === 'openai' && flowState.supportsContributionMode && Boolean(state?.contributionMode), contributionMode: activeFlowId === 'openai' && flowState.supportsContributionMode && Boolean(state?.contributionMode),
@@ -320,7 +318,7 @@
} }
const canSelectPhoneSignup = activeFlowId === 'openai' const canSelectPhoneSignup = activeFlowId === 'openai'
&& Boolean(flowState.supportsPhoneSignup) && Boolean(flowState.supportsPhoneSignup)
&& Boolean(integrationTargetState.supportsPhoneSignup) && Boolean(targetState.supportsPhoneSignup)
&& runtimeLocks.phoneVerificationEnabled && runtimeLocks.phoneVerificationEnabled
&& !runtimeLocks.plusModeEnabled && !runtimeLocks.plusModeEnabled
&& !runtimeLocks.contributionMode; && !runtimeLocks.contributionMode;
@@ -340,7 +338,7 @@
: effectiveSignupMethods[0]); : effectiveSignupMethods[0]);
const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function' const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function'
&& isRegisteredFlowId(activeFlowId) && isRegisteredFlowId(activeFlowId)
? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveIntegrationTargetId) ? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveTargetId)
: []; : [];
return { return {
@@ -351,38 +349,38 @@
canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode), canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode),
canSwitchFlow: Boolean(flowState.canSwitchFlow), canSwitchFlow: Boolean(flowState.canSwitchFlow),
canUsePhoneSignup: canSelectPhoneSignup, canUsePhoneSignup: canSelectPhoneSignup,
canUseSelectedPanelMode: integrationTargetSupported, canUseSelectedTarget: targetSupported,
effectiveIntegrationTargetId, effectivePanelMode: effectiveTargetId,
effectivePanelMode: effectiveIntegrationTargetId,
effectiveSignupMethod, effectiveSignupMethod,
effectiveSignupMethods, effectiveSignupMethods,
effectiveSourceId: effectiveIntegrationTargetId, effectiveTargetId,
flowCapabilities: flowState, flowCapabilities: flowState,
integrationTargetCapabilities: integrationTargetState, panelCapabilities: targetState,
panelCapabilities: integrationTargetState, panelMode: effectiveTargetId,
panelMode: effectiveIntegrationTargetId,
requestedIntegrationTargetId,
requestedPanelMode: requestedIntegrationTargetId,
requestedSignupMethod, requestedSignupMethod,
requestedTargetId,
runtimeLocks, runtimeLocks,
shouldWarnCpaPhoneSignup: effectiveSignupMethod === SIGNUP_METHOD_PHONE shouldWarnCpaPhoneSignup: effectiveSignupMethod === SIGNUP_METHOD_PHONE
&& Boolean(integrationTargetState.requiresPhoneSignupWarning), && Boolean(targetState.requiresPhoneSignupWarning),
stepDefinitionOptions: { stepDefinitionOptions: {
activeFlowId, activeFlowId,
integrationTargetId: effectiveIntegrationTargetId, integrationTargetId: effectiveTargetId,
panelMode: effectiveIntegrationTargetId, panelMode: effectiveTargetId,
targetId: effectiveTargetId,
plusModeEnabled: runtimeLocks.plusModeEnabled, plusModeEnabled: runtimeLocks.plusModeEnabled,
signupMethod: effectiveSignupMethod, signupMethod: effectiveSignupMethod,
}, },
supportedIntegrationTargets, supportedPanelModes: supportedTargetIds,
supportedPanelModes: supportedIntegrationTargets, supportedTargetIds,
targetCapabilities: targetState,
targetId: effectiveTargetId,
visibleGroupIds, visibleGroupIds,
}; };
} }
function buildPhoneSignupValidationError(capabilityState = {}) { function buildPhoneSignupValidationError(capabilityState = {}) {
const flowState = capabilityState.flowCapabilities || {}; const flowState = capabilityState.flowCapabilities || {};
const integrationTargetState = capabilityState.integrationTargetCapabilities || {}; const targetState = capabilityState.targetCapabilities || {};
const runtimeLocks = capabilityState.runtimeLocks || {}; const runtimeLocks = capabilityState.runtimeLocks || {};
if (!flowState.supportsPhoneSignup) { if (!flowState.supportsPhoneSignup) {
@@ -391,10 +389,10 @@
message: '当前 flow 不支持手机号注册。', message: '当前 flow 不支持手机号注册。',
}; };
} }
if (!integrationTargetState.supportsPhoneSignup) { if (!targetState.supportsPhoneSignup) {
return { return {
code: 'phone_signup_panel_unsupported', code: 'phone_signup_panel_unsupported',
message: `当前来源 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 不支持手机号注册。`, message: `当前来源 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 不支持手机号注册。`,
}; };
} }
if (!runtimeLocks.phoneVerificationEnabled) { if (!runtimeLocks.phoneVerificationEnabled) {
@@ -427,13 +425,13 @@
const errors = []; const errors = [];
if ( if (
Array.isArray(capabilityState.supportedIntegrationTargets) Array.isArray(capabilityState.supportedTargetIds)
&& capabilityState.supportedIntegrationTargets.length > 0 && capabilityState.supportedTargetIds.length > 0
&& capabilityState.canUseSelectedPanelMode === false && capabilityState.canUseSelectedTarget === false
) { ) {
errors.push({ errors.push({
code: 'panel_mode_unsupported', code: 'panel_mode_unsupported',
message: `当前 flow 不支持 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 来源。`, message: `当前 flow 不支持 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 来源。`,
}); });
} }
@@ -481,18 +479,17 @@
const shouldReconcileSignupMethod = MODE_SWITCH_RELEVANT_KEYS.some((key) => changedKeySet.has(key)); const shouldReconcileSignupMethod = MODE_SWITCH_RELEVANT_KEYS.some((key) => changedKeySet.has(key));
if ( if (
(changedKeySet.has('panelMode') || changedKeySet.has('openaiIntegrationTargetId') || changedKeySet.has('kiroIntegrationTargetId')) (changedKeySet.has('panelMode') || changedKeySet.has('openaiIntegrationTargetId') || changedKeySet.has('kiroTargetId'))
&& Array.isArray(capabilityState.supportedIntegrationTargets) && Array.isArray(capabilityState.supportedTargetIds)
&& capabilityState.supportedIntegrationTargets.length > 0 && capabilityState.supportedTargetIds.length > 0
&& capabilityState.canUseSelectedPanelMode === false && capabilityState.canUseSelectedTarget === false
) { ) {
normalizedUpdates.panelMode = capabilityState.effectiveIntegrationTargetId; normalizedUpdates.panelMode = capabilityState.effectiveTargetId;
normalizedUpdates.openaiIntegrationTargetId = capabilityState.effectiveIntegrationTargetId; normalizedUpdates.openaiIntegrationTargetId = capabilityState.effectiveTargetId;
normalizedUpdates.kiroIntegrationTargetId = capabilityState.effectiveIntegrationTargetId; normalizedUpdates.kiroTargetId = capabilityState.effectiveTargetId;
normalizedUpdates.kiroSourceId = capabilityState.effectiveIntegrationTargetId;
errors.push({ errors.push({
code: 'panel_mode_unsupported', code: 'panel_mode_unsupported',
message: `当前 flow 不支持 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 来源。`, message: `当前 flow 不支持 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 来源。`,
}); });
} }
@@ -556,9 +553,9 @@
return { return {
canUsePhoneSignup, canUsePhoneSignup,
getFlowCapabilities, getFlowCapabilities,
getOpenAiIntegrationTargetCapabilities, getOpenAiTargetCapabilities,
normalizeFlowId, normalizeFlowId,
normalizeOpenAiIntegrationTargetId, normalizeOpenAiTargetId,
normalizeSignupMethod, normalizeSignupMethod,
resolveSidepanelCapabilities, resolveSidepanelCapabilities,
resolveSignupMethod, resolveSignupMethod,
@@ -571,15 +568,15 @@
createFlowCapabilityRegistry, createFlowCapabilityRegistry,
DEFAULT_FLOW_CAPABILITIES, DEFAULT_FLOW_CAPABILITIES,
DEFAULT_FLOW_ID, DEFAULT_FLOW_ID,
DEFAULT_INTEGRATION_TARGET_CAPABILITIES, DEFAULT_TARGET_CAPABILITIES,
DEFAULT_OPENAI_INTEGRATION_TARGET_ID, DEFAULT_OPENAI_TARGET_ID,
FLOW_CAPABILITIES, FLOW_CAPABILITIES,
OPENAI_INTEGRATION_TARGET_CAPABILITIES, OPENAI_TARGET_CAPABILITIES,
SIGNUP_METHOD_EMAIL, SIGNUP_METHOD_EMAIL,
SIGNUP_METHOD_PHONE, SIGNUP_METHOD_PHONE,
VALID_OPENAI_INTEGRATION_TARGET_IDS, VALID_OPENAI_TARGET_IDS,
normalizeFlowId, normalizeFlowId,
normalizeOpenAiIntegrationTargetId, normalizeOpenAiTargetId,
normalizeSignupMethod, normalizeSignupMethod,
}; };
}); });
+98 -71
View File
@@ -2,11 +2,11 @@
root.MultiPageFlowRegistry = factory(); root.MultiPageFlowRegistry = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() { })(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() {
const DEFAULT_FLOW_ID = 'openai'; const DEFAULT_FLOW_ID = 'openai';
const DEFAULT_OPENAI_INTEGRATION_TARGET_ID = 'cpa'; const DEFAULT_OPENAI_TARGET_ID = 'cpa';
const DEFAULT_KIRO_INTEGRATION_TARGET_ID = 'kiro-rs'; const DEFAULT_KIRO_TARGET_ID = 'kiro-rs';
const DEFAULT_KIRO_PUBLICATION_TARGET_ID = 'kiro-rs'; const DEFAULT_KIRO_PUBLICATION_TARGET_ID = 'kiro-rs';
const DEFAULT_KIRO_RS_URL = 'https://kiro.leftcode.xyz/admin'; const DEFAULT_KIRO_RS_URL = 'https://kiro.leftcode.xyz/admin';
const OPENAI_INTEGRATION_TARGET_IDS = Object.freeze(['cpa', 'sub2api', 'codex2api']); const OPENAI_TARGET_IDS = Object.freeze(['cpa', 'sub2api', 'codex2api']);
const SHARED_SERVICE_IDS = Object.freeze(['account', 'email', 'proxy']); const SHARED_SERVICE_IDS = Object.freeze(['account', 'email', 'proxy']);
const DEFAULT_FLOW_CAPABILITIES = Object.freeze({ const DEFAULT_FLOW_CAPABILITIES = Object.freeze({
@@ -15,12 +15,12 @@
supportsPhoneVerificationSettings: false, supportsPhoneVerificationSettings: false,
supportsPlusMode: false, supportsPlusMode: false,
supportsContributionMode: false, supportsContributionMode: false,
supportedIntegrationTargets: [], supportedTargetIds: [],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false, supportsOauthTimeoutBudget: false,
canSwitchFlow: true, canSwitchFlow: true,
stepDefinitionMode: 'default', stepDefinitionMode: 'default',
sourceSelectorLabel: '来源', targetSelectorLabel: '来源',
}); });
function freezeDeep(value) { function freezeDeep(value) {
@@ -44,7 +44,7 @@
supportsPhoneVerificationSettings: true, supportsPhoneVerificationSettings: true,
supportsPlusMode: true, supportsPlusMode: true,
supportsContributionMode: true, supportsContributionMode: true,
supportedIntegrationTargets: [...OPENAI_INTEGRATION_TARGET_IDS], supportedTargetIds: [...OPENAI_TARGET_IDS],
supportsLuckmail: true, supportsLuckmail: true,
supportsOauthTimeoutBudget: true, supportsOauthTimeoutBudget: true,
stepDefinitionMode: 'openai-dynamic', stepDefinitionMode: 'openai-dynamic',
@@ -55,7 +55,7 @@
'openai-oauth', 'openai-oauth',
'openai-step6', 'openai-step6',
], ],
integrationTargets: { targets: {
cpa: { cpa: {
id: 'cpa', id: 'cpa',
label: 'CPA 面板', label: 'CPA 面板',
@@ -203,13 +203,13 @@
services: ['account', 'email', 'proxy'], services: ['account', 'email', 'proxy'],
capabilities: { capabilities: {
...DEFAULT_FLOW_CAPABILITIES, ...DEFAULT_FLOW_CAPABILITIES,
supportedIntegrationTargets: [DEFAULT_KIRO_INTEGRATION_TARGET_ID], supportedTargetIds: [DEFAULT_KIRO_TARGET_ID],
stepDefinitionMode: 'kiro-device-auth', stepDefinitionMode: 'kiro',
}, },
baseGroups: [ baseGroups: [
'kiro-runtime-status', 'kiro-runtime-status',
], ],
integrationTargets: { targets: {
'kiro-rs': { 'kiro-rs': {
id: 'kiro-rs', id: 'kiro-rs',
label: 'kiro.rs', label: 'kiro.rs',
@@ -223,13 +223,22 @@
}, },
}, },
runtimeSources: { runtimeSources: {
'kiro-device-auth': { 'kiro-register-page': {
flowId: 'kiro', flowId: 'kiro',
kind: 'flow-page', kind: 'flow-page',
label: 'Kiro 授权页', label: 'Kiro 注册页',
readyPolicy: 'top-frame-only', readyPolicy: 'top-frame-only',
family: 'kiro-device-auth-family', family: 'kiro-register-page-family',
driverId: 'content/kiro-device-auth-page', driverId: 'content/kiro/register-page',
cleanupScopes: [],
},
'kiro-desktop-authorize': {
flowId: 'kiro',
kind: 'flow-page',
label: 'Kiro 桌面授权页',
readyPolicy: 'top-frame-only',
family: 'kiro-desktop-authorize-family',
driverId: 'content/kiro/desktop-authorize-page',
cleanupScopes: [], cleanupScopes: [],
}, },
'kiro-rs-admin': { 'kiro-rs-admin': {
@@ -243,25 +252,43 @@
}, },
}, },
driverDefinitions: { driverDefinitions: {
'content/kiro-device-auth-page': { 'content/kiro/register-page': {
sourceId: 'kiro-device-auth', sourceId: 'kiro-register-page',
commands: [ commands: [
'kiro-submit-email', 'kiro-submit-email',
'kiro-submit-name', 'kiro-submit-name',
'kiro-submit-verification-code', 'kiro-submit-verification-code',
'kiro-fill-password', 'kiro-submit-password',
'kiro-confirm-access', 'kiro-complete-register-consent',
], ],
}, },
'background/kiro-device-auth': { 'content/kiro/desktop-authorize-page': {
sourceId: 'kiro-device-auth', sourceId: 'kiro-desktop-authorize',
commands: [ commands: [
'kiro-start-device-login', 'kiro-complete-desktop-authorize',
],
},
'background/kiro-register': {
sourceId: 'kiro-register-page',
commands: [
'kiro-open-register-page',
'kiro-submit-email', 'kiro-submit-email',
'kiro-submit-name', 'kiro-submit-name',
'kiro-submit-verification-code', 'kiro-submit-verification-code',
'kiro-fill-password', 'kiro-submit-password',
'kiro-confirm-access', 'kiro-complete-register-consent',
],
},
'background/kiro-desktop-authorize': {
sourceId: 'kiro-desktop-authorize',
commands: [
'kiro-start-desktop-authorize',
'kiro-complete-desktop-authorize',
],
},
'background/kiro-publisher-kiro-rs': {
sourceId: 'kiro-rs-admin',
commands: [
'kiro-upload-credential', 'kiro-upload-credential',
], ],
}, },
@@ -362,69 +389,69 @@
return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId); return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId);
} }
function getDefaultIntegrationTargetId(flowId) { function getDefaultTargetId(flowId) {
return normalizeFlowId(flowId) === 'kiro' return normalizeFlowId(flowId) === 'kiro'
? DEFAULT_KIRO_INTEGRATION_TARGET_ID ? DEFAULT_KIRO_TARGET_ID
: DEFAULT_OPENAI_INTEGRATION_TARGET_ID; : DEFAULT_OPENAI_TARGET_ID;
} }
function normalizeOpenAiIntegrationTargetId(value = '', fallback = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) { function normalizeOpenAiTargetId(value = '', fallback = DEFAULT_OPENAI_TARGET_ID) {
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
if (OPENAI_INTEGRATION_TARGET_IDS.includes(normalized)) { if (OPENAI_TARGET_IDS.includes(normalized)) {
return normalized; return normalized;
} }
const fallbackValue = String(fallback || '').trim().toLowerCase(); const fallbackValue = String(fallback || '').trim().toLowerCase();
return OPENAI_INTEGRATION_TARGET_IDS.includes(fallbackValue) return OPENAI_TARGET_IDS.includes(fallbackValue)
? fallbackValue ? fallbackValue
: DEFAULT_OPENAI_INTEGRATION_TARGET_ID; : DEFAULT_OPENAI_TARGET_ID;
} }
function normalizeKiroIntegrationTargetId(value = '', fallback = DEFAULT_KIRO_INTEGRATION_TARGET_ID) { function normalizeKiroTargetId(value = '', fallback = DEFAULT_KIRO_TARGET_ID) {
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
if (normalized === DEFAULT_KIRO_INTEGRATION_TARGET_ID) { if (normalized === DEFAULT_KIRO_TARGET_ID) {
return normalized; return normalized;
} }
const fallbackValue = String(fallback || '').trim().toLowerCase(); const fallbackValue = String(fallback || '').trim().toLowerCase();
return fallbackValue === DEFAULT_KIRO_INTEGRATION_TARGET_ID return fallbackValue === DEFAULT_KIRO_TARGET_ID
? fallbackValue ? fallbackValue
: DEFAULT_KIRO_INTEGRATION_TARGET_ID; : DEFAULT_KIRO_TARGET_ID;
} }
function normalizeIntegrationTargetId(flowId, integrationTargetId = '', fallback = undefined) { function normalizeTargetId(flowId, targetId = '', fallback = undefined) {
const normalizedFlowId = normalizeFlowId(flowId); const normalizedFlowId = normalizeFlowId(flowId);
if (normalizedFlowId === 'kiro') { if (normalizedFlowId === 'kiro') {
return normalizeKiroIntegrationTargetId( return normalizeKiroTargetId(
integrationTargetId, targetId,
fallback || DEFAULT_KIRO_INTEGRATION_TARGET_ID fallback || DEFAULT_KIRO_TARGET_ID
); );
} }
return normalizeOpenAiIntegrationTargetId( return normalizeOpenAiTargetId(
integrationTargetId, targetId,
fallback || DEFAULT_OPENAI_INTEGRATION_TARGET_ID fallback || DEFAULT_OPENAI_TARGET_ID
); );
} }
function getIntegrationTargetDefinitions(flowId) { function getTargetDefinitions(flowId) {
return getFlowDefinition(flowId)?.integrationTargets || {}; return getFlowDefinition(flowId)?.targets || {};
} }
function getIntegrationTargetDefinition(flowId, integrationTargetId) { function getTargetDefinition(flowId, targetId) {
const normalizedFlowId = normalizeFlowId(flowId); const normalizedFlowId = normalizeFlowId(flowId);
const normalizedIntegrationTargetId = normalizeIntegrationTargetId( const normalizedTargetId = normalizeTargetId(
normalizedFlowId, normalizedFlowId,
integrationTargetId, targetId,
getDefaultIntegrationTargetId(normalizedFlowId) getDefaultTargetId(normalizedFlowId)
); );
return getIntegrationTargetDefinitions(normalizedFlowId)[normalizedIntegrationTargetId] || null; return getTargetDefinitions(normalizedFlowId)[normalizedTargetId] || null;
} }
function getIntegrationTargetOptions(flowId) { function getTargetOptions(flowId) {
return Object.values(getIntegrationTargetDefinitions(flowId)); return Object.values(getTargetDefinitions(flowId));
} }
function getIntegrationTargetLabel(flowId, integrationTargetId) { function getTargetLabel(flowId, targetId) {
return getIntegrationTargetDefinition(flowId, integrationTargetId)?.label return getTargetDefinition(flowId, targetId)?.label
|| normalizeIntegrationTargetId(flowId, integrationTargetId); || normalizeTargetId(flowId, targetId);
} }
function getPublicationTargetDefinitions(flowId) { function getPublicationTargetDefinitions(flowId) {
@@ -450,17 +477,17 @@
}; };
} }
function getVisibleGroupIds(flowId, integrationTargetId, options = {}) { function getVisibleGroupIds(flowId, targetId, options = {}) {
const normalizedFlowId = normalizeFlowId(flowId); const normalizedFlowId = normalizeFlowId(flowId);
const flowDefinition = getFlowDefinition(normalizedFlowId); const flowDefinition = getFlowDefinition(normalizedFlowId);
const normalizedIntegrationTargetId = normalizeIntegrationTargetId( const normalizedTargetId = normalizeTargetId(
normalizedFlowId, normalizedFlowId,
integrationTargetId, targetId,
getDefaultIntegrationTargetId(normalizedFlowId) getDefaultTargetId(normalizedFlowId)
); );
const integrationTargetDefinition = getIntegrationTargetDefinition( const targetDefinition = getTargetDefinition(
normalizedFlowId, normalizedFlowId,
normalizedIntegrationTargetId normalizedTargetId
); );
const includeSharedServices = options?.includeSharedServices !== false; const includeSharedServices = options?.includeSharedServices !== false;
const serviceGroups = includeSharedServices const serviceGroups = includeSharedServices
@@ -470,7 +497,7 @@
: []; : [];
return Array.from(new Set([ return Array.from(new Set([
...(Array.isArray(flowDefinition?.baseGroups) ? flowDefinition.baseGroups : []), ...(Array.isArray(flowDefinition?.baseGroups) ? flowDefinition.baseGroups : []),
...(Array.isArray(integrationTargetDefinition?.groups) ? integrationTargetDefinition.groups : []), ...(Array.isArray(targetDefinition?.groups) ? targetDefinition.groups : []),
...serviceGroups, ...serviceGroups,
])); ]));
} }
@@ -503,33 +530,33 @@
return { return {
DEFAULT_FLOW_CAPABILITIES, DEFAULT_FLOW_CAPABILITIES,
DEFAULT_FLOW_ID, DEFAULT_FLOW_ID,
DEFAULT_KIRO_INTEGRATION_TARGET_ID, DEFAULT_KIRO_TARGET_ID,
DEFAULT_KIRO_PUBLICATION_TARGET_ID, DEFAULT_KIRO_PUBLICATION_TARGET_ID,
DEFAULT_KIRO_RS_URL, DEFAULT_KIRO_RS_URL,
DEFAULT_OPENAI_INTEGRATION_TARGET_ID, DEFAULT_OPENAI_TARGET_ID,
FLOW_DEFINITIONS, FLOW_DEFINITIONS,
OPENAI_INTEGRATION_TARGET_IDS, OPENAI_TARGET_IDS,
SETTINGS_GROUP_DEFINITIONS, SETTINGS_GROUP_DEFINITIONS,
SHARED_SERVICE_IDS, SHARED_SERVICE_IDS,
getDefaultIntegrationTargetId,
getDriverDefinitions, getDriverDefinitions,
getDefaultTargetId,
getFlowCapabilities, getFlowCapabilities,
getFlowDefinition, getFlowDefinition,
getFlowLabel, getFlowLabel,
getIntegrationTargetDefinition,
getIntegrationTargetDefinitions,
getIntegrationTargetLabel,
getIntegrationTargetOptions,
getPublicationTargetDefinition, getPublicationTargetDefinition,
getPublicationTargetDefinitions, getPublicationTargetDefinitions,
getRegisteredFlowIds, getRegisteredFlowIds,
getRuntimeSourceDefinitions, getRuntimeSourceDefinitions,
getSettingsGroupDefinition, getSettingsGroupDefinition,
getSettingsGroupDefinitions, getSettingsGroupDefinitions,
getTargetDefinition,
getTargetDefinitions,
getTargetLabel,
getTargetOptions,
getVisibleGroupIds, getVisibleGroupIds,
normalizeFlowId, normalizeFlowId,
normalizeIntegrationTargetId, normalizeKiroTargetId,
normalizeKiroIntegrationTargetId, normalizeOpenAiTargetId,
normalizeOpenAiIntegrationTargetId, normalizeTargetId,
}; };
}); });
+43 -40
View File
@@ -35,8 +35,8 @@
const defaultFlowId = String( const defaultFlowId = String(
deps.defaultFlowId || flowRegistry.DEFAULT_FLOW_ID || 'openai' deps.defaultFlowId || flowRegistry.DEFAULT_FLOW_ID || 'openai'
).trim().toLowerCase() || 'openai'; ).trim().toLowerCase() || 'openai';
const defaultOpenAiIntegrationTargetId = flowRegistry.DEFAULT_OPENAI_INTEGRATION_TARGET_ID || 'cpa'; const defaultOpenAiTargetId = flowRegistry.DEFAULT_OPENAI_TARGET_ID || 'cpa';
const defaultKiroIntegrationTargetId = flowRegistry.DEFAULT_KIRO_INTEGRATION_TARGET_ID || 'kiro-rs'; const defaultKiroTargetId = flowRegistry.DEFAULT_KIRO_TARGET_ID || 'kiro-rs';
const defaultKiroRsUrl = flowRegistry.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin'; const defaultKiroRsUrl = flowRegistry.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin';
const normalizeFlowId = typeof flowRegistry.normalizeFlowId === 'function' const normalizeFlowId = typeof flowRegistry.normalizeFlowId === 'function'
? flowRegistry.normalizeFlowId ? flowRegistry.normalizeFlowId
@@ -44,8 +44,8 @@
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
return normalized || String(fallback || '').trim().toLowerCase() || defaultFlowId; return normalized || String(fallback || '').trim().toLowerCase() || defaultFlowId;
}); });
const normalizeIntegrationTargetId = typeof flowRegistry.normalizeIntegrationTargetId === 'function' const normalizeTargetId = typeof flowRegistry.normalizeTargetId === 'function'
? flowRegistry.normalizeIntegrationTargetId ? flowRegistry.normalizeTargetId
: ((_flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase()); : ((_flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase());
function buildDefaultSettingsState() { function buildDefaultSettingsState() {
@@ -67,7 +67,7 @@
}, },
flows: { flows: {
openai: { openai: {
integrationTargetId: defaultOpenAiIntegrationTargetId, integrationTargetId: defaultOpenAiTargetId,
integrationTargets: { integrationTargets: {
cpa: { cpa: {
vpsUrl: '', vpsUrl: '',
@@ -106,8 +106,8 @@
}, },
}, },
kiro: { kiro: {
integrationTargetId: defaultKiroIntegrationTargetId, targetId: defaultKiroTargetId,
integrationTargets: { targets: {
'kiro-rs': { 'kiro-rs': {
baseUrl: defaultKiroRsUrl, baseUrl: defaultKiroRsUrl,
apiKey: '', apiKey: '',
@@ -117,7 +117,7 @@
stepExecutionRange: { stepExecutionRange: {
enabled: false, enabled: false,
fromStep: 1, fromStep: 1,
toStep: 7, toStep: 9,
}, },
}, },
}, },
@@ -141,7 +141,7 @@
?? defaults.activeFlowId, ?? defaults.activeFlowId,
defaults.activeFlowId defaults.activeFlowId
); );
const openaiIntegrationTargetId = normalizeIntegrationTargetId( const openaiIntegrationTargetId = normalizeTargetId(
'openai', 'openai',
nested?.flows?.openai?.integrationTargetId nested?.flows?.openai?.integrationTargetId
?? input?.openaiIntegrationTargetId ?? input?.openaiIntegrationTargetId
@@ -149,13 +149,12 @@
?? defaults.flows.openai.integrationTargetId, ?? defaults.flows.openai.integrationTargetId,
defaults.flows.openai.integrationTargetId defaults.flows.openai.integrationTargetId
); );
const kiroIntegrationTargetId = normalizeIntegrationTargetId( const kiroTargetId = normalizeTargetId(
'kiro', 'kiro',
nested?.flows?.kiro?.integrationTargetId nested?.flows?.kiro?.targetId
?? input?.kiroIntegrationTargetId ?? input?.kiroTargetId
?? input?.kiroSourceId ?? defaults.flows.kiro.targetId,
?? defaults.flows.kiro.integrationTargetId, defaults.flows.kiro.targetId
defaults.flows.kiro.integrationTargetId
); );
const stepExecutionRangeByFlow = isPlainObject(input?.stepExecutionRangeByFlow) const stepExecutionRangeByFlow = isPlainObject(input?.stepExecutionRangeByFlow)
? input.stepExecutionRangeByFlow ? input.stepExecutionRangeByFlow
@@ -313,22 +312,22 @@
}, },
}, },
kiro: { kiro: {
integrationTargetId: kiroIntegrationTargetId, targetId: kiroTargetId,
integrationTargets: { targets: {
'kiro-rs': { 'kiro-rs': {
...defaults.flows.kiro.integrationTargets['kiro-rs'], ...defaults.flows.kiro.targets['kiro-rs'],
...getIntegrationTargetValue(nested, (state) => state.flows?.kiro?.integrationTargets?.['kiro-rs']), ...getIntegrationTargetValue(nested, (state) => state.flows?.kiro?.targets?.['kiro-rs']),
baseUrl: String( baseUrl: String(
input?.kiroRsUrl input?.kiroRsUrl
?? input?.kiroRsBaseUrl ?? input?.kiroRsBaseUrl
?? nested?.flows?.kiro?.integrationTargets?.['kiro-rs']?.baseUrl ?? nested?.flows?.kiro?.targets?.['kiro-rs']?.baseUrl
?? defaults.flows.kiro.integrationTargets['kiro-rs'].baseUrl ?? defaults.flows.kiro.targets['kiro-rs'].baseUrl
).trim() || defaults.flows.kiro.integrationTargets['kiro-rs'].baseUrl, ).trim() || defaults.flows.kiro.targets['kiro-rs'].baseUrl,
apiKey: String( apiKey: String(
input?.kiroRsKey input?.kiroRsKey
?? input?.kiroRsApiKey ?? input?.kiroRsApiKey
?? nested?.flows?.kiro?.integrationTargets?.['kiro-rs']?.apiKey ?? nested?.flows?.kiro?.targets?.['kiro-rs']?.apiKey
?? defaults.flows.kiro.integrationTargets['kiro-rs'].apiKey ?? defaults.flows.kiro.targets['kiro-rs'].apiKey
), ),
}, },
}, },
@@ -379,16 +378,21 @@
return cloneValue(normalizedState?.flows?.[normalizedFlowId] || {}); return cloneValue(normalizedState?.flows?.[normalizedFlowId] || {});
} }
function getSelectedIntegrationTargetId(settingsState = {}, flowId) { function getSelectedTargetId(settingsState = {}, flowId) {
const normalizedState = normalizeSettingsState(settingsState); const normalizedState = normalizeSettingsState(settingsState);
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
const flowSettings = normalizedState?.flows?.[normalizedFlowId] || {}; const flowSettings = normalizedState?.flows?.[normalizedFlowId] || {};
return normalizeIntegrationTargetId( if (normalizedFlowId === 'kiro') {
return normalizeTargetId(
normalizedFlowId,
flowSettings?.targetId,
defaultKiroTargetId
);
}
return normalizeTargetId(
normalizedFlowId, normalizedFlowId,
flowSettings?.integrationTargetId, flowSettings?.integrationTargetId,
normalizedFlowId === 'kiro' defaultOpenAiTargetId
? defaultKiroIntegrationTargetId
: defaultOpenAiIntegrationTargetId
); );
} }
@@ -414,10 +418,9 @@
const openaiState = normalizedState.flows.openai; const openaiState = normalizedState.flows.openai;
const kiroState = normalizedState.flows.kiro; const kiroState = normalizedState.flows.kiro;
next.activeFlowId = normalizedState.activeFlowId; next.activeFlowId = normalizedState.activeFlowId;
next.openaiIntegrationTargetId = getSelectedIntegrationTargetId(normalizedState, 'openai'); next.openaiIntegrationTargetId = getSelectedTargetId(normalizedState, 'openai');
next.kiroIntegrationTargetId = getSelectedIntegrationTargetId(normalizedState, 'kiro'); next.kiroTargetId = getSelectedTargetId(normalizedState, 'kiro');
next.panelMode = next.openaiIntegrationTargetId; next.panelMode = next.openaiIntegrationTargetId;
next.kiroSourceId = next.kiroIntegrationTargetId;
next.vpsUrl = openaiState.integrationTargets.cpa.vpsUrl; next.vpsUrl = openaiState.integrationTargets.cpa.vpsUrl;
next.vpsPassword = openaiState.integrationTargets.cpa.vpsPassword; next.vpsPassword = openaiState.integrationTargets.cpa.vpsPassword;
next.localCpaStep9Mode = openaiState.integrationTargets.cpa.localCpaStep9Mode; next.localCpaStep9Mode = openaiState.integrationTargets.cpa.localCpaStep9Mode;
@@ -440,8 +443,8 @@
next.ipProxyEnabled = normalizedState.services.proxy.enabled; next.ipProxyEnabled = normalizedState.services.proxy.enabled;
next.ipProxyService = normalizedState.services.proxy.provider; next.ipProxyService = normalizedState.services.proxy.provider;
next.ipProxyMode = normalizedState.services.proxy.mode; next.ipProxyMode = normalizedState.services.proxy.mode;
next.kiroRsUrl = kiroState.integrationTargets['kiro-rs'].baseUrl; next.kiroRsUrl = kiroState.targets['kiro-rs'].baseUrl;
next.kiroRsKey = kiroState.integrationTargets['kiro-rs'].apiKey; next.kiroRsKey = kiroState.targets['kiro-rs'].apiKey;
next.stepExecutionRangeByFlow = buildStepExecutionRangeByFlow(normalizedState); next.stepExecutionRangeByFlow = buildStepExecutionRangeByFlow(normalizedState);
next.settingsSchemaVersion = normalizedState.schemaVersion; next.settingsSchemaVersion = normalizedState.schemaVersion;
next.settingsState = cloneValue(normalizedState); next.settingsState = cloneValue(normalizedState);
@@ -451,18 +454,18 @@
function getFlowInputState(settingsState = {}, flowId) { function getFlowInputState(settingsState = {}, flowId) {
const normalizedState = normalizeSettingsState(settingsState); const normalizedState = normalizeSettingsState(settingsState);
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
const integrationTargetId = getSelectedIntegrationTargetId(normalizedState, normalizedFlowId); const targetId = getSelectedTargetId(normalizedState, normalizedFlowId);
if (normalizedFlowId === 'kiro') { if (normalizedFlowId === 'kiro') {
return { return {
activeFlowId: normalizedFlowId, activeFlowId: normalizedFlowId,
integrationTargetId, targetId,
kiroRsUrl: normalizedState.flows.kiro.integrationTargets['kiro-rs'].baseUrl, kiroRsUrl: normalizedState.flows.kiro.targets['kiro-rs'].baseUrl,
kiroRsKey: normalizedState.flows.kiro.integrationTargets['kiro-rs'].apiKey, kiroRsKey: normalizedState.flows.kiro.targets['kiro-rs'].apiKey,
}; };
} }
return { return {
activeFlowId: normalizedFlowId, activeFlowId: normalizedFlowId,
integrationTargetId, targetId,
}; };
} }
@@ -472,7 +475,7 @@
buildStepExecutionRangeByFlow, buildStepExecutionRangeByFlow,
getFlowInputState, getFlowInputState,
getFlowSettings, getFlowSettings,
getSelectedIntegrationTargetId, getSelectedTargetId,
mergeSettingsState, mergeSettingsState,
normalizeSettingsState, normalizeSettingsState,
}; };
+5 -3
View File
@@ -123,7 +123,8 @@
'mail-2925', 'mail-2925',
'inbucket-mail', 'inbucket-mail',
'plus-checkout', 'plus-checkout',
'kiro-device-auth', 'kiro-register-page',
'kiro-desktop-authorize',
]); ]);
function normalizeHostname(hostname = '') { function normalizeHostname(hostname = '') {
@@ -325,7 +326,8 @@
return candidate.hostname.endsWith('paypal.com'); return candidate.hostname.endsWith('paypal.com');
case 'gopay-flow': case 'gopay-flow':
return /gopay|gojek/i.test(candidate.hostname); return /gopay|gojek/i.test(candidate.hostname);
case 'kiro-device-auth': case 'kiro-register-page':
case 'kiro-desktop-authorize':
return isKiroAuthHost(candidate.hostname); return isKiroAuthHost(candidate.hostname);
default: default:
return false; return false;
@@ -349,7 +351,7 @@
if (normalizedHostname === 'www.icloud.com' || normalizedHostname === 'www.icloud.com.cn') return 'icloud-mail'; if (normalizedHostname === 'www.icloud.com' || normalizedHostname === 'www.icloud.com.cn') return 'icloud-mail';
if (normalizedUrl.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail'; if (normalizedUrl.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail';
if (normalizedUrl.includes('2925.com')) return 'mail-2925'; if (normalizedUrl.includes('2925.com')) return 'mail-2925';
if (isKiroAuthHost(normalizedHostname)) return 'kiro-device-auth'; if (isKiroAuthHost(normalizedHostname)) return 'kiro-register-page';
if (isSignupEntryHost(normalizedHostname)) return 'chatgpt'; if (isSignupEntryHost(normalizedHostname)) return 'chatgpt';
return 'unknown-source'; return 'unknown-source';
} }
+2 -2
View File
@@ -258,11 +258,11 @@
</div> </div>
</div> </div>
<div class="data-row" id="row-kiro-device-code" style="display:none;"> <div class="data-row" id="row-kiro-device-code" style="display:none;">
<span class="data-label">设备</span> <span class="data-label">授权</span>
<span id="display-kiro-device-code" class="data-value mono">未生成</span> <span id="display-kiro-device-code" class="data-value mono">未生成</span>
</div> </div>
<div class="data-row" id="row-kiro-login-url" style="display:none;"> <div class="data-row" id="row-kiro-login-url" style="display:none;">
<span class="data-label">登录地址</span> <span class="data-label">授权地址</span>
<span id="display-kiro-login-url" class="data-value mono">未生成</span> <span id="display-kiro-login-url" class="data-value mono">未生成</span>
</div> </div>
<div class="data-row" id="row-kiro-upload-status" style="display:none;"> <div class="data-row" id="row-kiro-upload-status" style="display:none;">
+105 -106
View File
@@ -4129,11 +4129,11 @@ function collectSettingsPayload() {
return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa'; return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa';
}); });
const rawPanelMode = normalizePanelModeSafe(selectPanelMode?.value || latestState?.panelMode || 'cpa'); const rawPanelMode = normalizePanelModeSafe(selectPanelMode?.value || latestState?.panelMode || 'cpa');
const selectedSourceId = typeof getSelectedSourceId === 'function' const selectedTargetId = typeof getSelectedTargetId === 'function'
? getSelectedSourceId(activeFlowId) ? getSelectedTargetId(activeFlowId)
: (activeFlowId === defaultFlowId : (activeFlowId === defaultFlowId
? rawPanelMode ? rawPanelMode
: String(selectPanelMode?.value || latestState?.kiroSourceId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs'); : String(selectPanelMode?.value || latestState?.kiroTargetId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs');
const rawPlusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled const rawPlusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
? Boolean(inputPlusModeEnabled.checked) ? Boolean(inputPlusModeEnabled.checked)
: Boolean(latestState?.plusModeEnabled); : Boolean(latestState?.plusModeEnabled);
@@ -4141,7 +4141,7 @@ function collectSettingsPayload() {
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
? resolveCurrentSidepanelCapabilities({ ? resolveCurrentSidepanelCapabilities({
activeFlowId, activeFlowId,
sourceId: selectedSourceId, targetId: selectedTargetId,
panelMode: rawPanelMode, panelMode: rawPanelMode,
signupMethod: selectedSignupMethod, signupMethod: selectedSignupMethod,
state: { state: {
@@ -4149,7 +4149,7 @@ function collectSettingsPayload() {
activeFlowId, activeFlowId,
...(activeFlowId === defaultFlowId ...(activeFlowId === defaultFlowId
? { panelMode: rawPanelMode } ? { panelMode: rawPanelMode }
: { kiroSourceId: selectedSourceId }), : { kiroTargetId: selectedTargetId }),
plusModeEnabled: rawPlusModeEnabled, plusModeEnabled: rawPlusModeEnabled,
phoneVerificationEnabled: rawPhoneVerificationEnabled, phoneVerificationEnabled: rawPhoneVerificationEnabled,
signupMethod: selectedSignupMethod, signupMethod: selectedSignupMethod,
@@ -4164,13 +4164,14 @@ function collectSettingsPayload() {
? registry.resolveSidepanelCapabilities({ ? registry.resolveSidepanelCapabilities({
activeFlowId, activeFlowId,
panelMode: rawPanelMode, panelMode: rawPanelMode,
targetId: selectedTargetId,
signupMethod: selectedSignupMethod, signupMethod: selectedSignupMethod,
state: { state: {
...(latestState || {}), ...(latestState || {}),
activeFlowId, activeFlowId,
...(activeFlowId === defaultFlowId ...(activeFlowId === defaultFlowId
? { panelMode: rawPanelMode } ? { panelMode: rawPanelMode }
: { kiroSourceId: selectedSourceId }), : { kiroTargetId: selectedTargetId }),
plusModeEnabled: rawPlusModeEnabled, plusModeEnabled: rawPlusModeEnabled,
phoneVerificationEnabled: rawPhoneVerificationEnabled, phoneVerificationEnabled: rawPhoneVerificationEnabled,
signupMethod: selectedSignupMethod, signupMethod: selectedSignupMethod,
@@ -4179,7 +4180,7 @@ function collectSettingsPayload() {
: null; : null;
})(); })();
const effectivePanelMode = capabilityState?.effectivePanelMode || capabilityState?.panelMode || rawPanelMode; const effectivePanelMode = capabilityState?.effectivePanelMode || capabilityState?.panelMode || rawPanelMode;
const effectiveSourceId = capabilityState?.effectiveSourceId || selectedSourceId; const effectiveTargetId = capabilityState?.effectiveTargetId || selectedTargetId;
const effectivePlusModeEnabled = capabilityState const effectivePlusModeEnabled = capabilityState
? Boolean(capabilityState.runtimeLocks?.plusModeEnabled) ? Boolean(capabilityState.runtimeLocks?.plusModeEnabled)
: rawPlusModeEnabled; : rawPlusModeEnabled;
@@ -4246,10 +4247,10 @@ function collectSettingsPayload() {
const defaultKiroRsUrl = String( const defaultKiroRsUrl = String(
flowRegistryApi?.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin' flowRegistryApi?.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin'
).trim() || 'https://kiro.leftcode.xyz/admin'; ).trim() || 'https://kiro.leftcode.xyz/admin';
const normalizeKiroSourceIdSafe = typeof normalizeSourceIdForFlow === 'function' const normalizeKiroTargetIdSafe = typeof normalizeTargetIdForFlow === 'function'
? normalizeSourceIdForFlow ? normalizeTargetIdForFlow
: ((_flowId, sourceId = '', fallback = 'kiro-rs') => { : ((_flowId, targetId = '', fallback = 'kiro-rs') => {
const normalized = String(sourceId || '').trim().toLowerCase(); const normalized = String(targetId || '').trim().toLowerCase();
return normalized || String(fallback || '').trim().toLowerCase() || 'kiro-rs'; return normalized || String(fallback || '').trim().toLowerCase() || 'kiro-rs';
}); });
return { return {
@@ -4257,11 +4258,11 @@ function collectSettingsPayload() {
...(contributionModeEnabled ? {} : { ...(contributionModeEnabled ? {} : {
...(activeFlowId === defaultFlowId ? { panelMode: effectivePanelMode } : {}), ...(activeFlowId === defaultFlowId ? { panelMode: effectivePanelMode } : {}),
}), }),
kiroSourceId: normalizeKiroSourceIdSafe( kiroTargetId: normalizeKiroTargetIdSafe(
'kiro', 'kiro',
activeFlowId === 'kiro' activeFlowId === 'kiro'
? effectiveSourceId ? effectiveTargetId
: (latestState?.kiroSourceId || 'kiro-rs'), : (latestState?.kiroTargetId || 'kiro-rs'),
'kiro-rs' 'kiro-rs'
), ),
kiroRsUrl: String( kiroRsUrl: String(
@@ -8240,26 +8241,26 @@ function normalizeFlowId(value = '', fallback = DEFAULT_ACTIVE_FLOW_ID) {
return normalized || fallbackValue || DEFAULT_ACTIVE_FLOW_ID; return normalized || fallbackValue || DEFAULT_ACTIVE_FLOW_ID;
} }
function getDefaultSourceIdForFlow(flowId = DEFAULT_ACTIVE_FLOW_ID) { function getDefaultTargetIdForFlow(flowId = DEFAULT_ACTIVE_FLOW_ID) {
const registry = getFlowRegistry(); const registry = getFlowRegistry();
if (registry?.getDefaultSourceId) { if (registry?.getDefaultTargetId) {
return registry.getDefaultSourceId(normalizeFlowId(flowId)); return registry.getDefaultTargetId(normalizeFlowId(flowId));
} }
return normalizeFlowId(flowId) === 'kiro' ? 'kiro-rs' : 'cpa'; return normalizeFlowId(flowId) === 'kiro' ? 'kiro-rs' : 'cpa';
} }
function normalizeSourceIdForFlow(flowId = DEFAULT_ACTIVE_FLOW_ID, sourceId = '', fallback = '') { function normalizeTargetIdForFlow(flowId = DEFAULT_ACTIVE_FLOW_ID, targetId = '', fallback = '') {
const normalizedFlowId = normalizeFlowId(flowId); const normalizedFlowId = normalizeFlowId(flowId);
const registry = getFlowRegistry(); const registry = getFlowRegistry();
const fallbackSourceId = fallback || getDefaultSourceIdForFlow(normalizedFlowId); const fallbackTargetId = fallback || getDefaultTargetIdForFlow(normalizedFlowId);
if (registry?.normalizeSourceId) { if (registry?.normalizeTargetId) {
return registry.normalizeSourceId(normalizedFlowId, sourceId, fallbackSourceId); return registry.normalizeTargetId(normalizedFlowId, targetId, fallbackTargetId);
} }
if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) { if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) {
return normalizePanelMode(sourceId || fallbackSourceId); return normalizePanelMode(targetId || fallbackTargetId);
} }
const normalized = String(sourceId || '').trim().toLowerCase(); const normalized = String(targetId || '').trim().toLowerCase();
return normalized || String(fallbackSourceId || '').trim().toLowerCase() || 'kiro-rs'; return normalized || String(fallbackTargetId || '').trim().toLowerCase() || 'kiro-rs';
} }
function getSelectedFlowId(state = latestState) { function getSelectedFlowId(state = latestState) {
@@ -8272,36 +8273,36 @@ function getSelectedFlowId(state = latestState) {
); );
} }
function getSelectedSourceIdForState(state = latestState, flowId = getSelectedFlowId(state)) { function getSelectedTargetIdForState(state = latestState, flowId = getSelectedFlowId(state)) {
const normalizedFlowId = normalizeFlowId(flowId); const normalizedFlowId = normalizeFlowId(flowId);
const schema = getSettingsSchema(); const schema = getSettingsSchema();
if (schema?.getSelectedSourceId) { if (schema?.getSelectedTargetId) {
return schema.getSelectedSourceId(state || {}, normalizedFlowId); return schema.getSelectedTargetId(state || {}, normalizedFlowId);
} }
if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) { if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) {
return normalizePanelMode(state?.panelMode || getDefaultSourceIdForFlow(normalizedFlowId)); return normalizePanelMode(state?.panelMode || getDefaultTargetIdForFlow(normalizedFlowId));
} }
return normalizeSourceIdForFlow( return normalizeTargetIdForFlow(
normalizedFlowId, normalizedFlowId,
state?.kiroSourceId || '', state?.kiroTargetId || '',
getDefaultSourceIdForFlow(normalizedFlowId) getDefaultTargetIdForFlow(normalizedFlowId)
); );
} }
function getSelectedSourceId(flowId = getSelectedFlowId()) { function getSelectedTargetId(flowId = getSelectedFlowId()) {
const normalizedFlowId = normalizeFlowId(flowId); const normalizedFlowId = normalizeFlowId(flowId);
const selectedValue = typeof selectPanelMode !== 'undefined' && selectPanelMode const selectedValue = typeof selectPanelMode !== 'undefined' && selectPanelMode
? selectPanelMode.value ? selectPanelMode.value
: ''; : '';
if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) { if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) {
return normalizePanelMode( return normalizePanelMode(
selectedValue || latestState?.panelMode || getDefaultSourceIdForFlow(normalizedFlowId) selectedValue || latestState?.panelMode || getDefaultTargetIdForFlow(normalizedFlowId)
); );
} }
return normalizeSourceIdForFlow( return normalizeTargetIdForFlow(
normalizedFlowId, normalizedFlowId,
selectedValue || latestState?.kiroSourceId || '', selectedValue || latestState?.kiroTargetId || '',
getDefaultSourceIdForFlow(normalizedFlowId) getDefaultTargetIdForFlow(normalizedFlowId)
); );
} }
@@ -8325,35 +8326,35 @@ function renderFlowSelectorOptions(selectedFlowId = getSelectedFlowId()) {
return flowIds; return flowIds;
} }
function renderSourceSelectorOptions(flowId = getSelectedFlowId(), selectedSourceId = '') { function renderTargetSelectorOptions(flowId = getSelectedFlowId(), selectedTargetId = '') {
if (!selectPanelMode) { if (!selectPanelMode) {
return []; return [];
} }
const registry = getFlowRegistry(); const registry = getFlowRegistry();
const normalizedFlowId = normalizeFlowId(flowId); const normalizedFlowId = normalizeFlowId(flowId);
const sourceOptions = Array.isArray(registry?.getSourceOptions?.(normalizedFlowId)) const targetOptions = Array.isArray(registry?.getTargetOptions?.(normalizedFlowId))
? registry.getSourceOptions(normalizedFlowId) ? registry.getTargetOptions(normalizedFlowId)
: []; : [];
const normalizedSourceId = normalizeSourceIdForFlow( const normalizedTargetId = normalizeTargetIdForFlow(
normalizedFlowId, normalizedFlowId,
selectedSourceId, selectedTargetId,
getDefaultSourceIdForFlow(normalizedFlowId) getDefaultTargetIdForFlow(normalizedFlowId)
); );
selectPanelMode.innerHTML = ''; selectPanelMode.innerHTML = '';
sourceOptions.forEach((sourceOption) => { targetOptions.forEach((targetOption) => {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = sourceOption.id; option.value = targetOption.id;
option.textContent = sourceOption.label || sourceOption.id; option.textContent = targetOption.label || targetOption.id;
selectPanelMode.appendChild(option); selectPanelMode.appendChild(option);
}); });
if (labelSourceSelector) { if (labelSourceSelector) {
labelSourceSelector.textContent = '来源'; labelSourceSelector.textContent = '来源';
} }
selectPanelMode.disabled = sourceOptions.length <= 1; selectPanelMode.disabled = targetOptions.length <= 1;
if (sourceOptions.length > 0) { if (targetOptions.length > 0) {
selectPanelMode.value = normalizedSourceId; selectPanelMode.value = normalizedTargetId;
} }
return sourceOptions; return targetOptions;
} }
function collectVisibleSettingsTargets(visibleGroupIds = []) { function collectVisibleSettingsTargets(visibleGroupIds = []) {
@@ -8414,11 +8415,11 @@ function applyFlowSettingsGroupVisibility(visibleGroupIds = []) {
function syncFlowSelectorsFromState(state = latestState) { function syncFlowSelectorsFromState(state = latestState) {
const activeFlowId = normalizeFlowId(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID); const activeFlowId = normalizeFlowId(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID);
renderFlowSelectorOptions(activeFlowId); renderFlowSelectorOptions(activeFlowId);
const sourceId = getSelectedSourceIdForState(state, activeFlowId); const targetId = getSelectedTargetIdForState(state, activeFlowId);
renderSourceSelectorOptions(activeFlowId, sourceId); renderTargetSelectorOptions(activeFlowId, targetId);
return { return {
activeFlowId, activeFlowId,
sourceId, targetId,
}; };
} }
@@ -8450,25 +8451,26 @@ function resolveCurrentSidepanelCapabilities(options = {}) {
...(options?.state || {}), ...(options?.state || {}),
activeFlowId, activeFlowId,
}; };
const sourceId = options?.sourceId !== undefined const targetId = options?.targetId !== undefined
? options.sourceId ? options.targetId
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID
? (options?.panelMode ?? state?.panelMode) ? (options?.panelMode ?? state?.panelMode)
: (options?.kiroSourceId ?? state?.kiroSourceId)); : (options?.kiroTargetId ?? state?.kiroTargetId));
if (activeFlowId === DEFAULT_ACTIVE_FLOW_ID) { if (activeFlowId === DEFAULT_ACTIVE_FLOW_ID) {
state.panelMode = normalizePanelMode( state.panelMode = normalizePanelMode(
sourceId || state?.panelMode || getDefaultSourceIdForFlow(activeFlowId) targetId || state?.panelMode || getDefaultTargetIdForFlow(activeFlowId)
); );
} else { } else {
state.kiroSourceId = normalizeSourceIdForFlow( state.kiroTargetId = normalizeTargetIdForFlow(
activeFlowId, activeFlowId,
sourceId || state?.kiroSourceId || '', targetId || state?.kiroTargetId || '',
getDefaultSourceIdForFlow(activeFlowId) getDefaultTargetIdForFlow(activeFlowId)
); );
} }
return registry.resolveSidepanelCapabilities({ return registry.resolveSidepanelCapabilities({
activeFlowId, activeFlowId,
panelMode: state?.panelMode, panelMode: state?.panelMode,
targetId: activeFlowId === DEFAULT_ACTIVE_FLOW_ID ? state?.panelMode : state?.kiroTargetId,
signupMethod: options?.signupMethod ?? state?.signupMethod, signupMethod: options?.signupMethod ?? state?.signupMethod,
state, state,
}); });
@@ -9912,7 +9914,7 @@ function applySettingsState(state) {
? syncFlowSelectorsFromState(state) ? syncFlowSelectorsFromState(state)
: { : {
activeFlowId: String(state?.activeFlowId || state?.flowId || defaultActiveFlowId).trim().toLowerCase() || defaultActiveFlowId, activeFlowId: String(state?.activeFlowId || state?.flowId || defaultActiveFlowId).trim().toLowerCase() || defaultActiveFlowId,
sourceId: String(state?.panelMode || 'cpa').trim().toLowerCase() || 'cpa', targetId: String(state?.panelMode || 'cpa').trim().toLowerCase() || 'cpa',
}; };
if (typeof applyOperationDelayState === 'function') { if (typeof applyOperationDelayState === 'function') {
applyOperationDelayState(state); applyOperationDelayState(state);
@@ -10029,24 +10031,21 @@ function applySettingsState(state) {
} }
if (typeof displayKiroDeviceCode !== 'undefined' && displayKiroDeviceCode) { if (typeof displayKiroDeviceCode !== 'undefined' && displayKiroDeviceCode) {
const kiroDeviceCode = String( const kiroDeviceCode = String(
state?.flows?.kiro?.auth?.deviceCode state?.kiroRuntime?.register?.userCode
|| state?.kiroDeviceCode
|| '' || ''
).trim(); ).trim();
displayKiroDeviceCode.textContent = kiroDeviceCode || '未生成'; displayKiroDeviceCode.textContent = kiroDeviceCode || '未生成';
} }
if (typeof displayKiroLoginUrl !== 'undefined' && displayKiroLoginUrl) { if (typeof displayKiroLoginUrl !== 'undefined' && displayKiroLoginUrl) {
const kiroLoginUrl = String( const kiroLoginUrl = String(
state?.flows?.kiro?.auth?.loginUrl state?.kiroRuntime?.register?.loginUrl
|| state?.kiroLoginUrl
|| '' || ''
).trim(); ).trim();
displayKiroLoginUrl.textContent = kiroLoginUrl || '未生成'; displayKiroLoginUrl.textContent = kiroLoginUrl || '未生成';
} }
if (typeof displayKiroUploadStatus !== 'undefined' && displayKiroUploadStatus) { if (typeof displayKiroUploadStatus !== 'undefined' && displayKiroUploadStatus) {
const kiroUploadStatus = String( const kiroUploadStatus = String(
state?.flows?.kiro?.upload?.status state?.kiroRuntime?.upload?.status
|| state?.kiroUploadStatus
|| '' || ''
).trim(); ).trim();
displayKiroUploadStatus.textContent = getKiroUploadStatusLabel(kiroUploadStatus); displayKiroUploadStatus.textContent = getKiroUploadStatusLabel(kiroUploadStatus);
@@ -10139,8 +10138,8 @@ function applySettingsState(state) {
if (typeof selectFlow !== 'undefined' && selectFlow) { if (typeof selectFlow !== 'undefined' && selectFlow) {
selectFlow.value = appliedFlowSelection.activeFlowId; selectFlow.value = appliedFlowSelection.activeFlowId;
} }
if (selectPanelMode && appliedFlowSelection.sourceId) { if (selectPanelMode && appliedFlowSelection.targetId) {
selectPanelMode.value = appliedFlowSelection.sourceId; selectPanelMode.value = appliedFlowSelection.targetId;
} }
inputCodex2ApiUrl.value = state?.codex2apiUrl || ''; inputCodex2ApiUrl.value = state?.codex2apiUrl || '';
inputCodex2ApiAdminKey.value = state?.codex2apiAdminKey || ''; inputCodex2ApiAdminKey.value = state?.codex2apiAdminKey || '';
@@ -12091,36 +12090,36 @@ function updatePanelModeUI() {
const activeFlowId = typeof getSelectedFlowId === 'function' const activeFlowId = typeof getSelectedFlowId === 'function'
? getSelectedFlowId(latestState) ? getSelectedFlowId(latestState)
: normalizeFlowId(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID); : normalizeFlowId(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID);
const sourceId = typeof getSelectedSourceId === 'function' const targetId = typeof getSelectedTargetId === 'function'
? getSelectedSourceId(activeFlowId) ? getSelectedTargetId(activeFlowId)
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID
? normalizePanelMode(selectPanelMode?.value || latestState?.panelMode || 'cpa') ? normalizePanelMode(selectPanelMode?.value || latestState?.panelMode || 'cpa')
: String(selectPanelMode?.value || latestState?.kiroSourceId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs'); : String(selectPanelMode?.value || latestState?.kiroTargetId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs');
const rawPanelMode = activeFlowId === DEFAULT_ACTIVE_FLOW_ID const rawPanelMode = activeFlowId === DEFAULT_ACTIVE_FLOW_ID
? normalizePanelMode(sourceId || latestState?.panelMode || 'cpa') ? normalizePanelMode(targetId || latestState?.panelMode || 'cpa')
: normalizePanelMode(latestState?.panelMode || 'cpa'); : normalizePanelMode(latestState?.panelMode || 'cpa');
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
? resolveCurrentSidepanelCapabilities({ ? resolveCurrentSidepanelCapabilities({
activeFlowId, activeFlowId,
sourceId, targetId,
panelMode: rawPanelMode, panelMode: rawPanelMode,
state: { state: {
...(latestState || {}), ...(latestState || {}),
activeFlowId, activeFlowId,
...(activeFlowId === DEFAULT_ACTIVE_FLOW_ID ...(activeFlowId === DEFAULT_ACTIVE_FLOW_ID
? { panelMode: rawPanelMode } ? { panelMode: rawPanelMode }
: { kiroSourceId: sourceId }), : { kiroTargetId: targetId }),
}, },
}) })
: null; : null;
const effectiveSourceId = capabilityState?.effectiveSourceId || sourceId; const effectiveTargetId = capabilityState?.effectiveTargetId || targetId;
renderFlowSelectorOptions(activeFlowId); renderFlowSelectorOptions(activeFlowId);
renderSourceSelectorOptions(activeFlowId, effectiveSourceId); renderTargetSelectorOptions(activeFlowId, effectiveTargetId);
if (selectFlow) { if (selectFlow) {
selectFlow.value = activeFlowId; selectFlow.value = activeFlowId;
} }
if (selectPanelMode) { if (selectPanelMode) {
selectPanelMode.value = effectiveSourceId; selectPanelMode.value = effectiveTargetId;
} }
const visibleGroupIds = Array.isArray(capabilityState?.visibleGroupIds) const visibleGroupIds = Array.isArray(capabilityState?.visibleGroupIds)
? capabilityState.visibleGroupIds ? capabilityState.visibleGroupIds
@@ -13202,7 +13201,7 @@ stepsList?.addEventListener('click', async (event) => {
if (step === gpcCreateStep && !(await ensureGpcApiKeyReadyForStart())) { if (step === gpcCreateStep && !(await ensureGpcApiKeyReadyForStart())) {
return; return;
} }
const shouldPersistSharedPassword = nodeId === 'fill-password' || nodeId === 'kiro-fill-password'; const shouldPersistSharedPassword = nodeId === 'fill-password' || nodeId === 'kiro-submit-password';
if (shouldPersistSharedPassword && inputPassword.value !== (latestState?.customPassword || '')) { if (shouldPersistSharedPassword && inputPassword.value !== (latestState?.customPassword || '')) {
await chrome.runtime.sendMessage({ await chrome.runtime.sendMessage({
type: 'SAVE_SETTING', type: 'SAVE_SETTING',
@@ -13553,12 +13552,12 @@ async function startAutoRunFromCurrentSettings() {
const activeFlowId = typeof getSelectedFlowId === 'function' const activeFlowId = typeof getSelectedFlowId === 'function'
? getSelectedFlowId(latestState) ? getSelectedFlowId(latestState)
: (String(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID); : (String(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID);
const sourceId = typeof getSelectedSourceId === 'function' const targetId = typeof getSelectedTargetId === 'function'
? getSelectedSourceId(activeFlowId) ? getSelectedTargetId(activeFlowId)
: ( : (
activeFlowId === DEFAULT_ACTIVE_FLOW_ID activeFlowId === DEFAULT_ACTIVE_FLOW_ID
? normalizePanelMode(latestState?.panelMode || 'cpa') ? normalizePanelMode(latestState?.panelMode || 'cpa')
: (String(latestState?.kiroSourceId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs') : (String(latestState?.kiroTargetId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs')
); );
inputAutoDelayMinutes.value = String(delayMinutes); inputAutoDelayMinutes.value = String(delayMinutes);
btnAutoRun.innerHTML = delayEnabled btnAutoRun.innerHTML = delayEnabled
@@ -13571,7 +13570,7 @@ async function startAutoRunFromCurrentSettings() {
totalRuns, totalRuns,
delayMinutes, delayMinutes,
activeFlowId, activeFlowId,
sourceId, targetId,
autoRunSkipFailures, autoRunSkipFailures,
contributionMode: Boolean(latestState?.contributionMode), contributionMode: Boolean(latestState?.contributionMode),
contributionNickname, contributionNickname,
@@ -14102,32 +14101,32 @@ selectPanelMode.addEventListener('change', async () => {
const activeFlowId = typeof getSelectedFlowId === 'function' const activeFlowId = typeof getSelectedFlowId === 'function'
? getSelectedFlowId(latestState) ? getSelectedFlowId(latestState)
: normalizeFlowId(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID); : normalizeFlowId(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID);
const defaultSourceId = typeof getDefaultSourceIdForFlow === 'function' const defaultTargetId = typeof getDefaultTargetIdForFlow === 'function'
? getDefaultSourceIdForFlow(activeFlowId) ? getDefaultTargetIdForFlow(activeFlowId)
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID ? 'cpa' : 'kiro-rs'); : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID ? 'cpa' : 'kiro-rs');
const previousSourceId = typeof getSelectedSourceIdForState === 'function' const previousTargetId = typeof getSelectedTargetIdForState === 'function'
? getSelectedSourceIdForState(latestState, activeFlowId) ? getSelectedTargetIdForState(latestState, activeFlowId)
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID
? normalizePanelMode(latestState?.panelMode || defaultSourceId) ? normalizePanelMode(latestState?.panelMode || defaultTargetId)
: String(latestState?.kiroSourceId || defaultSourceId).trim().toLowerCase() || defaultSourceId); : String(latestState?.kiroTargetId || defaultTargetId).trim().toLowerCase() || defaultTargetId);
let nextSourceId = typeof normalizeSourceIdForFlow === 'function' let nextTargetId = typeof normalizeTargetIdForFlow === 'function'
? normalizeSourceIdForFlow(activeFlowId, selectPanelMode.value, defaultSourceId) ? normalizeTargetIdForFlow(activeFlowId, selectPanelMode.value, defaultTargetId)
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID
? normalizePanelMode(selectPanelMode.value) ? normalizePanelMode(selectPanelMode.value)
: String(selectPanelMode.value || defaultSourceId).trim().toLowerCase() || defaultSourceId); : String(selectPanelMode.value || defaultTargetId).trim().toLowerCase() || defaultTargetId);
if (activeFlowId === DEFAULT_ACTIVE_FLOW_ID) { if (activeFlowId === DEFAULT_ACTIVE_FLOW_ID) {
const nextPanelMode = normalizePanelMode(nextSourceId); const nextPanelMode = normalizePanelMode(nextTargetId);
selectPanelMode.value = nextPanelMode; selectPanelMode.value = nextPanelMode;
const confirmed = await confirmCpaPhoneSignupIfNeeded({ const confirmed = await confirmCpaPhoneSignupIfNeeded({
signupMethod: getSelectedSignupMethod(), signupMethod: getSelectedSignupMethod(),
panelMode: nextPanelMode, panelMode: nextPanelMode,
}); });
if (!confirmed) { if (!confirmed) {
selectPanelMode.value = previousSourceId; selectPanelMode.value = previousTargetId;
updatePanelModeUI(); updatePanelModeUI();
return; return;
} }
nextSourceId = nextPanelMode; nextTargetId = nextPanelMode;
syncLatestState({ syncLatestState({
activeFlowId, activeFlowId,
flowId: activeFlowId, flowId: activeFlowId,
@@ -14137,7 +14136,7 @@ selectPanelMode.addEventListener('change', async () => {
syncLatestState({ syncLatestState({
activeFlowId, activeFlowId,
flowId: activeFlowId, flowId: activeFlowId,
kiroSourceId: nextSourceId, kiroTargetId: nextTargetId,
}); });
} }
updatePanelModeUI(); updatePanelModeUI();
@@ -14941,23 +14940,23 @@ selectFlow?.addEventListener('change', () => {
activeFlowId: nextActiveFlowId, activeFlowId: nextActiveFlowId,
flowId: nextActiveFlowId, flowId: nextActiveFlowId,
}; };
const defaultSourceId = typeof getDefaultSourceIdForFlow === 'function' const defaultTargetId = typeof getDefaultTargetIdForFlow === 'function'
? getDefaultSourceIdForFlow(nextActiveFlowId) ? getDefaultTargetIdForFlow(nextActiveFlowId)
: (nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID ? 'cpa' : 'kiro-rs'); : (nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID ? 'cpa' : 'kiro-rs');
const nextSourceId = typeof getSelectedSourceIdForState === 'function' const nextTargetId = typeof getSelectedTargetIdForState === 'function'
? getSelectedSourceIdForState(nextStateBase, nextActiveFlowId) ? getSelectedTargetIdForState(nextStateBase, nextActiveFlowId)
: (nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID : (nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID
? normalizePanelMode(nextStateBase?.panelMode || defaultSourceId) ? normalizePanelMode(nextStateBase?.panelMode || defaultTargetId)
: String(nextStateBase?.kiroSourceId || defaultSourceId).trim().toLowerCase() || defaultSourceId); : String(nextStateBase?.kiroTargetId || defaultTargetId).trim().toLowerCase() || defaultTargetId);
syncLatestState({ syncLatestState({
activeFlowId: nextActiveFlowId, activeFlowId: nextActiveFlowId,
flowId: nextActiveFlowId, flowId: nextActiveFlowId,
...(nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID ...(nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID
? { panelMode: normalizePanelMode(nextSourceId || defaultSourceId) } ? { panelMode: normalizePanelMode(nextTargetId || defaultTargetId) }
: { : {
kiroSourceId: typeof normalizeSourceIdForFlow === 'function' kiroTargetId: typeof normalizeTargetIdForFlow === 'function'
? normalizeSourceIdForFlow(nextActiveFlowId, nextSourceId, defaultSourceId) ? normalizeTargetIdForFlow(nextActiveFlowId, nextTargetId, defaultTargetId)
: (String(nextSourceId || defaultSourceId).trim().toLowerCase() || defaultSourceId), : (String(nextTargetId || defaultTargetId).trim().toLowerCase() || defaultTargetId),
}), }),
}); });
updatePanelModeUI(); updatePanelModeUI();
@@ -15882,7 +15881,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
message.payload.panelMode !== undefined message.payload.panelMode !== undefined
|| message.payload.activeFlowId !== undefined || message.payload.activeFlowId !== undefined
|| message.payload.flowId !== undefined || message.payload.flowId !== undefined
|| message.payload.kiroSourceId !== undefined || message.payload.kiroTargetId !== undefined
) { ) {
if (typeof syncFlowSelectorsFromState === 'function') { if (typeof syncFlowSelectorsFromState === 'function') {
syncFlowSelectorsFromState(latestState); syncFlowSelectorsFromState(latestState);
+22 -20
View File
@@ -9,12 +9,14 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
const executedNodeIds = []; const executedNodeIds = [];
const kiroNodeIds = [ const kiroNodeIds = [
'kiro-start-device-login', 'kiro-open-register-page',
'kiro-submit-email', 'kiro-submit-email',
'kiro-submit-name', 'kiro-submit-name',
'kiro-submit-verification-code', 'kiro-submit-verification-code',
'kiro-fill-password', 'kiro-submit-password',
'kiro-confirm-access', 'kiro-complete-register-consent',
'kiro-start-desktop-authorize',
'kiro-complete-desktop-authorize',
'kiro-upload-credential', 'kiro-upload-credential',
]; ];
const openAiNodeIds = ['open-chatgpt', 'submit-signup-email', 'fill-password']; const openAiNodeIds = ['open-chatgpt', 'submit-signup-email', 'fill-password'];
@@ -24,10 +26,9 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
activeFlowId: 'kiro', activeFlowId: 'kiro',
flowId: 'kiro', flowId: 'kiro',
panelMode: 'cpa', panelMode: 'cpa',
kiroSourceId: 'kiro-rs', kiroTargetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example/admin', kiroRsUrl: 'https://kiro.example/admin',
kiroRsKey: 'demo-key', kiroRsKey: 'demo-key',
kiroRsApiRegion: 'ap-east-1',
customFutureFlowField: 'future-ready', customFutureFlowField: 'future-ready',
plusModeEnabled: false, plusModeEnabled: false,
plusPaymentMethod: 'paypal', plusPaymentMethod: 'paypal',
@@ -41,16 +42,18 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
signupMethod: 'email', signupMethod: 'email',
stepExecutionRangeByFlow: { stepExecutionRangeByFlow: {
openai: { enabled: false, fromStep: 1, toStep: 11 }, openai: { enabled: false, fromStep: 1, toStep: 11 },
kiro: { enabled: false, fromStep: 1, toStep: 7 }, kiro: { enabled: false, fromStep: 1, toStep: 9 },
}, },
nodeStatuses: { nodeStatuses: {
'open-chatgpt': 'stopped', 'open-chatgpt': 'stopped',
'kiro-start-device-login': 'pending', 'kiro-open-register-page': 'pending',
'kiro-submit-email': 'pending', 'kiro-submit-email': 'pending',
'kiro-submit-name': 'pending', 'kiro-submit-name': 'pending',
'kiro-submit-verification-code': 'pending', 'kiro-submit-verification-code': 'pending',
'kiro-fill-password': 'pending', 'kiro-submit-password': 'pending',
'kiro-confirm-access': 'pending', 'kiro-complete-register-consent': 'pending',
'kiro-start-desktop-authorize': 'pending',
'kiro-complete-desktop-authorize': 'pending',
'kiro-upload-credential': 'pending', 'kiro-upload-credential': 'pending',
}, },
tabRegistry: { tabRegistry: {
@@ -113,10 +116,9 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
activeFlowId: prevState.activeFlowId, activeFlowId: prevState.activeFlowId,
flowId: prevState.activeFlowId, flowId: prevState.activeFlowId,
panelMode: prevState.panelMode, panelMode: prevState.panelMode,
kiroSourceId: prevState.kiroSourceId, kiroTargetId: prevState.kiroTargetId,
kiroRsUrl: prevState.kiroRsUrl, kiroRsUrl: prevState.kiroRsUrl,
kiroRsKey: prevState.kiroRsKey, kiroRsKey: prevState.kiroRsKey,
kiroRsApiRegion: prevState.kiroRsApiRegion,
customFutureFlowField: prevState.customFutureFlowField, customFutureFlowField: prevState.customFutureFlowField,
}; };
}, },
@@ -167,10 +169,9 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
activeFlowId: 'openai', activeFlowId: 'openai',
flowId: 'openai', flowId: 'openai',
panelMode: 'cpa', panelMode: 'cpa',
kiroSourceId: '', kiroTargetId: '',
kiroRsUrl: '', kiroRsUrl: '',
kiroRsKey: '', kiroRsKey: '',
kiroRsApiRegion: '',
customFutureFlowField: '', customFutureFlowField: '',
plusModeEnabled: false, plusModeEnabled: false,
plusPaymentMethod: 'paypal', plusPaymentMethod: 'paypal',
@@ -184,7 +185,7 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
signupMethod: 'email', signupMethod: 'email',
stepExecutionRangeByFlow: { stepExecutionRangeByFlow: {
openai: { enabled: false, fromStep: 1, toStep: 11 }, openai: { enabled: false, fromStep: 1, toStep: 11 },
kiro: { enabled: false, fromStep: 1, toStep: 7 }, kiro: { enabled: false, fromStep: 1, toStep: 9 },
}, },
nodeStatuses: {}, nodeStatuses: {},
tabRegistry: {}, tabRegistry: {},
@@ -195,18 +196,19 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
executedNodeIds.push(nodeId); executedNodeIds.push(nodeId);
assert.equal(currentState.activeFlowId, 'kiro'); assert.equal(currentState.activeFlowId, 'kiro');
assert.equal(currentState.flowId, 'kiro'); assert.equal(currentState.flowId, 'kiro');
assert.equal(currentState.kiroSourceId, 'kiro-rs'); assert.equal(currentState.kiroTargetId, 'kiro-rs');
assert.equal(currentState.kiroRsApiRegion, 'ap-east-1');
assert.equal(currentState.customFutureFlowField, 'future-ready'); assert.equal(currentState.customFutureFlowField, 'future-ready');
currentState = { currentState = {
...currentState, ...currentState,
nodeStatuses: { nodeStatuses: {
'kiro-start-device-login': 'completed', 'kiro-open-register-page': 'completed',
'kiro-submit-email': 'completed', 'kiro-submit-email': 'completed',
'kiro-submit-name': 'completed', 'kiro-submit-name': 'completed',
'kiro-submit-verification-code': 'completed', 'kiro-submit-verification-code': 'completed',
'kiro-fill-password': 'completed', 'kiro-submit-password': 'completed',
'kiro-confirm-access': 'completed', 'kiro-complete-register-consent': 'completed',
'kiro-start-desktop-authorize': 'completed',
'kiro-complete-desktop-authorize': 'completed',
'kiro-upload-credential': 'completed', 'kiro-upload-credential': 'completed',
}, },
}; };
@@ -239,6 +241,6 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
await controller.autoRunLoop(1, { autoRunSkipFailures: false, mode: 'restart' }); await controller.autoRunLoop(1, { autoRunSkipFailures: false, mode: 'restart' });
assert.deepStrictEqual(executedNodeIds, ['kiro-start-device-login']); assert.deepStrictEqual(executedNodeIds, ['kiro-open-register-page']);
assert.equal(helperCalls, 1); assert.equal(helperCalls, 1);
}); });
@@ -145,13 +145,13 @@ const self = {
} }
return String(fallback || 'openai').trim().toLowerCase() === 'kiro' ? 'kiro' : 'openai'; return String(fallback || 'openai').trim().toLowerCase() === 'kiro' ? 'kiro' : 'openai';
}, },
normalizeSourceId(flowId, sourceId, fallback = 'kiro-rs') { normalizeTargetId(flowId, targetId, fallback = 'kiro-rs') {
const normalizedFlowId = this.normalizeFlowId(flowId); const normalizedFlowId = this.normalizeFlowId(flowId);
if (normalizedFlowId !== 'kiro') { if (normalizedFlowId !== 'kiro') {
return 'cpa'; return 'cpa';
} }
const normalizedSourceId = String(sourceId || '').trim().toLowerCase(); const normalizedTargetId = String(targetId || '').trim().toLowerCase();
return normalizedSourceId === 'kiro-rs' ? normalizedSourceId : fallback; return normalizedTargetId === 'kiro-rs' ? normalizedTargetId : fallback;
}, },
}, },
GoPayUtils: { GoPayUtils: {
@@ -276,7 +276,7 @@ return {
assert.equal(api.normalizePersistentSettingValue('signupMethod', 'unknown'), 'email'); assert.equal(api.normalizePersistentSettingValue('signupMethod', 'unknown'), 'email');
assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'codex'), 'openai'); assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'codex'), 'openai');
assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'kiro'), 'kiro'); assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'kiro'), 'kiro');
assert.equal(api.normalizePersistentSettingValue('kiroSourceId', 'unknown'), 'kiro-rs'); assert.equal(api.normalizePersistentSettingValue('kiroTargetId', 'unknown'), 'kiro-rs');
assert.equal(api.normalizePersistentSettingValue('kiroRsUrl', ''), 'https://kiro.leftcode.xyz/admin'); assert.equal(api.normalizePersistentSettingValue('kiroRsUrl', ''), 'https://kiro.leftcode.xyz/admin');
assert.equal(api.normalizePersistentSettingValue('kiroRsKey', ' key-1 '), ' key-1 '); assert.equal(api.normalizePersistentSettingValue('kiroRsKey', ' key-1 '), ' key-1 ');
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', '5SIM'), '5sim'); assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', '5SIM'), '5sim');
@@ -0,0 +1,48 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadDesktopAuthorizeRunnerApi() {
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
const clientSource = fs.readFileSync('background/kiro/desktop-client.js', 'utf8');
const runnerSource = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
const globalScope = {};
new Function('self', `${stateSource}; ${clientSource}; ${runnerSource}; return self;`)(globalScope);
return globalScope.MultiPageBackgroundKiroDesktopAuthorizeRunner;
}
test('kiro desktop authorize runner exposes a factory and callback parser', () => {
const api = loadDesktopAuthorizeRunnerApi();
assert.equal(typeof api?.createKiroDesktopAuthorizeRunner, 'function');
assert.equal(typeof api?.parseDesktopCallbackUrl, 'function');
});
test('parseDesktopCallbackUrl validates state and redirect port', () => {
const api = loadDesktopAuthorizeRunnerApi();
const success = api.parseDesktopCallbackUrl(
'http://127.0.0.1:43121/oauth/callback?code=auth-code-001&state=state-001',
'state-001',
43121
);
assert.deepEqual(success, {
url: 'http://127.0.0.1:43121/oauth/callback?code=auth-code-001&state=state-001',
code: 'auth-code-001',
state: 'state-001',
});
const badState = api.parseDesktopCallbackUrl(
'http://127.0.0.1:43121/oauth/callback?code=auth-code-001&state=wrong-state',
'state-001',
43121
);
assert.equal(Object.prototype.hasOwnProperty.call(badState, 'code'), false);
assert.match(badState.error, /state/i);
const badPort = api.parseDesktopCallbackUrl(
'http://127.0.0.1:43122/oauth/callback?code=auth-code-001&state=state-001',
'state-001',
43121
);
assert.equal(badPort, null);
});
@@ -1,880 +0,0 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadKiroDeviceAuthApi() {
const source = fs.readFileSync('background/steps/kiro-device-auth.js', 'utf8');
return new Function('self', `${source}; return self.MultiPageBackgroundKiroDeviceAuth;`)({});
}
function createResponse({ ok = true, status = 200, json = null, text = '' } = {}) {
const bodyText = text || (json ? JSON.stringify(json) : '');
return {
ok,
status,
statusText: bodyText || `HTTP ${status}`,
async text() {
return bodyText;
},
};
}
function mergeUpdates(updatesList = []) {
return updatesList.reduce((acc, item) => Object.assign(acc, item), {});
}
function createChromeRecorder() {
const updates = [];
return {
updates,
chrome: {
tabs: {
update: async (tabId, update) => {
updates.push({ tabId, update });
},
},
},
};
}
test('kiro device auth module exposes a factory', () => {
const api = loadKiroDeviceAuthApi();
assert.equal(typeof api?.createKiroDeviceAuthExecutor, 'function');
assert.equal(typeof api?.startBuilderIdDeviceLogin, 'function');
assert.equal(typeof api?.uploadBuilderIdCredential, 'function');
});
test('kiro start device login opens the auth tab and waits for the email entry page', async () => {
const api = loadKiroDeviceAuthApi();
const fetchCalls = [];
const stateUpdates = [];
const registerCalls = [];
const reuseCalls = [];
const completeCalls = [];
const contentReadyCalls = [];
const contentMessages = [];
const stableWaitCalls = [];
const removedCookies = [];
const browsingDataCalls = [];
const { chrome, updates: tabUpdates } = createChromeRecorder();
chrome.cookies = {
getAllCookieStores: async () => [{ id: 'store-a' }],
getAll: async () => [
{ domain: '.view.awsapps.com', path: '/start', name: 'awsapps', storeId: 'store-a' },
{ domain: '.oidc.us-east-1.amazonaws.com', path: '/', name: 'oidc', storeId: 'store-a' },
{ domain: '.signin.aws', path: '/', name: 'signin', storeId: 'store-a' },
{ domain: '.profile.aws.amazon.com', path: '/', name: 'profile-amazon', storeId: 'store-a' },
{ domain: '.example.com', path: '/', name: 'keep', storeId: 'store-a' },
],
remove: async (details) => {
removedCookies.push(details);
return details;
},
};
chrome.browsingData = {
removeCookies: async (details) => {
browsingDataCalls.push(details);
},
};
const executor = api.createKiroDeviceAuthExecutor({
addLog: async () => {},
chrome,
completeNodeFromBackground: async (nodeId, payload) => {
completeCalls.push({ nodeId, payload });
},
ensureContentScriptReadyOnTab: async (source, tabId, options = {}) => {
contentReadyCalls.push({ source, tabId, options });
},
fetchImpl: async (url, options = {}) => {
fetchCalls.push({
url,
method: options.method || 'GET',
body: options.body ? JSON.parse(options.body) : null,
});
if (url.endsWith('/client/register')) {
return createResponse({
ok: true,
status: 200,
json: {
clientId: 'client-001',
clientSecret: 'secret-001',
},
});
}
if (url.endsWith('/device_authorization')) {
return createResponse({
ok: true,
status: 200,
json: {
deviceCode: 'device-code-001',
userCode: 'ABCD-1234',
verificationUri: 'https://device.example.com/start',
verificationUriComplete: 'https://device.example.com/complete',
interval: 7,
expiresIn: 900,
},
});
}
throw new Error(`Unexpected fetch URL: ${url}`);
},
getState: async () => ({}),
registerTab: async (source, tabId) => {
registerCalls.push({ source, tabId });
},
KIRO_DEVICE_AUTH_INJECT_FILES: [
'shared/source-registry.js',
'content/utils.js',
'content/kiro-device-auth-page.js',
],
reuseOrCreateTab: async (source, url) => {
reuseCalls.push({ source, url });
return 88;
},
sendToContentScriptResilient: async (source, message, options = {}) => {
contentMessages.push({ source, message, options });
return {
ok: true,
state: 'email_entry',
url: 'https://device.example.com/complete',
};
},
setState: async (updates) => {
stateUpdates.push(updates);
},
sleepWithStop: async () => {},
throwIfStopped: () => {},
waitForTabStableComplete: async (tabId, options = {}) => {
stableWaitCalls.push({ tabId, options });
},
});
await executor.executeKiroStartDeviceLogin({
nodeId: 'kiro-start-device-login',
});
assert.deepEqual(removedCookies, [
{
url: 'https://view.awsapps.com/start',
name: 'awsapps',
storeId: 'store-a',
},
{
url: 'https://oidc.us-east-1.amazonaws.com/',
name: 'oidc',
storeId: 'store-a',
},
{
url: 'https://signin.aws/',
name: 'signin',
storeId: 'store-a',
},
{
url: 'https://profile.aws.amazon.com/',
name: 'profile-amazon',
storeId: 'store-a',
},
]);
assert.deepEqual(browsingDataCalls, [{
since: 0,
origins: [
'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',
],
}]);
assert.equal(fetchCalls.length, 2);
assert.equal(fetchCalls[0].url, 'https://oidc.us-east-1.amazonaws.com/client/register');
assert.equal(fetchCalls[1].url, 'https://oidc.us-east-1.amazonaws.com/device_authorization');
assert.deepEqual(fetchCalls[1].body, {
clientId: 'client-001',
clientSecret: 'secret-001',
startUrl: 'https://view.awsapps.com/start',
});
assert.deepEqual(reuseCalls, [{
source: 'kiro-device-auth',
url: 'https://device.example.com/complete',
}]);
assert.deepEqual(registerCalls, [{
source: 'kiro-device-auth',
tabId: 88,
}]);
assert.deepEqual(tabUpdates, [{
tabId: 88,
update: { active: true },
}]);
assert.deepEqual(stableWaitCalls, [{
tabId: 88,
options: {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: 2500,
initialDelayMs: 300,
},
}]);
assert.equal(contentReadyCalls.length, 1);
assert.equal(contentMessages.length, 1);
assert.equal(contentMessages[0].message.type, 'ENSURE_KIRO_PAGE_STATE');
assert.deepEqual(contentMessages[0].message.payload.targetStates, ['email_entry']);
const finalState = mergeUpdates(stateUpdates);
assert.equal(finalState.kiroClientId, 'client-001');
assert.equal(finalState.kiroClientSecret, 'secret-001');
assert.equal(finalState.kiroDeviceAuthorizationCode, 'device-code-001');
assert.equal(finalState.kiroDeviceCode, 'ABCD-1234');
assert.equal(finalState.kiroLoginUrl, 'https://device.example.com/complete');
assert.equal(finalState.kiroAuthRegion, 'us-east-1');
assert.equal(finalState.kiroAuthIntervalSeconds, 7);
assert.equal(finalState.kiroAuthStatus, 'waiting_user');
assert.equal(finalState.kiroUploadStatus, 'waiting_login');
assert.equal(finalState.kiroFullName, '');
assert.equal(finalState.kiroVerificationRequestedAt, 0);
assert.equal(completeCalls.length, 1);
assert.equal(completeCalls[0].nodeId, 'kiro-start-device-login');
assert.equal(completeCalls[0].payload.kiroDeviceCode, 'ABCD-1234');
assert.equal(completeCalls[0].payload.kiroLoginUrl, 'https://device.example.com/complete');
});
test('kiro submit email resolves the signup email, reactivates the auth tab, and waits for the name page', async () => {
const api = loadKiroDeviceAuthApi();
const stateUpdates = [];
const completeCalls = [];
const resolvedEmails = [];
const contentMessages = [];
const stableWaitCalls = [];
const { chrome, updates: tabUpdates } = createChromeRecorder();
let ensureCallIndex = 0;
const executor = api.createKiroDeviceAuthExecutor({
addLog: async () => {},
chrome,
completeNodeFromBackground: async (nodeId, payload) => {
completeCalls.push({ nodeId, payload });
},
ensureContentScriptReadyOnTab: async () => {},
getState: async () => ({
kiroAuthTabId: 88,
kiroLoginUrl: 'https://device.example.com/complete',
email: '',
mailProvider: '163',
}),
isTabAlive: async (source) => source === 'kiro-device-auth',
KIRO_DEVICE_AUTH_INJECT_FILES: [
'shared/source-registry.js',
'content/utils.js',
'content/kiro-device-auth-page.js',
],
resolveSignupEmailForFlow: async (state, options = {}) => {
resolvedEmails.push({ state, options });
return 'user@example.com';
},
sendToContentScriptResilient: async (source, message, options = {}) => {
contentMessages.push({ source, message, options });
if (message.type === 'ENSURE_KIRO_PAGE_STATE') {
ensureCallIndex += 1;
return {
ok: true,
state: ensureCallIndex === 1 ? 'email_entry' : 'name_entry',
url: ensureCallIndex === 1
? 'https://device.example.com/complete'
: 'https://device.example.com/name',
};
}
if (message.type === 'EXECUTE_NODE') {
return {
ok: true,
submitted: true,
state: 'email_submitted',
url: 'https://device.example.com/complete',
};
}
throw new Error(`Unexpected content message: ${message.type}`);
},
setState: async (updates) => {
stateUpdates.push(updates);
},
sleepWithStop: async () => {},
throwIfStopped: () => {},
waitForTabStableComplete: async (tabId, options = {}) => {
stableWaitCalls.push({ tabId, options });
},
});
await executor.executeKiroSubmitEmail({
nodeId: 'kiro-submit-email',
kiroAuthTabId: 88,
kiroLoginUrl: 'https://device.example.com/complete',
email: '',
mailProvider: '163',
});
assert.equal(resolvedEmails.length, 1);
assert.equal(resolvedEmails[0].state.nodeId, 'kiro-submit-email');
assert.deepEqual(resolvedEmails[0].options, {
preserveAccountIdentity: true,
});
assert.deepEqual(tabUpdates, [
{ tabId: 88, update: { active: true } },
{ tabId: 88, update: { active: true } },
]);
assert.deepEqual(stableWaitCalls, [
{
tabId: 88,
options: {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: 2500,
initialDelayMs: 300,
},
},
{
tabId: 88,
options: {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: 1500,
initialDelayMs: 150,
},
},
]);
assert.equal(contentMessages.length, 3);
assert.deepEqual(contentMessages[0].message.payload.targetStates, ['email_entry']);
assert.equal(contentMessages[1].message.nodeId, 'kiro-submit-email');
assert.deepEqual(contentMessages[1].message.payload, { email: 'user@example.com' });
assert.deepEqual(contentMessages[2].message.payload.targetStates, ['name_entry']);
const finalState = mergeUpdates(stateUpdates);
assert.equal(finalState.kiroAuthorizedEmail, 'user@example.com');
assert.equal(finalState.kiroAuthError, '');
assert.equal(finalState.kiroAuthStatus, 'waiting_user');
assert.equal(finalState.kiroUploadError, '');
assert.equal(finalState.kiroUploadStatus, 'waiting_login');
assert.equal(finalState.kiroFullName, '');
assert.equal(finalState.kiroVerificationRequestedAt, 0);
assert.equal(completeCalls.length, 1);
assert.equal(completeCalls[0].nodeId, 'kiro-submit-email');
assert.equal(completeCalls[0].payload.email, 'user@example.com');
assert.equal(completeCalls[0].payload.accountIdentifierType, 'email');
assert.equal(completeCalls[0].payload.accountIdentifier, 'user@example.com');
assert.equal(completeCalls[0].payload.kiroNextState, 'name_entry');
assert.equal(completeCalls[0].payload.kiroNextUrl, 'https://device.example.com/name');
});
test('kiro submit name generates a full name and waits for the otp page', async () => {
const api = loadKiroDeviceAuthApi();
const stateUpdates = [];
const completeCalls = [];
const contentMessages = [];
const { chrome, updates: tabUpdates } = createChromeRecorder();
let ensureCallIndex = 0;
const executor = api.createKiroDeviceAuthExecutor({
addLog: async () => {},
chrome,
completeNodeFromBackground: async (nodeId, payload) => {
completeCalls.push({ nodeId, payload });
},
ensureContentScriptReadyOnTab: async () => {},
generateRandomName: () => ({ firstName: 'Ada', lastName: 'Lovelace' }),
getState: async () => ({
kiroAuthTabId: 88,
kiroAuthorizedEmail: 'user@example.com',
kiroLoginUrl: 'https://device.example.com/complete',
}),
isTabAlive: async (source) => source === 'kiro-device-auth',
sendToContentScriptResilient: async (_source, message) => {
contentMessages.push(message);
if (message.type === 'ENSURE_KIRO_PAGE_STATE') {
ensureCallIndex += 1;
return {
ok: true,
state: ensureCallIndex === 1 ? 'name_entry' : 'otp_page',
url: ensureCallIndex === 1
? 'https://device.example.com/name'
: 'https://device.example.com/verify',
};
}
if (message.type === 'EXECUTE_NODE') {
return {
ok: true,
submitted: true,
state: 'name_submitted',
url: 'https://device.example.com/name',
};
}
throw new Error(`Unexpected content message: ${message.type}`);
},
setState: async (updates) => {
stateUpdates.push(updates);
},
sleepWithStop: async () => {},
throwIfStopped: () => {},
waitForTabStableComplete: async () => {},
});
await executor.executeKiroSubmitName({
nodeId: 'kiro-submit-name',
kiroAuthTabId: 88,
kiroAuthorizedEmail: 'user@example.com',
kiroLoginUrl: 'https://device.example.com/complete',
});
assert.deepEqual(tabUpdates, [{
tabId: 88,
update: { active: true },
}]);
assert.equal(contentMessages.length, 3);
assert.deepEqual(contentMessages[0].payload.targetStates, ['name_entry']);
assert.equal(contentMessages[1].nodeId, 'kiro-submit-name');
assert.deepEqual(contentMessages[1].payload, { fullName: 'Ada Lovelace' });
assert.deepEqual(contentMessages[2].payload.targetStates, ['otp_page']);
const finalState = mergeUpdates(stateUpdates);
assert.equal(finalState.kiroFullName, 'Ada Lovelace');
assert.equal(finalState.kiroAuthError, '');
assert.equal(finalState.kiroUploadError, '');
assert.equal(typeof finalState.kiroVerificationRequestedAt, 'number');
assert.equal(finalState.kiroVerificationRequestedAt > 0, true);
assert.equal(completeCalls.length, 1);
assert.equal(completeCalls[0].nodeId, 'kiro-submit-name');
assert.equal(completeCalls[0].payload.kiroFullName, 'Ada Lovelace');
assert.equal(completeCalls[0].payload.kiroNextState, 'otp_page');
});
test('kiro submit verification code polls mail, returns to the auth tab, and waits for the password page', async () => {
const api = loadKiroDeviceAuthApi();
const stateUpdates = [];
const completeCalls = [];
const mailPollCalls = [];
const contentMessages = [];
const mailOpenCalls = [];
const { chrome, updates: tabUpdates } = createChromeRecorder();
let ensureCallIndex = 0;
const executor = api.createKiroDeviceAuthExecutor({
addLog: async () => {},
chrome,
completeNodeFromBackground: async (nodeId, payload) => {
completeCalls.push({ nodeId, payload });
},
ensureContentScriptReadyOnTab: async () => {},
getMailConfig: () => ({
source: 'mail-163',
url: 'https://mail.example.com/inbox',
label: '163 邮箱',
}),
getState: async () => ({
kiroAuthTabId: 88,
kiroAuthorizedEmail: 'user@example.com',
kiroLoginUrl: 'https://device.example.com/complete',
kiroVerificationRequestedAt: 1700000000000,
mailProvider: '163',
}),
isTabAlive: async (source) => source === 'kiro-device-auth',
reuseOrCreateTab: async (source, url) => {
mailOpenCalls.push({ source, url });
return 66;
},
sendToContentScriptResilient: async (_source, message) => {
contentMessages.push(message);
if (message.type === 'ENSURE_KIRO_PAGE_STATE') {
ensureCallIndex += 1;
return {
ok: true,
state: ensureCallIndex === 1 ? 'otp_page' : 'password_page',
url: ensureCallIndex === 1
? 'https://device.example.com/verify'
: 'https://device.example.com/password',
};
}
if (message.type === 'EXECUTE_NODE') {
return {
ok: true,
submitted: true,
state: 'verification_submitted',
url: 'https://device.example.com/verify',
};
}
throw new Error(`Unexpected content message: ${message.type}`);
},
sendToMailContentScriptResilient: async (mail, message, options = {}) => {
mailPollCalls.push({ mail, message, options });
return {
ok: true,
code: '654321',
emailTimestamp: 1700000005000,
mailId: 'mail-1',
};
},
setState: async (updates) => {
stateUpdates.push(updates);
},
sleepWithStop: async () => {},
throwIfStopped: () => {},
waitForTabStableComplete: async () => {},
});
await executor.executeKiroSubmitVerificationCode({
nodeId: 'kiro-submit-verification-code',
kiroAuthTabId: 88,
kiroAuthorizedEmail: 'user@example.com',
kiroLoginUrl: 'https://device.example.com/complete',
kiroVerificationRequestedAt: 1700000000000,
mailProvider: '163',
});
assert.deepEqual(mailOpenCalls, [{
source: 'mail-163',
url: 'https://mail.example.com/inbox',
}]);
assert.equal(mailPollCalls.length, 1);
assert.equal(mailPollCalls[0].message.type, 'POLL_EMAIL');
assert.equal(mailPollCalls[0].message.payload.targetEmail, 'user@example.com');
assert.equal(mailPollCalls[0].message.payload.filterAfterTimestamp, 1700000000000);
assert.deepEqual(tabUpdates, [
{ tabId: 88, update: { active: true } },
{ tabId: 88, update: { active: true } },
]);
assert.equal(contentMessages.length, 3);
assert.deepEqual(contentMessages[0].payload.targetStates, ['otp_page']);
assert.equal(contentMessages[1].nodeId, 'kiro-submit-verification-code');
assert.deepEqual(contentMessages[1].payload, { code: '654321' });
assert.deepEqual(contentMessages[2].payload.targetStates, ['password_page']);
const finalState = mergeUpdates(stateUpdates);
assert.equal(finalState.kiroAuthError, '');
assert.equal(finalState.kiroUploadError, '');
assert.equal(completeCalls.length, 1);
assert.equal(completeCalls[0].nodeId, 'kiro-submit-verification-code');
assert.equal(completeCalls[0].payload.code, '654321');
assert.equal(completeCalls[0].payload.mailId, 'mail-1');
assert.equal(completeCalls[0].payload.kiroNextState, 'password_page');
});
test('kiro fill password reuses the shared password state and waits for the page to leave password state', async () => {
const api = loadKiroDeviceAuthApi();
const stateUpdates = [];
const completeCalls = [];
const contentMessages = [];
const savedPasswords = [];
const { chrome, updates: tabUpdates } = createChromeRecorder();
const executor = api.createKiroDeviceAuthExecutor({
addLog: async () => {},
chrome,
completeNodeFromBackground: async (nodeId, payload) => {
completeCalls.push({ nodeId, payload });
},
ensureContentScriptReadyOnTab: async () => {},
getState: async () => ({
kiroAuthTabId: 88,
kiroLoginUrl: 'https://device.example.com/complete',
customPassword: 'SharedPass123!',
}),
isTabAlive: async (source) => source === 'kiro-device-auth',
sendToContentScriptResilient: async (_source, message) => {
contentMessages.push(message);
if (message.type === 'ENSURE_KIRO_PAGE_STATE') {
return {
ok: true,
state: 'password_page',
url: 'https://device.example.com/password',
};
}
if (message.type === 'ENSURE_KIRO_STATE_CHANGE') {
return {
ok: true,
state: 'authorization_page',
url: 'https://device.example.com/authorize',
};
}
if (message.type === 'EXECUTE_NODE') {
return {
ok: true,
submitted: true,
state: 'password_submitted',
url: 'https://device.example.com/password',
};
}
throw new Error(`Unexpected content message: ${message.type}`);
},
setPasswordState: async (password) => {
savedPasswords.push(password);
},
setState: async (updates) => {
stateUpdates.push(updates);
},
sleepWithStop: async () => {},
throwIfStopped: () => {},
waitForTabStableComplete: async () => {},
});
await executor.executeKiroFillPassword({
nodeId: 'kiro-fill-password',
kiroAuthTabId: 88,
kiroLoginUrl: 'https://device.example.com/complete',
customPassword: 'SharedPass123!',
});
assert.deepEqual(savedPasswords, ['SharedPass123!']);
assert.deepEqual(tabUpdates, [{
tabId: 88,
update: { active: true },
}]);
assert.equal(contentMessages.length, 3);
assert.deepEqual(contentMessages[0].payload.targetStates, ['password_page']);
assert.equal(contentMessages[1].nodeId, 'kiro-fill-password');
assert.deepEqual(contentMessages[1].payload, { password: 'SharedPass123!' });
assert.deepEqual(contentMessages[2].payload.fromStates, ['password_page']);
const finalState = mergeUpdates(stateUpdates);
assert.equal(finalState.kiroAuthError, '');
assert.equal(finalState.kiroUploadError, '');
assert.equal(completeCalls.length, 1);
assert.equal(completeCalls[0].nodeId, 'kiro-fill-password');
assert.equal(completeCalls[0].payload.kiroNextState, 'authorization_page');
assert.equal(completeCalls[0].payload.kiroNextUrl, 'https://device.example.com/authorize');
});
test('kiro confirm access completes the authorization page and then polls until refresh token is captured', async () => {
const api = loadKiroDeviceAuthApi();
const fetchCalls = [];
const stateUpdates = [];
const sleepCalls = [];
const completeCalls = [];
const contentMessages = [];
const { chrome, updates: tabUpdates } = createChromeRecorder();
let pollCount = 0;
const executor = api.createKiroDeviceAuthExecutor({
addLog: async () => {},
chrome,
completeNodeFromBackground: async (nodeId, payload) => {
completeCalls.push({ nodeId, payload });
},
ensureContentScriptReadyOnTab: async () => {},
fetchImpl: async (url, options = {}) => {
fetchCalls.push({
url,
method: options.method || 'GET',
body: options.body ? JSON.parse(options.body) : null,
});
pollCount += 1;
if (pollCount === 1) {
return createResponse({
ok: false,
status: 400,
json: { error: 'authorization_pending' },
});
}
return createResponse({
ok: true,
status: 200,
json: {
accessToken: 'access-001',
refreshToken: 'refresh-001',
expiresIn: 3600,
},
});
},
getState: async () => ({
kiroAuthTabId: 88,
kiroClientId: 'client-001',
kiroClientSecret: 'secret-001',
kiroDeviceAuthorizationCode: 'device-code-001',
kiroAuthRegion: 'us-east-1',
kiroAuthExpiresAt: Date.now() + 60000,
kiroAuthIntervalSeconds: 5,
kiroLoginUrl: 'https://device.example.com/complete',
}),
isTabAlive: async (source) => source === 'kiro-device-auth',
sendToContentScriptResilient: async (_source, message) => {
contentMessages.push(message);
if (message.type === 'ENSURE_KIRO_PAGE_STATE') {
return {
ok: true,
state: 'authorization_page',
url: 'https://device.example.com/authorize',
};
}
if (message.type === 'EXECUTE_NODE') {
return {
ok: true,
submitted: true,
state: 'success_page',
url: 'https://device.example.com/success',
};
}
throw new Error(`Unexpected content message: ${message.type}`);
},
setState: async (updates) => {
stateUpdates.push(updates);
},
sleepWithStop: async (ms) => {
sleepCalls.push(ms);
},
throwIfStopped: () => {},
waitForTabStableComplete: async () => {},
});
await executor.executeKiroConfirmAccess({
nodeId: 'kiro-confirm-access',
kiroAuthTabId: 88,
kiroClientId: 'client-001',
kiroClientSecret: 'secret-001',
kiroDeviceAuthorizationCode: 'device-code-001',
kiroAuthRegion: 'us-east-1',
kiroAuthExpiresAt: Date.now() + 60000,
kiroAuthIntervalSeconds: 5,
kiroLoginUrl: 'https://device.example.com/complete',
});
assert.deepEqual(tabUpdates, [{
tabId: 88,
update: { active: true },
}]);
assert.equal(contentMessages.length, 2);
assert.deepEqual(contentMessages[0].payload.targetStates, ['authorization_page', 'success_page']);
assert.equal(contentMessages[1].nodeId, 'kiro-confirm-access');
assert.equal(fetchCalls.length, 2);
assert.equal(fetchCalls[0].url, 'https://oidc.us-east-1.amazonaws.com/token');
assert.deepEqual(fetchCalls[0].body, {
clientId: 'client-001',
clientSecret: 'secret-001',
grantType: 'urn:ietf:params:oauth:grant-type:device_code',
deviceCode: 'device-code-001',
});
assert.deepEqual(sleepCalls, [5000]);
const finalState = mergeUpdates(stateUpdates);
assert.equal(finalState.kiroAuthStatus, 'authorized');
assert.equal(finalState.kiroRefreshToken, 'refresh-001');
assert.equal(finalState.kiroAccessToken, 'access-001');
assert.equal(finalState.kiroUploadStatus, 'ready_to_upload');
assert.equal(completeCalls.length, 1);
assert.equal(completeCalls[0].nodeId, 'kiro-confirm-access');
assert.equal(completeCalls[0].payload.kiroRefreshToken, 'refresh-001');
assert.equal(completeCalls[0].payload.kiroNextState, 'success_page');
assert.equal(completeCalls[0].payload.kiroNextUrl, 'https://device.example.com/success');
});
test('kiro upload credential checks connection and uploads builder id credential to kiro.rs', async () => {
const api = loadKiroDeviceAuthApi();
const fetchCalls = [];
const stateUpdates = [];
const completeCalls = [];
const executor = api.createKiroDeviceAuthExecutor({
addLog: async () => {},
completeNodeFromBackground: async (nodeId, payload) => {
completeCalls.push({ nodeId, payload });
},
fetchImpl: async (url, options = {}) => {
fetchCalls.push({
url,
method: options.method || 'GET',
headers: options.headers || {},
body: options.body ? JSON.parse(options.body) : null,
});
if (options.method === 'GET') {
return createResponse({
ok: true,
status: 200,
json: { success: true },
});
}
return createResponse({
ok: true,
status: 200,
json: {
success: true,
message: 'uploaded',
credentialId: 321,
email: 'aws-user@example.com',
},
});
},
getState: async () => ({
kiroRefreshToken: 'refresh-001',
kiroClientId: 'client-001',
kiroClientSecret: 'secret-001',
kiroAuthRegion: 'ap-southeast-1',
kiroAuthorizedEmail: 'cached@example.com',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'admin-key-001',
ipProxyEnabled: true,
ipProxyProtocol: 'socks5',
ipProxyHost: '127.0.0.1',
ipProxyPort: '1080',
ipProxyUsername: 'proxy-user',
ipProxyPassword: 'proxy-pass',
}),
setState: async (updates) => {
stateUpdates.push(updates);
},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
await executor.executeKiroUploadCredential({
nodeId: 'kiro-upload-credential',
kiroRefreshToken: 'refresh-001',
kiroClientId: 'client-001',
kiroClientSecret: 'secret-001',
kiroAuthRegion: 'ap-southeast-1',
kiroAuthorizedEmail: 'cached@example.com',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'admin-key-001',
ipProxyEnabled: true,
ipProxyProtocol: 'socks5',
ipProxyHost: '127.0.0.1',
ipProxyPort: '1080',
ipProxyUsername: 'proxy-user',
ipProxyPassword: 'proxy-pass',
});
assert.equal(fetchCalls.length, 2);
assert.equal(fetchCalls[0].url, 'https://kiro.example.com/api/admin/credentials');
assert.equal(fetchCalls[0].method, 'GET');
assert.equal(fetchCalls[0].headers['x-api-key'], 'admin-key-001');
assert.equal(fetchCalls[1].url, 'https://kiro.example.com/api/admin/credentials');
assert.equal(fetchCalls[1].method, 'POST');
assert.equal(fetchCalls[1].headers['x-api-key'], 'admin-key-001');
assert.deepEqual(fetchCalls[1].body, {
refreshToken: 'refresh-001',
clientId: 'client-001',
clientSecret: 'secret-001',
region: 'ap-southeast-1',
email: 'cached@example.com',
priority: 0,
authMethod: 'IdC',
provider: 'BuilderId',
proxyUrl: 'socks5://127.0.0.1:1080',
proxyUsername: 'proxy-user',
proxyPassword: 'proxy-pass',
});
const finalState = mergeUpdates(stateUpdates);
assert.equal(finalState.kiroLastConnectionMessage, 'kiro.rs 连接正常(HTTP 200');
assert.equal(finalState.kiroAuthorizedEmail, 'aws-user@example.com');
assert.equal(finalState.kiroCredentialId, 321);
assert.equal(finalState.kiroUploadStatus, '上传成功');
assert.equal(typeof finalState.kiroLastUploadAt, 'number');
assert.equal(finalState.kiroLastUploadAt > 0, true);
assert.equal(completeCalls.length, 1);
assert.equal(completeCalls[0].nodeId, 'kiro-upload-credential');
assert.equal(completeCalls[0].payload.kiroCredentialId, 321);
assert.equal(completeCalls[0].payload.kiroUploadStatus, '上传成功');
});
@@ -0,0 +1,66 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadPublisherApi() {
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
const publisherSource = fs.readFileSync('background/kiro/publisher-kiro-rs.js', 'utf8');
const globalScope = {};
new Function('self', `${stateSource}; ${publisherSource}; return self;`)(globalScope);
return globalScope.MultiPageBackgroundKiroPublisherKiroRs;
}
test('kiro publisher exposes a factory and upload payload helpers', () => {
const api = loadPublisherApi();
assert.equal(typeof api?.createKiroRsPublisher, 'function');
assert.equal(typeof api?.buildKiroRsPayload, 'function');
assert.equal(typeof api?.buildMachineId, 'function');
});
test('kiro publisher builds kiro.rs payload from desktop auth runtime without profileArn', async () => {
const api = loadPublisherApi();
const payload = api.buildKiroRsPayload({
kiroTargetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'demo-key',
ipProxyEnabled: true,
ipProxyHost: '1.2.3.4',
ipProxyPort: '8080',
ipProxyProtocol: 'http',
ipProxyUsername: 'proxy-user',
ipProxyPassword: 'proxy-pass',
kiroRuntime: {
register: {
email: 'aws-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-token-001',
},
upload: {
targetId: 'kiro-rs',
},
},
});
const machineId = await api.buildMachineId('refresh-token-001');
assert.deepEqual(payload, {
targetId: 'kiro-rs',
region: 'us-east-1',
email: 'aws-user@example.com',
refreshToken: 'refresh-token-001',
clientId: 'client-001',
clientSecret: 'secret-001',
authMethod: 'idc',
authRegion: 'us-east-1',
apiRegion: 'us-east-1',
proxyUrl: 'http://1.2.3.4:8080',
proxyUsername: 'proxy-user',
proxyPassword: 'proxy-pass',
});
assert.equal(machineId.length, 64);
assert.match(machineId, /^[0-9a-f]{64}$/);
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'profileArn'), false);
});
@@ -0,0 +1,59 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadRegisterRunnerApi() {
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
const runnerSource = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
const globalScope = {};
new Function('self', `${stateSource}; ${runnerSource}; return self;`)(globalScope);
return globalScope.MultiPageBackgroundKiroRegisterRunner;
}
test('kiro register runner module exposes a factory and device login bootstrap helper', () => {
const api = loadRegisterRunnerApi();
assert.equal(typeof api?.createKiroRegisterRunner, 'function');
assert.equal(typeof api?.startBuilderIdDeviceLogin, 'function');
});
test('startBuilderIdDeviceLogin registers Builder ID client and returns login bootstrap payload', async () => {
const api = loadRegisterRunnerApi();
const requests = [];
const result = await api.startBuilderIdDeviceLogin('us-east-1', async (url, options = {}) => {
requests.push({ url, options });
if (url.endsWith('/client/register')) {
return {
ok: true,
text: async () => JSON.stringify({
clientId: 'client-001',
clientSecret: 'secret-001',
}),
};
}
if (url.endsWith('/device_authorization')) {
return {
ok: true,
text: async () => JSON.stringify({
deviceCode: 'device-code-001',
userCode: 'ABCD-1234',
verificationUri: 'https://view.awsapps.com/start',
verificationUriComplete: 'https://view.awsapps.com/start?user_code=ABCD-1234',
interval: 7,
expiresIn: 600,
}),
};
}
throw new Error(`Unexpected request: ${url}`);
});
assert.equal(requests.length, 2);
assert.equal(requests[0].url, 'https://oidc.us-east-1.amazonaws.com/client/register');
assert.equal(requests[1].url, 'https://oidc.us-east-1.amazonaws.com/device_authorization');
assert.equal(result.clientId, 'client-001');
assert.equal(result.clientSecret, 'secret-001');
assert.equal(result.deviceCode, 'device-code-001');
assert.equal(result.userCode, 'ABCD-1234');
assert.equal(result.verificationUriComplete, 'https://view.awsapps.com/start?user_code=ABCD-1234');
assert.equal(result.interval, 7);
assert.equal(result.region, 'us-east-1');
});
+141
View File
@@ -0,0 +1,141 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadKiroStateApi() {
const source = fs.readFileSync('background/kiro/state.js', 'utf8');
const globalScope = {};
return new Function('self', `${source}; return self.MultiPageBackgroundKiroState;`)(globalScope);
}
test('background imports kiro state module and routes Kiro runtime through dedicated helpers', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /background\/kiro\/state\.js/);
assert.match(source, /const kiroStateHelpers = self\.MultiPageBackgroundKiroState/);
assert.match(source, /kiroStateHelpers\?\.buildStateView/);
assert.match(source, /kiroStateHelpers\?\.buildSessionStatePatch/);
assert.match(source, /kiroStateHelpers\?\.buildDownstreamResetPatch/);
assert.match(source, /kiroStateHelpers\?\.applyNodeCompletionPayload/);
});
test('kiro state module exposes canonical nested kiroRuntime view', () => {
const api = loadKiroStateApi();
const view = api.buildStateView({
kiroTargetId: 'kiro-rs',
kiroRuntime: {
session: {
currentStage: 'desktop-authorize',
registerTabId: 88,
pageState: 'name_entry',
pageUrl: 'https://view.awsapps.com/start',
},
register: {
email: 'aws-user@example.com',
fullName: 'Ada Lovelace',
userCode: 'ABCD-1234',
loginUrl: 'https://device.example.com/complete',
status: 'waiting_name',
},
desktopAuth: {
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-001',
status: 'authorized',
},
upload: {
targetId: 'kiro-rs',
status: 'ready_to_upload',
credentialId: 321,
},
},
});
assert.equal(view.kiroTargetId, 'kiro-rs');
assert.equal(view.kiroRuntime.session.currentStage, 'desktop-authorize');
assert.equal(view.kiroRuntime.session.registerTabId, 88);
assert.equal(view.kiroRuntime.register.email, 'aws-user@example.com');
assert.equal(view.kiroRuntime.register.userCode, 'ABCD-1234');
assert.equal(view.kiroRuntime.desktopAuth.clientId, 'client-001');
assert.equal(view.kiroRuntime.desktopAuth.refreshToken, 'refresh-001');
assert.equal(view.kiroRuntime.upload.status, 'ready_to_upload');
assert.equal(view.kiroRuntime.upload.credentialId, 321);
});
test('kiro state session patch accepts canonical nested runtime updates', () => {
const api = loadKiroStateApi();
const patch = api.buildSessionStatePatch({
kiroRuntime: api.buildDefaultRuntimeState(),
}, {
kiroRuntime: {
session: {
currentStage: 'register',
pageState: 'otp_page',
pageUrl: 'https://signin.aws/register',
},
register: {
email: 'aws-user@example.com',
fullName: 'Ada Lovelace',
verificationRequestedAt: 1700000000000,
},
desktopAuth: {
status: 'waiting_callback',
},
upload: {
status: 'waiting_register',
},
},
});
assert.equal(patch.kiroRuntime.session.currentStage, 'register');
assert.equal(patch.kiroRuntime.session.pageState, 'otp_page');
assert.equal(patch.kiroRuntime.session.pageUrl, 'https://signin.aws/register');
assert.equal(patch.kiroRuntime.register.email, 'aws-user@example.com');
assert.equal(patch.kiroRuntime.register.fullName, 'Ada Lovelace');
assert.equal(patch.kiroRuntime.register.verificationRequestedAt, 1700000000000);
assert.equal(patch.kiroRuntime.desktopAuth.status, 'waiting_callback');
assert.equal(patch.kiroRuntime.upload.status, 'waiting_register');
});
test('kiro state reset helpers clear downstream runtime and fresh keep-state preserves only target selection', () => {
const api = loadKiroStateApi();
const currentState = {
kiroTargetId: 'kiro-rs',
kiroRuntime: {
session: {
currentStage: 'upload',
registerTabId: 88,
},
register: {
email: 'aws-user@example.com',
fullName: 'Ada Lovelace',
status: 'completed',
},
desktopAuth: {
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-001',
status: 'authorized',
},
upload: {
targetId: 'kiro-rs',
status: 'uploaded',
credentialId: 321,
},
},
};
const resetPatch = api.buildDownstreamResetPatch('kiro-submit-email', currentState);
assert.equal(resetPatch.kiroRuntime.session.currentStage, 'register');
assert.equal(resetPatch.kiroRuntime.register.email, '');
assert.equal(resetPatch.kiroRuntime.register.fullName, '');
assert.equal(resetPatch.kiroRuntime.desktopAuth.refreshToken, '');
assert.equal(resetPatch.kiroRuntime.upload.status, '');
assert.equal(resetPatch.kiroRuntime.upload.credentialId, null);
const keepState = api.buildFreshKeepState(currentState);
assert.equal(keepState.kiroTargetId, 'kiro-rs');
assert.equal(keepState.kiroRuntime.register.email, '');
assert.equal(keepState.kiroRuntime.desktopAuth.refreshToken, '');
assert.equal(keepState.kiroRuntime.upload.status, '');
assert.equal(keepState.kiroRuntime.upload.targetId, 'kiro-rs');
});
@@ -361,7 +361,7 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
validations.push({ validations.push({
activeFlowId: validationState?.activeFlowId, activeFlowId: validationState?.activeFlowId,
flowId: validationState?.flowId, flowId: validationState?.flowId,
kiroSourceId: validationState?.kiroSourceId, kiroTargetId: validationState?.kiroTargetId,
optionActiveFlowId: options?.activeFlowId, optionActiveFlowId: options?.activeFlowId,
}); });
return { ok: true, errors: [] }; return { ok: true, errors: [] };
@@ -373,21 +373,21 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
payload: { payload: {
totalRuns: 1, totalRuns: 1,
activeFlowId: 'kiro', activeFlowId: 'kiro',
sourceId: 'kiro-rs', targetId: 'kiro-rs',
}, },
}); });
assert.equal(response.ok, true); assert.equal(response.ok, true);
assert.equal(state.activeFlowId, 'kiro'); assert.equal(state.activeFlowId, 'kiro');
assert.equal(state.flowId, 'kiro'); assert.equal(state.flowId, 'kiro');
assert.equal(state.kiroSourceId, 'kiro-rs'); assert.equal(state.kiroTargetId, 'kiro-rs');
assert.deepStrictEqual(calls, [ assert.deepStrictEqual(calls, [
{ {
type: 'setState', type: 'setState',
updates: { updates: {
activeFlowId: 'kiro', activeFlowId: 'kiro',
flowId: 'kiro', flowId: 'kiro',
kiroSourceId: 'kiro-rs', kiroTargetId: 'kiro-rs',
}, },
}, },
{ {
@@ -409,7 +409,7 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
{ {
activeFlowId: 'kiro', activeFlowId: 'kiro',
flowId: 'kiro', flowId: 'kiro',
kiroSourceId: 'kiro-rs', kiroTargetId: 'kiro-rs',
optionActiveFlowId: 'kiro', optionActiveFlowId: 'kiro',
}, },
]); ]);
@@ -147,7 +147,7 @@ test('runtime-state patch prefers explicit activeFlowId over stale legacy flowId
DEFAULT_ACTIVE_FLOW_ID: 'openai', DEFAULT_ACTIVE_FLOW_ID: 'openai',
defaultNodeStatuses: { defaultNodeStatuses: {
'open-chatgpt': 'pending', 'open-chatgpt': 'pending',
'kiro-start-device-login': 'pending', 'kiro-open-register-page': 'pending',
}, },
}); });
@@ -160,7 +160,7 @@ test('runtime-state patch prefers explicit activeFlowId over stale legacy flowId
}, { }, {
activeFlowId: 'kiro', activeFlowId: 'kiro',
nodeStatuses: { nodeStatuses: {
'kiro-start-device-login': 'running', 'kiro-open-register-page': 'running',
}, },
}); });
@@ -168,6 +168,6 @@ test('runtime-state patch prefers explicit activeFlowId over stale legacy flowId
assert.equal(patch.flowId, 'kiro'); assert.equal(patch.flowId, 'kiro');
assert.deepStrictEqual(patch.nodeStatuses, { assert.deepStrictEqual(patch.nodeStatuses, {
'open-chatgpt': 'pending', 'open-chatgpt': 'pending',
'kiro-start-device-login': 'running', 'kiro-open-register-page': 'running',
}); });
}); });
@@ -60,9 +60,8 @@ const DEFAULT_SUB2API_GROUP_NAMES = ['codex', 'openai-plus'];
const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([ const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
'activeFlowId', 'activeFlowId',
'openaiIntegrationTargetId', 'openaiIntegrationTargetId',
'kiroIntegrationTargetId',
'panelMode', 'panelMode',
'kiroSourceId', 'kiroTargetId',
'vpsUrl', 'vpsUrl',
'vpsPassword', 'vpsPassword',
'localCpaStep9Mode', 'localCpaStep9Mode',
@@ -101,7 +100,7 @@ const PERSISTED_SETTING_DEFAULTS = {
ipProxyEnabled: false, ipProxyEnabled: false,
ipProxyService: '711proxy', ipProxyService: '711proxy',
ipProxyMode: 'account', ipProxyMode: 'account',
kiroSourceId: 'kiro-rs', kiroTargetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.leftcode.xyz/admin', kiroRsUrl: 'https://kiro.leftcode.xyz/admin',
kiroRsKey: '', kiroRsKey: '',
stepExecutionRangeByFlow: {}, stepExecutionRangeByFlow: {},
@@ -194,15 +193,15 @@ test('buildPersistentSettingsPayload writes canonical settings schema into persi
}, { fillDefaults: true }); }, { fillDefaults: true });
assert.equal(payload.activeFlowId, 'kiro'); assert.equal(payload.activeFlowId, 'kiro');
assert.equal(payload.kiroSourceId, 'kiro-rs'); assert.equal(payload.kiroTargetId, 'kiro-rs');
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin'); assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(payload.kiroRsKey, 'secret-key'); assert.equal(payload.kiroRsKey, 'secret-key');
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false); assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
assert.equal(payload.settingsSchemaVersion, 4); assert.equal(payload.settingsSchemaVersion, 4);
assert.equal(payload.settingsState.activeFlowId, 'kiro'); assert.equal(payload.settingsState.activeFlowId, 'kiro');
assert.equal(payload.settingsState.flows.kiro.integrationTargetId, 'kiro-rs'); assert.equal(payload.settingsState.flows.kiro.targetId, 'kiro-rs');
assert.equal( assert.equal(
payload.settingsState.flows.kiro.integrationTargets['kiro-rs'].baseUrl, payload.settingsState.flows.kiro.targets['kiro-rs'].baseUrl,
'https://kiro.example.com/admin' 'https://kiro.example.com/admin'
); );
}); });
@@ -256,15 +255,15 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
}, },
}, },
kiro: { kiro: {
integrationTargetId: 'kiro-rs', targetId: 'kiro-rs',
integrationTargets: { targets: {
'kiro-rs': { 'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin', baseUrl: 'https://kiro.example.com/admin',
apiKey: 'schema-only-key', apiKey: 'schema-only-key',
}, },
}, },
autoRun: { autoRun: {
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 }, stepExecutionRange: { enabled: true, fromStep: 1, toStep: 9 },
}, },
}, },
}, },
@@ -272,7 +271,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
}, { requireKnownKeys: true }); }, { requireKnownKeys: true });
assert.equal(payload.activeFlowId, 'kiro'); assert.equal(payload.activeFlowId, 'kiro');
assert.equal(payload.kiroSourceId, 'kiro-rs'); assert.equal(payload.kiroTargetId, 'kiro-rs');
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin'); assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(payload.kiroRsKey, 'schema-only-key'); assert.equal(payload.kiroRsKey, 'schema-only-key');
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false); assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
@@ -357,15 +356,15 @@ const chrome = {
}, },
}, },
kiro: { kiro: {
integrationTargetId: 'kiro-rs', targetId: 'kiro-rs',
integrationTargets: { targets: {
'kiro-rs': { 'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin', baseUrl: 'https://kiro.example.com/admin',
apiKey: 'stored-key', apiKey: 'stored-key',
}, },
}, },
autoRun: { autoRun: {
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 }, stepExecutionRange: { enabled: true, fromStep: 1, toStep: 9 },
}, },
}, },
}, },
@@ -389,7 +388,7 @@ const chrome = {
assert.deepEqual(state.stepExecutionRangeByFlow.kiro, { assert.deepEqual(state.stepExecutionRangeByFlow.kiro, {
enabled: true, enabled: true,
fromStep: 1, fromStep: 1,
toStep: 7, toStep: 9,
}); });
}); });
@@ -466,15 +465,15 @@ function getRemovedKeys() {
}, },
}, },
kiro: { kiro: {
integrationTargetId: 'kiro-rs', targetId: 'kiro-rs',
integrationTargets: { targets: {
'kiro-rs': { 'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin', baseUrl: 'https://kiro.example.com/admin',
apiKey: 'nested-only-key', apiKey: 'nested-only-key',
}, },
}, },
autoRun: { autoRun: {
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 }, stepExecutionRange: { enabled: true, fromStep: 1, toStep: 9 },
}, },
}, },
}, },
@@ -484,6 +483,7 @@ function getRemovedKeys() {
const write = api.getPersistedWrites().at(-1); const write = api.getPersistedWrites().at(-1);
assert.equal(persisted.activeFlowId, 'kiro'); assert.equal(persisted.activeFlowId, 'kiro');
assert.equal(persisted.kiroTargetId, 'kiro-rs');
assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin'); assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(persisted.kiroRsKey, 'nested-only-key'); assert.equal(persisted.kiroRsKey, 'nested-only-key');
assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false); assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false);
@@ -494,7 +494,7 @@ function getRemovedKeys() {
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRegion'), false); assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRegion'), false);
assert.equal(write.settingsSchemaVersion, 4); assert.equal(write.settingsSchemaVersion, 4);
assert.equal(write.settingsState.activeFlowId, 'kiro'); assert.equal(write.settingsState.activeFlowId, 'kiro');
assert.equal(write.settingsState.flows.kiro.integrationTargetId, 'kiro-rs'); assert.equal(write.settingsState.flows.kiro.targetId, 'kiro-rs');
assert.ok(api.getRemovedKeys().includes('panelMode')); assert.ok(api.getRemovedKeys().includes('panelMode'));
assert.ok(api.getRemovedKeys().includes('kiroRsUrl')); assert.ok(api.getRemovedKeys().includes('kiroRsUrl'));
}); });
+8 -2
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict'); const assert = require('node:assert/strict');
const fs = require('node:fs'); const fs = require('node:fs');
test('background imports workflow step modules including Kiro device auth', () => { test('background imports workflow step modules including rebuilt Kiro modules', () => {
const source = fs.readFileSync('background.js', 'utf8'); const source = fs.readFileSync('background.js', 'utf8');
[ [
@@ -16,8 +16,14 @@ test('background imports workflow step modules including Kiro device auth', () =
'background/steps/fetch-login-code.js', 'background/steps/fetch-login-code.js',
'background/steps/confirm-oauth.js', 'background/steps/confirm-oauth.js',
'background/steps/platform-verify.js', 'background/steps/platform-verify.js',
'background/steps/kiro-device-auth.js', 'background/kiro/state.js',
'background/kiro/register-runner.js',
'background/kiro/desktop-client.js',
'background/kiro/desktop-authorize-runner.js',
'background/kiro/publisher-kiro-rs.js',
].forEach((path) => { ].forEach((path) => {
assert.match(source, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); assert.match(source, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
}); });
assert.doesNotMatch(source, /background\/steps\/kiro-device-auth\.js/);
}); });
@@ -8,21 +8,21 @@ test('background node registry preserves node metadata even before an executor i
const registry = api.createNodeRegistry([ const registry = api.createNodeRegistry([
{ {
flowId: 'kiro', flowId: 'kiro',
nodeId: 'kiro-start-device-login', nodeId: 'kiro-open-register-page',
displayOrder: 1, displayOrder: 1,
executeKey: 'kiro-start-device-login', executeKey: 'kiro-open-register-page',
title: 'Start device login', title: '打开注册页',
}, },
]); ]);
const node = registry.getNodeDefinition('kiro-start-device-login'); const node = registry.getNodeDefinition('kiro-open-register-page');
assert.equal(node.flowId, 'kiro'); assert.equal(node.flowId, 'kiro');
assert.equal(node.displayOrder, 1); assert.equal(node.displayOrder, 1);
assert.equal(node.title, 'Start device login'); assert.equal(node.title, '打开注册页');
assert.throws( assert.throws(
() => registry.executeNode('kiro-start-device-login', {}), () => registry.executeNode('kiro-open-register-page', {}),
/Missing node executor: kiro-start-device-login/ /Missing node executor: kiro-open-register-page/
); );
}); });
+26 -23
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict'); const assert = require('node:assert/strict');
const fs = require('node:fs'); const fs = require('node:fs');
test('background imports node registry and shared workflow definitions', () => { test('background imports node registry and wires the rebuilt Kiro executors', () => {
const source = fs.readFileSync('background.js', 'utf8'); const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /background\/steps\/registry\.js/); assert.match(source, /background\/steps\/registry\.js/);
assert.match(source, /data\/step-definitions\.js/); assert.match(source, /data\/step-definitions\.js/);
@@ -12,28 +12,31 @@ test('background imports node registry and shared workflow definitions', () => {
assert.match(source, /const stepRegistryCache = new Map\(\);/); assert.match(source, /const stepRegistryCache = new Map\(\);/);
assert.match(source, /const definitions = getNodeDefinitionsForState\(state\);/); assert.match(source, /const definitions = getNodeDefinitionsForState\(state\);/);
assert.match(source, /stepRegistryCache\.set\(cacheKey, buildStepRegistry\(definitions\)\)/); assert.match(source, /stepRegistryCache\.set\(cacheKey, buildStepRegistry\(definitions\)\)/);
assert.match(source, /'bind-email': \(state\) => step8Executor\.executeBindEmail\(state\)/);
assert.match(source, /'fetch-bind-email-code': \(state\) => step8Executor\.executeFetchBindEmailCode\(state\)/); assert.match(source, /background\/kiro\/register-runner\.js/);
assert.match(source, /'relogin-bound-email': \(state\) => executeReloginBoundEmail\(state\)/); assert.match(source, /background\/kiro\/desktop-client\.js/);
assert.match(source, /'fetch-bound-email-login-code': \(state\) => step8Executor\.executeBoundEmailLoginCode\(state\)/); assert.match(source, /background\/kiro\/desktop-authorize-runner\.js/);
assert.match(source, /'post-bound-email-phone-verification': \(state\) => step8Executor\.executeBoundEmailPostLoginPhoneVerification\(state\)/); assert.match(source, /background\/kiro\/publisher-kiro-rs\.js/);
assert.match(source, /background\/steps\/create-plus-checkout\.js/); assert.doesNotMatch(source, /background\/steps\/kiro-device-auth\.js/);
assert.match(source, /background\/steps\/fill-plus-checkout\.js/);
assert.match(source, /background\/steps\/gopay-manual-confirm\.js/); assert.match(source, /const kiroRegisterRunner = self\.MultiPageBackgroundKiroRegisterRunner\?\.createKiroRegisterRunner\(/);
assert.match(source, /'gopay-subscription-confirm': \(state\) => goPayManualConfirmExecutor\.executeGoPayManualConfirm\(state\)/); assert.match(source, /const kiroDesktopAuthorizeRunner = self\.MultiPageBackgroundKiroDesktopAuthorizeRunner\?\.createKiroDesktopAuthorizeRunner\(/);
assert.match(source, /background\/steps\/paypal-approve\.js/); assert.match(source, /const kiroPublisher = self\.MultiPageBackgroundKiroPublisherKiroRs\?\.createKiroRsPublisher\(/);
assert.match(source, /background\/steps\/gopay-approve\.js/);
assert.match(source, /background\/steps\/plus-return-confirm\.js/); assert.match(source, /'kiro-open-register-page': \(state\) => kiroRegisterRunner\.executeKiroOpenRegisterPage\(state\)/);
assert.match(source, /background\/steps\/kiro-device-auth\.js/); assert.match(source, /'kiro-submit-email': \(state\) => kiroRegisterRunner\.executeKiroSubmitEmail\(state\)/);
assert.match(source, /const kiroDeviceAuthExecutor = self\.MultiPageBackgroundKiroDeviceAuth\?\.createKiroDeviceAuthExecutor\(/); assert.match(source, /'kiro-submit-name': \(state\) => kiroRegisterRunner\.executeKiroSubmitName\(state\)/);
assert.match(source, /'kiro-start-device-login': \(state\) => kiroDeviceAuthExecutor\.executeKiroStartDeviceLogin\(state\)/); assert.match(source, /'kiro-submit-verification-code': \(state\) => kiroRegisterRunner\.executeKiroSubmitVerificationCode\(state\)/);
assert.match(source, /'kiro-submit-email': \(state\) => kiroDeviceAuthExecutor\.executeKiroSubmitEmail\(state\)/); assert.match(source, /'kiro-submit-password': \(state\) => kiroRegisterRunner\.executeKiroSubmitPassword\(state\)/);
assert.match(source, /'kiro-submit-name': \(state\) => kiroDeviceAuthExecutor\.executeKiroSubmitName\(state\)/); assert.match(source, /'kiro-complete-register-consent': \(state\) => kiroRegisterRunner\.executeKiroCompleteRegisterConsent\(state\)/);
assert.match(source, /'kiro-submit-verification-code': \(state\) => kiroDeviceAuthExecutor\.executeKiroSubmitVerificationCode\(state\)/); assert.match(source, /'kiro-start-desktop-authorize': \(state\) => kiroDesktopAuthorizeRunner\.executeKiroStartDesktopAuthorize\(state\)/);
assert.match(source, /'kiro-fill-password': \(state\) => kiroDeviceAuthExecutor\.executeKiroFillPassword\(state\)/); assert.match(source, /'kiro-complete-desktop-authorize': \(state\) => kiroDesktopAuthorizeRunner\.executeKiroCompleteDesktopAuthorize\(state\)/);
assert.match(source, /'kiro-confirm-access': \(state\) => kiroDeviceAuthExecutor\.executeKiroConfirmAccess\(state\)/); assert.match(source, /'kiro-upload-credential': \(state\) => kiroPublisher\.executeKiroUploadCredential\(state\)/);
assert.match(source, /'kiro-upload-credential': \(state\) => kiroDeviceAuthExecutor\.executeKiroUploadCredential\(state\)/);
assert.match(source, /'kiro-start-device-login',[\s\S]*'kiro-submit-email',[\s\S]*'kiro-submit-name',[\s\S]*'kiro-submit-verification-code',[\s\S]*'kiro-fill-password',[\s\S]*'kiro-confirm-access',[\s\S]*'kiro-upload-credential'/); assert.match(
source,
/'kiro-open-register-page',[\s\S]*'kiro-submit-email',[\s\S]*'kiro-submit-name',[\s\S]*'kiro-submit-verification-code',[\s\S]*'kiro-submit-password',[\s\S]*'kiro-complete-register-consent',[\s\S]*'kiro-start-desktop-authorize',[\s\S]*'kiro-complete-desktop-authorize',[\s\S]*'kiro-upload-credential'/
);
}); });
test('GoPay approve executor receives debugger click and manual OTP helpers', () => { test('GoPay approve executor receives debugger click and manual OTP helpers', () => {
+2 -2
View File
@@ -178,7 +178,7 @@ test('tab runtime replays retryable transport recovery hook and surfaces a local
getSourceLabel: () => 'Kiro 授权页', getSourceLabel: () => 'Kiro 授权页',
getState: async () => ({ getState: async () => ({
tabRegistry: { tabRegistry: {
'kiro-device-auth': { tabId: 9, ready: true }, 'kiro-register-page': { tabId: 9, ready: true },
}, },
sourceLastUrls: {}, sourceLastUrls: {},
}), }),
@@ -190,7 +190,7 @@ test('tab runtime replays retryable transport recovery hook and surfaces a local
}); });
await assert.rejects( await assert.rejects(
runtime.sendToContentScriptResilient('kiro-device-auth', { runtime.sendToContentScriptResilient('kiro-register-page', {
type: 'ENSURE_KIRO_PAGE_STATE', type: 'ENSURE_KIRO_PAGE_STATE',
payload: {}, payload: {},
}, { }, {
+6 -7
View File
@@ -73,7 +73,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
assert.equal(capabilityState.canUsePhoneSignup, false); assert.equal(capabilityState.canUsePhoneSignup, false);
assert.equal(capabilityState.effectiveSignupMethod, 'email'); assert.equal(capabilityState.effectiveSignupMethod, 'email');
assert.equal(capabilityState.panelMode, 'codex2api'); assert.equal(capabilityState.panelMode, 'codex2api');
assert.deepEqual(capabilityState.supportedIntegrationTargets, []); assert.deepEqual(capabilityState.supportedTargetIds, []);
}); });
test('flow capability registry exposes Kiro as an independent flow with its own visible groups', () => { test('flow capability registry exposes Kiro as an independent flow with its own visible groups', () => {
@@ -83,7 +83,7 @@ test('flow capability registry exposes Kiro as an independent flow with its own
const capabilityState = registry.resolveSidepanelCapabilities({ const capabilityState = registry.resolveSidepanelCapabilities({
state: { state: {
activeFlowId: 'kiro', activeFlowId: 'kiro',
kiroIntegrationTargetId: 'kiro-rs', kiroTargetId: 'kiro-rs',
openaiIntegrationTargetId: 'sub2api', openaiIntegrationTargetId: 'sub2api',
signupMethod: 'phone', signupMethod: 'phone',
plusModeEnabled: true, plusModeEnabled: true,
@@ -95,7 +95,7 @@ test('flow capability registry exposes Kiro as an independent flow with its own
assert.equal(capabilityState.canShowPhoneSettings, false); assert.equal(capabilityState.canShowPhoneSettings, false);
assert.equal(capabilityState.canShowPlusSettings, false); assert.equal(capabilityState.canShowPlusSettings, false);
assert.equal(capabilityState.effectiveSignupMethod, 'email'); assert.equal(capabilityState.effectiveSignupMethod, 'email');
assert.equal(capabilityState.effectiveIntegrationTargetId, 'kiro-rs'); assert.equal(capabilityState.effectiveTargetId, 'kiro-rs');
assert.deepEqual( assert.deepEqual(
capabilityState.visibleGroupIds, capabilityState.visibleGroupIds,
['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy'] ['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
@@ -109,7 +109,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
openai: api.FLOW_CAPABILITIES.openai, openai: api.FLOW_CAPABILITIES.openai,
'site-a': { 'site-a': {
...api.DEFAULT_FLOW_CAPABILITIES, ...api.DEFAULT_FLOW_CAPABILITIES,
supportedIntegrationTargets: ['cpa'], supportedTargetIds: ['cpa'],
}, },
}, },
}); });
@@ -147,7 +147,7 @@ test('flow capability registry normalizes unsupported mode switches back to the
openai: api.FLOW_CAPABILITIES.openai, openai: api.FLOW_CAPABILITIES.openai,
'site-a': { 'site-a': {
...api.DEFAULT_FLOW_CAPABILITIES, ...api.DEFAULT_FLOW_CAPABILITIES,
supportedIntegrationTargets: ['cpa'], supportedTargetIds: ['cpa'],
}, },
}, },
}); });
@@ -174,8 +174,7 @@ test('flow capability registry normalizes unsupported mode switches back to the
assert.deepEqual(validation.normalizedUpdates, { assert.deepEqual(validation.normalizedUpdates, {
panelMode: 'cpa', panelMode: 'cpa',
openaiIntegrationTargetId: 'cpa', openaiIntegrationTargetId: 'cpa',
kiroIntegrationTargetId: 'cpa', kiroTargetId: 'cpa',
kiroSourceId: 'cpa',
signupMethod: 'email', signupMethod: 'email',
phoneVerificationEnabled: false, phoneVerificationEnabled: false,
plusModeEnabled: false, plusModeEnabled: false,
+11 -12
View File
@@ -13,15 +13,15 @@ function loadApis() {
};`)(scope); };`)(scope);
} }
test('flow registry exposes canonical flow and integration target metadata', () => { test('flow registry exposes canonical flow and target metadata', () => {
const { flowRegistry } = loadApis(); const { flowRegistry } = loadApis();
assert.deepEqual(flowRegistry.getRegisteredFlowIds(), ['openai', 'kiro']); assert.deepEqual(flowRegistry.getRegisteredFlowIds(), ['openai', 'kiro']);
assert.equal(flowRegistry.normalizeFlowId('kiro'), 'kiro'); assert.equal(flowRegistry.normalizeFlowId('kiro'), 'kiro');
assert.equal(flowRegistry.normalizeFlowId('unknown'), 'openai'); assert.equal(flowRegistry.normalizeFlowId('unknown'), 'openai');
assert.equal(flowRegistry.getFlowLabel('openai'), 'Codex / OpenAI'); assert.equal(flowRegistry.getFlowLabel('openai'), 'Codex / OpenAI');
assert.equal(flowRegistry.normalizeIntegrationTargetId('openai', 'sub2api'), 'sub2api'); assert.equal(flowRegistry.normalizeTargetId('openai', 'sub2api'), 'sub2api');
assert.equal(flowRegistry.normalizeIntegrationTargetId('kiro', 'anything-else'), 'kiro-rs'); assert.equal(flowRegistry.normalizeTargetId('kiro', 'anything-else'), 'kiro-rs');
assert.deepEqual( assert.deepEqual(
flowRegistry.getVisibleGroupIds('openai', 'cpa'), flowRegistry.getVisibleGroupIds('openai', 'cpa'),
['openai-plus', 'openai-phone', 'openai-oauth', 'openai-step6', 'openai-target-cpa', 'service-account', 'service-email', 'service-proxy'] ['openai-plus', 'openai-phone', 'openai-oauth', 'openai-step6', 'openai-target-cpa', 'service-account', 'service-email', 'service-proxy']
@@ -31,7 +31,7 @@ test('flow registry exposes canonical flow and integration target metadata', ()
['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy'] ['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
); );
assert.deepEqual( assert.deepEqual(
flowRegistry.getIntegrationTargetOptions('openai').map((entry) => entry.id), flowRegistry.getTargetOptions('openai').map((entry) => entry.id),
['cpa', 'sub2api', 'codex2api'] ['cpa', 'sub2api', 'codex2api']
); );
assert.equal(flowRegistry.getPublicationTargetDefinition('kiro', 'kiro-rs')?.label, 'kiro.rs'); assert.equal(flowRegistry.getPublicationTargetDefinition('kiro', 'kiro-rs')?.label, 'kiro.rs');
@@ -52,7 +52,7 @@ test('settings schema normalizes view input into canonical nested namespaces', (
kiroRsKey: 'secret-key', kiroRsKey: 'secret-key',
stepExecutionRangeByFlow: { stepExecutionRangeByFlow: {
openai: { enabled: true, fromStep: 2, toStep: 9 }, openai: { enabled: true, fromStep: 2, toStep: 9 },
kiro: { enabled: true, fromStep: 1, toStep: 7 }, kiro: { enabled: true, fromStep: 1, toStep: 9 },
}, },
}); });
@@ -61,13 +61,13 @@ test('settings schema normalizes view input into canonical nested namespaces', (
assert.equal(normalized.services.proxy.enabled, true); assert.equal(normalized.services.proxy.enabled, true);
assert.equal(normalized.services.account.customPassword, 'SharedSecret123!'); assert.equal(normalized.services.account.customPassword, 'SharedSecret123!');
assert.equal(normalized.flows.openai.integrationTargetId, 'sub2api'); assert.equal(normalized.flows.openai.integrationTargetId, 'sub2api');
assert.equal(normalized.flows.kiro.integrationTargetId, 'kiro-rs'); assert.equal(normalized.flows.kiro.targetId, 'kiro-rs');
assert.equal(normalized.flows.kiro.integrationTargets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin'); assert.equal(normalized.flows.kiro.targets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin');
assert.equal(normalized.flows.kiro.integrationTargets['kiro-rs'].apiKey, 'secret-key'); assert.equal(normalized.flows.kiro.targets['kiro-rs'].apiKey, 'secret-key');
assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, { assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, {
enabled: true, enabled: true,
fromStep: 1, fromStep: 1,
toStep: 7, toStep: 9,
}); });
}); });
@@ -76,7 +76,7 @@ test('settings schema can project canonical state into a read view without legac
const schema = settingsSchema.createSettingsSchema(); const schema = settingsSchema.createSettingsSchema();
const normalized = schema.normalizeSettingsState({ const normalized = schema.normalizeSettingsState({
activeFlowId: 'kiro', activeFlowId: 'kiro',
kiroIntegrationTargetId: 'kiro-rs', kiroTargetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example.com/admin', kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'key-123', kiroRsKey: 'key-123',
}); });
@@ -84,9 +84,8 @@ test('settings schema can project canonical state into a read view without legac
assert.equal(view.activeFlowId, 'kiro'); assert.equal(view.activeFlowId, 'kiro');
assert.equal(view.openaiIntegrationTargetId, 'cpa'); assert.equal(view.openaiIntegrationTargetId, 'cpa');
assert.equal(view.kiroIntegrationTargetId, 'kiro-rs'); assert.equal(view.kiroTargetId, 'kiro-rs');
assert.equal(view.panelMode, 'cpa'); assert.equal(view.panelMode, 'cpa');
assert.equal(view.kiroSourceId, 'kiro-rs');
assert.equal(view.kiroRsUrl, 'https://kiro.example.com/admin'); assert.equal(view.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(view.kiroRsKey, 'key-123'); assert.equal(view.kiroRsKey, 'key-123');
assert.equal(view.settingsSchemaVersion, 4); assert.equal(view.settingsSchemaVersion, 4);
@@ -68,7 +68,7 @@ const latestState = {
activeFlowId: 'openai', activeFlowId: 'openai',
flowId: 'openai', flowId: 'openai',
panelMode: 'cpa', panelMode: 'cpa',
kiroSourceId: 'kiro-rs', kiroTargetId: 'kiro-rs',
}; };
const inputAutoSkipFailures = { checked: false }; const inputAutoSkipFailures = { checked: false };
const inputContributionNickname = { value: 'tester' }; const inputContributionNickname = { value: 'tester' };
@@ -118,10 +118,10 @@ function normalizePanelMode(value = '', fallback = 'cpa') {
function getSelectedFlowId(state = latestState) { function getSelectedFlowId(state = latestState) {
return String(selectFlow.value || state.activeFlowId || state.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID; return String(selectFlow.value || state.activeFlowId || state.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
} }
function getSelectedSourceId(flowId = getSelectedFlowId()) { function getSelectedTargetId(flowId = getSelectedFlowId()) {
return String( return String(
flowId === 'kiro' flowId === 'kiro'
? (selectPanelMode.value || latestState.kiroSourceId || 'kiro-rs') ? (selectPanelMode.value || latestState.kiroTargetId || 'kiro-rs')
: normalizePanelMode(selectPanelMode.value || latestState.panelMode || 'cpa') : normalizePanelMode(selectPanelMode.value || latestState.panelMode || 'cpa')
).trim().toLowerCase() || (flowId === 'kiro' ? 'kiro-rs' : 'cpa'); ).trim().toLowerCase() || (flowId === 'kiro' ? 'kiro-rs' : 'cpa');
} }
@@ -225,7 +225,7 @@ test('startAutoRunFromCurrentSettings sends current flow selection with auto run
selectPanelMode.value = 'kiro-rs'; selectPanelMode.value = 'kiro-rs';
latestState.activeFlowId = 'openai'; latestState.activeFlowId = 'openai';
latestState.flowId = 'openai'; latestState.flowId = 'openai';
latestState.kiroSourceId = 'kiro-rs'; latestState.kiroTargetId = 'kiro-rs';
events.push({ type: 'flow-switch-race' }); events.push({ type: 'flow-switch-race' });
}`, }`,
}); });
@@ -235,7 +235,7 @@ test('startAutoRunFromCurrentSettings sends current flow selection with auto run
assert.equal(result, true); assert.equal(result, true);
assert.equal(sendEvent.message.payload.activeFlowId, 'kiro'); assert.equal(sendEvent.message.payload.activeFlowId, 'kiro');
assert.equal(sendEvent.message.payload.sourceId, 'kiro-rs'); assert.equal(sendEvent.message.payload.targetId, 'kiro-rs');
}); });
test('startAutoRunFromCurrentSettings blocks when shared flow capability validation fails', async () => { test('startAutoRunFromCurrentSettings blocks when shared flow capability validation fails', async () => {
+5 -5
View File
@@ -184,14 +184,14 @@ function normalizePanelMode(value = '', fallback = 'cpa') {
function getSelectedFlowId() { function getSelectedFlowId() {
return latestState.activeFlowId; return latestState.activeFlowId;
} }
function getSelectedSourceId() { function getSelectedTargetId() {
return 'cpa'; return 'cpa';
} }
function renderFlowSelectorOptions(flowId) { function renderFlowSelectorOptions(flowId) {
calls.push({ type: 'render-flow', flowId }); calls.push({ type: 'render-flow', flowId });
} }
function renderSourceSelectorOptions(flowId, sourceId) { function renderTargetSelectorOptions(flowId, targetId) {
calls.push({ type: 'render-source', flowId, sourceId }); calls.push({ type: 'render-target', flowId, targetId });
} }
function applyFlowSettingsGroupVisibility(visibleGroupIds) { function applyFlowSettingsGroupVisibility(visibleGroupIds) {
calls.push({ type: 'groups', visibleGroupIds: [...visibleGroupIds] }); calls.push({ type: 'groups', visibleGroupIds: [...visibleGroupIds] });
@@ -207,7 +207,7 @@ function resolveCurrentSidepanelCapabilities() {
visibleGroupIds: ['service-account', 'openai-plus', 'openai-phone'], visibleGroupIds: ['service-account', 'openai-plus', 'openai-phone'],
effectivePanelMode: 'cpa', effectivePanelMode: 'cpa',
panelMode: 'cpa', panelMode: 'cpa',
effectiveSourceId: 'cpa', effectiveTargetId: 'cpa',
}; };
} }
const document = { const document = {
@@ -228,7 +228,7 @@ return {
assert.deepEqual( assert.deepEqual(
api.calls.map((entry) => entry.type), api.calls.map((entry) => entry.type),
['render-flow', 'render-source', 'groups', 'plus', 'phone'] ['render-flow', 'render-target', 'groups', 'plus', 'phone']
); );
assert.equal(api.selectFlow.value, 'openai'); assert.equal(api.selectFlow.value, 'openai');
assert.equal(api.selectPanelMode.value, 'cpa'); assert.equal(api.selectPanelMode.value, 'cpa');
+45 -73
View File
@@ -2,6 +2,14 @@ const test = require('node:test');
const assert = require('node:assert/strict'); const assert = require('node:assert/strict');
const fs = require('node:fs'); const fs = require('node:fs');
function loadSourceRegistry() {
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const sourceRegistrySource = fs.readFileSync('shared/source-registry.js', 'utf8');
const globalScope = {};
new Function('self', `${flowRegistrySource}; ${sourceRegistrySource}; return self;`)(globalScope);
return globalScope.MultiPageSourceRegistry.createSourceRegistry();
}
test('background imports shared source registry module', () => { test('background imports shared source registry module', () => {
const source = fs.readFileSync('background.js', 'utf8'); const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /shared\/flow-registry\.js/); assert.match(source, /shared\/flow-registry\.js/);
@@ -22,42 +30,24 @@ test('manifest loads shared source registry before content utils in static bundl
} }
}); });
test('manifest ships a static Kiro auth bundle for cross-page recovery', () => { test('manifest no longer ships a static Kiro content bundle', () => {
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8')); const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
const kiroEntry = (manifest.content_scripts || []).find((entry) => { const hasStaticKiroBundle = (manifest.content_scripts || []).some((entry) => {
const matches = Array.isArray(entry.matches) ? entry.matches : []; const scripts = Array.isArray(entry.js) ? entry.js : [];
return matches.includes('https://view.awsapps.com/*') return scripts.includes('content/kiro/register-page.js')
&& matches.includes('https://signin.aws/*') || scripts.includes('content/kiro/desktop-authorize-page.js');
&& matches.includes('https://signin.aws.amazon.com/*')
&& matches.includes('https://profile.aws/*')
&& matches.includes('https://profile.aws.amazon.com/*');
}); });
assert.ok(kiroEntry, 'missing static Kiro auth content script entry'); assert.equal(hasStaticKiroBundle, false);
assert.deepEqual(kiroEntry.js, [
'shared/source-registry.js',
'content/utils.js',
'content/kiro-device-auth-page.js',
]);
}); });
test('shared source registry exposes canonical source, alias, detection, and ready policies', () => { test('shared source registry exposes canonical Kiro sources and drivers', () => {
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8'); const registry = loadSourceRegistry();
const source = fs.readFileSync('shared/source-registry.js', 'utf8');
const api = new Function('self', `${flowRegistrySource}; ${source}; return self.MultiPageSourceRegistry;`)({});
const registry = api.createSourceRegistry();
assert.equal(registry.resolveCanonicalSource('signup-page'), 'openai-auth'); assert.equal(registry.resolveCanonicalSource('signup-page'), 'openai-auth');
assert.deepEqual(registry.getSourceKeys('signup-page'), ['openai-auth', 'signup-page']); assert.deepEqual(registry.getSourceKeys('signup-page'), ['openai-auth', 'signup-page']);
assert.equal(registry.getSourceLabel('openai-auth'), '认证页'); assert.equal(registry.getSourceLabel('openai-auth'), '认证页');
assert.equal(
registry.matchesSourceUrlFamily(
'openai-auth',
'https://chatgpt.com/',
'https://auth.openai.com/authorize?client_id=test'
),
true
);
assert.equal( assert.equal(
registry.detectSourceFromLocation({ registry.detectSourceFromLocation({
url: 'https://auth.openai.com/create-account', url: 'https://auth.openai.com/create-account',
@@ -65,6 +55,21 @@ test('shared source registry exposes canonical source, alias, detection, and rea
}), }),
'openai-auth' 'openai-auth'
); );
assert.equal(
registry.detectSourceFromLocation({
url: 'https://view.awsapps.com/start',
hostname: 'view.awsapps.com',
}),
'kiro-register-page'
);
assert.equal(
registry.detectSourceFromLocation({
injectedSource: 'kiro-desktop-authorize',
url: 'https://signin.aws/register',
hostname: 'signin.aws',
}),
'kiro-desktop-authorize'
);
assert.equal( assert.equal(
registry.detectSourceFromLocation({ registry.detectSourceFromLocation({
url: 'https://example.com/', url: 'https://example.com/',
@@ -72,29 +77,10 @@ test('shared source registry exposes canonical source, alias, detection, and rea
}), }),
'unknown-source' 'unknown-source'
); );
assert.equal(registry.detectSourceFromLocation({
url: 'https://view.awsapps.com/start',
hostname: 'view.awsapps.com',
}), 'kiro-device-auth');
assert.equal(registry.detectSourceFromLocation({
url: 'https://signin.aws/register',
hostname: 'signin.aws',
}), 'kiro-device-auth');
assert.equal(registry.detectSourceFromLocation({
url: 'https://profile.aws/complete',
hostname: 'profile.aws',
}), 'kiro-device-auth');
assert.equal(registry.detectSourceFromLocation({
url: 'https://signin.aws.amazon.com/register',
hostname: 'signin.aws.amazon.com',
}), 'kiro-device-auth');
assert.equal(registry.detectSourceFromLocation({
url: 'https://profile.aws.amazon.com/complete',
hostname: 'profile.aws.amazon.com',
}), 'kiro-device-auth');
assert.equal( assert.equal(
registry.matchesSourceUrlFamily( registry.matchesSourceUrlFamily(
'kiro-device-auth', 'kiro-register-page',
'https://signin.aws/register', 'https://signin.aws/register',
'https://view.awsapps.com/start' 'https://view.awsapps.com/start'
), ),
@@ -102,7 +88,7 @@ test('shared source registry exposes canonical source, alias, detection, and rea
); );
assert.equal( assert.equal(
registry.matchesSourceUrlFamily( registry.matchesSourceUrlFamily(
'kiro-device-auth', 'kiro-desktop-authorize',
'https://profile.aws/complete', 'https://profile.aws/complete',
'https://signin.aws/register' 'https://signin.aws/register'
), ),
@@ -110,38 +96,24 @@ test('shared source registry exposes canonical source, alias, detection, and rea
); );
assert.equal( assert.equal(
registry.matchesSourceUrlFamily( registry.matchesSourceUrlFamily(
'kiro-device-auth', 'kiro-desktop-authorize',
'https://profile.aws.amazon.com/complete',
'https://signin.aws.amazon.com/register'
),
true
);
assert.equal(
registry.matchesSourceUrlFamily(
'kiro-device-auth',
'https://oidc.us-east-1.amazonaws.com/authorize', 'https://oidc.us-east-1.amazonaws.com/authorize',
'https://view.awsapps.com/start' 'https://view.awsapps.com/start'
), ),
true true
); );
assert.equal(registry.shouldReportReadyForFrame('mail-163', true), false); assert.equal(registry.shouldReportReadyForFrame('mail-163', true), false);
assert.equal(registry.shouldReportReadyForFrame('unknown-source', false), false); assert.equal(registry.shouldReportReadyForFrame('kiro-register-page', true), false);
assert.equal(registry.shouldReportReadyForFrame('kiro-desktop-authorize', true), false);
assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth'); assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth');
assert.equal(registry.driverAcceptsCommand('openai-auth', 'submit-signup-email'), true); assert.equal(registry.driverAcceptsCommand('openai-auth', 'submit-signup-email'), true);
assert.equal(registry.driverAcceptsCommand('openai-auth', 'post-login-phone-verification'), true);
assert.equal(registry.driverAcceptsCommand('openai-auth', 'bind-email'), true);
assert.equal(registry.driverAcceptsCommand('openai-auth', 'fetch-bind-email-code'), true);
assert.equal(registry.driverAcceptsCommand('content/platform-panel', 'platform-verify'), true); assert.equal(registry.driverAcceptsCommand('content/platform-panel', 'platform-verify'), true);
assert.equal(registry.driverAcceptsCommand('openai-auth', 'platform-verify'), false); assert.equal(registry.driverAcceptsCommand('openai-auth', 'platform-verify'), false);
assert.equal(registry.driverAcceptsCommand('content/kiro-device-auth-page', 'kiro-submit-email'), true); assert.equal(registry.driverAcceptsCommand('content/kiro/register-page', 'kiro-submit-password'), true);
assert.equal(registry.driverAcceptsCommand('content/kiro-device-auth-page', 'kiro-submit-name'), true); assert.equal(registry.driverAcceptsCommand('content/kiro/desktop-authorize-page', 'kiro-complete-desktop-authorize'), true);
assert.equal(registry.driverAcceptsCommand('content/kiro-device-auth-page', 'kiro-submit-verification-code'), true); assert.equal(registry.driverAcceptsCommand('background/kiro-register', 'kiro-open-register-page'), true);
assert.equal(registry.driverAcceptsCommand('content/kiro-device-auth-page', 'kiro-fill-password'), true); assert.equal(registry.driverAcceptsCommand('background/kiro-desktop-authorize', 'kiro-start-desktop-authorize'), true);
assert.equal(registry.driverAcceptsCommand('content/kiro-device-auth-page', 'kiro-confirm-access'), true); assert.equal(registry.driverAcceptsCommand('background/kiro-publisher-kiro-rs', 'kiro-upload-credential'), true);
assert.equal(registry.driverAcceptsCommand('background/kiro-device-auth', 'kiro-start-device-login'), true);
assert.equal(registry.driverAcceptsCommand('background/kiro-device-auth', 'kiro-submit-email'), true);
assert.equal(registry.driverAcceptsCommand('background/kiro-device-auth', 'kiro-submit-name'), true);
assert.equal(registry.driverAcceptsCommand('background/kiro-device-auth', 'kiro-submit-verification-code'), true);
assert.equal(registry.driverAcceptsCommand('background/kiro-device-auth', 'kiro-fill-password'), true);
assert.equal(registry.driverAcceptsCommand('background/kiro-device-auth', 'kiro-confirm-access'), true);
}); });
+18 -12
View File
@@ -167,35 +167,41 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
assert.deepStrictEqual( assert.deepStrictEqual(
kiroSteps.map((step) => step.key), kiroSteps.map((step) => step.key),
[ [
'kiro-start-device-login', 'kiro-open-register-page',
'kiro-submit-email', 'kiro-submit-email',
'kiro-submit-name', 'kiro-submit-name',
'kiro-submit-verification-code', 'kiro-submit-verification-code',
'kiro-fill-password', 'kiro-submit-password',
'kiro-confirm-access', 'kiro-complete-register-consent',
'kiro-start-desktop-authorize',
'kiro-complete-desktop-authorize',
'kiro-upload-credential', 'kiro-upload-credential',
] ]
); );
assert.equal(kiroSteps.every((step) => step.flowId === 'kiro'), true); assert.equal(kiroSteps.every((step) => step.flowId === 'kiro'), true);
assert.equal(kiroSteps[0].driverId, 'background/kiro-device-auth'); assert.equal(kiroSteps[0].driverId, 'background/kiro-register');
assert.equal(kiroSteps[6].sourceId, 'kiro-rs-admin'); assert.equal(kiroSteps[8].sourceId, 'kiro-rs-admin');
assert.equal(kiroSteps[0].title, '启动设备登录'); assert.equal(kiroSteps[0].title, '打开注册页');
assert.equal(kiroSteps[1].title, '获取邮箱并继续'); assert.equal(kiroSteps[1].title, '获取邮箱并继续');
assert.equal(kiroSteps[2].title, '填写姓名并继续'); assert.equal(kiroSteps[2].title, '填写姓名并继续');
assert.equal(kiroSteps[3].title, '获取验证码并继续'); assert.equal(kiroSteps[3].title, '获取验证码并继续');
assert.equal(kiroSteps[4].title, '设置密码并继续'); assert.equal(kiroSteps[4].title, '设置密码并继续');
assert.equal(kiroSteps[5].title, '确认访问并授权'); assert.equal(kiroSteps[5].title, '完成注册授权');
assert.equal(kiroSteps[6].title, '上传凭据到 kiro.rs'); assert.equal(kiroSteps[6].title, '启动桌面授权');
assert.deepStrictEqual(api.getStepIds({ activeFlowId: 'kiro' }), [1, 2, 3, 4, 5, 6, 7]); assert.equal(kiroSteps[7].title, '完成桌面授权');
assert.equal(api.getLastStepId({ activeFlowId: 'kiro' }), 7); assert.equal(kiroSteps[8].title, '上传凭据到 kiro.rs');
assert.deepStrictEqual(api.getStepIds({ activeFlowId: 'kiro' }), [1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert.equal(api.getLastStepId({ activeFlowId: 'kiro' }), 9);
assert.deepStrictEqual( assert.deepStrictEqual(
api.getNodes({ activeFlowId: 'kiro' }).map((node) => node.next), api.getNodes({ activeFlowId: 'kiro' }).map((node) => node.next),
[ [
['kiro-submit-email'], ['kiro-submit-email'],
['kiro-submit-name'], ['kiro-submit-name'],
['kiro-submit-verification-code'], ['kiro-submit-verification-code'],
['kiro-fill-password'], ['kiro-submit-password'],
['kiro-confirm-access'], ['kiro-complete-register-consent'],
['kiro-start-desktop-authorize'],
['kiro-complete-desktop-authorize'],
['kiro-upload-credential'], ['kiro-upload-credential'],
[], [],
] ]
+45 -29
View File
@@ -13,7 +13,7 @@
这是一个 Chrome 扩展,用于承载多套注册与认证自动化 flow。当前已注册两条主 flow: 这是一个 Chrome 扩展,用于承载多套注册与认证自动化 flow。当前已注册两条主 flow:
- `openai`OpenAI / ChatGPT OAuth 注册、登录、平台绑定与 Plus 扩展链路 - `openai`OpenAI / ChatGPT OAuth 注册、登录、平台绑定与 Plus 扩展链路
- `kiro`Builder ID device auth + `kiro.rs` 凭据上传链路 - `kiro`AWS Builder ID 注册页 + 桌面授权 + `kiro.rs` 凭据上传链路
它的核心价值不是“打开一个页面点几个按钮”,而是把下面这些环节串成一条完整可恢复的自动化链路: 它的核心价值不是“打开一个页面点几个按钮”,而是把下面这些环节串成一条完整可恢复的自动化链路:
@@ -43,7 +43,7 @@
- 向后台发送命令 - 向后台发送命令
- 接收后台广播并更新 UI - 接收后台广播并更新 UI
- 动态渲染步骤列表 - 动态渲染步骤列表
- 维护 `activeFlowId + sourceId` 的双层选择;`openai` flow 继续把 `panelMode` 为 legacy 来源映射,`kiro` flow 则改用 `kiroSourceId` - 维护 `activeFlowId + targetId` 的双层选择;`openai` flow 继续把 `panelMode` 归一为 legacy integration target`kiro` flow 则使用独立的 `flows.kiro.targetId`
- 按当前 flow capability 决定显隐分组;Kiro flow 只显示来源下拉、`kiro.rs URL / API Key`、共享邮箱服务、共享 IP 代理与 Kiro 运行状态,不显示 OpenAI 接码 / Plus / 平台绑定配置 - 按当前 flow capability 决定显隐分组;Kiro flow 只显示来源下拉、`kiro.rs URL / API Key`、共享邮箱服务、共享 IP 代理与 Kiro 运行状态,不显示 OpenAI 接码 / Plus / 平台绑定配置
- 管理顶部“贡献”按钮与贡献模式主面板;贡献模式本身是 sidepanel 的运行态 UI 模式,不是新的 `panelMode` 来源 - 管理顶部“贡献”按钮与贡献模式主面板;贡献模式本身是 sidepanel 的运行态 UI 模式,不是新的 `panelMode` 来源
- 在贡献模式下复用同一套主自动流程启动,并在面板内展示贡献链路的 `OAUTH / 回调 / 总状态` 三块实时状态 - 在贡献模式下复用同一套主自动流程启动,并在面板内展示贡献链路的 `OAUTH / 回调 / 总状态` 三块实时状态
@@ -132,7 +132,7 @@
### 3.4 步骤注册 ### 3.4 步骤注册
[data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js) 提供共享步骤元数据,并按当前 `activeFlowId` 输出 flow-specific workflow。`openai` flow 会继续按 `plusPaymentMethod / signupMethod` 动态解析步骤标题与节点集合,`kiro` flow 则输出固定的 3 节点 workflow。 [data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js) 提供共享步骤元数据,并按当前 `activeFlowId` 输出 flow-specific workflow。`openai` flow 会继续按 `plusPaymentMethod / signupMethod` 动态解析步骤标题与节点集合,`kiro` flow 则输出固定的 9 节点注册/桌面授权/上传 workflow。
[background/steps/registry.js](c:/Users/projectf/Downloads/codex注册扩展/background/steps/registry.js) 负责把“workflow node 元数据”映射到“步骤执行器”,同时保留 `flowId / nodeId / displayOrder / executeKey` 等稳定节点元数据。 [background/steps/registry.js](c:/Users/projectf/Downloads/codex注册扩展/background/steps/registry.js) 负责把“workflow node 元数据”映射到“步骤执行器”,同时保留 `flowId / nodeId / displayOrder / executeKey` 等稳定节点元数据。
这意味着: 这意味着:
@@ -170,7 +170,7 @@
- `contributionStatusMessage` - `contributionStatusMessage`
- `contributionLastPollAt` - `contributionLastPollAt`
- OAuth 链接 - OAuth 链接
- Kiro 运行态:`kiroDeviceCode / kiroLoginUrl / kiroAuthStatus / kiroRefreshToken / kiroUploadStatus / kiroAuthorizedEmail / kiroCredentialId` - Kiro 运行态:使用 `kiroRuntime.session / register / desktopAuth / upload` 命名空间,保存注册页授权码/地址、桌面授权 PKCE 凭据、上传状态和当前 Kiro 会话进度
- 当前轮冻结后的注册方式 `resolvedSignupMethod` - 当前轮冻结后的注册方式 `resolvedSignupMethod`
- 当前统一账号标识 `accountIdentifierType / accountIdentifier` - 当前统一账号标识 `accountIdentifierType / accountIdentifier`
- 当前邮箱 / 密码 - 当前邮箱 / 密码
@@ -207,7 +207,7 @@
保存持久配置与账号运行历史: 保存持久配置与账号运行历史:
- 当前 flow 与来源配置:`activeFlowId`、OpenAI 的 `panelMode`、Kiro 的 `kiroSourceId` - 当前 flow 与目标配置:`activeFlowId`、OpenAI 的 `panelMode`(归一到 integration target)、Kiro 的 `flows.kiro.targetId`
- flow-aware 配置树:`flows.openai.*``flows.kiro.*` - flow-aware 配置树:`flows.openai.*``flows.kiro.*`
- CPA / SUB2API 配置 - CPA / SUB2API 配置
- Codex2API 配置 - Codex2API 配置
@@ -227,7 +227,7 @@
- 接码开关、接码平台 `phoneSmsProvider`,以及 HeroSMS / 5sim 各自的 API Key、国家/地区、价格上限和平台扩展设置 - 接码开关、接码平台 `phoneSmsProvider`,以及 HeroSMS / 5sim 各自的 API Key、国家/地区、价格上限和平台扩展设置
- iCloud 相关偏好 - iCloud 相关偏好
- LuckMail API 配置 - LuckMail API 配置
- Kiro 来源配置:`kiroRsUrl / kiroRsKey / kiroRsPriority / kiroRsEndpoint / kiroRsAuthRegion / kiroRsApiRegion` - Kiro 目标配置:`flows.kiro.targetId``flows.kiro.targets["kiro-rs"] = { baseUrl, apiKey }`
- 自动运行默认配置 - 自动运行默认配置
- OAuth 授权后链总超时开关 `oauthFlowTimeoutEnabled`:默认开启;关闭后会立即清空已存在的 OAuth 总预算 deadline,仅保留各步骤本地等待超时 - OAuth 授权后链总超时开关 `oauthFlowTimeoutEnabled`:默认开启;关闭后会立即清空已存在的 OAuth 总预算 deadline,仅保留各步骤本地等待超时
- flow 级执行范围配置 `stepExecutionRangeByFlow`:按 flowId 保存,例如 `openai: { enabled: true, fromStep: 3, toStep: 6 }`;侧栏中的 `codex` 会归一到内部默认 flowId `openai`。这是通用配置结构,但当前只给 codex/openai flow 暴露 UI。 - flow 级执行范围配置 `stepExecutionRangeByFlow`:按 flowId 保存,例如 `openai: { enabled: true, fromStep: 3, toStep: 6 }`;侧栏中的 `codex` 会归一到内部默认 flowId `openai`。这是通用配置结构,但当前只给 codex/openai flow 暴露 UI。
@@ -237,7 +237,7 @@
注意: 注意:
- `contributionMode` 不属于持久配置,也不参与导入/导出;它只存在于运行态 - `contributionMode` 不属于持久配置,也不参与导入/导出;它只存在于运行态
- `panelMode` 现在只代表 `openai` flow 下的 `cpa | sub2api | codex2api` 三个 legacy 来源;`kiro` flow 使用独立的 `kiroSourceId` - `panelMode` 现在只代表 `openai` flow 下的 `cpa | sub2api | codex2api` 三个 legacy 来源;`kiro` flow 使用独立的 `flows.kiro.targetId`
账号运行历史会默认通过 [scripts/hotmail_helper.py](c:/Users/projectf/Downloads/codex注册扩展/scripts/hotmail_helper.py) 的本地 helper 地址尝试同步写入 `data/account-run-history.json` 快照文件;如果 helper 没有运行,则静默跳过,不再要求用户先手动打开一个“本地同步”开关。 账号运行历史会默认通过 [scripts/hotmail_helper.py](c:/Users/projectf/Downloads/codex注册扩展/scripts/hotmail_helper.py) 的本地 helper 地址尝试同步写入 `data/account-run-history.json` 快照文件;如果 helper 没有运行,则静默跳过,不再要求用户先手动打开一个“本地同步”开关。
这条配置链路独立于 `mailProvider` 和 Hotmail 的接码模式。 这条配置链路独立于 `mailProvider` 和 Hotmail 的接码模式。
@@ -620,39 +620,55 @@ Plus 模式可见步骤:
## 6.2 Kiro Flow 链路 ## 6.2 Kiro Flow 链路
Kiro flow 的目标不是复用 OpenAI 注册链,而是独完成“ Builder ID refresh token -> 上传到 `kiro.rs`”这条链路。 Kiro flow 的目标不是复用 OpenAI 注册链,而是独完成“注册 Builder ID -> 获取桌面端 refresh token -> 上传到 `kiro.rs`”这条链路。
当前节点只有 3 个 当前 Kiro workflow 固定为 9 个节点:
1. `kiro-start-device-login` 1. `kiro-open-register-page`
2. `kiro-await-device-login` 2. `kiro-submit-email`
3. `kiro-upload-credential` 3. `kiro-submit-name`
4. `kiro-submit-verification-code`
5. `kiro-submit-password`
6. `kiro-complete-register-consent`
7. `kiro-start-desktop-authorize`
8. `kiro-complete-desktop-authorize`
9. `kiro-upload-credential`
链路如下: 链路如下:
1. sidepanel 把当前 flow 切到 `kiro`来源固定为 `kiro-rs` 1. sidepanel 把当前 flow 切到 `kiro`target 固定为 `kiro-rs`
2. 用户填写 `kiro.rs URL / API Key` 2. 用户填写 `kiro.rs URL / API Key`
3. 步骤 1 调用 AWS Builder ID OIDC 3. 步骤 1~6 在 AWS Builder ID 注册页完成
- 注册 public client - 清理 AWS Builder ID 相关 cookies
- 请求 `device_authorization` - 打开注册页并等待邮箱输入框
- 打开 `verificationUriComplete` - 通过共享邮箱服务生成注册邮箱并提交
- 保存 `kiroDeviceCode / kiroLoginUrl / kiroClientId / kiroClientSecret` - 填写姓名、拉取验证码、设置账户密码
4. 步骤 2 轮询 token 接口,直到用户完成 device login - 点击“确认并继续 / 允许访问”,完成 Builder ID 注册页授权
- 成功后保存 `kiroRefreshToken` - 保存注册页授权码、授权地址、注册邮箱和当前页面状态到 `kiroRuntime.register`
- 状态进入 `ready_to_upload` 4. 步骤 7 在后台注册桌面 OIDC client,并生成 PKCE 参数:
5. 步骤 3 调用 `kiro.rs` Admin API - 生成 `state / codeVerifier / codeChallenge`
- 生成 localhost callback `redirectUri`
- 构建桌面授权地址并打开授权标签页
5. 步骤 8 在桌面授权页完成浏览器侧授权:
- 处理可能出现的 relogin、OTP、consent
- 监听 `http://127.0.0.1:<port>/oauth/callback?...`
- 校验 callback `state`
-`authorization_code + PKCE` 兑换 `accessToken / refreshToken`
- 保存 `clientId / clientSecret / refreshToken / accessToken``kiroRuntime.desktopAuth`
6. 步骤 9 调用 `kiro.rs` Admin API
- 先探活 `/api/admin/credentials` - 先探活 `/api/admin/credentials`
- 再用 `x-api-key` 上传 Builder ID 凭据 - 再用 `x-api-key` 上传桌面端 Builder ID 凭据
- payload 会带上 `refreshToken / clientId / clientSecret / region` - payload 固定包含 `refreshToken / clientId / clientSecret / region / authRegion / apiRegion / machineId / email`
- 如果当前启用了共享 IP 代理,也会把 `proxyUrl / proxyUsername / proxyPassword` 一并上传 - 如果当前启用了共享 IP 代理,也会把 `proxyUrl / proxyUsername / proxyPassword` 一并上传
- 上传结果保存到 `kiroRuntime.upload`
这条链路的关键边界是: 这条链路的关键边界是:
- Kiro 只复用共享邮箱服务和共享 IP 代理服务,不复用 OpenAI 接码 / Plus / 平台绑定 / 贡献模式链路 - Kiro 只复用共享账户密码、共享邮箱服务和共享 IP 代理服务,不复用 OpenAI 接码 / Plus / 平台绑定 / 贡献模式链路
- Kiro 自动运行不再走 OpenAI 专用步骤文案和重开逻辑,而是走通用 linear node runner - Kiro 自动运行走通用 linear node runner,但执行器、运行态、页面驱动和上传器全部独立在 `background/kiro/*``content/kiro/*`
- Kiro 节点完成回写直接按 `flowId=kiro` 写运行态,不会再误入 OpenAI 的旧 `step` 分支 - Kiro 运行态统一落在 `kiroRuntime` 命名空间,不再散落为多个平铺字段
- 每次页面加载完成后固定等待 1 秒,再继续下一次输入、点击或状态判断。 - `kiro.rs` 上传不再发送 `profileArn`machineId 固定按 `sha256("KotlinNativeAPI/" + refreshToken)` 生成
- 第一版不实时抓取外部地址网站;地址自动化只依赖本地 seed query 和 checkout 页面内置 Google 地址推荐。 - Kiro 注册页和桌面授权页不再静态注入 content script,而是由后台按 source 动态注入,避免同域 AWS 页面被错误归到旧 source
## 7. 邮箱与 provider 链路 ## 7. 邮箱与 provider 链路
+21 -6
View File
@@ -21,7 +21,7 @@
- `.gitignore`:定义仓库忽略规则,当前忽略 `docs/md/``.github/``_metadata/``.vscode/` 等目录。 - `.gitignore`:定义仓库忽略规则,当前忽略 `docs/md/``.github/``_metadata/``.vscode/` 等目录。
- `LICENSE`:项目许可证文件。 - `LICENSE`:项目许可证文件。
- `README.md`:面向使用者的项目介绍、安装说明、能力清单与操作指引。 - `README.md`:面向使用者的项目介绍、安装说明、能力清单与操作指引。
- `background.js`:扩展后台 Service Worker 入口壳,负责模块装配、初始化、全局常量、少量保留的领域函数与运行入口;当前已按 `activeFlowId` 装配 flow-aware 的步骤定义、执行注册表、自动运行与状态同步,`openai` flow 继续承接 `oauthFlowTimeoutEnabled``stepExecutionRangeByFlow``kiro` flow 则走独立的 device auth 与 `kiro.rs` 上传链路。 - `background.js`:扩展后台 Service Worker 入口壳,负责模块装配、初始化、全局常量、少量保留的领域函数与运行入口;当前已按 `activeFlowId` 装配 flow-aware 的步骤定义、执行注册表、自动运行与状态同步,`openai` flow 继续承接 `oauthFlowTimeoutEnabled``stepExecutionRangeByFlow``kiro` flow 则走独立的“注册页 1-6 步 -> 桌面授权 7-8 步 -> `kiro.rs` 上传 9 步”链路。
- `cloudmail-utils.js`Cloud Mail / SkyMail 相关的纯工具函数,负责 API 地址、域名、鉴权头、邮件列表响应与邮件正文归一化。 - `cloudmail-utils.js`Cloud Mail / SkyMail 相关的纯工具函数,负责 API 地址、域名、鉴权头、邮件列表响应与邮件正文归一化。
- `cloudflare-temp-email-utils.js`Cloudflare Temp Email 相关的纯工具函数,负责 URL、域名、邮件内容与 MIME 数据归一化。 - `cloudflare-temp-email-utils.js`Cloudflare Temp Email 相关的纯工具函数,负责 URL、域名、邮件内容与 MIME 数据归一化。
- `gopay-utils.js`GoPay / GPC Plus 支付相关纯工具函数,负责支付方式归一化、GoPay 手机号/OTP/PIN 归一化、GPC API 地址归一化、API Key 请求头、任务创建/查询/OTP/PIN/停止 URL 与余额响应解析。 - `gopay-utils.js`GoPay / GPC Plus 支付相关纯工具函数,负责支付方式归一化、GoPay 手机号/OTP/PIN 归一化、GPC API 地址归一化、API Key 请求头、任务创建/查询/OTP/PIN/停止 URL 与余额响应解析。
@@ -52,6 +52,11 @@
- `background/cloudmail-provider.js`Cloud Mail / SkyMail 后台 provider 模块,负责 Token 获取、邮箱创建、邮件列表读取、验证码轮询和生成/转发收件目标邮箱选择;新生成的 Cloud Mail 地址会统一走共享注册邮箱状态持久化,既回写带来源标记的注册邮箱运行态,也能在 Step 8 `add-email` 的手机号身份链路中保留 `signupPhone* / accountIdentifier*` - `background/cloudmail-provider.js`Cloud Mail / SkyMail 后台 provider 模块,负责 Token 获取、邮箱创建、邮件列表读取、验证码轮询和生成/转发收件目标邮箱选择;新生成的 Cloud Mail 地址会统一走共享注册邮箱状态持久化,既回写带来源标记的注册邮箱运行态,也能在 Step 8 `add-email` 的手机号身份链路中保留 `signupPhone* / accountIdentifier*`
- `background/yyds-mail-provider.js`YYDS Mail 后台 provider 模块,负责通过 `X-API-Key` 创建临时邮箱、保存返回的地址与临时 Bearer token、按 `/messages``/messages/{id}` 读取验证码邮件,并在成功收尾或 reset 时清空当前运行态邮箱。 - `background/yyds-mail-provider.js`YYDS Mail 后台 provider 模块,负责通过 `X-API-Key` 创建临时邮箱、保存返回的地址与临时 Bearer token、按 `/messages``/messages/{id}` 读取验证码邮件,并在成功收尾或 reset 时清空当前运行态邮箱。
- `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口与自定义邮箱池读取;当 provider 为 2925 且 `mail2925Mode = provide` 时,会先确保当前账号池里已有可用账号,再基于该账号生成别名邮箱;若 `mail2925Mode = receive`,则会回退到普通邮箱生成链路;当生成方式为 iCloud 且 `icloudFetchMode = always_new` 时,会强制跳过未用别名复用并新建别名;当生成方式为 `custom-pool` 时,会按当前目标轮次读取邮箱池中的对应邮箱;Duck 生成前会优先结合 `registrationEmailState` 与侧栏当前可见邮箱解析对比基线,避免重复拿到旧地址;所有生成结果都会统一走共享注册邮箱状态持久化,普通邮箱流仍切回邮箱身份,Step 8 `add-email` 的手机号身份流则保留当前手机号身份。 - `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口与自定义邮箱池读取;当 provider 为 2925 且 `mail2925Mode = provide` 时,会先确保当前账号池里已有可用账号,再基于该账号生成别名邮箱;若 `mail2925Mode = receive`,则会回退到普通邮箱生成链路;当生成方式为 iCloud 且 `icloudFetchMode = always_new` 时,会强制跳过未用别名复用并新建别名;当生成方式为 `custom-pool` 时,会按当前目标轮次读取邮箱池中的对应邮箱;Duck 生成前会优先结合 `registrationEmailState` 与侧栏当前可见邮箱解析对比基线,避免重复拿到旧地址;所有生成结果都会统一走共享注册邮箱状态持久化,普通邮箱流仍切回邮箱身份,Step 8 `add-email` 的手机号身份流则保留当前手机号身份。
- `background/kiro/state.js`:Kiro 独立运行态模型与状态工具,负责 `kiroRuntime.session / register / desktopAuth / upload` 的默认值、归一化、节点完成回写、下游重置和 fresh-attempt keep-state 构建。
- `background/kiro/register-runner.js`:Kiro 注册页执行器,负责步骤 1~6 的编排、AWS Builder ID cookies 清理、注册标签页管理、邮箱/姓名/验证码/密码/授权确认,以及注册页运行态推进。
- `background/kiro/desktop-client.js`:Kiro 桌面授权协议层,负责桌面 OIDC client 注册、PKCE 参数生成、授权地址组装、callback 参数校验和授权码换 token。
- `background/kiro/desktop-authorize-runner.js`:Kiro 桌面授权执行器,负责步骤 7~8 的标签页打开、授权页轮询、localhost callback 捕获,以及桌面授权完成后的凭据落库。
- `background/kiro/publisher-kiro-rs.js`Kiro 发布器,负责 `kiro.rs` 地址归一化、连通性探测、machineId 计算、上传 payload 构建与最终凭据上传。
- `background/ip-proxy-core.js`:IP 代理核心模块,负责代理条目解析、账号/接口代理池运行态、PAC 应用与清除、代理鉴权回填、出口探测、失败时的目标站点 fail-close 防漏规则,以及自动运行成功阈值后的代理切换支撑。 - `background/ip-proxy-core.js`:IP 代理核心模块,负责代理条目解析、账号/接口代理池运行态、PAC 应用与清除、代理鉴权回填、出口探测、失败时的目标站点 fail-close 防漏规则,以及自动运行成功阈值后的代理切换支撑。
- `background/ip-proxy-provider-711proxy.js`711Proxy provider 规则模块,负责从账号串中识别和写回 `region / session / sessTime` 等参数,并在固定账号模式下把侧栏配置转换为最终生效的代理账号。 - `background/ip-proxy-provider-711proxy.js`711Proxy provider 规则模块,负责从账号串中识别和写回 `region / session / sessTime` 等参数,并在固定账号模式下把侧栏配置转换为最终生效的代理账号。
- `background/logging-status.js`:后台日志、步骤状态、错误信息和若干状态判断的公共工具层;日志条目统一写入结构化 `step / stepKey`,sidepanel 只读取该元数据渲染步骤标签,不再从日志正文解析步骤号;当前额外承接 `add-phone / 手机号页` 这类认证 fatal 错误的共享判定,并会把 Step 2 的“手机号输入模式未切成功”与真正的 auth `add-phone` 页面区分开,避免自动运行误停机。 - `background/logging-status.js`:后台日志、步骤状态、错误信息和若干状态判断的公共工具层;日志条目统一写入结构化 `step / stepKey`,sidepanel 只读取该元数据渲染步骤标签,不再从日志正文解析步骤号;当前额外承接 `add-phone / 手机号页` 这类认证 fatal 错误的共享判定,并会把 Step 2 的“手机号输入模式未切成功”与真正的 auth `add-phone` 页面区分开,避免自动运行误停机。
@@ -93,6 +98,8 @@
- `content/gmail-mail.js`:Gmail 邮箱轮询脚本,负责在 Gmail 页面中匹配验证码邮件。 - `content/gmail-mail.js`:Gmail 邮箱轮询脚本,负责在 Gmail 页面中匹配验证码邮件。
- `content/icloud-mail.js`:iCloud 邮箱页面脚本,负责在 iCloud Mail 页面中读取邮件详情和验证码。 - `content/icloud-mail.js`:iCloud 邮箱页面脚本,负责在 iCloud Mail 页面中读取邮件详情和验证码。
- `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。 - `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。
- `content/kiro/register-page.js`:Kiro 注册页内容脚本,负责识别注册页状态并执行邮箱、姓名、验证码、密码与“确认并继续 / 允许访问”等页面交互。
- `content/kiro/desktop-authorize-page.js`:Kiro 桌面授权页内容脚本,负责识别桌面授权过程中的 relogin、OTP、consent 与跳转完成状态,并向后台汇报授权页进度。
- `content/mail-163.js`163 / 163 VIP / 126 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。 - `content/mail-163.js`163 / 163 VIP / 126 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。
- `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路;当后台显式开启 `mail2925MatchTargetEmail` 时,会对邮件里显式出现的目标邮箱做弱匹配,避免 `receive` 模式误捞别人的验证码。 - `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路;当后台显式开启 `mail2925MatchTargetEmail` 时,会对邮件里显式出现的目标邮箱做弱匹配,避免 `receive` 模式误捞别人的验证码。
- `content/paypal-flow.js`:PayPal 页面脚本,负责识别登录表单、填写 PayPal 账号密码、在账号页固定执行 `focus -> clear -> fill -> blur` 重新触发邮箱输入事件、处理页面内可见通行密钥提示,并点击 PayPal 授权页的“同意并继续”按钮。 - `content/paypal-flow.js`:PayPal 页面脚本,负责识别登录表单、填写 PayPal 账号密码、在账号页固定执行 `focus -> clear -> fill -> blur` 重新触发邮箱输入事件、处理页面内可见通行密钥提示,并点击 PayPal 授权页的“同意并继续”按钮。
@@ -109,7 +116,7 @@
- `data/names.js`:随机姓名、生日等测试数据源。 - `data/names.js`:随机姓名、生日等测试数据源。
- `data/address-sources.js`:Plus 模式本地地址 seed 表,负责按国家选择用于触发 checkout 内置 Google 地址推荐的查询词和结构化地址 fallback;第一版不实时抓取外部地址网站。 - `data/address-sources.js`:Plus 模式本地地址 seed 表,负责按国家选择用于触发 checkout 内置 Google 地址推荐的查询词和结构化地址 fallback;第一版不实时抓取外部地址网站。
- `data/step-definitions.js`:共享步骤元数据,前后台共同使用,用于动态渲染、workflow 生成和步骤注册;当前 `openai` flow 会按 `plusPaymentMethod / signupMethod` 动态生成普通/Plus workflow`kiro` flow 则提供独立的 3 节点 device auth workflow。 - `data/step-definitions.js`:共享步骤元数据,前后台共同使用,用于动态渲染、workflow 生成和步骤注册;当前 `openai` flow 会按 `plusPaymentMethod / signupMethod` 动态生成普通/Plus workflow`kiro` flow 则提供独立的 9 节点注册/桌面授权/上传 workflow。
## `docs/` ## `docs/`
@@ -134,6 +141,10 @@
- `icons/icon16.png`:扩展 16 像素图标。 - `icons/icon16.png`:扩展 16 像素图标。
- `icons/icon48.png`:扩展 48 像素图标。 - `icons/icon48.png`:扩展 48 像素图标。
## `md/`
- `md/kiro-flow-独立重构开发方案.md`:Kiro 独立 flow 升级方案与阶段清单,记录根因分析、架构决策、分阶段开发计划、自检标准与最终收口要求。
## `scripts/` ## `scripts/`
- `scripts/gpc_sms_helper_macos.py`GPC Plus helper 的 macOS 本地短信验证码 helper,读取本机 Messages 数据库中的 GoPay/OpenAI 短信 OTP,并通过 `http://127.0.0.1:18767/latest-otp?phone=...&consume=1` 提供给扩展按手机号自动轮询;`/otp` 仍保留兼容,`consume=1` 会消费本次返回的验证码记录,避免下次重复读取。 - `scripts/gpc_sms_helper_macos.py`GPC Plus helper 的 macOS 本地短信验证码 helper,读取本机 Messages 数据库中的 GoPay/OpenAI 短信 OTP,并通过 `http://127.0.0.1:18767/latest-otp?phone=...&consume=1` 提供给扩展按手机号自动轮询;`/otp` 仍保留兼容,`consume=1` 会消费本次返回的验证码记录,避免下次重复读取。
@@ -142,8 +153,8 @@
## `shared/` ## `shared/`
- `shared/flow-capabilities.js`:flow 能力矩阵归一化模块,负责根据 `activeFlowId` 与来源解析 sidepanel 的显隐能力、来源作用域、贡献模式边界与步骤范围 UI 作用域。 - `shared/flow-capabilities.js`:flow 能力矩阵归一化模块,负责根据 `activeFlowId` 与来源解析 sidepanel 的显隐能力、来源作用域、贡献模式边界与步骤范围 UI 作用域。
- `shared/flow-registry.js`flow/source/settings group 总注册表,定义 `openai / kiro` 两套 flow、各自来源、可见分组、runtime source、driver 定义,以及默认来源和默认 `kiro.rs` 配置。 - `shared/flow-registry.js`flow/source/settings group 总注册表,定义 `openai / kiro` 两套 flow、各自 target/runtime source、可见分组、driver 定义,以及默认 target 和默认 `kiro.rs` 配置。
- `shared/settings-schema.js`:统一设置 schema 与归一化层,负责把旧平铺字段与新 `flows.*` 结构互转,并维护 `activeFlowId``kiroSourceId``kiroRs*``stepExecutionRangeByFlow` 等跨 flow 配置 - `shared/settings-schema.js`:统一设置 schema 与归一化层,负责把持久配置收敛到 `settingsState.services.*``settingsState.flows.*` 结构,并维护 `activeFlowId`OpenAI 的 integration target、Kiro 的 `flows.kiro.targetId / targets``stepExecutionRangeByFlow`
- `shared/source-registry.js`:运行时来源注册表,负责合并 flow runtime source 与共享 mail source,统一 source family、driver、URL 归属和 cleanup owner 判定。 - `shared/source-registry.js`:运行时来源注册表,负责合并 flow runtime source 与共享 mail source,统一 source family、driver、URL 归属和 cleanup owner 判定。
## `sidepanel/` ## `sidepanel/`
@@ -185,7 +196,7 @@
Hotmail / 2925 的新增表单显隐统一接到 `sidepanel/account-pool-ui.js` 这一层共享 helper;IP 代理配置保存、服务配置快照、开关应用和运行态回显由这里接入消息路由,具体解析/状态渲染下沉到 `sidepanel/ip-proxy-panel.js`;侧边栏初始化与点击“自动”前会刷新一次贡献站 Hotmail / 2925 的新增表单显隐统一接到 `sidepanel/account-pool-ui.js` 这一层共享 helper;IP 代理配置保存、服务配置快照、开关应用和运行态回显由这里接入消息路由,具体解析/状态渲染下沉到 `sidepanel/ip-proxy-panel.js`;侧边栏初始化与点击“自动”前会刷新一次贡献站
公开内容摘要,并按本地关闭版本决定是否展示轻提示,同时在首次初始化后按现有规则决定是否弹出新手引导提示;当前会保存、回显并热更新 `oauthFlowTimeoutEnabled` 设置;日志渲染只读取结构化 `entry.step` 生成步骤标签,不再正则解析日志正文;注册手机号输入框只同步运行态身份,不写入持久配置。 公开内容摘要,并按本地关闭版本决定是否展示轻提示,同时在首次初始化后按现有规则决定是否弹出新手引导提示;当前会保存、回显并热更新 `oauthFlowTimeoutEnabled` 设置;日志渲染只读取结构化 `entry.step` 生成步骤标签,不再正则解析日志正文;注册手机号输入框只同步运行态身份,不写入持久配置。
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Ultra` / 历史 `Pro` / legacy `v` 版本族排序、缓存读取与版本展示。 - `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Ultra` / 历史 `Pro` / legacy `v` 版本族排序、缓存读取与版本展示。
- 补充:`sidepanel/sidepanel.html``sidepanel/sidepanel.js` 当前已改为 flow-aware 侧边栏主界面;顶部提供 `flow/source`下拉`openai` flow 继续把 `panelMode` 作为 legacy 来源映射`kiro` flow 则改用 `kiroSourceId``kiro.rs URL / API Key` 专属字段,并只显示 Kiro 运行状态与共享邮箱/IP 代理配置。 - 补充:`sidepanel/sidepanel.html``sidepanel/sidepanel.js` 当前已改为 flow-aware 侧边栏主界面;顶部提供 `flow/target`层选择`openai` flow 继续把 `panelMode` 归一为 integration target`kiro` flow 则使用独立 `targetId``kiro.rs URL / API Key` 专属字段,并只显示 Kiro 运行状态与共享邮箱/IP 代理配置。
## `tests/` ## `tests/`
@@ -197,11 +208,15 @@
- `tests/auto-run-timer-session-guard.test.js`:测试自动运行的旧计时计划在 Stop 使 session 失效后,不能再把已停止的自动流程重新拉起。 - `tests/auto-run-timer-session-guard.test.js`:测试自动运行的旧计时计划在 Stop 使 session 失效后,不能再把已停止的自动流程重新拉起。
- `tests/auto-run-add-phone-stop.test.js`:测试自动运行控制器在开启自动重试时,遇到 `add-phone / 手机号页` 会直接结束当前轮并继续下一轮,而不是把整条自动流程暂停。 - `tests/auto-run-add-phone-stop.test.js`:测试自动运行控制器在开启自动重试时,遇到 `add-phone / 手机号页` 会直接结束当前轮并继续下一轮,而不是把整条自动流程暂停。
- `tests/auto-run-step6-restart.test.js`:测试自动运行在后半段授权链路遇错时会回到步骤 7 重开,并在命中 add-phone 或泛化手机号页 fatal 错误时停止重开。 - `tests/auto-run-step6-restart.test.js`:测试自动运行在后半段授权链路遇错时会回到步骤 7 重开,并在命中 add-phone 或泛化手机号页 fatal 错误时停止重开。
- `tests/auto-run-kiro-flow-selection.test.js`:测试自动运行切到 `kiro` flow 后会按 Kiro 9 节点 workflow 选择待执行节点,并且不会回退到 OpenAI 节点序列。
- `tests/auto-step-random-delay.test.js`:测试自动运行步间延迟与旧配置键兼容解析。 - `tests/auto-step-random-delay.test.js`:测试自动运行步间延迟与旧配置键兼容解析。
- `tests/background-auto-run-module.test.js`:测试自动运行控制器模块已接入且导出工厂。 - `tests/background-auto-run-module.test.js`:测试自动运行控制器模块已接入且导出工厂。
- `tests/background-account-run-history-module.test.js`:测试账号记录模块已接入、导出工厂,能够保留并归一化停止记录、归一化失败标签、计算自动重试次数、归并同一轮邮箱/手机号身份,并支持清理后同步完整快照。 - `tests/background-account-run-history-module.test.js`:测试账号记录模块已接入、导出工厂,能够保留并归一化停止记录、归一化失败标签、计算自动重试次数、归并同一轮邮箱/手机号身份,并支持清理后同步完整快照。
- `tests/background-account-history-settings.test.js`:测试账号运行历史的独立配置项归一化,不再复用 Hotmail 模式作为启停条件。 - `tests/background-account-history-settings.test.js`:测试账号运行历史的独立配置项归一化,不再复用 Hotmail 模式作为启停条件。
- `tests/background-kiro-device-auth-module.test.js`:测试 Kiro device auth 模块已接入且导出工厂,并覆盖启动授权、等待授权完成、上传到 `kiro.rs` 的主链路 - `tests/background-kiro-state-module.test.js`:测试 Kiro 运行态模块已接入且导出工厂,并覆盖默认命名空间、状态归一化、节点完成回写和下游重置规则
- `tests/background-kiro-register-runner-module.test.js`:测试 Kiro 注册页执行器已接入且导出工厂,并覆盖步骤 1~6 的状态推进、页面命令派发与注册态回写。
- `tests/background-kiro-desktop-authorize-runner-module.test.js`:测试 Kiro 桌面授权执行器已接入且导出工厂,并覆盖 callback 捕获、state 校验、token 兑换和授权态更新。
- `tests/background-kiro-publisher-kiro-rs-module.test.js`:测试 `kiro.rs` 发布器已接入且导出工厂,并覆盖 machineId 生成、payload 构建、连通性探测与上传成功/失败处理。
- `tests/background-contribution-mode.test.js`:测试贡献模式的后台运行态与公开 OAuth 接入,覆盖 `contributionMode` 只存在于运行态、`SET_CONTRIBUTION_MODE / POLL_CONTRIBUTION_STATUS` 消息接入、reset 保留贡献运行态,以及 callback 自动提交在“无需手动提交”场景下的兼容处理。 - `tests/background-contribution-mode.test.js`:测试贡献模式的后台运行态与公开 OAuth 接入,覆盖 `contributionMode` 只存在于运行态、`SET_CONTRIBUTION_MODE / POLL_CONTRIBUTION_STATUS` 消息接入、reset 保留贡献运行态,以及 callback 自动提交在“无需手动提交”场景下的兼容处理。
- `tests/background-custom-email-pool.test.js`:测试后台对自定义邮箱池生成方式,以及自定义邮箱服务号池的归一化、标签文案和按轮次取邮箱逻辑。 - `tests/background-custom-email-pool.test.js`:测试后台对自定义邮箱池生成方式,以及自定义邮箱服务号池的归一化、标签文案和按轮次取邮箱逻辑。
- `tests/background-generated-email-module.test.js`:测试生成邮箱辅助模块已接入且导出工厂,并覆盖 Duck 对比基线优先级、2925 receive 模式回退普通邮箱生成、自定义邮箱池按轮次取值、2925 provide 模式账号池预热,以及 Duck / iCloud 在保留手机号身份场景下的共享持久化传参。 - `tests/background-generated-email-module.test.js`:测试生成邮箱辅助模块已接入且导出工厂,并覆盖 Duck 对比基线优先级、2925 receive 模式回退普通邮箱生成、自定义邮箱池按轮次取值、2925 provide 模式账号池预热,以及 Duck / iCloud 在保留手机号身份场景下的共享持久化传参。