feat: rebuild kiro flow as independent desktop auth pipeline
This commit is contained in:
+110
-168
@@ -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-')) {
|
||||||
return {
|
const kiroResets = typeof kiroStateHelpers?.buildDownstreamResetPatch === 'function'
|
||||||
flowStartTime: null,
|
? kiroStateHelpers.buildDownstreamResetPatch(stepKey, state)
|
||||||
kiroAccessToken: '',
|
: {};
|
||||||
kiroAuthError: '',
|
if (Object.keys(kiroResets).length > 0) {
|
||||||
kiroAuthExpiresAt: 0,
|
return {
|
||||||
kiroAuthIntervalSeconds: 0,
|
...(stepKey === 'kiro-open-register-page' ? { flowStartTime: null } : {}),
|
||||||
kiroAuthRegion: '',
|
...kiroResets,
|
||||||
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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -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
@@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -0,0 +1,335 @@
|
|||||||
|
console.log('[MultiPage:kiro-desktop-authorize-page] Content script loaded on', location.href);
|
||||||
|
|
||||||
|
const KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL = 'data-multipage-kiro-desktop-authorize-page-listener';
|
||||||
|
const KIRO_DESKTOP_ALLOW_TEXT_PATTERN = /allow access|authorize|continue|allow|允许访问|授权|继续/i;
|
||||||
|
const KIRO_DESKTOP_LOADING_TEXT_PATTERN = /loading|redirecting|please wait|加载中|跳转中|请稍候/i;
|
||||||
|
const KIRO_DESKTOP_CLOUDFRONT_403_TEXT_PATTERN = /403 error|the request could not be satisfied|generated by cloudfront/i;
|
||||||
|
|
||||||
|
const KIRO_DESKTOP_EMAIL_INPUT_SELECTOR = [
|
||||||
|
'input[placeholder="username@example.com"]',
|
||||||
|
'input[type="email"]',
|
||||||
|
'input[name="email"]',
|
||||||
|
'input[autocomplete="username"]',
|
||||||
|
].join(', ');
|
||||||
|
|
||||||
|
const KIRO_DESKTOP_PASSWORD_INPUT_SELECTOR = [
|
||||||
|
'input[type="password"]',
|
||||||
|
'input[name="password"]',
|
||||||
|
'input[id*="password" i]',
|
||||||
|
'input[autocomplete="current-password"]',
|
||||||
|
].join(', ');
|
||||||
|
|
||||||
|
const KIRO_DESKTOP_OTP_INPUT_SELECTOR = [
|
||||||
|
'input[autocomplete="one-time-code"]',
|
||||||
|
'input[inputmode="numeric"]',
|
||||||
|
'input[placeholder*="6-digit" i]',
|
||||||
|
'input[placeholder*="6 位" i]',
|
||||||
|
'input[name*="otp" i]',
|
||||||
|
'input[name*="code" i]',
|
||||||
|
].join(', ');
|
||||||
|
|
||||||
|
function isVisibleDesktopElement(element) {
|
||||||
|
if (!element) return false;
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
return style.display !== 'none'
|
||||||
|
&& style.visibility !== 'hidden'
|
||||||
|
&& rect.width > 0
|
||||||
|
&& rect.height > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectVisibleDesktopElements(selector) {
|
||||||
|
return Array.from(document.querySelectorAll(selector))
|
||||||
|
.filter((element) => isVisibleDesktopElement(element));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFirstVisibleDesktopElement(selector) {
|
||||||
|
return collectVisibleDesktopElements(selector)[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDesktopPageText() {
|
||||||
|
return String(document.body?.textContent || '')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDesktopActionText(element) {
|
||||||
|
return [
|
||||||
|
element?.textContent,
|
||||||
|
element?.value,
|
||||||
|
element?.getAttribute?.('aria-label'),
|
||||||
|
element?.getAttribute?.('title'),
|
||||||
|
element?.getAttribute?.('data-testid'),
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function findDesktopActionButton(options = {}) {
|
||||||
|
const {
|
||||||
|
preferredSelectors = [],
|
||||||
|
textPattern = null,
|
||||||
|
formOwner = null,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
for (const selector of preferredSelectors) {
|
||||||
|
const preferred = findFirstVisibleDesktopElement(selector);
|
||||||
|
if (preferred && !preferred.disabled && preferred.getAttribute('aria-disabled') !== 'true') {
|
||||||
|
return preferred;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = collectVisibleDesktopElements('button, [role="button"], input[type="submit"], input[type="button"]')
|
||||||
|
.filter((element) => !element.disabled && element.getAttribute('aria-disabled') !== 'true');
|
||||||
|
|
||||||
|
const prioritized = formOwner
|
||||||
|
? candidates.filter((element) => (element.form || element.closest?.('form') || null) === formOwner)
|
||||||
|
: [];
|
||||||
|
const pool = prioritized.length ? prioritized : candidates;
|
||||||
|
if (textPattern instanceof RegExp) {
|
||||||
|
return pool.find((element) => textPattern.test(getDesktopActionText(element))) || null;
|
||||||
|
}
|
||||||
|
return pool[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectDesktopFatalState(pageText = '', currentUrl = '', pageTitle = '') {
|
||||||
|
const combinedText = `${pageTitle} ${pageText}`.replace(/\s+/g, ' ').trim();
|
||||||
|
if (KIRO_DESKTOP_CLOUDFRONT_403_TEXT_PATTERN.test(combinedText)) {
|
||||||
|
return {
|
||||||
|
state: 'cloudfront_403_page',
|
||||||
|
url: currentUrl,
|
||||||
|
fatalMessage: 'Kiro 桌面授权页返回 403(CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectKiroDesktopAuthorizeState() {
|
||||||
|
const currentUrl = location.href;
|
||||||
|
const pageText = getDesktopPageText();
|
||||||
|
const fatalState = detectDesktopFatalState(pageText, currentUrl, document.title || '');
|
||||||
|
if (fatalState) {
|
||||||
|
return fatalState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^https?:\/\/(127\.0\.0\.1|localhost):/i.test(currentUrl)) {
|
||||||
|
const parsed = new URL(currentUrl);
|
||||||
|
if (parsed.searchParams.get('error')) {
|
||||||
|
return {
|
||||||
|
state: 'callback_error',
|
||||||
|
url: currentUrl,
|
||||||
|
error: parsed.searchParams.get('error_description') || parsed.searchParams.get('error') || 'unknown_error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
state: 'callback_page',
|
||||||
|
url: currentUrl,
|
||||||
|
code: parsed.searchParams.get('code') || '',
|
||||||
|
stateValue: parsed.searchParams.get('state') || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const otpInput = findFirstVisibleDesktopElement(KIRO_DESKTOP_OTP_INPUT_SELECTOR);
|
||||||
|
if (otpInput) {
|
||||||
|
return {
|
||||||
|
state: 'otp_page',
|
||||||
|
url: currentUrl,
|
||||||
|
otpInput,
|
||||||
|
continueButton: findDesktopActionButton({
|
||||||
|
preferredSelectors: ['button[data-testid="email-verification-verify-button"]'],
|
||||||
|
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
|
||||||
|
formOwner: otpInput.form || otpInput.closest?.('form') || null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailInput = findFirstVisibleDesktopElement(KIRO_DESKTOP_EMAIL_INPUT_SELECTOR);
|
||||||
|
if (emailInput) {
|
||||||
|
return {
|
||||||
|
state: 'relogin_email',
|
||||||
|
url: currentUrl,
|
||||||
|
emailInput,
|
||||||
|
continueButton: findDesktopActionButton({
|
||||||
|
preferredSelectors: ['button[data-testid="test-primary-button"]'],
|
||||||
|
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
|
||||||
|
formOwner: emailInput.form || emailInput.closest?.('form') || null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordInput = findFirstVisibleDesktopElement(KIRO_DESKTOP_PASSWORD_INPUT_SELECTOR);
|
||||||
|
if (passwordInput) {
|
||||||
|
return {
|
||||||
|
state: 'relogin_password',
|
||||||
|
url: currentUrl,
|
||||||
|
passwordInput,
|
||||||
|
continueButton: findDesktopActionButton({
|
||||||
|
preferredSelectors: ['button[data-testid="test-primary-button"]'],
|
||||||
|
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
|
||||||
|
formOwner: passwordInput.form || passwordInput.closest?.('form') || null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const consentButton = findDesktopActionButton({
|
||||||
|
preferredSelectors: [
|
||||||
|
'button[data-testid="confirm-button"]',
|
||||||
|
'button[data-testid="allow-access-button"]',
|
||||||
|
],
|
||||||
|
textPattern: KIRO_DESKTOP_ALLOW_TEXT_PATTERN,
|
||||||
|
});
|
||||||
|
if (consentButton) {
|
||||||
|
return {
|
||||||
|
state: 'consent_page',
|
||||||
|
url: currentUrl,
|
||||||
|
actionButton: consentButton,
|
||||||
|
actionText: getDesktopActionText(consentButton),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (KIRO_DESKTOP_LOADING_TEXT_PATTERN.test(pageText)) {
|
||||||
|
return {
|
||||||
|
state: 'redirecting',
|
||||||
|
url: currentUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: 'loading',
|
||||||
|
url: currentUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCurrentDesktopAuthorizeState() {
|
||||||
|
const detected = detectKiroDesktopAuthorizeState();
|
||||||
|
if (detected.state === 'cloudfront_403_page') {
|
||||||
|
throw new Error(detected.fatalMessage || 'Kiro 桌面授权页出现 403。');
|
||||||
|
}
|
||||||
|
if (detected.state === 'callback_error') {
|
||||||
|
throw new Error(`Kiro 桌面授权回调失败:${detected.error || 'unknown_error'}`);
|
||||||
|
}
|
||||||
|
return detected;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitDesktopEmail(payload = {}) {
|
||||||
|
const email = String(payload?.email || '').trim();
|
||||||
|
if (!email) {
|
||||||
|
throw new Error('缺少桌面授权邮箱,无法继续。');
|
||||||
|
}
|
||||||
|
const currentState = await getCurrentDesktopAuthorizeState();
|
||||||
|
if (currentState.state !== 'relogin_email' || !currentState.emailInput || !currentState.continueButton) {
|
||||||
|
throw new Error(`当前桌面授权页不是邮箱重登状态:${currentState.state}`);
|
||||||
|
}
|
||||||
|
fillInput(currentState.emailInput, email);
|
||||||
|
await sleep(150);
|
||||||
|
simulateClick(currentState.continueButton);
|
||||||
|
return {
|
||||||
|
submitted: true,
|
||||||
|
state: 'email_submitted',
|
||||||
|
url: location.href,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitDesktopPassword(payload = {}) {
|
||||||
|
const password = String(payload?.password || '');
|
||||||
|
if (!password) {
|
||||||
|
throw new Error('缺少桌面授权密码,无法继续。');
|
||||||
|
}
|
||||||
|
const currentState = await getCurrentDesktopAuthorizeState();
|
||||||
|
if (currentState.state !== 'relogin_password' || !currentState.passwordInput || !currentState.continueButton) {
|
||||||
|
throw new Error(`当前桌面授权页不是密码重登状态:${currentState.state}`);
|
||||||
|
}
|
||||||
|
fillInput(currentState.passwordInput, password);
|
||||||
|
await sleep(150);
|
||||||
|
simulateClick(currentState.continueButton);
|
||||||
|
return {
|
||||||
|
submitted: true,
|
||||||
|
state: 'password_submitted',
|
||||||
|
url: location.href,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitDesktopOtp(payload = {}) {
|
||||||
|
const code = String(payload?.code || '').trim();
|
||||||
|
if (!code) {
|
||||||
|
throw new Error('缺少桌面授权验证码,无法继续。');
|
||||||
|
}
|
||||||
|
const currentState = await getCurrentDesktopAuthorizeState();
|
||||||
|
if (currentState.state !== 'otp_page' || !currentState.otpInput || !currentState.continueButton) {
|
||||||
|
throw new Error(`当前桌面授权页不是验证码状态:${currentState.state}`);
|
||||||
|
}
|
||||||
|
fillInput(currentState.otpInput, code);
|
||||||
|
await sleep(150);
|
||||||
|
simulateClick(currentState.continueButton);
|
||||||
|
return {
|
||||||
|
submitted: true,
|
||||||
|
state: 'otp_submitted',
|
||||||
|
url: location.href,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmDesktopConsent() {
|
||||||
|
const currentState = await getCurrentDesktopAuthorizeState();
|
||||||
|
if (currentState.state !== 'consent_page' || !currentState.actionButton) {
|
||||||
|
throw new Error(`当前桌面授权页不是授权确认状态:${currentState.state}`);
|
||||||
|
}
|
||||||
|
simulateClick(currentState.actionButton);
|
||||||
|
return {
|
||||||
|
submitted: true,
|
||||||
|
state: 'consent_submitted',
|
||||||
|
url: location.href,
|
||||||
|
actionText: currentState.actionText || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKiroDesktopAuthorizeCommand(message) {
|
||||||
|
switch (message.type) {
|
||||||
|
case 'GET_KIRO_DESKTOP_AUTHORIZE_STATE':
|
||||||
|
return getCurrentDesktopAuthorizeState();
|
||||||
|
case 'EXECUTE_KIRO_DESKTOP_AUTHORIZE_ACTION': {
|
||||||
|
const action = String(message.payload?.action || '').trim();
|
||||||
|
if (action === 'submit-email') {
|
||||||
|
return submitDesktopEmail(message.payload || {});
|
||||||
|
}
|
||||||
|
if (action === 'submit-password') {
|
||||||
|
return submitDesktopPassword(message.payload || {});
|
||||||
|
}
|
||||||
|
if (action === 'submit-otp') {
|
||||||
|
return submitDesktopOtp(message.payload || {});
|
||||||
|
}
|
||||||
|
if (action === 'confirm-consent') {
|
||||||
|
return confirmDesktopConsent();
|
||||||
|
}
|
||||||
|
throw new Error(`desktop-authorize-page.js 不处理动作:${action}`);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.documentElement.getAttribute(KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL) !== '1') {
|
||||||
|
document.documentElement.setAttribute(KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL, '1');
|
||||||
|
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||||
|
if (
|
||||||
|
message.type === 'GET_KIRO_DESKTOP_AUTHORIZE_STATE'
|
||||||
|
|| message.type === 'EXECUTE_KIRO_DESKTOP_AUTHORIZE_ACTION'
|
||||||
|
) {
|
||||||
|
resetStopState();
|
||||||
|
handleKiroDesktopAuthorizeCommand(message)
|
||||||
|
.then((result) => {
|
||||||
|
sendResponse({ ok: true, ...(result || {}) });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (isStopError(error)) {
|
||||||
|
sendResponse({ stopped: true, error: error.message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendResponse({ error: error?.message || String(error || '未知错误') });
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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 注册页返回 403(CloudFront 拒绝请求),通常是当前代理/IP/区域触发了 AWS 风控,请更换代理后重试。',
|
fatalMessage: 'Kiro 注册页返回 403(CloudFront 拒绝请求),通常是当前代理 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 注册页返回 403(CloudFront 拒绝请求),通常是当前代理/IP/区域触发了 AWS 风控,请更换代理后重试。';
|
return 'Kiro 注册页返回 403(CloudFront 拒绝请求),通常是当前代理 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
@@ -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',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
@@ -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'));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
@@ -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: {},
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 在保留手机号身份场景下的共享持久化传参。
|
||||||
|
|||||||
Reference in New Issue
Block a user