feat: add flow-aware kiro device auth workflow
This commit is contained in:
+391
-58
@@ -1,6 +1,8 @@
|
|||||||
// background.js — Service Worker: orchestration, state, tab management, message routing
|
// background.js — Service Worker: orchestration, state, tab management, message routing
|
||||||
|
|
||||||
importScripts(
|
importScripts(
|
||||||
|
'shared/flow-registry.js',
|
||||||
|
'shared/settings-schema.js',
|
||||||
'shared/source-registry.js',
|
'shared/source-registry.js',
|
||||||
'shared/flow-capabilities.js',
|
'shared/flow-capabilities.js',
|
||||||
'managed-alias-utils.js',
|
'managed-alias-utils.js',
|
||||||
@@ -51,6 +53,7 @@ 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',
|
||||||
@@ -136,9 +139,23 @@ const PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = self.MultiPageStepDe
|
|||||||
phoneSignupReloginAfterBindEmailEnabled: true,
|
phoneSignupReloginAfterBindEmailEnabled: true,
|
||||||
}) || PLUS_GPC_PHONE_STEP_DEFINITIONS;
|
}) || PLUS_GPC_PHONE_STEP_DEFINITIONS;
|
||||||
const PLUS_STEP_DEFINITIONS = PLUS_PAYPAL_STEP_DEFINITIONS;
|
const PLUS_STEP_DEFINITIONS = PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||||
const ALL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getAllSteps?.({
|
const REGISTERED_STEP_FLOW_IDS = self.MultiPageStepDefinitions?.getRegisteredFlowIds?.() || [DEFAULT_ACTIVE_FLOW_ID];
|
||||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
const ALL_STEP_DEFINITIONS = (() => {
|
||||||
}) || [
|
if (self.MultiPageStepDefinitions?.getAllSteps) {
|
||||||
|
const keyedDefinitions = new Map();
|
||||||
|
for (const flowId of REGISTERED_STEP_FLOW_IDS) {
|
||||||
|
const definitions = self.MultiPageStepDefinitions.getAllSteps({ activeFlowId: flowId });
|
||||||
|
for (const definition of Array.isArray(definitions) ? definitions : []) {
|
||||||
|
const key = `${flowId}:${Number(definition?.id) || 0}:${String(definition?.key || '').trim()}`;
|
||||||
|
keyedDefinitions.set(key, definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const allDefinitions = Array.from(keyedDefinitions.values());
|
||||||
|
if (allDefinitions.length) {
|
||||||
|
return allDefinitions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [
|
||||||
...NORMAL_STEP_DEFINITIONS,
|
...NORMAL_STEP_DEFINITIONS,
|
||||||
...NORMAL_PHONE_STEP_DEFINITIONS,
|
...NORMAL_PHONE_STEP_DEFINITIONS,
|
||||||
...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
@@ -151,7 +168,8 @@ const ALL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getAllSteps?.({
|
|||||||
...PLUS_GPC_STEP_DEFINITIONS,
|
...PLUS_GPC_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
...PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
const STEP_IDS = Array.from(new Set(ALL_STEP_DEFINITIONS
|
const STEP_IDS = Array.from(new Set(ALL_STEP_DEFINITIONS
|
||||||
.map((definition) => Number(definition?.id))
|
.map((definition) => Number(definition?.id))
|
||||||
.filter(Number.isFinite)))
|
.filter(Number.isFinite)))
|
||||||
@@ -870,6 +888,11 @@ function setupDeclarativeNetRequestRules() {
|
|||||||
|
|
||||||
const PERSISTED_SETTING_DEFAULTS = {
|
const PERSISTED_SETTING_DEFAULTS = {
|
||||||
panelMode: 'cpa',
|
panelMode: 'cpa',
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
kiroSourceId: 'kiro-rs',
|
||||||
|
kiroRsUrl: self.MultiPageFlowRegistry?.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin',
|
||||||
|
kiroRsKey: '',
|
||||||
|
kiroRegion: self.MultiPageFlowRegistry?.DEFAULT_KIRO_REGION || 'us-east-1',
|
||||||
vpsUrl: '',
|
vpsUrl: '',
|
||||||
vpsPassword: '',
|
vpsPassword: '',
|
||||||
localCpaStep9Mode: DEFAULT_LOCAL_CPA_STEP9_MODE,
|
localCpaStep9Mode: DEFAULT_LOCAL_CPA_STEP9_MODE,
|
||||||
@@ -1051,6 +1074,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||||
|
const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState'];
|
||||||
const SETTINGS_EXPORT_SCHEMA_VERSION = 1;
|
const SETTINGS_EXPORT_SCHEMA_VERSION = 1;
|
||||||
const SETTINGS_EXPORT_FILENAME_PREFIX = 'multipage-settings';
|
const SETTINGS_EXPORT_FILENAME_PREFIX = 'multipage-settings';
|
||||||
const STEP6_REGISTRATION_SUCCESS_WAIT_MS = 20000;
|
const STEP6_REGISTRATION_SUCCESS_WAIT_MS = 20000;
|
||||||
@@ -2773,6 +2797,30 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
switch (key) {
|
switch (key) {
|
||||||
case 'panelMode':
|
case 'panelMode':
|
||||||
return normalizePanelMode(value);
|
return normalizePanelMode(value);
|
||||||
|
case 'activeFlowId':
|
||||||
|
if (typeof self.MultiPageFlowRegistry?.normalizeFlowId === 'function') {
|
||||||
|
return self.MultiPageFlowRegistry.normalizeFlowId(value, DEFAULT_ACTIVE_FLOW_ID);
|
||||||
|
}
|
||||||
|
return String(value || '').trim().toLowerCase() === 'kiro' ? 'kiro' : DEFAULT_ACTIVE_FLOW_ID;
|
||||||
|
case 'kiroSourceId':
|
||||||
|
if (typeof self.MultiPageFlowRegistry?.normalizeSourceId === 'function') {
|
||||||
|
return self.MultiPageFlowRegistry.normalizeSourceId('kiro', value, 'kiro-rs');
|
||||||
|
}
|
||||||
|
return String(value || '').trim().toLowerCase() === 'kiro-rs' ? 'kiro-rs' : 'kiro-rs';
|
||||||
|
case 'kiroRsUrl':
|
||||||
|
return String(
|
||||||
|
value
|
||||||
|
|| self.MultiPageFlowRegistry?.DEFAULT_KIRO_RS_URL
|
||||||
|
|| 'https://kiro.leftcode.xyz/admin'
|
||||||
|
).trim() || 'https://kiro.leftcode.xyz/admin';
|
||||||
|
case 'kiroRsKey':
|
||||||
|
return String(value || '');
|
||||||
|
case 'kiroRegion':
|
||||||
|
return String(
|
||||||
|
value
|
||||||
|
|| self.MultiPageFlowRegistry?.DEFAULT_KIRO_REGION
|
||||||
|
|| 'us-east-1'
|
||||||
|
).trim() || 'us-east-1';
|
||||||
case 'vpsUrl':
|
case 'vpsUrl':
|
||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
case 'vpsPassword':
|
case 'vpsPassword':
|
||||||
@@ -3198,6 +3246,11 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPlainObjectForSettingsSchema = typeof isPlainObjectValue === 'function'
|
||||||
|
? isPlainObjectValue
|
||||||
|
: ((value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value));
|
||||||
|
const hasExplicitSettingsState = isPlainObjectForSettingsSchema(normalizedInput.settingsState);
|
||||||
|
|
||||||
const payload = {};
|
const payload = {};
|
||||||
let matchedKeyCount = 0;
|
let matchedKeyCount = 0;
|
||||||
for (const key of persistedSettingKeys) {
|
for (const key of persistedSettingKeys) {
|
||||||
@@ -3223,7 +3276,7 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
|||||||
payload.heroSmsReuseEnabled = normalizedReuseEnabled;
|
payload.heroSmsReuseEnabled = normalizedReuseEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requireKnownKeys && matchedKeyCount === 0) {
|
if (requireKnownKeys && matchedKeyCount === 0 && !hasExplicitSettingsState) {
|
||||||
throw new Error('\u914d\u7f6e\u6587\u4ef6\u4e2d\u6ca1\u6709\u53ef\u8bc6\u522b\u7684\u914d\u7f6e\u5185\u5bb9\u3002');
|
throw new Error('\u914d\u7f6e\u6587\u4ef6\u4e2d\u6ca1\u6709\u53ef\u8bc6\u522b\u7684\u914d\u7f6e\u5185\u5bb9\u3002');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3301,12 +3354,54 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
|||||||
payload.ipProxyRegion = String(activeProfile?.region || payload.ipProxyRegion || '').trim();
|
payload.ipProxyRegion = String(activeProfile?.region || payload.ipProxyRegion || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasExplicitSettingsSchema = hasExplicitSettingsState
|
||||||
|
|| Object.prototype.hasOwnProperty.call(normalizedInput, 'settingsSchemaVersion');
|
||||||
|
if (fillDefaults || hasExplicitSettingsSchema) {
|
||||||
|
const settingsSchemaApi = typeof getSettingsSchemaApi === 'function'
|
||||||
|
? getSettingsSchemaApi()
|
||||||
|
: null;
|
||||||
|
if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildLegacySettingsPayload) {
|
||||||
|
const settingsSchemaInput = {};
|
||||||
|
for (const key of persistedSettingKeys) {
|
||||||
|
if (normalizedInput[key] !== undefined) {
|
||||||
|
settingsSchemaInput[key] = payload[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const normalizedSettingsState = settingsSchemaApi.normalizeSettingsState({
|
||||||
|
...settingsSchemaInput,
|
||||||
|
...(isPlainObjectForSettingsSchema(normalizedInput.settingsState)
|
||||||
|
? { settingsState: normalizedInput.settingsState }
|
||||||
|
: {}),
|
||||||
|
...(Object.prototype.hasOwnProperty.call(normalizedInput, 'settingsSchemaVersion')
|
||||||
|
? { settingsSchemaVersion: normalizedInput.settingsSchemaVersion }
|
||||||
|
: {}),
|
||||||
|
}, {
|
||||||
|
activeFlowId: settingsSchemaInput.activeFlowId || normalizedInput.activeFlowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
});
|
||||||
|
Object.assign(
|
||||||
|
payload,
|
||||||
|
settingsSchemaApi.buildLegacySettingsPayload(normalizedSettingsState, payload)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSettingsSchemaApi() {
|
||||||
|
if (typeof self.MultiPageSettingsSchema?.createSettingsSchema !== 'function') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return self.MultiPageSettingsSchema.createSettingsSchema({
|
||||||
|
flowRegistry: self.MultiPageFlowRegistry,
|
||||||
|
defaultFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function getPersistedSettings() {
|
async function getPersistedSettings() {
|
||||||
const stored = await chrome.storage.local.get([
|
const stored = await chrome.storage.local.get([
|
||||||
...PERSISTED_SETTING_KEYS,
|
...PERSISTED_SETTING_KEYS,
|
||||||
|
...PERSISTED_SETTINGS_SCHEMA_KEYS,
|
||||||
...LEGACY_AUTO_STEP_DELAY_KEYS,
|
...LEGACY_AUTO_STEP_DELAY_KEYS,
|
||||||
...LEGACY_VERIFICATION_RESEND_COUNT_KEYS,
|
...LEGACY_VERIFICATION_RESEND_COUNT_KEYS,
|
||||||
]);
|
]);
|
||||||
@@ -3396,11 +3491,87 @@ async function setState(updates) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setPersistentSettings(updates) {
|
async function setPersistentSettings(updates) {
|
||||||
const persistedUpdates = buildPersistentSettingsPayload(updates);
|
const currentSettings = await getPersistedSettings();
|
||||||
|
const nextUpdates = updates && typeof updates === 'object' && !Array.isArray(updates)
|
||||||
|
? updates
|
||||||
|
: {};
|
||||||
|
const settingsSchemaApi = typeof getSettingsSchemaApi === 'function'
|
||||||
|
? getSettingsSchemaApi()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
let persistedUpdates;
|
||||||
|
if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildLegacySettingsPayload) {
|
||||||
|
const isPlainObjectForSettingsSchema = typeof isPlainObjectValue === 'function'
|
||||||
|
? isPlainObjectValue
|
||||||
|
: ((value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value));
|
||||||
|
const cloneSettingsValue = (value) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map((entry) => cloneSettingsValue(entry));
|
||||||
|
}
|
||||||
|
if (isPlainObjectForSettingsSchema(value)) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(value).map(([key, entryValue]) => [key, cloneSettingsValue(entryValue)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
const mergeSettingsState = (baseValue, patchValue) => {
|
||||||
|
if (Array.isArray(patchValue)) {
|
||||||
|
return patchValue.map((entry) => cloneSettingsValue(entry));
|
||||||
|
}
|
||||||
|
if (!isPlainObjectForSettingsSchema(patchValue)) {
|
||||||
|
return patchValue === undefined ? cloneSettingsValue(baseValue) : patchValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseObject = isPlainObjectForSettingsSchema(baseValue) ? baseValue : {};
|
||||||
|
const nextObject = {
|
||||||
|
...cloneSettingsValue(baseObject),
|
||||||
|
};
|
||||||
|
for (const [key, entryValue] of Object.entries(patchValue)) {
|
||||||
|
nextObject[key] = mergeSettingsState(baseObject[key], entryValue);
|
||||||
|
}
|
||||||
|
return nextObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentSettingsState = settingsSchemaApi.normalizeSettingsState(
|
||||||
|
isPlainObjectForSettingsSchema(currentSettings?.settingsState)
|
||||||
|
? { settingsState: currentSettings.settingsState }
|
||||||
|
: currentSettings,
|
||||||
|
{
|
||||||
|
activeFlowId: currentSettings?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const mergedSettingsState = isPlainObjectForSettingsSchema(nextUpdates.settingsState)
|
||||||
|
? mergeSettingsState(currentSettingsState, nextUpdates.settingsState)
|
||||||
|
: currentSettingsState;
|
||||||
|
const explicitFlatUpdates = {
|
||||||
|
...nextUpdates,
|
||||||
|
};
|
||||||
|
delete explicitFlatUpdates.settingsSchemaVersion;
|
||||||
|
delete explicitFlatUpdates.settingsState;
|
||||||
|
|
||||||
|
persistedUpdates = buildPersistentSettingsPayload({
|
||||||
|
...explicitFlatUpdates,
|
||||||
|
settingsSchemaVersion: nextUpdates.settingsSchemaVersion ?? currentSettings.settingsSchemaVersion,
|
||||||
|
settingsState: mergedSettingsState,
|
||||||
|
}, {
|
||||||
|
fillDefaults: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
persistedUpdates = buildPersistentSettingsPayload({
|
||||||
|
...currentSettings,
|
||||||
|
...nextUpdates,
|
||||||
|
settingsSchemaVersion: nextUpdates.settingsSchemaVersion ?? currentSettings.settingsSchemaVersion,
|
||||||
|
settingsState: nextUpdates.settingsState ?? currentSettings.settingsState,
|
||||||
|
}, {
|
||||||
|
fillDefaults: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(persistedUpdates).length > 0) {
|
if (Object.keys(persistedUpdates).length > 0) {
|
||||||
await chrome.storage.local.set(persistedUpdates);
|
await chrome.storage.local.set(persistedUpdates);
|
||||||
}
|
}
|
||||||
|
return persistedUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSettingsExportFilename(date = new Date()) {
|
function buildSettingsExportFilename(date = new Date()) {
|
||||||
@@ -3469,10 +3640,10 @@ async function importSettingsBundle(configBundle) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await setPersistentSettings(importedSettings);
|
const persistedSettings = await setPersistentSettings(importedSettings) || importedSettings;
|
||||||
|
|
||||||
const sessionUpdates = {
|
const sessionUpdates = {
|
||||||
...importedSettings,
|
...persistedSettings,
|
||||||
currentHotmailAccountId: null,
|
currentHotmailAccountId: null,
|
||||||
email: null,
|
email: null,
|
||||||
registrationEmailState: { ...DEFAULT_REGISTRATION_EMAIL_STATE },
|
registrationEmailState: { ...DEFAULT_REGISTRATION_EMAIL_STATE },
|
||||||
@@ -3480,7 +3651,7 @@ async function importSettingsBundle(configBundle) {
|
|||||||
|
|
||||||
await setState(sessionUpdates);
|
await setState(sessionUpdates);
|
||||||
broadcastDataUpdate({
|
broadcastDataUpdate({
|
||||||
...importedSettings,
|
...persistedSettings,
|
||||||
currentHotmailAccountId: null,
|
currentHotmailAccountId: null,
|
||||||
...(sessionUpdates.email !== undefined ? { email: sessionUpdates.email } : {}),
|
...(sessionUpdates.email !== undefined ? { email: sessionUpdates.email } : {}),
|
||||||
registrationEmailState: sessionUpdates.registrationEmailState,
|
registrationEmailState: sessionUpdates.registrationEmailState,
|
||||||
@@ -8727,6 +8898,57 @@ 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') {
|
||||||
|
return {
|
||||||
|
flowStartTime: null,
|
||||||
|
kiroAccessToken: '',
|
||||||
|
kiroAuthError: '',
|
||||||
|
kiroAuthExpiresAt: 0,
|
||||||
|
kiroAuthIntervalSeconds: 0,
|
||||||
|
kiroAuthRegion: '',
|
||||||
|
kiroAuthStatus: '',
|
||||||
|
kiroAuthTabId: null,
|
||||||
|
kiroAuthorizedEmail: '',
|
||||||
|
kiroClientId: '',
|
||||||
|
kiroClientSecret: '',
|
||||||
|
kiroCredentialId: null,
|
||||||
|
kiroDeviceAuthorizationCode: '',
|
||||||
|
kiroDeviceCode: '',
|
||||||
|
kiroLastConnectionMessage: '',
|
||||||
|
kiroLastUploadAt: 0,
|
||||||
|
kiroLoginUrl: '',
|
||||||
|
kiroRefreshToken: '',
|
||||||
|
kiroUploadError: '',
|
||||||
|
kiroUploadStatus: '',
|
||||||
|
kiroUserCode: '',
|
||||||
|
kiroVerificationUri: '',
|
||||||
|
kiroVerificationUriComplete: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (stepKey === 'kiro-await-device-login') {
|
||||||
|
return {
|
||||||
|
kiroAccessToken: '',
|
||||||
|
kiroAuthError: '',
|
||||||
|
kiroAuthStatus: 'waiting_user',
|
||||||
|
kiroAuthorizedEmail: '',
|
||||||
|
kiroCredentialId: null,
|
||||||
|
kiroLastConnectionMessage: '',
|
||||||
|
kiroLastUploadAt: 0,
|
||||||
|
kiroRefreshToken: '',
|
||||||
|
kiroUploadError: '',
|
||||||
|
kiroUploadStatus: 'waiting_login',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (stepKey === 'kiro-upload-credential') {
|
||||||
|
return {
|
||||||
|
kiroAuthorizedEmail: '',
|
||||||
|
kiroCredentialId: null,
|
||||||
|
kiroLastConnectionMessage: '',
|
||||||
|
kiroLastUploadAt: 0,
|
||||||
|
kiroUploadError: '',
|
||||||
|
kiroUploadStatus: 'ready_to_upload',
|
||||||
|
};
|
||||||
|
}
|
||||||
const plusRuntimeResets = {
|
const plusRuntimeResets = {
|
||||||
plusCheckoutTabId: null,
|
plusCheckoutTabId: null,
|
||||||
plusCheckoutUrl: null,
|
plusCheckoutUrl: null,
|
||||||
@@ -9892,6 +10114,44 @@ async function handleStepData(step, payload) {
|
|||||||
|
|
||||||
async function handleNodeData(nodeId, payload) {
|
async function handleNodeData(nodeId, payload) {
|
||||||
const state = await getState();
|
const state = await getState();
|
||||||
|
const nodeDefinition = getNodeDefinitionForState(nodeId, state);
|
||||||
|
if (String(nodeDefinition?.flowId || '').trim().toLowerCase() === 'kiro') {
|
||||||
|
const kiroFieldKeys = [
|
||||||
|
'kiroAccessToken',
|
||||||
|
'kiroAuthError',
|
||||||
|
'kiroAuthExpiresAt',
|
||||||
|
'kiroAuthIntervalSeconds',
|
||||||
|
'kiroAuthRegion',
|
||||||
|
'kiroAuthStatus',
|
||||||
|
'kiroAuthTabId',
|
||||||
|
'kiroAuthorizedEmail',
|
||||||
|
'kiroClientId',
|
||||||
|
'kiroClientSecret',
|
||||||
|
'kiroCredentialId',
|
||||||
|
'kiroDeviceAuthorizationCode',
|
||||||
|
'kiroDeviceCode',
|
||||||
|
'kiroLastConnectionMessage',
|
||||||
|
'kiroLastUploadAt',
|
||||||
|
'kiroLoginUrl',
|
||||||
|
'kiroRefreshToken',
|
||||||
|
'kiroUploadError',
|
||||||
|
'kiroUploadStatus',
|
||||||
|
'kiroUserCode',
|
||||||
|
'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) {
|
||||||
|
await setState(updates);
|
||||||
|
broadcastDataUpdate(updates);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const step = getStepIdByNodeIdForState(nodeId, state);
|
const step = getStepIdByNodeIdForState(nodeId, state);
|
||||||
if (!Number.isInteger(step) || step <= 0) {
|
if (!Number.isInteger(step) || step <= 0) {
|
||||||
return;
|
return;
|
||||||
@@ -9933,6 +10193,9 @@ 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-await-device-login',
|
||||||
|
'kiro-upload-credential',
|
||||||
]);
|
]);
|
||||||
const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([
|
const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([
|
||||||
'fill-password',
|
'fill-password',
|
||||||
@@ -10611,7 +10874,8 @@ async function executeNode(nodeId, options = {}) {
|
|||||||
state = await getState();
|
state = await getState();
|
||||||
|
|
||||||
// Set flow start time on first step
|
// Set flow start time on first step
|
||||||
if (normalizedNodeId === 'open-chatgpt' && !state.flowStartTime) {
|
const firstNodeIdForFlow = String(getNodeIdsForState(state)?.[0] || '').trim();
|
||||||
|
if (normalizedNodeId === firstNodeIdForFlow && !state.flowStartTime) {
|
||||||
await setState({ flowStartTime: Date.now() });
|
await setState({ flowStartTime: Date.now() });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11977,6 +12241,70 @@ async function runAutoSequenceFromNodeGraph(startNodeId, context = {}) {
|
|||||||
setRestartNode(nodeId);
|
setRestartNode(nodeId);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
const initialFlowState = await getState();
|
||||||
|
const activeFlowId = String(initialFlowState?.activeFlowId || initialFlowState?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
|
||||||
|
|
||||||
|
if (activeFlowId !== DEFAULT_ACTIVE_FLOW_ID) {
|
||||||
|
await broadcastAutoRunStatus('running', {
|
||||||
|
currentRun: targetRun,
|
||||||
|
totalRuns,
|
||||||
|
attemptRun: attemptRuns,
|
||||||
|
});
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (continueCurrentAttempt) {
|
||||||
|
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续当前进度,从节点 ${currentStartNodeId} 开始(第 ${attemptRuns} 次尝试)===`, 'info');
|
||||||
|
} else {
|
||||||
|
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:第 ${attemptRuns} 次尝试,开始执行 ${activeFlowId} flow ===`, 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
let latestState = await getState();
|
||||||
|
let nodeIds = getAutoRunWorkflowNodeIds(latestState);
|
||||||
|
let nodeIndex = Math.max(0, getNodeIndex(latestState, currentStartNodeId));
|
||||||
|
|
||||||
|
while (nodeIndex < nodeIds.length) {
|
||||||
|
latestState = await getState();
|
||||||
|
nodeIds = getAutoRunWorkflowNodeIds(latestState);
|
||||||
|
const nodeId = nodeIds[nodeIndex];
|
||||||
|
if (!nodeId) {
|
||||||
|
nodeIndex += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (typeof isNodeExecutionAllowedForState === 'function' && !isNodeExecutionAllowedForState(nodeId, latestState)) {
|
||||||
|
nodeIndex += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentStatus = getNodeStatusForNode(latestState, nodeId);
|
||||||
|
if (isStepDoneStatus(currentStatus)) {
|
||||||
|
await addLog(`自动运行:节点 ${nodeId} 当前状态为 ${currentStatus},将直接继续后续流程。`, 'info');
|
||||||
|
nodeIndex += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await executeNodeAndWaitWithAutoRunIdleLogWatchdog(nodeId, getAutoRunNodeDelayMs(nodeId));
|
||||||
|
nodeIndex += 1;
|
||||||
|
} catch (err) {
|
||||||
|
attachFailedNode(err, nodeId, latestState);
|
||||||
|
if (isStopError(err)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (await restartCurrentNodeAfterIdle(nodeId, err)) {
|
||||||
|
latestState = await getState();
|
||||||
|
nodeIds = getAutoRunWorkflowNodeIds(latestState);
|
||||||
|
nodeIndex = Math.max(0, getNodeIndex(latestState, currentStartNodeId));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
@@ -12740,6 +13068,17 @@ const plusReturnConfirmExecutor = self.MultiPageBackgroundPlusReturnConfirm?.cre
|
|||||||
sleepWithStop,
|
sleepWithStop,
|
||||||
waitForTabUrlMatchUntilStopped,
|
waitForTabUrlMatchUntilStopped,
|
||||||
});
|
});
|
||||||
|
const kiroDeviceAuthExecutor = self.MultiPageBackgroundKiroDeviceAuth?.createKiroDeviceAuthExecutor({
|
||||||
|
addLog,
|
||||||
|
completeNodeFromBackground,
|
||||||
|
fetchImpl: typeof fetch === 'function' ? fetch.bind(globalThis) : null,
|
||||||
|
getState,
|
||||||
|
registerTab,
|
||||||
|
reuseOrCreateTab,
|
||||||
|
setState,
|
||||||
|
sleepWithStop,
|
||||||
|
throwIfStopped,
|
||||||
|
});
|
||||||
const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({
|
const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({
|
||||||
addLog,
|
addLog,
|
||||||
chrome,
|
chrome,
|
||||||
@@ -12817,6 +13156,9 @@ 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-await-device-login': (state) => kiroDeviceAuthExecutor.executeKiroAwaitDeviceLogin(state),
|
||||||
|
'kiro-upload-credential': (state) => kiroDeviceAuthExecutor.executeKiroUploadCredential(state),
|
||||||
};
|
};
|
||||||
const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter({
|
const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter({
|
||||||
addLog,
|
addLog,
|
||||||
@@ -12960,6 +13302,7 @@ function buildNodeRegistry(definitions = []) {
|
|||||||
return self.MultiPageBackgroundStepRegistry?.createNodeRegistry(
|
return self.MultiPageBackgroundStepRegistry?.createNodeRegistry(
|
||||||
definitions.map((definition) => ({
|
definitions.map((definition) => ({
|
||||||
...definition,
|
...definition,
|
||||||
|
legacyStepId: definition.legacyStepId || definition.id,
|
||||||
nodeId: definition.nodeId || definition.key,
|
nodeId: definition.nodeId || definition.key,
|
||||||
displayOrder: definition.displayOrder || definition.order,
|
displayOrder: definition.displayOrder || definition.order,
|
||||||
executeKey: definition.executeKey || definition.key,
|
executeKey: definition.executeKey || definition.key,
|
||||||
@@ -12968,75 +13311,65 @@ function buildNodeRegistry(definitions = []) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function acquireTopLevelAuthChainExecution(step, state = {}) {
|
||||||
|
return acquireTopLevelAuthChainExecutionForNode(getNodeIdByStepForState(step, state), state);
|
||||||
|
}
|
||||||
|
|
||||||
function buildStepRegistry(definitions = []) {
|
function buildStepRegistry(definitions = []) {
|
||||||
const nodeRegistry = buildNodeRegistry(definitions);
|
const normalizedDefinitions = (Array.isArray(definitions) ? definitions : [])
|
||||||
|
.map((definition) => ({
|
||||||
|
...definition,
|
||||||
|
legacyStepId: Number(definition?.legacyStepId ?? definition?.id) || 0,
|
||||||
|
nodeId: String(definition?.nodeId || definition?.key || '').trim(),
|
||||||
|
}))
|
||||||
|
.filter((definition) => definition.nodeId);
|
||||||
|
const nodeRegistry = buildNodeRegistry(normalizedDefinitions);
|
||||||
|
const stepToNodeDefinition = new Map(
|
||||||
|
normalizedDefinitions
|
||||||
|
.filter((definition) => Number.isInteger(definition.legacyStepId) && definition.legacyStepId > 0)
|
||||||
|
.map((definition) => [definition.legacyStepId, definition])
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
executeNode: (nodeId, state) => nodeRegistry.executeNode(nodeId, state),
|
executeNode: (nodeId, state) => nodeRegistry.executeNode(nodeId, state),
|
||||||
getNodeDefinition: (nodeId) => nodeRegistry.getNodeDefinition(nodeId),
|
getNodeDefinition: (nodeId) => nodeRegistry.getNodeDefinition(nodeId),
|
||||||
getOrderedNodes: () => nodeRegistry.getOrderedNodes(),
|
getOrderedNodes: () => nodeRegistry.getOrderedNodes(),
|
||||||
executeStep: (step, state) => {
|
executeStep: (step, state) => {
|
||||||
const nodeId = String(getStepDefinitionForState(step, state)?.key || '').trim();
|
const nodeId = String(stepToNodeDefinition.get(Number(step))?.nodeId || '').trim();
|
||||||
if (!nodeId) {
|
if (!nodeId) {
|
||||||
throw new Error(`未知节点:${step}`);
|
throw new Error(`Unknown step: ${step}`);
|
||||||
}
|
}
|
||||||
return nodeRegistry.executeNode(nodeId, state);
|
return nodeRegistry.executeNode(nodeId, state);
|
||||||
},
|
},
|
||||||
getStepDefinition: (step) => {
|
getStepDefinition: (step) => {
|
||||||
const nodeId = String(getStepDefinitionForState(step, {})?.key || '').trim();
|
const nodeId = String(stepToNodeDefinition.get(Number(step))?.nodeId || '').trim();
|
||||||
return nodeId ? nodeRegistry.getNodeDefinition(nodeId) : null;
|
return nodeId ? nodeRegistry.getNodeDefinition(nodeId) : null;
|
||||||
},
|
},
|
||||||
getOrderedSteps: () => nodeRegistry.getOrderedNodes(),
|
getOrderedSteps: () => nodeRegistry.getOrderedNodes(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function acquireTopLevelAuthChainExecution(step, state = {}) {
|
const stepRegistryCache = new Map();
|
||||||
return acquireTopLevelAuthChainExecutionForNode(getNodeIdByStepForState(step, state), state);
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalStepRegistry = buildStepRegistry(NORMAL_STEP_DEFINITIONS);
|
|
||||||
const normalPhoneStepRegistry = buildStepRegistry(NORMAL_PHONE_STEP_DEFINITIONS);
|
|
||||||
const normalPhoneBoundEmailReloginStepRegistry = buildStepRegistry(NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS);
|
|
||||||
const plusPayPalStepRegistry = buildStepRegistry(PLUS_PAYPAL_STEP_DEFINITIONS);
|
|
||||||
const plusPayPalPhoneStepRegistry = buildStepRegistry(PLUS_PAYPAL_PHONE_STEP_DEFINITIONS);
|
|
||||||
const plusPayPalPhoneBoundEmailReloginStepRegistry = buildStepRegistry(PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS);
|
|
||||||
const plusGoPayStepRegistry = buildStepRegistry(PLUS_GOPAY_STEP_DEFINITIONS);
|
|
||||||
const plusGoPayPhoneStepRegistry = buildStepRegistry(PLUS_GOPAY_PHONE_STEP_DEFINITIONS);
|
|
||||||
const plusGoPayPhoneBoundEmailReloginStepRegistry = buildStepRegistry(PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS);
|
|
||||||
const plusGpcStepRegistry = buildStepRegistry(PLUS_GPC_STEP_DEFINITIONS);
|
|
||||||
const plusGpcPhoneStepRegistry = buildStepRegistry(PLUS_GPC_PHONE_STEP_DEFINITIONS);
|
|
||||||
const plusGpcPhoneBoundEmailReloginStepRegistry = buildStepRegistry(PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS);
|
|
||||||
|
|
||||||
function getStepRegistryForState(state = {}) {
|
function getStepRegistryForState(state = {}) {
|
||||||
const activeFlowId = String(state?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
|
const definitions = getNodeDefinitionsForState(state);
|
||||||
if (activeFlowId !== DEFAULT_ACTIVE_FLOW_ID) {
|
const activeFlowId = String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
|
||||||
throw new Error(`当前尚未注册 flow=${activeFlowId} 的步骤执行器。`);
|
const cacheKey = `${activeFlowId}:${(Array.isArray(definitions) ? definitions : [])
|
||||||
|
.map((definition) => [
|
||||||
|
Number(definition?.legacyStepId ?? definition?.id) || 0,
|
||||||
|
String(definition?.nodeId || definition?.key || '').trim(),
|
||||||
|
String(definition?.executeKey || definition?.key || '').trim(),
|
||||||
|
Number(definition?.displayOrder ?? definition?.order) || 0,
|
||||||
|
].join(':'))
|
||||||
|
.join('|')}`;
|
||||||
|
|
||||||
|
if (!cacheKey || cacheKey === `${activeFlowId}:`) {
|
||||||
|
return buildStepRegistry([]);
|
||||||
}
|
}
|
||||||
const signupMethod = getSignupMethodForStepDefinitions(state);
|
if (!stepRegistryCache.has(cacheKey)) {
|
||||||
const useBoundEmailRelogin = signupMethod === SIGNUP_METHOD_PHONE
|
stepRegistryCache.set(cacheKey, buildStepRegistry(definitions));
|
||||||
&& Boolean(state?.phoneSignupReloginAfterBindEmailEnabled);
|
|
||||||
if (!isPlusModeState(state)) {
|
|
||||||
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
|
||||||
return useBoundEmailRelogin ? normalPhoneBoundEmailReloginStepRegistry : normalPhoneStepRegistry;
|
|
||||||
}
|
}
|
||||||
return normalStepRegistry;
|
return stepRegistryCache.get(cacheKey);
|
||||||
}
|
|
||||||
const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod);
|
|
||||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
|
||||||
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
|
||||||
return useBoundEmailRelogin ? plusGpcPhoneBoundEmailReloginStepRegistry : plusGpcPhoneStepRegistry;
|
|
||||||
}
|
|
||||||
return plusGpcStepRegistry;
|
|
||||||
}
|
|
||||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) {
|
|
||||||
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
|
||||||
return useBoundEmailRelogin ? plusGoPayPhoneBoundEmailReloginStepRegistry : plusGoPayPhoneStepRegistry;
|
|
||||||
}
|
|
||||||
return plusGoPayStepRegistry;
|
|
||||||
}
|
|
||||||
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
|
||||||
return useBoundEmailRelogin ? plusPayPalPhoneBoundEmailReloginStepRegistry : plusPayPalPhoneStepRegistry;
|
|
||||||
}
|
|
||||||
return plusPayPalStepRegistry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestOAuthUrlFromPanel(state, options = {}) {
|
async function requestOAuthUrlFromPanel(state, options = {}) {
|
||||||
|
|||||||
+69
-15
@@ -120,6 +120,40 @@
|
|||||||
'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({
|
||||||
|
openai: OPENAI_FLOW_FIELD_GROUPS,
|
||||||
|
kiro: KIRO_FLOW_FIELD_GROUPS,
|
||||||
|
});
|
||||||
|
|
||||||
function isPlainObject(value) {
|
function isPlainObject(value) {
|
||||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||||
@@ -210,11 +244,12 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function flattenOpenAiFlowState(flowState = {}) {
|
function flattenFlowStateById(flowState = {}, flowId = 'openai') {
|
||||||
const openaiState = normalizePlainObject(flowState.openai);
|
const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {};
|
||||||
|
const scopedState = normalizePlainObject(flowState[flowId]);
|
||||||
const next = {};
|
const next = {};
|
||||||
for (const [groupKey, fields] of Object.entries(OPENAI_FLOW_FIELD_GROUPS)) {
|
for (const [groupKey, fields] of Object.entries(fieldGroups)) {
|
||||||
const group = normalizePlainObject(openaiState[groupKey]);
|
const group = normalizePlainObject(scopedState[groupKey]);
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
if (Object.prototype.hasOwnProperty.call(group, field)) {
|
if (Object.prototype.hasOwnProperty.call(group, field)) {
|
||||||
next[field] = cloneValue(group[field]);
|
next[field] = cloneValue(group[field]);
|
||||||
@@ -224,23 +259,29 @@
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildOpenAiFlowState(baseValue = {}, state = {}) {
|
function buildScopedFlowState(baseFlowState = {}, state = {}, flowId = 'openai') {
|
||||||
const baseFlowState = cloneValue(normalizePlainObject(baseValue));
|
const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {};
|
||||||
const baseOpenAi = cloneValue(normalizePlainObject(baseFlowState.openai));
|
const baseScopedState = cloneValue(normalizePlainObject(baseFlowState[flowId]));
|
||||||
const openaiState = {
|
const scopedState = {
|
||||||
...baseOpenAi,
|
...baseScopedState,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [groupKey, fields] of Object.entries(OPENAI_FLOW_FIELD_GROUPS)) {
|
for (const [groupKey, fields] of Object.entries(fieldGroups)) {
|
||||||
openaiState[groupKey] = {
|
scopedState[groupKey] = {
|
||||||
...cloneValue(normalizePlainObject(baseOpenAi[groupKey])),
|
...cloneValue(normalizePlainObject(baseScopedState[groupKey])),
|
||||||
...pickDefinedFields(state, fields),
|
...pickDefinedFields(state, fields),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return scopedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOpenAiFlowState(baseValue = {}, state = {}) {
|
||||||
|
const baseFlowState = cloneValue(normalizePlainObject(baseValue));
|
||||||
return {
|
return {
|
||||||
...baseFlowState,
|
...baseFlowState,
|
||||||
openai: openaiState,
|
openai: buildScopedFlowState(baseFlowState, state, 'openai'),
|
||||||
|
kiro: buildScopedFlowState(baseFlowState, state, 'kiro'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +306,11 @@
|
|||||||
luckmail: {},
|
luckmail: {},
|
||||||
identity: {},
|
identity: {},
|
||||||
},
|
},
|
||||||
|
kiro: {
|
||||||
|
auth: {},
|
||||||
|
upload: {},
|
||||||
|
identity: {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -375,9 +421,12 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(next, flattenOpenAiFlowState(flowState));
|
Object.assign(next, flattenFlowStateById(flowState, 'openai'));
|
||||||
|
Object.assign(next, flattenFlowStateById(flowState, 'kiro'));
|
||||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'flowState')) {
|
if (Object.prototype.hasOwnProperty.call(runtimeState, 'flowState')) {
|
||||||
Object.assign(next, flattenOpenAiFlowState(normalizePlainObject(runtimeState.flowState)));
|
const runtimeFlowState = normalizePlainObject(runtimeState.flowState);
|
||||||
|
Object.assign(next, flattenFlowStateById(runtimeFlowState, 'openai'));
|
||||||
|
Object.assign(next, flattenFlowStateById(runtimeFlowState, 'kiro'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return next;
|
return next;
|
||||||
@@ -396,6 +445,9 @@
|
|||||||
flowState: cloneValue(runtimeState.flowState),
|
flowState: cloneValue(runtimeState.flowState),
|
||||||
sharedState: cloneValue(runtimeState.sharedState),
|
sharedState: cloneValue(runtimeState.sharedState),
|
||||||
serviceState: cloneValue(runtimeState.serviceState),
|
serviceState: cloneValue(runtimeState.serviceState),
|
||||||
|
flows: cloneValue(runtimeState.flowState),
|
||||||
|
shared: cloneValue(runtimeState.sharedState),
|
||||||
|
services: cloneValue(runtimeState.serviceState),
|
||||||
runtimeState,
|
runtimeState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -422,6 +474,8 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
DEFAULT_ACTIVE_FLOW_ID,
|
DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
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,527 @@
|
|||||||
|
(function attachBackgroundKiroDeviceAuth(root, factory) {
|
||||||
|
root.MultiPageBackgroundKiroDeviceAuth = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroDeviceAuthModule() {
|
||||||
|
const DEFAULT_REGION = 'us-east-1';
|
||||||
|
const DEVICE_LOGIN_START_URL = 'https://view.awsapps.com/start';
|
||||||
|
const DEFAULT_SCOPES = Object.freeze([
|
||||||
|
'codewhisperer:completions',
|
||||||
|
'codewhisperer:analysis',
|
||||||
|
'codewhisperer:conversations',
|
||||||
|
'codewhisperer:transformations',
|
||||||
|
'codewhisperer:taskassist',
|
||||||
|
]);
|
||||||
|
|
||||||
|
function cleanString(value = '') {
|
||||||
|
return String(value ?? '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRegion(value = '', fallback = DEFAULT_REGION) {
|
||||||
|
return cleanString(value) || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOidcBaseUrl(region = DEFAULT_REGION) {
|
||||||
|
return `https://oidc.${normalizeRegion(region)}.amazonaws.com`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeKiroRsBaseUrl(value = '') {
|
||||||
|
const normalized = cleanString(value).replace(/\/+$/, '');
|
||||||
|
if (!normalized) {
|
||||||
|
throw new Error('Missing kiro.rs admin URL.');
|
||||||
|
}
|
||||||
|
return normalized.endsWith('/admin')
|
||||||
|
? normalized.slice(0, -'/admin'.length)
|
||||||
|
: normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readResponse(response) {
|
||||||
|
const text = await response.text();
|
||||||
|
let json = null;
|
||||||
|
try {
|
||||||
|
json = text ? JSON.parse(text) : null;
|
||||||
|
} catch (_error) {
|
||||||
|
json = null;
|
||||||
|
}
|
||||||
|
return { text, json };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(error) {
|
||||||
|
return error instanceof Error ? error.message : String(error ?? 'Unknown error');
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePositiveInteger(value, fallback) {
|
||||||
|
const numeric = Math.floor(Number(value));
|
||||||
|
if (Number.isInteger(numeric) && numeric > 0) {
|
||||||
|
return numeric;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCredentialUploadOptions(state = {}) {
|
||||||
|
const next = {
|
||||||
|
priority: Math.max(0, Math.floor(Number(state?.kiroRsPriority) || 0)),
|
||||||
|
authMethod: 'IdC',
|
||||||
|
provider: 'BuilderId',
|
||||||
|
};
|
||||||
|
|
||||||
|
const endpoint = cleanString(state?.kiroRsEndpoint);
|
||||||
|
const authRegion = cleanString(state?.kiroRsAuthRegion);
|
||||||
|
const apiRegion = cleanString(state?.kiroRsApiRegion);
|
||||||
|
if (endpoint) {
|
||||||
|
next.endpoint = endpoint;
|
||||||
|
}
|
||||||
|
if (authRegion) {
|
||||||
|
next.authRegion = authRegion;
|
||||||
|
}
|
||||||
|
if (apiRegion) {
|
||||||
|
next.apiRegion = apiRegion;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state?.ipProxyEnabled) {
|
||||||
|
const proxyUrl = cleanString(state?.ipProxyApiUrl)
|
||||||
|
|| (() => {
|
||||||
|
const host = cleanString(state?.ipProxyHost);
|
||||||
|
const port = cleanString(state?.ipProxyPort);
|
||||||
|
if (!host || !port) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const protocol = cleanString(state?.ipProxyProtocol) || 'http';
|
||||||
|
return `${protocol}://${host}:${port}`;
|
||||||
|
})();
|
||||||
|
if (proxyUrl) {
|
||||||
|
next.proxyUrl = proxyUrl;
|
||||||
|
}
|
||||||
|
const proxyUsername = cleanString(state?.ipProxyUsername);
|
||||||
|
const proxyPassword = String(state?.ipProxyPassword || '');
|
||||||
|
if (proxyUsername) {
|
||||||
|
next.proxyUsername = proxyUsername;
|
||||||
|
}
|
||||||
|
if (proxyPassword) {
|
||||||
|
next.proxyPassword = proxyPassword;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startBuilderIdDeviceLogin(region, fetchImpl) {
|
||||||
|
const normalizedRegion = normalizeRegion(region);
|
||||||
|
const oidcBaseUrl = buildOidcBaseUrl(normalizedRegion);
|
||||||
|
const registerResponse = await fetchImpl(`${oidcBaseUrl}/client/register`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
clientName: 'Codex Registration Extension',
|
||||||
|
clientType: 'public',
|
||||||
|
scopes: DEFAULT_SCOPES,
|
||||||
|
grantTypes: ['urn:ietf:params:oauth:grant-type:device_code', 'refresh_token'],
|
||||||
|
issuerUrl: DEVICE_LOGIN_START_URL,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const registerBody = await readResponse(registerResponse);
|
||||||
|
if (!registerResponse.ok) {
|
||||||
|
throw new Error(`Builder ID client registration failed: ${cleanString(registerBody.text || registerResponse.statusText) || registerResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientId = cleanString(registerBody.json?.clientId);
|
||||||
|
const clientSecret = String(registerBody.json?.clientSecret || '');
|
||||||
|
if (!clientId || !clientSecret) {
|
||||||
|
throw new Error('Builder ID client registration response is missing client credentials.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const authorizationResponse = await fetchImpl(`${oidcBaseUrl}/device_authorization`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
startUrl: DEVICE_LOGIN_START_URL,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const authorizationBody = await readResponse(authorizationResponse);
|
||||||
|
if (!authorizationResponse.ok) {
|
||||||
|
throw new Error(`Builder ID device authorization failed: ${cleanString(authorizationBody.text || authorizationResponse.statusText) || authorizationResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deviceCode = String(authorizationBody.json?.deviceCode || '');
|
||||||
|
const userCode = cleanString(authorizationBody.json?.userCode);
|
||||||
|
const verificationUri = cleanString(authorizationBody.json?.verificationUri);
|
||||||
|
const verificationUriComplete = cleanString(
|
||||||
|
authorizationBody.json?.verificationUriComplete || verificationUri
|
||||||
|
);
|
||||||
|
const interval = normalizePositiveInteger(authorizationBody.json?.interval, 5);
|
||||||
|
const expiresIn = normalizePositiveInteger(authorizationBody.json?.expiresIn, 600);
|
||||||
|
if (!deviceCode || !userCode || !verificationUriComplete) {
|
||||||
|
throw new Error('Builder ID device authorization response is missing required fields.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
deviceCode,
|
||||||
|
expiresAt: Date.now() + expiresIn * 1000,
|
||||||
|
expiresIn,
|
||||||
|
interval,
|
||||||
|
region: normalizedRegion,
|
||||||
|
userCode,
|
||||||
|
verificationUri,
|
||||||
|
verificationUriComplete,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollBuilderIdDeviceAuth(params = {}, fetchImpl) {
|
||||||
|
const oidcBaseUrl = buildOidcBaseUrl(params.region);
|
||||||
|
const response = await fetchImpl(`${oidcBaseUrl}/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
clientId: params.clientId,
|
||||||
|
clientSecret: params.clientSecret,
|
||||||
|
grantType: 'urn:ietf:params:oauth:grant-type:device_code',
|
||||||
|
deviceCode: params.deviceCode,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const body = await readResponse(response);
|
||||||
|
if (response.status === 200) {
|
||||||
|
return {
|
||||||
|
completed: true,
|
||||||
|
accessToken: String(body.json?.accessToken || ''),
|
||||||
|
refreshToken: String(body.json?.refreshToken || ''),
|
||||||
|
expiresIn: normalizePositiveInteger(body.json?.expiresIn, 3600),
|
||||||
|
region: normalizeRegion(params.region),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (response.status === 400) {
|
||||||
|
const errorCode = cleanString(body.json?.error);
|
||||||
|
if (errorCode === 'authorization_pending') {
|
||||||
|
return { completed: false, status: 'pending' };
|
||||||
|
}
|
||||||
|
if (errorCode === 'slow_down') {
|
||||||
|
return { completed: false, status: 'slow_down' };
|
||||||
|
}
|
||||||
|
if (errorCode === 'expired_token') {
|
||||||
|
throw new Error('Kiro device login expired.');
|
||||||
|
}
|
||||||
|
if (errorCode === 'access_denied') {
|
||||||
|
throw new Error('User denied the Builder ID device login request.');
|
||||||
|
}
|
||||||
|
throw new Error(`Builder ID authorization failed: ${errorCode || cleanString(body.text || response.statusText) || response.status}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Builder ID token request failed: HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkKiroRsConnection(baseUrl, apiKey, fetchImpl) {
|
||||||
|
const normalizedBaseUrl = normalizeKiroRsBaseUrl(baseUrl);
|
||||||
|
const response = await fetchImpl(`${normalizedBaseUrl}/api/admin/credentials`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'x-api-key': String(apiKey || ''),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const body = await readResponse(response);
|
||||||
|
if (response.ok) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
message: `kiro.rs connection ok (HTTP ${response.status})`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (response.status === 405) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
message: 'kiro.rs upload endpoint is reachable.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
message: `kiro.rs API key rejected (HTTP ${response.status})`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (response.status === 404) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
message: 'kiro.rs admin endpoint not found.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
message: cleanString(body.json?.error?.message || body.json?.message || body.text || response.statusText)
|
||||||
|
|| `kiro.rs connection failed (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 credential upload failed: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
credentialId: Number(body.json?.credentialId || body.json?.credential_id || 0) || null,
|
||||||
|
email: cleanString(body.json?.email),
|
||||||
|
message: cleanString(body.json?.message) || 'Credential uploaded.',
|
||||||
|
raw: body.json,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createKiroDeviceAuthExecutor(deps = {}) {
|
||||||
|
const {
|
||||||
|
addLog = async () => {},
|
||||||
|
completeNodeFromBackground,
|
||||||
|
fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null,
|
||||||
|
getState = async () => ({}),
|
||||||
|
registerTab = async () => {},
|
||||||
|
reuseOrCreateTab = async () => null,
|
||||||
|
setState = async () => {},
|
||||||
|
sleepWithStop = async (ms) => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
},
|
||||||
|
throwIfStopped = () => {},
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
if (typeof completeNodeFromBackground !== 'function') {
|
||||||
|
throw new Error('Kiro device auth executor requires completeNodeFromBackground.');
|
||||||
|
}
|
||||||
|
if (typeof fetchImpl !== 'function') {
|
||||||
|
throw new Error('Kiro device auth executor requires fetch support.');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function log(message, level, nodeId) {
|
||||||
|
await addLog(message, level, { nodeId });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getExecutionState(state = {}) {
|
||||||
|
if (state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistFailure(updates = {}) {
|
||||||
|
if (updates && Object.keys(updates).length) {
|
||||||
|
await setState(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeKiroStartDeviceLogin(state = {}) {
|
||||||
|
const nodeId = String(state?.nodeId || 'kiro-start-device-login').trim();
|
||||||
|
try {
|
||||||
|
const latestState = await getExecutionState(state);
|
||||||
|
const auth = await startBuilderIdDeviceLogin(
|
||||||
|
latestState.kiroRegion || DEFAULT_REGION,
|
||||||
|
fetchImpl
|
||||||
|
);
|
||||||
|
const loginUrl = cleanString(auth.verificationUriComplete || auth.verificationUri);
|
||||||
|
const tabId = loginUrl ? await reuseOrCreateTab('kiro-device-auth', loginUrl) : null;
|
||||||
|
if (Number.isInteger(tabId)) {
|
||||||
|
await registerTab('kiro-device-auth', tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updates = {
|
||||||
|
kiroAccessToken: '',
|
||||||
|
kiroAuthError: '',
|
||||||
|
kiroAuthExpiresAt: auth.expiresAt,
|
||||||
|
kiroAuthIntervalSeconds: auth.interval,
|
||||||
|
kiroAuthRegion: auth.region,
|
||||||
|
kiroAuthStatus: 'waiting_user',
|
||||||
|
kiroAuthTabId: Number.isInteger(tabId) ? tabId : null,
|
||||||
|
kiroClientId: auth.clientId,
|
||||||
|
kiroClientSecret: auth.clientSecret,
|
||||||
|
kiroCredentialId: null,
|
||||||
|
kiroDeviceAuthorizationCode: auth.deviceCode,
|
||||||
|
kiroDeviceCode: auth.userCode,
|
||||||
|
kiroLastConnectionMessage: '',
|
||||||
|
kiroLastUploadAt: 0,
|
||||||
|
kiroLoginUrl: loginUrl,
|
||||||
|
kiroRefreshToken: '',
|
||||||
|
kiroUploadError: '',
|
||||||
|
kiroUploadStatus: 'waiting_login',
|
||||||
|
kiroUserCode: auth.userCode,
|
||||||
|
kiroVerificationUri: auth.verificationUri,
|
||||||
|
kiroVerificationUriComplete: loginUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
await setState(updates);
|
||||||
|
await log(`Kiro device login started. Open ${loginUrl} and approve with code ${auth.userCode}.`, 'info', nodeId);
|
||||||
|
await completeNodeFromBackground(nodeId, updates);
|
||||||
|
} catch (error) {
|
||||||
|
const message = getErrorMessage(error);
|
||||||
|
await persistFailure({
|
||||||
|
kiroAuthError: message,
|
||||||
|
kiroAuthStatus: 'error',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeKiroAwaitDeviceLogin(state = {}) {
|
||||||
|
const nodeId = String(state?.nodeId || 'kiro-await-device-login').trim();
|
||||||
|
try {
|
||||||
|
const latestState = await getExecutionState(state);
|
||||||
|
const clientId = cleanString(latestState.kiroClientId);
|
||||||
|
const clientSecret = String(latestState.kiroClientSecret || '');
|
||||||
|
const deviceCode = String(latestState.kiroDeviceAuthorizationCode || '');
|
||||||
|
const region = normalizeRegion(latestState.kiroAuthRegion || latestState.kiroRegion || DEFAULT_REGION);
|
||||||
|
const expiresAt = Math.max(0, Number(latestState.kiroAuthExpiresAt) || 0);
|
||||||
|
if (!clientId || !clientSecret || !deviceCode) {
|
||||||
|
throw new Error('Kiro device login has not been started yet.');
|
||||||
|
}
|
||||||
|
if (!expiresAt || expiresAt <= Date.now()) {
|
||||||
|
throw new Error('Kiro device login expired. Restart step 1.');
|
||||||
|
}
|
||||||
|
|
||||||
|
await setState({
|
||||||
|
kiroAuthError: '',
|
||||||
|
kiroAuthStatus: 'waiting_user',
|
||||||
|
kiroUploadStatus: 'waiting_login',
|
||||||
|
});
|
||||||
|
await log('Waiting for Kiro device login approval...', 'info', nodeId);
|
||||||
|
|
||||||
|
let intervalSeconds = normalizePositiveInteger(latestState.kiroAuthIntervalSeconds, 5);
|
||||||
|
while (Date.now() < expiresAt) {
|
||||||
|
throwIfStopped();
|
||||||
|
const result = await pollBuilderIdDeviceAuth({
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
deviceCode,
|
||||||
|
region,
|
||||||
|
}, fetchImpl);
|
||||||
|
if (result.completed) {
|
||||||
|
const updates = {
|
||||||
|
kiroAccessToken: result.accessToken,
|
||||||
|
kiroAuthError: '',
|
||||||
|
kiroAuthStatus: 'authorized',
|
||||||
|
kiroRefreshToken: result.refreshToken,
|
||||||
|
kiroUploadError: '',
|
||||||
|
kiroUploadStatus: 'ready_to_upload',
|
||||||
|
};
|
||||||
|
await setState(updates);
|
||||||
|
await log('Kiro device login approved. Refresh token captured.', 'ok', nodeId);
|
||||||
|
await completeNodeFromBackground(nodeId, updates);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status === 'slow_down') {
|
||||||
|
intervalSeconds = Math.max(intervalSeconds + 5, 10);
|
||||||
|
}
|
||||||
|
await sleepWithStop(intervalSeconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Kiro device login expired. Restart step 1.');
|
||||||
|
} catch (error) {
|
||||||
|
const message = getErrorMessage(error);
|
||||||
|
await persistFailure({
|
||||||
|
kiroAuthError: message,
|
||||||
|
kiroAuthStatus: /expired/i.test(message) ? 'expired' : 'error',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeKiroUploadCredential(state = {}) {
|
||||||
|
const nodeId = String(state?.nodeId || 'kiro-upload-credential').trim();
|
||||||
|
try {
|
||||||
|
const latestState = await getExecutionState(state);
|
||||||
|
const refreshToken = String(latestState.kiroRefreshToken || '');
|
||||||
|
const clientId = cleanString(latestState.kiroClientId);
|
||||||
|
const clientSecret = String(latestState.kiroClientSecret || '');
|
||||||
|
const region = normalizeRegion(latestState.kiroAuthRegion || latestState.kiroRegion || DEFAULT_REGION);
|
||||||
|
const kiroRsUrl = String(latestState.kiroRsUrl || '');
|
||||||
|
const kiroRsKey = String(latestState.kiroRsKey || '');
|
||||||
|
if (!refreshToken || !clientId || !clientSecret) {
|
||||||
|
throw new Error('Kiro refresh token is missing. Complete step 2 first.');
|
||||||
|
}
|
||||||
|
if (!cleanString(kiroRsUrl)) {
|
||||||
|
throw new Error('Missing kiro.rs admin URL.');
|
||||||
|
}
|
||||||
|
if (!cleanString(kiroRsKey)) {
|
||||||
|
throw new Error('Missing kiro.rs API key.');
|
||||||
|
}
|
||||||
|
|
||||||
|
await setState({
|
||||||
|
kiroUploadError: '',
|
||||||
|
kiroUploadStatus: 'uploading',
|
||||||
|
});
|
||||||
|
await log('Uploading Builder ID credential to kiro.rs...', 'info', nodeId);
|
||||||
|
|
||||||
|
const connection = await checkKiroRsConnection(kiroRsUrl, kiroRsKey, fetchImpl);
|
||||||
|
await setState({
|
||||||
|
kiroLastConnectionMessage: connection.message,
|
||||||
|
});
|
||||||
|
if (!connection.ok) {
|
||||||
|
throw new Error(connection.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadOptions = buildCredentialUploadOptions(latestState);
|
||||||
|
const uploadPayload = {
|
||||||
|
refreshToken,
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
region,
|
||||||
|
...(cleanString(latestState.kiroAuthorizedEmail)
|
||||||
|
? { email: cleanString(latestState.kiroAuthorizedEmail) }
|
||||||
|
: {}),
|
||||||
|
...uploadOptions,
|
||||||
|
};
|
||||||
|
const uploadResult = await uploadBuilderIdCredential(
|
||||||
|
kiroRsUrl,
|
||||||
|
kiroRsKey,
|
||||||
|
uploadPayload,
|
||||||
|
fetchImpl
|
||||||
|
);
|
||||||
|
const updates = {
|
||||||
|
kiroAuthorizedEmail: uploadResult.email || cleanString(latestState.kiroAuthorizedEmail),
|
||||||
|
kiroCredentialId: uploadResult.credentialId,
|
||||||
|
kiroLastUploadAt: Date.now(),
|
||||||
|
kiroUploadError: '',
|
||||||
|
kiroUploadStatus: uploadResult.message || 'uploaded',
|
||||||
|
};
|
||||||
|
|
||||||
|
await setState(updates);
|
||||||
|
await log(`kiro.rs upload completed: ${updates.kiroUploadStatus}`, 'ok', nodeId);
|
||||||
|
await completeNodeFromBackground(nodeId, updates);
|
||||||
|
} catch (error) {
|
||||||
|
const message = getErrorMessage(error);
|
||||||
|
await persistFailure({
|
||||||
|
kiroUploadError: message,
|
||||||
|
kiroUploadStatus: 'error',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
executeKiroAwaitDeviceLogin,
|
||||||
|
executeKiroStartDeviceLogin,
|
||||||
|
executeKiroUploadCredential,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildCredentialUploadOptions,
|
||||||
|
checkKiroRsConnection,
|
||||||
|
createKiroDeviceAuthExecutor,
|
||||||
|
normalizeKiroRsBaseUrl,
|
||||||
|
pollBuilderIdDeviceAuth,
|
||||||
|
startBuilderIdDeviceLogin,
|
||||||
|
uploadBuilderIdCredential,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -4,13 +4,15 @@
|
|||||||
function createNodeRegistry(definitions = []) {
|
function createNodeRegistry(definitions = []) {
|
||||||
const ordered = (Array.isArray(definitions) ? definitions : [])
|
const ordered = (Array.isArray(definitions) ? definitions : [])
|
||||||
.map((definition) => ({
|
.map((definition) => ({
|
||||||
|
legacyStepId: Number(definition?.legacyStepId ?? definition?.id) || 0,
|
||||||
|
flowId: String(definition?.flowId || '').trim(),
|
||||||
nodeId: String(definition?.nodeId || definition?.key || '').trim(),
|
nodeId: String(definition?.nodeId || definition?.key || '').trim(),
|
||||||
displayOrder: Number(definition?.displayOrder ?? definition?.order),
|
displayOrder: Number(definition?.displayOrder ?? definition?.order),
|
||||||
executeKey: String(definition?.executeKey || definition?.key || definition?.nodeId || '').trim(),
|
executeKey: String(definition?.executeKey || definition?.key || definition?.nodeId || '').trim(),
|
||||||
title: String(definition?.title || '').trim(),
|
title: String(definition?.title || '').trim(),
|
||||||
execute: definition?.execute,
|
execute: definition?.execute,
|
||||||
}))
|
}))
|
||||||
.filter((definition) => definition.nodeId && typeof definition.execute === 'function')
|
.filter((definition) => definition.nodeId)
|
||||||
.sort((left, right) => {
|
.sort((left, right) => {
|
||||||
const leftOrder = Number.isFinite(left.displayOrder) ? left.displayOrder : 0;
|
const leftOrder = Number.isFinite(left.displayOrder) ? left.displayOrder : 0;
|
||||||
const rightOrder = Number.isFinite(right.displayOrder) ? right.displayOrder : 0;
|
const rightOrder = Number.isFinite(right.displayOrder) ? right.displayOrder : 0;
|
||||||
@@ -31,7 +33,10 @@
|
|||||||
function executeNode(nodeId, state) {
|
function executeNode(nodeId, state) {
|
||||||
const definition = getNodeDefinition(nodeId);
|
const definition = getNodeDefinition(nodeId);
|
||||||
if (!definition) {
|
if (!definition) {
|
||||||
throw new Error(`未知节点:${nodeId}`);
|
throw new Error(`Unknown node: ${nodeId}`);
|
||||||
|
}
|
||||||
|
if (typeof definition.execute !== 'function') {
|
||||||
|
throw new Error(`Missing node executor: ${definition.executeKey || definition.nodeId}`);
|
||||||
}
|
}
|
||||||
return definition.execute(state);
|
return definition.execute(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,35 @@
|
|||||||
const PLUS_GPC_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
const PLUS_GPC_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||||
const PLUS_GPC_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
const PLUS_GPC_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
||||||
const PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
const PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
||||||
|
const KIRO_STEP_DEFINITIONS = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
order: 10,
|
||||||
|
key: 'kiro-start-device-login',
|
||||||
|
title: 'Start device login',
|
||||||
|
sourceId: 'kiro-device-auth',
|
||||||
|
driverId: 'background/kiro-device-auth',
|
||||||
|
command: 'kiro-start-device-login',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
order: 20,
|
||||||
|
key: 'kiro-await-device-login',
|
||||||
|
title: 'Wait for device login',
|
||||||
|
sourceId: 'kiro-device-auth',
|
||||||
|
driverId: 'background/kiro-device-auth',
|
||||||
|
command: 'kiro-await-device-login',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
order: 30,
|
||||||
|
key: 'kiro-upload-credential',
|
||||||
|
title: 'Upload credential to kiro.rs',
|
||||||
|
sourceId: 'kiro-rs-admin',
|
||||||
|
driverId: 'background/kiro-device-auth',
|
||||||
|
command: 'kiro-upload-credential',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const PHONE_SIGNUP_TITLE_OVERRIDES = Object.freeze({
|
const PHONE_SIGNUP_TITLE_OVERRIDES = Object.freeze({
|
||||||
'submit-signup-email': '注册并输入手机号',
|
'submit-signup-email': '注册并输入手机号',
|
||||||
@@ -238,6 +267,20 @@
|
|||||||
getPlusPaymentStepTitle: getOpenAiPlusPaymentStepTitle,
|
getPlusPaymentStepTitle: getOpenAiPlusPaymentStepTitle,
|
||||||
resolveStepTitle: getOpenAiResolvedStepTitle,
|
resolveStepTitle: getOpenAiResolvedStepTitle,
|
||||||
},
|
},
|
||||||
|
kiro: {
|
||||||
|
getAllSteps() {
|
||||||
|
return KIRO_STEP_DEFINITIONS;
|
||||||
|
},
|
||||||
|
getModeStepDefinitions() {
|
||||||
|
return KIRO_STEP_DEFINITIONS;
|
||||||
|
},
|
||||||
|
getPlusPaymentStepTitle() {
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
resolveStepTitle(step) {
|
||||||
|
return step?.title || '';
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function hasFlow(flowId) {
|
function hasFlow(flowId) {
|
||||||
|
|||||||
+105
-33
@@ -1,11 +1,21 @@
|
|||||||
(function attachMultiPageFlowCapabilities(root, factory) {
|
(function attachMultiPageFlowCapabilities(root, factory) {
|
||||||
root.MultiPageFlowCapabilities = factory();
|
root.MultiPageFlowCapabilities = factory();
|
||||||
})(typeof self !== 'undefined' ? self : globalThis, function createFlowCapabilitiesModule() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createFlowCapabilitiesModule() {
|
||||||
const DEFAULT_FLOW_ID = 'openai';
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
const DEFAULT_PANEL_MODE = 'cpa';
|
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
|
||||||
|
const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {};
|
||||||
|
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
|
||||||
|
const DEFAULT_PANEL_MODE = flowRegistryApi.DEFAULT_OPENAI_SOURCE_ID || 'cpa';
|
||||||
|
const LEGACY_OPENAI_FLOW_ALIAS = String(flowRegistryApi.LEGACY_OPENAI_FLOW_ALIAS || 'codex').trim().toLowerCase();
|
||||||
const SIGNUP_METHOD_EMAIL = 'email';
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
const SIGNUP_METHOD_PHONE = 'phone';
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
const VALID_PANEL_MODES = Object.freeze(['cpa', 'sub2api', 'codex2api']);
|
const VALID_PANEL_MODES = Array.isArray(flowRegistryApi.OPENAI_SOURCE_IDS)
|
||||||
|
? flowRegistryApi.OPENAI_SOURCE_IDS.slice()
|
||||||
|
: ['cpa', 'sub2api', 'codex2api'];
|
||||||
|
const REGISTERED_FLOW_IDS = Array.isArray(flowRegistryApi.getRegisteredFlowIds?.())
|
||||||
|
? flowRegistryApi.getRegisteredFlowIds().map((flowId) => String(flowId || '').trim().toLowerCase()).filter(Boolean)
|
||||||
|
: [DEFAULT_FLOW_ID];
|
||||||
|
const REGISTERED_FLOW_ID_SET = new Set(REGISTERED_FLOW_IDS);
|
||||||
|
|
||||||
const DEFAULT_FLOW_CAPABILITIES = Object.freeze({
|
const DEFAULT_FLOW_CAPABILITIES = Object.freeze({
|
||||||
supportsEmailSignup: true,
|
supportsEmailSignup: true,
|
||||||
@@ -16,28 +26,33 @@
|
|||||||
supportsPlatformBinding: [],
|
supportsPlatformBinding: [],
|
||||||
supportsLuckmail: false,
|
supportsLuckmail: false,
|
||||||
supportsOauthTimeoutBudget: false,
|
supportsOauthTimeoutBudget: false,
|
||||||
canSwitchFlow: false,
|
canSwitchFlow: true,
|
||||||
stepDefinitionMode: 'default',
|
stepDefinitionMode: 'default',
|
||||||
|
sourceSelectorLabel: '来源',
|
||||||
});
|
});
|
||||||
|
|
||||||
const FLOW_CAPABILITIES = Object.freeze({
|
const FLOW_CAPABILITIES = Object.freeze(
|
||||||
openai: Object.freeze({
|
Object.fromEntries(
|
||||||
|
(typeof flowRegistryApi.getRegisteredFlowIds === 'function'
|
||||||
|
? flowRegistryApi.getRegisteredFlowIds()
|
||||||
|
: [DEFAULT_FLOW_ID]
|
||||||
|
).map((flowId) => [
|
||||||
|
flowId,
|
||||||
|
Object.freeze({
|
||||||
...DEFAULT_FLOW_CAPABILITIES,
|
...DEFAULT_FLOW_CAPABILITIES,
|
||||||
supportsPhoneSignup: true,
|
...(typeof flowRegistryApi.getFlowCapabilities === 'function'
|
||||||
supportsPhoneVerificationSettings: true,
|
? flowRegistryApi.getFlowCapabilities(flowId)
|
||||||
supportsPlusMode: true,
|
: {}),
|
||||||
supportsContributionMode: true,
|
|
||||||
supportsPlatformBinding: ['cpa', 'sub2api', 'codex2api'],
|
|
||||||
supportsLuckmail: true,
|
|
||||||
supportsOauthTimeoutBudget: true,
|
|
||||||
stepDefinitionMode: 'openai-dynamic',
|
|
||||||
}),
|
}),
|
||||||
});
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const DEFAULT_PANEL_CAPABILITIES = Object.freeze({
|
const DEFAULT_PANEL_CAPABILITIES = Object.freeze({
|
||||||
supportsPhoneSignup: true,
|
supportsPhoneSignup: true,
|
||||||
requiresPhoneSignupWarning: false,
|
requiresPhoneSignupWarning: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const MODE_SWITCH_RELEVANT_KEYS = Object.freeze([
|
const MODE_SWITCH_RELEVANT_KEYS = Object.freeze([
|
||||||
'activeFlowId',
|
'activeFlowId',
|
||||||
'contributionMode',
|
'contributionMode',
|
||||||
@@ -45,6 +60,7 @@
|
|||||||
'phoneVerificationEnabled',
|
'phoneVerificationEnabled',
|
||||||
'plusModeEnabled',
|
'plusModeEnabled',
|
||||||
'signupMethod',
|
'signupMethod',
|
||||||
|
'kiroSourceId',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const PANEL_CAPABILITIES = Object.freeze({
|
const PANEL_CAPABILITIES = Object.freeze({
|
||||||
@@ -63,12 +79,30 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
|
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
|
||||||
|
if (typeof flowRegistryApi.normalizeFlowId === 'function') {
|
||||||
|
return flowRegistryApi.normalizeFlowId(value, fallback);
|
||||||
|
}
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
return normalized || String(fallback || '').trim().toLowerCase() || DEFAULT_FLOW_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCapabilityFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === LEGACY_OPENAI_FLOW_ALIAS) {
|
||||||
|
return DEFAULT_FLOW_ID;
|
||||||
|
}
|
||||||
if (normalized) {
|
if (normalized) {
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
const normalizedFallback = String(fallback || '').trim().toLowerCase();
|
||||||
return fallbackValue || DEFAULT_FLOW_ID;
|
if (normalizedFallback === LEGACY_OPENAI_FLOW_ALIAS) {
|
||||||
|
return DEFAULT_FLOW_ID;
|
||||||
|
}
|
||||||
|
return normalizedFallback || DEFAULT_FLOW_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRegisteredFlowId(flowId = '') {
|
||||||
|
return REGISTERED_FLOW_ID_SET.has(normalizeCapabilityFlowId(flowId, ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePanelMode(value = '', fallback = DEFAULT_PANEL_MODE) {
|
function normalizePanelMode(value = '', fallback = DEFAULT_PANEL_MODE) {
|
||||||
@@ -122,9 +156,14 @@
|
|||||||
flowCapabilities = FLOW_CAPABILITIES,
|
flowCapabilities = FLOW_CAPABILITIES,
|
||||||
panelCapabilities = PANEL_CAPABILITIES,
|
panelCapabilities = PANEL_CAPABILITIES,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
const settingsSchema = settingsSchemaApi.createSettingsSchema
|
||||||
|
? settingsSchemaApi.createSettingsSchema({
|
||||||
|
defaultFlowId,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
function getFlowCapabilities(flowId) {
|
function getFlowCapabilities(flowId) {
|
||||||
const normalizedFlowId = normalizeFlowId(flowId, defaultFlowId);
|
const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId);
|
||||||
const entry = flowCapabilities[normalizedFlowId] || null;
|
const entry = flowCapabilities[normalizedFlowId] || null;
|
||||||
return {
|
return {
|
||||||
...defaultFlowCapabilities,
|
...defaultFlowCapabilities,
|
||||||
@@ -156,9 +195,33 @@
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveEffectiveSourceId(activeFlowId, state = {}, requestedPanelMode = DEFAULT_PANEL_MODE) {
|
||||||
|
if (!isRegisteredFlowId(activeFlowId)) {
|
||||||
|
return normalizePanelMode(state?.panelMode || requestedPanelMode, requestedPanelMode || DEFAULT_PANEL_MODE);
|
||||||
|
}
|
||||||
|
if (settingsSchema?.getSelectedSourceId) {
|
||||||
|
const sourceFromSchema = settingsSchema.getSelectedSourceId({
|
||||||
|
...state,
|
||||||
|
activeFlowId,
|
||||||
|
}, activeFlowId);
|
||||||
|
if (sourceFromSchema) {
|
||||||
|
return sourceFromSchema;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof flowRegistryApi.normalizeSourceId === 'function') {
|
||||||
|
if (activeFlowId === 'openai') {
|
||||||
|
return flowRegistryApi.normalizeSourceId('openai', state?.panelMode || requestedPanelMode, DEFAULT_PANEL_MODE);
|
||||||
|
}
|
||||||
|
return flowRegistryApi.normalizeSourceId(activeFlowId, state?.kiroSourceId || '', flowRegistryApi.getDefaultSourceId?.(activeFlowId));
|
||||||
|
}
|
||||||
|
return activeFlowId === 'openai'
|
||||||
|
? normalizePanelMode(state?.panelMode || requestedPanelMode)
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
function resolveSidepanelCapabilities(options = {}) {
|
function resolveSidepanelCapabilities(options = {}) {
|
||||||
const state = options?.state || {};
|
const state = options?.state || {};
|
||||||
const activeFlowId = normalizeFlowId(
|
const activeFlowId = normalizeCapabilityFlowId(
|
||||||
options?.activeFlowId ?? state?.activeFlowId,
|
options?.activeFlowId ?? state?.activeFlowId,
|
||||||
defaultFlowId
|
defaultFlowId
|
||||||
);
|
);
|
||||||
@@ -173,20 +236,22 @@
|
|||||||
: supportedPanelModes.includes(requestedPanelMode);
|
: supportedPanelModes.includes(requestedPanelMode);
|
||||||
const effectivePanelMode = panelModeSupported
|
const effectivePanelMode = panelModeSupported
|
||||||
? requestedPanelMode
|
? requestedPanelMode
|
||||||
: supportedPanelModes[0];
|
: (supportedPanelModes[0] || requestedPanelMode);
|
||||||
const panelState = getPanelCapabilities(effectivePanelMode);
|
const panelState = getPanelCapabilities(effectivePanelMode);
|
||||||
|
const effectiveSourceId = resolveEffectiveSourceId(activeFlowId, state, effectivePanelMode);
|
||||||
const runtimeLocks = {
|
const runtimeLocks = {
|
||||||
autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked),
|
autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked),
|
||||||
contributionMode: flowState.supportsContributionMode && Boolean(state?.contributionMode),
|
contributionMode: activeFlowId === 'openai' && flowState.supportsContributionMode && Boolean(state?.contributionMode),
|
||||||
phoneVerificationEnabled: flowState.supportsPhoneVerificationSettings && Boolean(state?.phoneVerificationEnabled),
|
phoneVerificationEnabled: activeFlowId === 'openai' && flowState.supportsPhoneVerificationSettings && Boolean(state?.phoneVerificationEnabled),
|
||||||
plusModeEnabled: flowState.supportsPlusMode && Boolean(state?.plusModeEnabled),
|
plusModeEnabled: activeFlowId === 'openai' && flowState.supportsPlusMode && Boolean(state?.plusModeEnabled),
|
||||||
settingsMenuLocked: Boolean(options?.settingsMenuLocked ?? state?.settingsMenuLocked),
|
settingsMenuLocked: Boolean(options?.settingsMenuLocked ?? state?.settingsMenuLocked),
|
||||||
};
|
};
|
||||||
const effectiveSignupMethods = [];
|
const effectiveSignupMethods = [];
|
||||||
if (flowState.supportsEmailSignup !== false) {
|
if (flowState.supportsEmailSignup !== false) {
|
||||||
effectiveSignupMethods.push(SIGNUP_METHOD_EMAIL);
|
effectiveSignupMethods.push(SIGNUP_METHOD_EMAIL);
|
||||||
}
|
}
|
||||||
const canSelectPhoneSignup = Boolean(flowState.supportsPhoneSignup)
|
const canSelectPhoneSignup = activeFlowId === 'openai'
|
||||||
|
&& Boolean(flowState.supportsPhoneSignup)
|
||||||
&& Boolean(panelState.supportsPhoneSignup)
|
&& Boolean(panelState.supportsPhoneSignup)
|
||||||
&& runtimeLocks.phoneVerificationEnabled
|
&& runtimeLocks.phoneVerificationEnabled
|
||||||
&& !runtimeLocks.plusModeEnabled
|
&& !runtimeLocks.plusModeEnabled
|
||||||
@@ -205,19 +270,24 @@
|
|||||||
: (effectiveSignupMethods.includes(SIGNUP_METHOD_EMAIL)
|
: (effectiveSignupMethods.includes(SIGNUP_METHOD_EMAIL)
|
||||||
? SIGNUP_METHOD_EMAIL
|
? SIGNUP_METHOD_EMAIL
|
||||||
: effectiveSignupMethods[0]);
|
: effectiveSignupMethods[0]);
|
||||||
|
const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function'
|
||||||
|
&& isRegisteredFlowId(activeFlowId)
|
||||||
|
? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveSourceId)
|
||||||
|
: [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeFlowId,
|
activeFlowId,
|
||||||
canShowContributionMode: Boolean(flowState.supportsContributionMode),
|
canShowContributionMode: activeFlowId === 'openai' && Boolean(flowState.supportsContributionMode),
|
||||||
canShowLuckmail: Boolean(flowState.supportsLuckmail),
|
canShowLuckmail: activeFlowId === 'openai' && Boolean(flowState.supportsLuckmail),
|
||||||
canShowPhoneSettings: Boolean(flowState.supportsPhoneVerificationSettings),
|
canShowPhoneSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPhoneVerificationSettings),
|
||||||
canShowPlusSettings: Boolean(flowState.supportsPlusMode),
|
canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode),
|
||||||
canSwitchFlow: Boolean(flowState.canSwitchFlow),
|
canSwitchFlow: Boolean(flowState.canSwitchFlow),
|
||||||
canUsePhoneSignup: canSelectPhoneSignup,
|
canUsePhoneSignup: canSelectPhoneSignup,
|
||||||
canUseSelectedPanelMode: panelModeSupported,
|
canUseSelectedPanelMode: panelModeSupported,
|
||||||
effectivePanelMode,
|
effectivePanelMode,
|
||||||
effectiveSignupMethod,
|
effectiveSignupMethod,
|
||||||
effectiveSignupMethods,
|
effectiveSignupMethods,
|
||||||
|
effectiveSourceId,
|
||||||
flowCapabilities: flowState,
|
flowCapabilities: flowState,
|
||||||
panelCapabilities: panelState,
|
panelCapabilities: panelState,
|
||||||
panelMode: effectivePanelMode,
|
panelMode: effectivePanelMode,
|
||||||
@@ -233,6 +303,7 @@
|
|||||||
signupMethod: effectiveSignupMethod,
|
signupMethod: effectiveSignupMethod,
|
||||||
},
|
},
|
||||||
supportedPanelModes,
|
supportedPanelModes,
|
||||||
|
visibleGroupIds,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,13 +321,13 @@
|
|||||||
if (!panelState.supportsPhoneSignup) {
|
if (!panelState.supportsPhoneSignup) {
|
||||||
return {
|
return {
|
||||||
code: 'phone_signup_panel_unsupported',
|
code: 'phone_signup_panel_unsupported',
|
||||||
message: `当前面板模式 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 不支持手机号注册。`,
|
message: `当前来源 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 不支持手机号注册。`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!runtimeLocks.phoneVerificationEnabled) {
|
if (!runtimeLocks.phoneVerificationEnabled) {
|
||||||
return {
|
return {
|
||||||
code: 'phone_signup_phone_verification_disabled',
|
code: 'phone_signup_phone_verification_disabled',
|
||||||
message: '请先开启接码功能后再使用手机号注册。',
|
message: '请先开启接码设置后再使用手机号注册。',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (runtimeLocks.plusModeEnabled) {
|
if (runtimeLocks.plusModeEnabled) {
|
||||||
@@ -289,7 +360,7 @@
|
|||||||
) {
|
) {
|
||||||
errors.push({
|
errors.push({
|
||||||
code: 'panel_mode_unsupported',
|
code: 'panel_mode_unsupported',
|
||||||
message: `当前 flow 不支持 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 面板模式。`,
|
message: `当前 flow 不支持 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 来源。`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +416,7 @@
|
|||||||
normalizedUpdates.panelMode = capabilityState.effectivePanelMode;
|
normalizedUpdates.panelMode = capabilityState.effectivePanelMode;
|
||||||
errors.push({
|
errors.push({
|
||||||
code: 'panel_mode_unsupported',
|
code: 'panel_mode_unsupported',
|
||||||
message: `当前 flow 不支持 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 面板模式。`,
|
message: `当前 flow 不支持 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 来源。`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +444,7 @@
|
|||||||
normalizedUpdates.phoneVerificationEnabled = false;
|
normalizedUpdates.phoneVerificationEnabled = false;
|
||||||
errors.push({
|
errors.push({
|
||||||
code: 'phone_verification_unsupported',
|
code: 'phone_verification_unsupported',
|
||||||
message: '当前 flow 不支持接码配置。',
|
message: '当前 flow 不支持接码设置。',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,6 +501,7 @@
|
|||||||
PANEL_CAPABILITIES,
|
PANEL_CAPABILITIES,
|
||||||
SIGNUP_METHOD_EMAIL,
|
SIGNUP_METHOD_EMAIL,
|
||||||
SIGNUP_METHOD_PHONE,
|
SIGNUP_METHOD_PHONE,
|
||||||
|
VALID_PANEL_MODES,
|
||||||
normalizeFlowId,
|
normalizeFlowId,
|
||||||
normalizePanelMode,
|
normalizePanelMode,
|
||||||
normalizeSignupMethod,
|
normalizeSignupMethod,
|
||||||
|
|||||||
@@ -0,0 +1,509 @@
|
|||||||
|
(function attachMultiPageFlowRegistry(root, factory) {
|
||||||
|
root.MultiPageFlowRegistry = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() {
|
||||||
|
const DEFAULT_FLOW_ID = 'openai';
|
||||||
|
const LEGACY_OPENAI_FLOW_ALIAS = 'codex';
|
||||||
|
const DEFAULT_OPENAI_SOURCE_ID = 'cpa';
|
||||||
|
const DEFAULT_KIRO_SOURCE_ID = 'kiro-rs';
|
||||||
|
const DEFAULT_KIRO_REGION = 'us-east-1';
|
||||||
|
const DEFAULT_KIRO_RS_URL = 'https://kiro.leftcode.xyz/admin';
|
||||||
|
const OPENAI_SOURCE_IDS = Object.freeze(['cpa', 'sub2api', 'codex2api']);
|
||||||
|
const SHARED_SERVICE_IDS = Object.freeze(['email', 'proxy']);
|
||||||
|
|
||||||
|
const DEFAULT_FLOW_CAPABILITIES = Object.freeze({
|
||||||
|
supportsEmailSignup: true,
|
||||||
|
supportsPhoneSignup: false,
|
||||||
|
supportsPhoneVerificationSettings: false,
|
||||||
|
supportsPlusMode: false,
|
||||||
|
supportsContributionMode: false,
|
||||||
|
supportsPlatformBinding: [],
|
||||||
|
supportsLuckmail: false,
|
||||||
|
supportsOauthTimeoutBudget: false,
|
||||||
|
canSwitchFlow: true,
|
||||||
|
stepDefinitionMode: 'default',
|
||||||
|
sourceSelectorLabel: '来源',
|
||||||
|
});
|
||||||
|
|
||||||
|
function freezeDeep(value) {
|
||||||
|
if (!value || typeof value !== 'object' || Object.isFrozen(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
Object.getOwnPropertyNames(value).forEach((key) => {
|
||||||
|
freezeDeep(value[key]);
|
||||||
|
});
|
||||||
|
return Object.freeze(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FLOW_DEFINITIONS = freezeDeep({
|
||||||
|
openai: {
|
||||||
|
id: 'openai',
|
||||||
|
label: 'Codex / OpenAI',
|
||||||
|
services: ['email', 'proxy'],
|
||||||
|
capabilities: {
|
||||||
|
...DEFAULT_FLOW_CAPABILITIES,
|
||||||
|
supportsPhoneSignup: true,
|
||||||
|
supportsPhoneVerificationSettings: true,
|
||||||
|
supportsPlusMode: true,
|
||||||
|
supportsContributionMode: true,
|
||||||
|
supportsPlatformBinding: [...OPENAI_SOURCE_IDS],
|
||||||
|
supportsLuckmail: true,
|
||||||
|
supportsOauthTimeoutBudget: true,
|
||||||
|
stepDefinitionMode: 'openai-dynamic',
|
||||||
|
},
|
||||||
|
baseGroups: [
|
||||||
|
'openai-account',
|
||||||
|
'openai-plus',
|
||||||
|
'openai-phone',
|
||||||
|
'openai-oauth',
|
||||||
|
],
|
||||||
|
sources: {
|
||||||
|
cpa: {
|
||||||
|
id: 'cpa',
|
||||||
|
label: 'CPA 面板',
|
||||||
|
legacyPanelMode: 'cpa',
|
||||||
|
groups: ['openai-source-cpa'],
|
||||||
|
},
|
||||||
|
sub2api: {
|
||||||
|
id: 'sub2api',
|
||||||
|
label: 'SUB2API',
|
||||||
|
legacyPanelMode: 'sub2api',
|
||||||
|
groups: ['openai-source-sub2api'],
|
||||||
|
},
|
||||||
|
codex2api: {
|
||||||
|
id: 'codex2api',
|
||||||
|
label: 'Codex2API',
|
||||||
|
legacyPanelMode: 'codex2api',
|
||||||
|
groups: ['openai-source-codex2api'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
runtimeSources: {
|
||||||
|
'openai-auth': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-page',
|
||||||
|
label: '认证页',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'openai-auth-family',
|
||||||
|
driverId: 'content/signup-page',
|
||||||
|
cleanupScopes: ['oauth-localhost-callback'],
|
||||||
|
},
|
||||||
|
chatgpt: {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-entry',
|
||||||
|
label: 'ChatGPT 首页',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'chatgpt-entry-family',
|
||||||
|
driverId: null,
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'vps-panel': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'panel-page',
|
||||||
|
label: 'CPA 面板',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'vps-panel-family',
|
||||||
|
driverId: 'content/vps-panel',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'platform-panel': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'virtual-page',
|
||||||
|
label: '平台回调面板',
|
||||||
|
readyPolicy: 'disabled',
|
||||||
|
family: 'platform-panel-family',
|
||||||
|
driverId: 'content/platform-panel',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'sub2api-panel': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'panel-page',
|
||||||
|
label: 'SUB2API 后台',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'sub2api-panel-family',
|
||||||
|
driverId: 'content/sub2api-panel',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'codex2api-panel': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'panel-page',
|
||||||
|
label: 'Codex2API 后台',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'codex2api-panel-family',
|
||||||
|
driverId: 'content/sub2api-panel',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'plus-checkout': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-page',
|
||||||
|
label: 'Plus Checkout',
|
||||||
|
readyPolicy: 'top-frame-only',
|
||||||
|
family: 'plus-checkout-family',
|
||||||
|
driverId: 'content/plus-checkout',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'paypal-flow': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-page',
|
||||||
|
label: 'PayPal 授权页',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'paypal-flow-family',
|
||||||
|
driverId: 'content/paypal-flow',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'gopay-flow': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-page',
|
||||||
|
label: 'GoPay 授权页',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'gopay-flow-family',
|
||||||
|
driverId: 'content/gopay-flow',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
driverDefinitions: {
|
||||||
|
'content/signup-page': {
|
||||||
|
sourceId: 'openai-auth',
|
||||||
|
commands: [
|
||||||
|
'submit-signup-email',
|
||||||
|
'fill-password',
|
||||||
|
'fill-profile',
|
||||||
|
'oauth-login',
|
||||||
|
'submit-verification-code',
|
||||||
|
'post-login-phone-verification',
|
||||||
|
'bind-email',
|
||||||
|
'fetch-bind-email-code',
|
||||||
|
'confirm-oauth',
|
||||||
|
'detect-auth-state',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'content/sub2api-panel': {
|
||||||
|
sourceId: 'sub2api-panel',
|
||||||
|
commands: ['open-panel', 'fetch-oauth-url', 'platform-verify'],
|
||||||
|
},
|
||||||
|
'content/vps-panel': {
|
||||||
|
sourceId: 'vps-panel',
|
||||||
|
commands: ['open-panel', 'fetch-oauth-url', 'platform-verify'],
|
||||||
|
},
|
||||||
|
'content/platform-panel': {
|
||||||
|
sourceId: 'platform-panel',
|
||||||
|
commands: ['platform-verify', 'fetch-oauth-url'],
|
||||||
|
},
|
||||||
|
'content/plus-checkout': {
|
||||||
|
sourceId: 'plus-checkout',
|
||||||
|
commands: ['plus-checkout-create', 'plus-checkout-billing', 'plus-checkout-return'],
|
||||||
|
},
|
||||||
|
'content/paypal-flow': {
|
||||||
|
sourceId: 'paypal-flow',
|
||||||
|
commands: ['paypal-approve'],
|
||||||
|
},
|
||||||
|
'content/gopay-flow': {
|
||||||
|
sourceId: 'gopay-flow',
|
||||||
|
commands: ['gopay-subscription-confirm'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kiro: {
|
||||||
|
id: 'kiro',
|
||||||
|
label: 'Kiro',
|
||||||
|
services: ['email', 'proxy'],
|
||||||
|
capabilities: {
|
||||||
|
...DEFAULT_FLOW_CAPABILITIES,
|
||||||
|
stepDefinitionMode: 'kiro-device-auth',
|
||||||
|
},
|
||||||
|
baseGroups: [
|
||||||
|
'kiro-runtime-status',
|
||||||
|
],
|
||||||
|
sources: {
|
||||||
|
'kiro-rs': {
|
||||||
|
id: 'kiro-rs',
|
||||||
|
label: 'kiro.rs',
|
||||||
|
groups: ['kiro-source-kiro-rs'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
runtimeSources: {
|
||||||
|
'kiro-device-auth': {
|
||||||
|
flowId: 'kiro',
|
||||||
|
kind: 'flow-page',
|
||||||
|
label: 'Kiro 授权页',
|
||||||
|
readyPolicy: 'top-frame-only',
|
||||||
|
family: 'kiro-device-auth-family',
|
||||||
|
driverId: null,
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'kiro-rs-admin': {
|
||||||
|
flowId: 'kiro',
|
||||||
|
kind: 'virtual-page',
|
||||||
|
label: 'kiro.rs Admin',
|
||||||
|
readyPolicy: 'disabled',
|
||||||
|
family: 'kiro-rs-admin-family',
|
||||||
|
driverId: null,
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
driverDefinitions: {
|
||||||
|
'background/kiro-device-auth': {
|
||||||
|
sourceId: 'kiro-device-auth',
|
||||||
|
commands: [
|
||||||
|
'kiro-start-device-login',
|
||||||
|
'kiro-await-device-login',
|
||||||
|
'kiro-upload-credential',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const SETTINGS_GROUP_DEFINITIONS = freezeDeep({
|
||||||
|
'service-email': {
|
||||||
|
id: 'service-email',
|
||||||
|
label: '邮箱服务',
|
||||||
|
},
|
||||||
|
'service-proxy': {
|
||||||
|
id: 'service-proxy',
|
||||||
|
label: 'IP 代理',
|
||||||
|
sectionIds: ['ip-proxy-section'],
|
||||||
|
},
|
||||||
|
'openai-source-cpa': {
|
||||||
|
id: 'openai-source-cpa',
|
||||||
|
label: 'CPA 来源',
|
||||||
|
rowIds: ['row-vps-url', 'row-vps-password', 'row-local-cpa-step9-mode'],
|
||||||
|
},
|
||||||
|
'openai-source-sub2api': {
|
||||||
|
id: 'openai-source-sub2api',
|
||||||
|
label: 'SUB2API 来源',
|
||||||
|
rowIds: [
|
||||||
|
'row-sub2api-url',
|
||||||
|
'row-sub2api-email',
|
||||||
|
'row-sub2api-password',
|
||||||
|
'row-sub2api-group',
|
||||||
|
'row-sub2api-account-priority',
|
||||||
|
'row-sub2api-default-proxy',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'openai-source-codex2api': {
|
||||||
|
id: 'openai-source-codex2api',
|
||||||
|
label: 'Codex2API 来源',
|
||||||
|
rowIds: ['row-codex2api-url', 'row-codex2api-admin-key'],
|
||||||
|
},
|
||||||
|
'openai-account': {
|
||||||
|
id: 'openai-account',
|
||||||
|
label: '账户',
|
||||||
|
rowIds: ['row-custom-password'],
|
||||||
|
},
|
||||||
|
'openai-plus': {
|
||||||
|
id: 'openai-plus',
|
||||||
|
label: 'Plus',
|
||||||
|
rowIds: [
|
||||||
|
'row-plus-mode',
|
||||||
|
'row-plus-payment-method',
|
||||||
|
'row-paypal-account',
|
||||||
|
'row-gpc-helper-api',
|
||||||
|
'row-gpc-helper-card-key',
|
||||||
|
'row-gpc-helper-phone-mode',
|
||||||
|
'row-gpc-helper-country-code',
|
||||||
|
'row-gpc-helper-phone',
|
||||||
|
'row-gpc-helper-otp-channel',
|
||||||
|
'row-gpc-helper-local-sms-enabled',
|
||||||
|
'row-gpc-helper-local-sms-url',
|
||||||
|
'row-gpc-helper-pin',
|
||||||
|
'row-gopay-country-code',
|
||||||
|
'row-gopay-phone',
|
||||||
|
'row-gopay-otp',
|
||||||
|
'row-gopay-pin',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'openai-phone': {
|
||||||
|
id: 'openai-phone',
|
||||||
|
label: '接码设置',
|
||||||
|
sectionIds: ['phone-verification-section'],
|
||||||
|
rowIds: ['row-signup-phone'],
|
||||||
|
},
|
||||||
|
'openai-oauth': {
|
||||||
|
id: 'openai-oauth',
|
||||||
|
label: 'OAuth',
|
||||||
|
rowIds: ['row-oauth-flow-timeout', 'row-oauth-display'],
|
||||||
|
},
|
||||||
|
'kiro-source-kiro-rs': {
|
||||||
|
id: 'kiro-source-kiro-rs',
|
||||||
|
label: 'kiro.rs 配置',
|
||||||
|
rowIds: ['row-kiro-rs-url', 'row-kiro-rs-key', 'row-kiro-region'],
|
||||||
|
},
|
||||||
|
'kiro-runtime-status': {
|
||||||
|
id: 'kiro-runtime-status',
|
||||||
|
label: 'Kiro 运行态',
|
||||||
|
rowIds: ['row-kiro-device-code', 'row-kiro-login-url', 'row-kiro-upload-status'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === LEGACY_OPENAI_FLOW_ALIAS) {
|
||||||
|
return DEFAULT_FLOW_ID;
|
||||||
|
}
|
||||||
|
if (normalized && Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, normalized)) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||||
|
if (fallbackValue === LEGACY_OPENAI_FLOW_ALIAS) {
|
||||||
|
return DEFAULT_FLOW_ID;
|
||||||
|
}
|
||||||
|
return Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, fallbackValue)
|
||||||
|
? fallbackValue
|
||||||
|
: DEFAULT_FLOW_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRegisteredFlowIds() {
|
||||||
|
return Object.keys(FLOW_DEFINITIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlowDefinition(flowId) {
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId);
|
||||||
|
return FLOW_DEFINITIONS[normalizedFlowId] || FLOW_DEFINITIONS[DEFAULT_FLOW_ID];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlowLabel(flowId) {
|
||||||
|
return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultSourceId(flowId) {
|
||||||
|
return normalizeFlowId(flowId) === 'kiro'
|
||||||
|
? DEFAULT_KIRO_SOURCE_ID
|
||||||
|
: DEFAULT_OPENAI_SOURCE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeOpenAiSourceId(value = '', fallback = DEFAULT_OPENAI_SOURCE_ID) {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (OPENAI_SOURCE_IDS.includes(normalized)) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||||
|
return OPENAI_SOURCE_IDS.includes(fallbackValue) ? fallbackValue : DEFAULT_OPENAI_SOURCE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeKiroSourceId(value = '', fallback = DEFAULT_KIRO_SOURCE_ID) {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === DEFAULT_KIRO_SOURCE_ID) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||||
|
return fallbackValue === DEFAULT_KIRO_SOURCE_ID ? fallbackValue : DEFAULT_KIRO_SOURCE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSourceId(flowId, sourceId = '', fallback = undefined) {
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId);
|
||||||
|
if (normalizedFlowId === 'kiro') {
|
||||||
|
return normalizeKiroSourceId(sourceId, fallback || DEFAULT_KIRO_SOURCE_ID);
|
||||||
|
}
|
||||||
|
return normalizeOpenAiSourceId(sourceId, fallback || DEFAULT_OPENAI_SOURCE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceDefinitions(flowId) {
|
||||||
|
return getFlowDefinition(flowId)?.sources || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceDefinition(flowId, sourceId) {
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId);
|
||||||
|
const normalizedSourceId = normalizeSourceId(normalizedFlowId, sourceId, getDefaultSourceId(normalizedFlowId));
|
||||||
|
return getSourceDefinitions(normalizedFlowId)[normalizedSourceId] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceOptions(flowId) {
|
||||||
|
return Object.values(getSourceDefinitions(flowId));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceLabel(flowId, sourceId) {
|
||||||
|
return getSourceDefinition(flowId, sourceId)?.label || normalizeSourceId(flowId, sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapPanelModeToSourceId(panelMode = '', fallback = DEFAULT_OPENAI_SOURCE_ID) {
|
||||||
|
return normalizeOpenAiSourceId(panelMode, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapSourceIdToPanelMode(flowId, sourceId = '', fallback = DEFAULT_OPENAI_SOURCE_ID) {
|
||||||
|
if (normalizeFlowId(flowId) !== DEFAULT_FLOW_ID) {
|
||||||
|
return normalizeOpenAiSourceId(fallback, DEFAULT_OPENAI_SOURCE_ID);
|
||||||
|
}
|
||||||
|
return normalizeOpenAiSourceId(sourceId, fallback || DEFAULT_OPENAI_SOURCE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlowCapabilities(flowId) {
|
||||||
|
return {
|
||||||
|
...DEFAULT_FLOW_CAPABILITIES,
|
||||||
|
...(getFlowDefinition(flowId)?.capabilities || {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisibleGroupIds(flowId, sourceId, options = {}) {
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId);
|
||||||
|
const flowDefinition = getFlowDefinition(normalizedFlowId);
|
||||||
|
const normalizedSourceId = normalizeSourceId(normalizedFlowId, sourceId, getDefaultSourceId(normalizedFlowId));
|
||||||
|
const sourceDefinition = getSourceDefinition(normalizedFlowId, normalizedSourceId);
|
||||||
|
const includeSharedServices = options?.includeSharedServices !== false;
|
||||||
|
const serviceGroups = includeSharedServices
|
||||||
|
? (Array.isArray(flowDefinition?.services) ? flowDefinition.services.map((serviceId) => `service-${serviceId}`) : [])
|
||||||
|
: [];
|
||||||
|
return Array.from(new Set([
|
||||||
|
...(Array.isArray(flowDefinition?.baseGroups) ? flowDefinition.baseGroups : []),
|
||||||
|
...(Array.isArray(sourceDefinition?.groups) ? sourceDefinition.groups : []),
|
||||||
|
...serviceGroups,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSettingsGroupDefinition(groupId) {
|
||||||
|
const normalizedGroupId = String(groupId || '').trim();
|
||||||
|
return SETTINGS_GROUP_DEFINITIONS[normalizedGroupId] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSettingsGroupDefinitions() {
|
||||||
|
return SETTINGS_GROUP_DEFINITIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRuntimeSourceDefinitions() {
|
||||||
|
const next = {};
|
||||||
|
Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => {
|
||||||
|
Object.assign(next, flowDefinition.runtimeSources || {});
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDriverDefinitions() {
|
||||||
|
const next = {};
|
||||||
|
Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => {
|
||||||
|
Object.assign(next, flowDefinition.driverDefinitions || {});
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
DEFAULT_FLOW_CAPABILITIES,
|
||||||
|
DEFAULT_FLOW_ID,
|
||||||
|
DEFAULT_KIRO_REGION,
|
||||||
|
DEFAULT_KIRO_RS_URL,
|
||||||
|
DEFAULT_KIRO_SOURCE_ID,
|
||||||
|
DEFAULT_OPENAI_SOURCE_ID,
|
||||||
|
FLOW_DEFINITIONS,
|
||||||
|
LEGACY_OPENAI_FLOW_ALIAS,
|
||||||
|
OPENAI_SOURCE_IDS,
|
||||||
|
SETTINGS_GROUP_DEFINITIONS,
|
||||||
|
SHARED_SERVICE_IDS,
|
||||||
|
getDefaultSourceId,
|
||||||
|
getDriverDefinitions,
|
||||||
|
getFlowCapabilities,
|
||||||
|
getFlowDefinition,
|
||||||
|
getFlowLabel,
|
||||||
|
getRegisteredFlowIds,
|
||||||
|
getRuntimeSourceDefinitions,
|
||||||
|
getSettingsGroupDefinition,
|
||||||
|
getSettingsGroupDefinitions,
|
||||||
|
getSourceDefinition,
|
||||||
|
getSourceDefinitions,
|
||||||
|
getSourceLabel,
|
||||||
|
getSourceOptions,
|
||||||
|
getVisibleGroupIds,
|
||||||
|
mapPanelModeToSourceId,
|
||||||
|
mapSourceIdToPanelMode,
|
||||||
|
normalizeFlowId,
|
||||||
|
normalizeKiroSourceId,
|
||||||
|
normalizeOpenAiSourceId,
|
||||||
|
normalizeSourceId,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,442 @@
|
|||||||
|
(function attachMultiPageSettingsSchema(root, factory) {
|
||||||
|
root.MultiPageSettingsSchema = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createSettingsSchemaModule() {
|
||||||
|
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 normalizeStepExecutionRangeEntry(value = {}, fallback = {}) {
|
||||||
|
const source = isPlainObject(value) ? value : {};
|
||||||
|
const fallbackSource = isPlainObject(fallback) ? fallback : {};
|
||||||
|
const fromStep = Math.max(1, Number(source.fromStep ?? fallbackSource.fromStep ?? 1) || 1);
|
||||||
|
const toStep = Math.max(fromStep, Number(source.toStep ?? fallbackSource.toStep ?? fromStep) || fromStep);
|
||||||
|
return {
|
||||||
|
enabled: Boolean(source.enabled ?? fallbackSource.enabled),
|
||||||
|
fromStep,
|
||||||
|
toStep,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSettingsSchema(deps = {}) {
|
||||||
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
|
const flowRegistry = deps.flowRegistry || rootScope.MultiPageFlowRegistry || {};
|
||||||
|
const defaultFlowId = String(deps.defaultFlowId || flowRegistry.DEFAULT_FLOW_ID || 'openai').trim().toLowerCase() || 'openai';
|
||||||
|
const defaultOpenAiSourceId = flowRegistry.DEFAULT_OPENAI_SOURCE_ID || 'cpa';
|
||||||
|
const defaultKiroSourceId = flowRegistry.DEFAULT_KIRO_SOURCE_ID || 'kiro-rs';
|
||||||
|
const defaultKiroRegion = flowRegistry.DEFAULT_KIRO_REGION || 'us-east-1';
|
||||||
|
const defaultKiroRsUrl = flowRegistry.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin';
|
||||||
|
const normalizeFlowId = typeof flowRegistry.normalizeFlowId === 'function'
|
||||||
|
? flowRegistry.normalizeFlowId
|
||||||
|
: ((value = '', fallback = defaultFlowId) => {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
return normalized || String(fallback || '').trim().toLowerCase() || defaultFlowId;
|
||||||
|
});
|
||||||
|
const normalizeSourceId = typeof flowRegistry.normalizeSourceId === 'function'
|
||||||
|
? flowRegistry.normalizeSourceId
|
||||||
|
: ((flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase());
|
||||||
|
const mapSourceIdToPanelMode = typeof flowRegistry.mapSourceIdToPanelMode === 'function'
|
||||||
|
? flowRegistry.mapSourceIdToPanelMode
|
||||||
|
: ((_flowId, sourceId = '', fallback = defaultOpenAiSourceId) => String(sourceId || fallback || defaultOpenAiSourceId).trim().toLowerCase());
|
||||||
|
const mapPanelModeToSourceId = typeof flowRegistry.mapPanelModeToSourceId === 'function'
|
||||||
|
? flowRegistry.mapPanelModeToSourceId
|
||||||
|
: ((panelMode = '', fallback = defaultOpenAiSourceId) => String(panelMode || fallback || defaultOpenAiSourceId).trim().toLowerCase());
|
||||||
|
|
||||||
|
function buildDefaultSettingsState() {
|
||||||
|
return {
|
||||||
|
schemaVersion: 3,
|
||||||
|
activeFlowId: defaultFlowId,
|
||||||
|
services: {
|
||||||
|
email: {
|
||||||
|
provider: '163',
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
enabled: false,
|
||||||
|
provider: '711proxy',
|
||||||
|
mode: 'account',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
flows: {
|
||||||
|
openai: {
|
||||||
|
source: {
|
||||||
|
selected: defaultOpenAiSourceId,
|
||||||
|
entries: {
|
||||||
|
cpa: {
|
||||||
|
vpsUrl: '',
|
||||||
|
vpsPassword: '',
|
||||||
|
localCpaStep9Mode: 'submit',
|
||||||
|
},
|
||||||
|
sub2api: {
|
||||||
|
sub2apiUrl: '',
|
||||||
|
sub2apiEmail: '',
|
||||||
|
sub2apiPassword: '',
|
||||||
|
sub2apiGroupName: 'codex',
|
||||||
|
sub2apiGroupNames: ['codex', 'openai-plus'],
|
||||||
|
sub2apiAccountPriority: 1,
|
||||||
|
sub2apiDefaultProxyName: '',
|
||||||
|
},
|
||||||
|
codex2api: {
|
||||||
|
codex2apiUrl: '',
|
||||||
|
codex2apiAdminKey: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
customPassword: '',
|
||||||
|
},
|
||||||
|
signup: {
|
||||||
|
signupMethod: 'email',
|
||||||
|
phoneVerificationEnabled: false,
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||||
|
},
|
||||||
|
plus: {
|
||||||
|
plusModeEnabled: false,
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: {
|
||||||
|
enabled: false,
|
||||||
|
fromStep: 1,
|
||||||
|
toStep: 11,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kiro: {
|
||||||
|
source: {
|
||||||
|
selected: defaultKiroSourceId,
|
||||||
|
entries: {
|
||||||
|
'kiro-rs': {
|
||||||
|
kiroRsUrl: defaultKiroRsUrl,
|
||||||
|
kiroRsKey: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
kiroRegion: defaultKiroRegion,
|
||||||
|
kiroRsPriority: 0,
|
||||||
|
kiroRsEndpoint: '',
|
||||||
|
kiroRsAuthRegion: '',
|
||||||
|
kiroRsApiRegion: '',
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: {
|
||||||
|
enabled: false,
|
||||||
|
fromStep: 1,
|
||||||
|
toStep: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceValue(settingsState, pathGetter, fallback = {}) {
|
||||||
|
return cloneValue(pathGetter(isPlainObject(settingsState) ? settingsState : {}) || fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSettingsState(input = {}, options = {}) {
|
||||||
|
const defaults = buildDefaultSettingsState();
|
||||||
|
const nested = isPlainObject(input?.settingsState)
|
||||||
|
? input.settingsState
|
||||||
|
: (isPlainObject(input) && isPlainObject(input.flows) && isPlainObject(input.services) ? input : {});
|
||||||
|
const activeFlowId = normalizeFlowId(
|
||||||
|
input?.activeFlowId
|
||||||
|
?? nested?.activeFlowId
|
||||||
|
?? options?.activeFlowId
|
||||||
|
?? defaults.activeFlowId,
|
||||||
|
defaults.activeFlowId
|
||||||
|
);
|
||||||
|
const openaiSelectedSource = normalizeSourceId(
|
||||||
|
'openai',
|
||||||
|
nested?.flows?.openai?.source?.selected
|
||||||
|
?? input?.panelMode
|
||||||
|
?? input?.openaiSourceId
|
||||||
|
?? defaults.flows.openai.source.selected,
|
||||||
|
defaults.flows.openai.source.selected
|
||||||
|
);
|
||||||
|
const kiroSelectedSource = normalizeSourceId(
|
||||||
|
'kiro',
|
||||||
|
nested?.flows?.kiro?.source?.selected
|
||||||
|
?? input?.kiroSourceId
|
||||||
|
?? defaults.flows.kiro.source.selected,
|
||||||
|
defaults.flows.kiro.source.selected
|
||||||
|
);
|
||||||
|
const stepExecutionRangeByFlow = isPlainObject(input?.stepExecutionRangeByFlow)
|
||||||
|
? input.stepExecutionRangeByFlow
|
||||||
|
: {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
schemaVersion: Number(input?.settingsSchemaVersion || nested?.schemaVersion || defaults.schemaVersion) || defaults.schemaVersion,
|
||||||
|
activeFlowId,
|
||||||
|
services: {
|
||||||
|
email: {
|
||||||
|
provider: String(
|
||||||
|
nested?.services?.email?.provider
|
||||||
|
?? input?.mailProvider
|
||||||
|
?? defaults.services.email.provider
|
||||||
|
).trim() || defaults.services.email.provider,
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
enabled: Boolean(
|
||||||
|
nested?.services?.proxy?.enabled
|
||||||
|
?? input?.ipProxyEnabled
|
||||||
|
?? defaults.services.proxy.enabled
|
||||||
|
),
|
||||||
|
provider: String(
|
||||||
|
nested?.services?.proxy?.provider
|
||||||
|
?? input?.ipProxyService
|
||||||
|
?? defaults.services.proxy.provider
|
||||||
|
).trim() || defaults.services.proxy.provider,
|
||||||
|
mode: String(
|
||||||
|
nested?.services?.proxy?.mode
|
||||||
|
?? input?.ipProxyMode
|
||||||
|
?? defaults.services.proxy.mode
|
||||||
|
).trim() || defaults.services.proxy.mode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
flows: {
|
||||||
|
openai: {
|
||||||
|
source: {
|
||||||
|
selected: openaiSelectedSource,
|
||||||
|
entries: {
|
||||||
|
cpa: {
|
||||||
|
...defaults.flows.openai.source.entries.cpa,
|
||||||
|
...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.cpa),
|
||||||
|
vpsUrl: String(input?.vpsUrl ?? nested?.flows?.openai?.source?.entries?.cpa?.vpsUrl ?? '').trim(),
|
||||||
|
vpsPassword: String(input?.vpsPassword ?? nested?.flows?.openai?.source?.entries?.cpa?.vpsPassword ?? ''),
|
||||||
|
localCpaStep9Mode: String(
|
||||||
|
input?.localCpaStep9Mode
|
||||||
|
?? nested?.flows?.openai?.source?.entries?.cpa?.localCpaStep9Mode
|
||||||
|
?? defaults.flows.openai.source.entries.cpa.localCpaStep9Mode
|
||||||
|
).trim() || defaults.flows.openai.source.entries.cpa.localCpaStep9Mode,
|
||||||
|
},
|
||||||
|
sub2api: {
|
||||||
|
...defaults.flows.openai.source.entries.sub2api,
|
||||||
|
...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.sub2api),
|
||||||
|
sub2apiUrl: String(input?.sub2apiUrl ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiUrl ?? '').trim(),
|
||||||
|
sub2apiEmail: String(input?.sub2apiEmail ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiEmail ?? '').trim(),
|
||||||
|
sub2apiPassword: String(input?.sub2apiPassword ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiPassword ?? ''),
|
||||||
|
sub2apiGroupName: String(input?.sub2apiGroupName ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiGroupName ?? defaults.flows.openai.source.entries.sub2api.sub2apiGroupName).trim() || defaults.flows.openai.source.entries.sub2api.sub2apiGroupName,
|
||||||
|
sub2apiGroupNames: Array.isArray(input?.sub2apiGroupNames)
|
||||||
|
? input.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean)
|
||||||
|
: (Array.isArray(nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiGroupNames)
|
||||||
|
? nested.flows.openai.source.entries.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean)
|
||||||
|
: [...defaults.flows.openai.source.entries.sub2api.sub2apiGroupNames]),
|
||||||
|
sub2apiAccountPriority: Math.max(1, Number(input?.sub2apiAccountPriority ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiAccountPriority ?? defaults.flows.openai.source.entries.sub2api.sub2apiAccountPriority) || defaults.flows.openai.source.entries.sub2api.sub2apiAccountPriority),
|
||||||
|
sub2apiDefaultProxyName: String(input?.sub2apiDefaultProxyName ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiDefaultProxyName ?? '').trim(),
|
||||||
|
},
|
||||||
|
codex2api: {
|
||||||
|
...defaults.flows.openai.source.entries.codex2api,
|
||||||
|
...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.codex2api),
|
||||||
|
codex2apiUrl: String(input?.codex2apiUrl ?? nested?.flows?.openai?.source?.entries?.codex2api?.codex2apiUrl ?? '').trim(),
|
||||||
|
codex2apiAdminKey: String(input?.codex2apiAdminKey ?? nested?.flows?.openai?.source?.entries?.codex2api?.codex2apiAdminKey ?? '').trim(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
customPassword: String(input?.customPassword ?? nested?.flows?.openai?.account?.customPassword ?? '').trim(),
|
||||||
|
},
|
||||||
|
signup: {
|
||||||
|
signupMethod: String(input?.signupMethod ?? nested?.flows?.openai?.signup?.signupMethod ?? defaults.flows.openai.signup.signupMethod).trim().toLowerCase() === 'phone' ? 'phone' : 'email',
|
||||||
|
phoneVerificationEnabled: Boolean(input?.phoneVerificationEnabled ?? nested?.flows?.openai?.signup?.phoneVerificationEnabled ?? defaults.flows.openai.signup.phoneVerificationEnabled),
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: Boolean(input?.phoneSignupReloginAfterBindEmailEnabled ?? nested?.flows?.openai?.signup?.phoneSignupReloginAfterBindEmailEnabled ?? defaults.flows.openai.signup.phoneSignupReloginAfterBindEmailEnabled),
|
||||||
|
},
|
||||||
|
plus: {
|
||||||
|
plusModeEnabled: Boolean(input?.plusModeEnabled ?? nested?.flows?.openai?.plus?.plusModeEnabled ?? defaults.flows.openai.plus.plusModeEnabled),
|
||||||
|
plusPaymentMethod: String(input?.plusPaymentMethod ?? nested?.flows?.openai?.plus?.plusPaymentMethod ?? defaults.flows.openai.plus.plusPaymentMethod).trim() || defaults.flows.openai.plus.plusPaymentMethod,
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: normalizeStepExecutionRangeEntry(
|
||||||
|
nested?.flows?.openai?.autoRun?.stepExecutionRange
|
||||||
|
?? stepExecutionRangeByFlow.openai
|
||||||
|
?? {},
|
||||||
|
defaults.flows.openai.autoRun.stepExecutionRange
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kiro: {
|
||||||
|
source: {
|
||||||
|
selected: kiroSelectedSource,
|
||||||
|
entries: {
|
||||||
|
'kiro-rs': {
|
||||||
|
...defaults.flows.kiro.source.entries['kiro-rs'],
|
||||||
|
...getSourceValue(nested, (state) => state.flows?.kiro?.source?.entries?.['kiro-rs']),
|
||||||
|
kiroRsUrl: String(
|
||||||
|
input?.kiroRsUrl
|
||||||
|
?? nested?.flows?.kiro?.source?.entries?.['kiro-rs']?.kiroRsUrl
|
||||||
|
?? defaults.flows.kiro.source.entries['kiro-rs'].kiroRsUrl
|
||||||
|
).trim() || defaults.flows.kiro.source.entries['kiro-rs'].kiroRsUrl,
|
||||||
|
kiroRsKey: String(
|
||||||
|
input?.kiroRsKey
|
||||||
|
?? nested?.flows?.kiro?.source?.entries?.['kiro-rs']?.kiroRsKey
|
||||||
|
?? defaults.flows.kiro.source.entries['kiro-rs'].kiroRsKey
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
kiroRegion: String(
|
||||||
|
input?.kiroRegion
|
||||||
|
?? nested?.flows?.kiro?.options?.kiroRegion
|
||||||
|
?? defaults.flows.kiro.options.kiroRegion
|
||||||
|
).trim() || defaults.flows.kiro.options.kiroRegion,
|
||||||
|
kiroRsPriority: Number(
|
||||||
|
input?.kiroRsPriority
|
||||||
|
?? nested?.flows?.kiro?.options?.kiroRsPriority
|
||||||
|
?? defaults.flows.kiro.options.kiroRsPriority
|
||||||
|
) || 0,
|
||||||
|
kiroRsEndpoint: String(
|
||||||
|
input?.kiroRsEndpoint
|
||||||
|
?? nested?.flows?.kiro?.options?.kiroRsEndpoint
|
||||||
|
?? defaults.flows.kiro.options.kiroRsEndpoint
|
||||||
|
).trim(),
|
||||||
|
kiroRsAuthRegion: String(
|
||||||
|
input?.kiroRsAuthRegion
|
||||||
|
?? nested?.flows?.kiro?.options?.kiroRsAuthRegion
|
||||||
|
?? defaults.flows.kiro.options.kiroRsAuthRegion
|
||||||
|
).trim(),
|
||||||
|
kiroRsApiRegion: String(
|
||||||
|
input?.kiroRsApiRegion
|
||||||
|
?? nested?.flows?.kiro?.options?.kiroRsApiRegion
|
||||||
|
?? defaults.flows.kiro.options.kiroRsApiRegion
|
||||||
|
).trim(),
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: normalizeStepExecutionRangeEntry(
|
||||||
|
nested?.flows?.kiro?.autoRun?.stepExecutionRange
|
||||||
|
?? stepExecutionRangeByFlow.kiro
|
||||||
|
?? {},
|
||||||
|
defaults.flows.kiro.autoRun.stepExecutionRange
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStepExecutionRangeByFlow(settingsState = {}) {
|
||||||
|
const normalizedState = normalizeSettingsState(settingsState);
|
||||||
|
return {
|
||||||
|
openai: normalizeStepExecutionRangeEntry(
|
||||||
|
normalizedState?.flows?.openai?.autoRun?.stepExecutionRange,
|
||||||
|
buildDefaultSettingsState().flows.openai.autoRun.stepExecutionRange
|
||||||
|
),
|
||||||
|
kiro: normalizeStepExecutionRangeEntry(
|
||||||
|
normalizedState?.flows?.kiro?.autoRun?.stepExecutionRange,
|
||||||
|
buildDefaultSettingsState().flows.kiro.autoRun.stepExecutionRange
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlowSettings(settingsState = {}, flowId) {
|
||||||
|
const normalizedState = normalizeSettingsState(settingsState);
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
|
||||||
|
return cloneValue(normalizedState?.flows?.[normalizedFlowId] || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedSourceId(settingsState = {}, flowId) {
|
||||||
|
const flowSettings = getFlowSettings(settingsState, flowId);
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId, normalizeSettingsState(settingsState).activeFlowId);
|
||||||
|
return normalizeSourceId(
|
||||||
|
normalizedFlowId,
|
||||||
|
flowSettings?.source?.selected,
|
||||||
|
normalizedFlowId === 'kiro' ? defaultKiroSourceId : defaultOpenAiSourceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLegacySettingsPayload(settingsState = {}, baseInput = {}) {
|
||||||
|
const normalizedState = normalizeSettingsState(settingsState);
|
||||||
|
const next = {
|
||||||
|
...(isPlainObject(baseInput) ? cloneValue(baseInput) : {}),
|
||||||
|
};
|
||||||
|
const openaiState = normalizedState.flows.openai;
|
||||||
|
const kiroState = normalizedState.flows.kiro;
|
||||||
|
next.activeFlowId = normalizedState.activeFlowId;
|
||||||
|
next.panelMode = mapSourceIdToPanelMode('openai', openaiState.source.selected, defaultOpenAiSourceId);
|
||||||
|
next.kiroSourceId = getSelectedSourceId(normalizedState, 'kiro');
|
||||||
|
next.vpsUrl = openaiState.source.entries.cpa.vpsUrl;
|
||||||
|
next.vpsPassword = openaiState.source.entries.cpa.vpsPassword;
|
||||||
|
next.localCpaStep9Mode = openaiState.source.entries.cpa.localCpaStep9Mode;
|
||||||
|
next.sub2apiUrl = openaiState.source.entries.sub2api.sub2apiUrl;
|
||||||
|
next.sub2apiEmail = openaiState.source.entries.sub2api.sub2apiEmail;
|
||||||
|
next.sub2apiPassword = openaiState.source.entries.sub2api.sub2apiPassword;
|
||||||
|
next.sub2apiGroupName = openaiState.source.entries.sub2api.sub2apiGroupName;
|
||||||
|
next.sub2apiGroupNames = cloneValue(openaiState.source.entries.sub2api.sub2apiGroupNames);
|
||||||
|
next.sub2apiAccountPriority = openaiState.source.entries.sub2api.sub2apiAccountPriority;
|
||||||
|
next.sub2apiDefaultProxyName = openaiState.source.entries.sub2api.sub2apiDefaultProxyName;
|
||||||
|
next.codex2apiUrl = openaiState.source.entries.codex2api.codex2apiUrl;
|
||||||
|
next.codex2apiAdminKey = openaiState.source.entries.codex2api.codex2apiAdminKey;
|
||||||
|
next.customPassword = openaiState.account.customPassword;
|
||||||
|
next.signupMethod = openaiState.signup.signupMethod;
|
||||||
|
next.phoneVerificationEnabled = openaiState.signup.phoneVerificationEnabled;
|
||||||
|
next.phoneSignupReloginAfterBindEmailEnabled = openaiState.signup.phoneSignupReloginAfterBindEmailEnabled;
|
||||||
|
next.plusModeEnabled = openaiState.plus.plusModeEnabled;
|
||||||
|
next.plusPaymentMethod = openaiState.plus.plusPaymentMethod;
|
||||||
|
next.mailProvider = normalizedState.services.email.provider;
|
||||||
|
next.ipProxyEnabled = normalizedState.services.proxy.enabled;
|
||||||
|
next.ipProxyService = normalizedState.services.proxy.provider;
|
||||||
|
next.ipProxyMode = normalizedState.services.proxy.mode;
|
||||||
|
next.kiroRsUrl = kiroState.source.entries['kiro-rs'].kiroRsUrl;
|
||||||
|
next.kiroRsKey = kiroState.source.entries['kiro-rs'].kiroRsKey;
|
||||||
|
next.kiroRegion = kiroState.options.kiroRegion;
|
||||||
|
next.kiroRsPriority = kiroState.options.kiroRsPriority;
|
||||||
|
next.kiroRsEndpoint = kiroState.options.kiroRsEndpoint;
|
||||||
|
next.kiroRsAuthRegion = kiroState.options.kiroRsAuthRegion;
|
||||||
|
next.kiroRsApiRegion = kiroState.options.kiroRsApiRegion;
|
||||||
|
next.stepExecutionRangeByFlow = buildStepExecutionRangeByFlow(normalizedState);
|
||||||
|
next.settingsSchemaVersion = normalizedState.schemaVersion;
|
||||||
|
next.settingsState = cloneValue(normalizedState);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlowInputState(settingsState = {}, flowId) {
|
||||||
|
const normalizedState = normalizeSettingsState(settingsState);
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
|
||||||
|
if (normalizedFlowId === 'kiro') {
|
||||||
|
return {
|
||||||
|
activeFlowId: normalizedFlowId,
|
||||||
|
sourceId: getSelectedSourceId(normalizedState, 'kiro'),
|
||||||
|
kiroRsUrl: normalizedState.flows.kiro.source.entries['kiro-rs'].kiroRsUrl,
|
||||||
|
kiroRsKey: normalizedState.flows.kiro.source.entries['kiro-rs'].kiroRsKey,
|
||||||
|
kiroRegion: normalizedState.flows.kiro.options.kiroRegion,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
activeFlowId: normalizedFlowId,
|
||||||
|
sourceId: getSelectedSourceId(normalizedState, 'openai'),
|
||||||
|
panelMode: mapSourceIdToPanelMode('openai', normalizedState.flows.openai.source.selected, defaultOpenAiSourceId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFlatInput(input = {}) {
|
||||||
|
const state = normalizeSettingsState(input);
|
||||||
|
return buildLegacySettingsPayload(state, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildDefaultSettingsState,
|
||||||
|
buildLegacySettingsPayload,
|
||||||
|
buildStepExecutionRangeByFlow,
|
||||||
|
getFlowInputState,
|
||||||
|
getFlowSettings,
|
||||||
|
getSelectedSourceId,
|
||||||
|
normalizeFlatInput,
|
||||||
|
normalizeSettingsState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createSettingsSchema,
|
||||||
|
};
|
||||||
|
});
|
||||||
+32
-123
@@ -1,29 +1,14 @@
|
|||||||
(function attachMultiPageSourceRegistry(root, factory) {
|
(function attachMultiPageSourceRegistry(root, factory) {
|
||||||
root.MultiPageSourceRegistry = factory();
|
root.MultiPageSourceRegistry = factory();
|
||||||
})(typeof self !== 'undefined' ? self : globalThis, function createSourceRegistryModule() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createSourceRegistryModule() {
|
||||||
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
|
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
|
||||||
|
|
||||||
const SOURCE_ALIASES = Object.freeze({
|
const SOURCE_ALIASES = Object.freeze({
|
||||||
'signup-page': 'openai-auth',
|
'signup-page': 'openai-auth',
|
||||||
});
|
});
|
||||||
|
|
||||||
const SOURCE_DEFINITIONS = Object.freeze({
|
const SHARED_SOURCE_DEFINITIONS = Object.freeze({
|
||||||
'openai-auth': {
|
|
||||||
flowId: 'openai',
|
|
||||||
kind: 'flow-page',
|
|
||||||
label: '认证页',
|
|
||||||
readyPolicy: 'allow-child-frame',
|
|
||||||
family: 'openai-auth-family',
|
|
||||||
driverId: 'content/signup-page',
|
|
||||||
cleanupScopes: ['oauth-localhost-callback'],
|
|
||||||
},
|
|
||||||
chatgpt: {
|
|
||||||
flowId: 'openai',
|
|
||||||
kind: 'flow-entry',
|
|
||||||
label: 'ChatGPT 首页',
|
|
||||||
readyPolicy: 'allow-child-frame',
|
|
||||||
family: 'chatgpt-entry-family',
|
|
||||||
driverId: null,
|
|
||||||
cleanupScopes: [],
|
|
||||||
},
|
|
||||||
'qq-mail': {
|
'qq-mail': {
|
||||||
flowId: null,
|
flowId: null,
|
||||||
kind: 'mail-provider',
|
kind: 'mail-provider',
|
||||||
@@ -87,69 +72,6 @@
|
|||||||
driverId: 'content/duck-mail',
|
driverId: 'content/duck-mail',
|
||||||
cleanupScopes: [],
|
cleanupScopes: [],
|
||||||
},
|
},
|
||||||
'vps-panel': {
|
|
||||||
flowId: 'openai',
|
|
||||||
kind: 'panel-page',
|
|
||||||
label: 'CPA 面板',
|
|
||||||
readyPolicy: 'allow-child-frame',
|
|
||||||
family: 'vps-panel-family',
|
|
||||||
driverId: 'content/vps-panel',
|
|
||||||
cleanupScopes: [],
|
|
||||||
},
|
|
||||||
'platform-panel': {
|
|
||||||
flowId: 'openai',
|
|
||||||
kind: 'virtual-page',
|
|
||||||
label: '平台回调面板',
|
|
||||||
readyPolicy: 'disabled',
|
|
||||||
family: 'platform-panel-family',
|
|
||||||
driverId: 'content/platform-panel',
|
|
||||||
cleanupScopes: [],
|
|
||||||
},
|
|
||||||
'sub2api-panel': {
|
|
||||||
flowId: 'openai',
|
|
||||||
kind: 'panel-page',
|
|
||||||
label: 'SUB2API 后台',
|
|
||||||
readyPolicy: 'allow-child-frame',
|
|
||||||
family: 'sub2api-panel-family',
|
|
||||||
driverId: 'content/sub2api-panel',
|
|
||||||
cleanupScopes: [],
|
|
||||||
},
|
|
||||||
'codex2api-panel': {
|
|
||||||
flowId: 'openai',
|
|
||||||
kind: 'panel-page',
|
|
||||||
label: 'Codex2API 后台',
|
|
||||||
readyPolicy: 'allow-child-frame',
|
|
||||||
family: 'codex2api-panel-family',
|
|
||||||
driverId: 'content/sub2api-panel',
|
|
||||||
cleanupScopes: [],
|
|
||||||
},
|
|
||||||
'plus-checkout': {
|
|
||||||
flowId: 'openai',
|
|
||||||
kind: 'flow-page',
|
|
||||||
label: 'Plus Checkout',
|
|
||||||
readyPolicy: 'top-frame-only',
|
|
||||||
family: 'plus-checkout-family',
|
|
||||||
driverId: 'content/plus-checkout',
|
|
||||||
cleanupScopes: [],
|
|
||||||
},
|
|
||||||
'paypal-flow': {
|
|
||||||
flowId: 'openai',
|
|
||||||
kind: 'flow-page',
|
|
||||||
label: 'PayPal 授权页',
|
|
||||||
readyPolicy: 'allow-child-frame',
|
|
||||||
family: 'paypal-flow-family',
|
|
||||||
driverId: 'content/paypal-flow',
|
|
||||||
cleanupScopes: [],
|
|
||||||
},
|
|
||||||
'gopay-flow': {
|
|
||||||
flowId: 'openai',
|
|
||||||
kind: 'flow-page',
|
|
||||||
label: 'GoPay 授权页',
|
|
||||||
readyPolicy: 'allow-child-frame',
|
|
||||||
family: 'gopay-flow-family',
|
|
||||||
driverId: 'content/gopay-flow',
|
|
||||||
cleanupScopes: [],
|
|
||||||
},
|
|
||||||
'unknown-source': {
|
'unknown-source': {
|
||||||
flowId: null,
|
flowId: null,
|
||||||
kind: 'unknown',
|
kind: 'unknown',
|
||||||
@@ -161,22 +83,7 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const DRIVER_DEFINITIONS = Object.freeze({
|
const SHARED_DRIVER_DEFINITIONS = Object.freeze({
|
||||||
'content/signup-page': {
|
|
||||||
sourceId: 'openai-auth',
|
|
||||||
commands: [
|
|
||||||
'submit-signup-email',
|
|
||||||
'fill-password',
|
|
||||||
'fill-profile',
|
|
||||||
'oauth-login',
|
|
||||||
'submit-verification-code',
|
|
||||||
'post-login-phone-verification',
|
|
||||||
'bind-email',
|
|
||||||
'fetch-bind-email-code',
|
|
||||||
'confirm-oauth',
|
|
||||||
'detect-auth-state',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'content/qq-mail': {
|
'content/qq-mail': {
|
||||||
sourceId: 'qq-mail',
|
sourceId: 'qq-mail',
|
||||||
commands: ['POLL_EMAIL'],
|
commands: ['POLL_EMAIL'],
|
||||||
@@ -201,30 +108,6 @@
|
|||||||
sourceId: 'duck-mail',
|
sourceId: 'duck-mail',
|
||||||
commands: ['FETCH_ALIAS_EMAIL'],
|
commands: ['FETCH_ALIAS_EMAIL'],
|
||||||
},
|
},
|
||||||
'content/sub2api-panel': {
|
|
||||||
sourceId: 'sub2api-panel',
|
|
||||||
commands: ['open-panel', 'fetch-oauth-url', 'platform-verify'],
|
|
||||||
},
|
|
||||||
'content/vps-panel': {
|
|
||||||
sourceId: 'vps-panel',
|
|
||||||
commands: ['open-panel', 'fetch-oauth-url', 'platform-verify'],
|
|
||||||
},
|
|
||||||
'content/platform-panel': {
|
|
||||||
sourceId: 'platform-panel',
|
|
||||||
commands: ['platform-verify', 'fetch-oauth-url'],
|
|
||||||
},
|
|
||||||
'content/plus-checkout': {
|
|
||||||
sourceId: 'plus-checkout',
|
|
||||||
commands: ['plus-checkout-create', 'plus-checkout-billing', 'plus-checkout-return'],
|
|
||||||
},
|
|
||||||
'content/paypal-flow': {
|
|
||||||
sourceId: 'paypal-flow',
|
|
||||||
commands: ['paypal-approve'],
|
|
||||||
},
|
|
||||||
'content/gopay-flow': {
|
|
||||||
sourceId: 'gopay-flow',
|
|
||||||
commands: ['gopay-subscription-confirm'],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const CLEANUP_SCOPE_OWNERS = Object.freeze({
|
const CLEANUP_SCOPE_OWNERS = Object.freeze({
|
||||||
@@ -240,9 +123,31 @@
|
|||||||
'mail-2925',
|
'mail-2925',
|
||||||
'inbucket-mail',
|
'inbucket-mail',
|
||||||
'plus-checkout',
|
'plus-checkout',
|
||||||
|
'kiro-device-auth',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function getRuntimeSourceDefinitions() {
|
||||||
|
return {
|
||||||
|
...(typeof flowRegistryApi.getRuntimeSourceDefinitions === 'function'
|
||||||
|
? flowRegistryApi.getRuntimeSourceDefinitions()
|
||||||
|
: {}),
|
||||||
|
...SHARED_SOURCE_DEFINITIONS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDriverDefinitions() {
|
||||||
|
return {
|
||||||
|
...(typeof flowRegistryApi.getDriverDefinitions === 'function'
|
||||||
|
? flowRegistryApi.getDriverDefinitions()
|
||||||
|
: {}),
|
||||||
|
...SHARED_DRIVER_DEFINITIONS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createSourceRegistry() {
|
function createSourceRegistry() {
|
||||||
|
const SOURCE_DEFINITIONS = getRuntimeSourceDefinitions();
|
||||||
|
const DRIVER_DEFINITIONS = getDriverDefinitions();
|
||||||
|
|
||||||
function parseUrlSafely(rawUrl) {
|
function parseUrlSafely(rawUrl) {
|
||||||
if (!rawUrl) return null;
|
if (!rawUrl) return null;
|
||||||
try {
|
try {
|
||||||
@@ -394,6 +299,9 @@
|
|||||||
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':
|
||||||
|
return candidate.hostname === 'view.awsapps.com'
|
||||||
|
|| candidate.hostname.endsWith('.amazonaws.com');
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -416,6 +324,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 (normalizedHostname === 'view.awsapps.com' || normalizedHostname.endsWith('.amazonaws.com')) return 'kiro-device-auth';
|
||||||
if (isSignupEntryHost(normalizedHostname)) return 'chatgpt';
|
if (isSignupEntryHost(normalizedHostname)) return 'chatgpt';
|
||||||
return 'unknown-source';
|
return 'unknown-source';
|
||||||
}
|
}
|
||||||
@@ -436,10 +345,10 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
detectSourceFromLocation,
|
detectSourceFromLocation,
|
||||||
|
driverAcceptsCommand,
|
||||||
getCleanupOwnerSource,
|
getCleanupOwnerSource,
|
||||||
getDriverIdForSource,
|
getDriverIdForSource,
|
||||||
getDriverMeta,
|
getDriverMeta,
|
||||||
driverAcceptsCommand,
|
|
||||||
getSourceKeys,
|
getSourceKeys,
|
||||||
getSourceLabel,
|
getSourceLabel,
|
||||||
getSourceMeta,
|
getSourceMeta,
|
||||||
|
|||||||
@@ -84,8 +84,16 @@
|
|||||||
return getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API ? 'SUB2API' : 'CPA';
|
return getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API ? 'SUB2API' : 'CPA';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getActiveFlowId(currentState = getLatestState()) {
|
||||||
|
return normalizeString(currentState.activeFlowId || currentState.flowId).toLowerCase() || 'openai';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isContributionModeAvailable(currentState = getLatestState()) {
|
||||||
|
return getActiveFlowId(currentState) === 'openai';
|
||||||
|
}
|
||||||
|
|
||||||
function isContributionModeEnabled(currentState = getLatestState()) {
|
function isContributionModeEnabled(currentState = getLatestState()) {
|
||||||
return Boolean(currentState.contributionMode);
|
return isContributionModeAvailable(currentState) && Boolean(currentState.contributionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasActiveContributionSession(currentState = getLatestState()) {
|
function hasActiveContributionSession(currentState = getLatestState()) {
|
||||||
@@ -107,13 +115,18 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncContributionButton(enabled, blocked) {
|
function syncContributionButton(enabled, blocked, available = true) {
|
||||||
if (!dom.btnContributionMode) {
|
if (!dom.btnContributionMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dom.btnContributionMode.classList.toggle('is-active', enabled);
|
dom.btnContributionMode.classList.toggle('is-active', enabled);
|
||||||
dom.btnContributionMode.setAttribute('aria-pressed', String(enabled));
|
dom.btnContributionMode.setAttribute('aria-pressed', String(enabled));
|
||||||
|
if (!available) {
|
||||||
|
dom.btnContributionMode.disabled = true;
|
||||||
|
dom.btnContributionMode.title = '当前 flow 不支持贡献模式';
|
||||||
|
return;
|
||||||
|
}
|
||||||
dom.btnContributionMode.disabled = enabled || blocked;
|
dom.btnContributionMode.disabled = enabled || blocked;
|
||||||
dom.btnContributionMode.title = blocked
|
dom.btnContributionMode.title = blocked
|
||||||
? '当前流程运行中,暂时不能切换贡献模式'
|
? '当前流程运行中,暂时不能切换贡献模式'
|
||||||
@@ -333,10 +346,11 @@
|
|||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
const currentState = getLatestState();
|
const currentState = getLatestState();
|
||||||
|
const available = isContributionModeAvailable(currentState);
|
||||||
const enabled = isContributionModeEnabled(currentState);
|
const enabled = isContributionModeEnabled(currentState);
|
||||||
const blocked = isModeSwitchBlocked();
|
const blocked = available ? isModeSwitchBlocked() : false;
|
||||||
const activeElement = typeof document !== 'undefined' ? document.activeElement : null;
|
const activeElement = typeof document !== 'undefined' ? document.activeElement : null;
|
||||||
const sourceLabel = getContributionSourceLabel(currentState);
|
const sourceLabel = available ? getContributionSourceLabel(currentState) : '';
|
||||||
|
|
||||||
if (enabled && dom.selectPanelMode) {
|
if (enabled && dom.selectPanelMode) {
|
||||||
dom.selectPanelMode.value = getContributionSource(currentState);
|
dom.selectPanelMode.value = getContributionSource(currentState);
|
||||||
@@ -346,7 +360,7 @@
|
|||||||
helpers.updateAccountRunHistorySettingsUI?.();
|
helpers.updateAccountRunHistorySettingsUI?.();
|
||||||
|
|
||||||
if (dom.contributionModePanel) {
|
if (dom.contributionModePanel) {
|
||||||
dom.contributionModePanel.hidden = !enabled;
|
dom.contributionModePanel.hidden = !available || !enabled;
|
||||||
}
|
}
|
||||||
if (dom.contributionModeText) {
|
if (dom.contributionModeText) {
|
||||||
dom.contributionModeText.textContent = getSummaryText({
|
dom.contributionModeText.textContent = getSummaryText({
|
||||||
@@ -380,22 +394,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
syncContributionRows(enabled);
|
syncContributionRows(enabled);
|
||||||
syncContributionButton(enabled, blocked);
|
syncContributionButton(enabled, blocked, available);
|
||||||
|
|
||||||
if (dom.selectPanelMode) {
|
if (dom.selectPanelMode) {
|
||||||
dom.selectPanelMode.disabled = enabled;
|
dom.selectPanelMode.disabled = available && enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dom.btnStartContribution) {
|
if (dom.btnStartContribution) {
|
||||||
dom.btnStartContribution.disabled = actionInFlight || blocked;
|
dom.btnStartContribution.disabled = !available || actionInFlight || blocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dom.btnOpenContributionUpload) {
|
if (dom.btnOpenContributionUpload) {
|
||||||
dom.btnOpenContributionUpload.disabled = false;
|
dom.btnOpenContributionUpload.disabled = !available;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dom.btnExitContributionMode) {
|
if (dom.btnExitContributionMode) {
|
||||||
dom.btnExitContributionMode.disabled = actionInFlight || blocked;
|
dom.btnExitContributionMode.disabled = !available || actionInFlight || blocked;
|
||||||
dom.btnExitContributionMode.title = blocked ? '当前流程运行中,暂时不能退出贡献模式' : '退出贡献模式';
|
dom.btnExitContributionMode.title = blocked ? '当前流程运行中,暂时不能退出贡献模式' : '退出贡献模式';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +417,7 @@
|
|||||||
dom.btnOpenAccountRecords.disabled = enabled;
|
dom.btnOpenAccountRecords.disabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enabled) {
|
if (available && enabled) {
|
||||||
helpers.closeConfigMenu?.();
|
helpers.closeConfigMenu?.();
|
||||||
helpers.closeAccountRecordsPanel?.();
|
helpers.closeAccountRecordsPanel?.();
|
||||||
ensurePolling();
|
ensurePolling();
|
||||||
|
|||||||
@@ -113,13 +113,12 @@
|
|||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">注册</span>
|
<span class="data-label">注册</span>
|
||||||
<select id="select-flow" class="data-select" aria-label="Flow 选择">
|
<select id="select-flow" class="data-select" aria-label="Flow 选择">
|
||||||
<option value="codex" selected>codex</option>
|
<option value="openai" selected>Codex / OpenAI</option>
|
||||||
<option value="pending-1" disabled>kiro</option>
|
<option value="kiro">Kiro</option>
|
||||||
<option value="pending-2" disabled>待添加</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">来源</span>
|
<span id="label-source-selector" class="data-label">来源</span>
|
||||||
<select id="select-panel-mode" class="data-select">
|
<select id="select-panel-mode" class="data-select">
|
||||||
<option value="cpa">CPA 面板</option>
|
<option value="cpa">CPA 面板</option>
|
||||||
<option value="sub2api">SUB2API</option>
|
<option value="sub2api">SUB2API</option>
|
||||||
@@ -242,6 +241,38 @@
|
|||||||
title="显示 Codex2API 管理密钥"></button>
|
title="显示 Codex2API 管理密钥"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row" id="row-kiro-rs-url" style="display:none;">
|
||||||
|
<span class="data-label">kiro.rs</span>
|
||||||
|
<input type="text" id="input-kiro-rs-url" class="data-input"
|
||||||
|
placeholder="https://kiro.leftcode.xyz/admin" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-kiro-rs-key" style="display:none;">
|
||||||
|
<span class="data-label">API Key</span>
|
||||||
|
<div class="input-with-icon">
|
||||||
|
<input type="password" id="input-kiro-rs-key" class="data-input data-input-with-icon"
|
||||||
|
placeholder="请输入 kiro.rs API Key" />
|
||||||
|
<button id="btn-toggle-kiro-rs-key" class="input-icon-btn" type="button"
|
||||||
|
data-password-toggle="input-kiro-rs-key" data-show-label="显示 kiro.rs API Key"
|
||||||
|
data-hide-label="隐藏 kiro.rs API Key" aria-label="显示 kiro.rs API Key"
|
||||||
|
title="显示 kiro.rs API Key"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-kiro-region" style="display:none;">
|
||||||
|
<span class="data-label">区域</span>
|
||||||
|
<input type="text" id="input-kiro-region" class="data-input" placeholder="us-east-1" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-kiro-device-code" style="display:none;">
|
||||||
|
<span class="data-label">设备码</span>
|
||||||
|
<span id="display-kiro-device-code" class="data-value mono">未生成</span>
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-kiro-login-url" style="display:none;">
|
||||||
|
<span class="data-label">登录地址</span>
|
||||||
|
<span id="display-kiro-login-url" class="data-value mono">未生成</span>
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-kiro-upload-status" style="display:none;">
|
||||||
|
<span class="data-label">上传状态</span>
|
||||||
|
<span id="display-kiro-upload-status" class="data-value mono">未开始</span>
|
||||||
|
</div>
|
||||||
<div class="data-row module-divider-start" id="row-custom-password">
|
<div class="data-row module-divider-start" id="row-custom-password">
|
||||||
<span class="data-label">账户密码</span>
|
<span class="data-label">账户密码</span>
|
||||||
<div class="input-with-icon">
|
<div class="input-with-icon">
|
||||||
@@ -1809,6 +1840,8 @@
|
|||||||
<script src="../hotmail-utils.js"></script>
|
<script src="../hotmail-utils.js"></script>
|
||||||
<script src="../luckmail-utils.js"></script>
|
<script src="../luckmail-utils.js"></script>
|
||||||
<script src="../yyds-mail-utils.js"></script>
|
<script src="../yyds-mail-utils.js"></script>
|
||||||
|
<script src="../shared/flow-registry.js"></script>
|
||||||
|
<script src="../shared/settings-schema.js"></script>
|
||||||
<script src="../shared/flow-capabilities.js"></script>
|
<script src="../shared/flow-capabilities.js"></script>
|
||||||
<script src="../data/step-definitions.js"></script>
|
<script src="../data/step-definitions.js"></script>
|
||||||
<script src="update-service.js"></script>
|
<script src="update-service.js"></script>
|
||||||
|
|||||||
+595
-65
@@ -82,6 +82,7 @@ const configMenu = document.getElementById('config-menu');
|
|||||||
const btnExportSettings = document.getElementById('btn-export-settings');
|
const btnExportSettings = document.getElementById('btn-export-settings');
|
||||||
const btnImportSettings = document.getElementById('btn-import-settings');
|
const btnImportSettings = document.getElementById('btn-import-settings');
|
||||||
const inputImportSettingsFile = document.getElementById('input-import-settings-file');
|
const inputImportSettingsFile = document.getElementById('input-import-settings-file');
|
||||||
|
const labelSourceSelector = document.getElementById('label-source-selector');
|
||||||
const selectPanelMode = document.getElementById('select-panel-mode');
|
const selectPanelMode = document.getElementById('select-panel-mode');
|
||||||
const rowVpsUrl = document.getElementById('row-vps-url');
|
const rowVpsUrl = document.getElementById('row-vps-url');
|
||||||
const inputVpsUrl = document.getElementById('input-vps-url');
|
const inputVpsUrl = document.getElementById('input-vps-url');
|
||||||
@@ -170,6 +171,18 @@ const rowCodex2ApiUrl = document.getElementById('row-codex2api-url');
|
|||||||
const inputCodex2ApiUrl = document.getElementById('input-codex2api-url');
|
const inputCodex2ApiUrl = document.getElementById('input-codex2api-url');
|
||||||
const rowCodex2ApiAdminKey = document.getElementById('row-codex2api-admin-key');
|
const rowCodex2ApiAdminKey = document.getElementById('row-codex2api-admin-key');
|
||||||
const inputCodex2ApiAdminKey = document.getElementById('input-codex2api-admin-key');
|
const inputCodex2ApiAdminKey = document.getElementById('input-codex2api-admin-key');
|
||||||
|
const rowKiroRsUrl = document.getElementById('row-kiro-rs-url');
|
||||||
|
const inputKiroRsUrl = document.getElementById('input-kiro-rs-url');
|
||||||
|
const rowKiroRsKey = document.getElementById('row-kiro-rs-key');
|
||||||
|
const inputKiroRsKey = document.getElementById('input-kiro-rs-key');
|
||||||
|
const rowKiroRegion = document.getElementById('row-kiro-region');
|
||||||
|
const inputKiroRegion = document.getElementById('input-kiro-region');
|
||||||
|
const rowKiroDeviceCode = document.getElementById('row-kiro-device-code');
|
||||||
|
const displayKiroDeviceCode = document.getElementById('display-kiro-device-code');
|
||||||
|
const rowKiroLoginUrl = document.getElementById('row-kiro-login-url');
|
||||||
|
const displayKiroLoginUrl = document.getElementById('display-kiro-login-url');
|
||||||
|
const rowKiroUploadStatus = document.getElementById('row-kiro-upload-status');
|
||||||
|
const displayKiroUploadStatus = document.getElementById('display-kiro-upload-status');
|
||||||
const rowCustomPassword = document.getElementById('row-custom-password');
|
const rowCustomPassword = document.getElementById('row-custom-password');
|
||||||
const rowPlusMode = document.getElementById('row-plus-mode');
|
const rowPlusMode = document.getElementById('row-plus-mode');
|
||||||
const inputPlusModeEnabled = document.getElementById('input-plus-mode-enabled');
|
const inputPlusModeEnabled = document.getElementById('input-plus-mode-enabled');
|
||||||
@@ -539,6 +552,7 @@ let currentPlusModeEnabled = false;
|
|||||||
let currentPlusPaymentMethod = DEFAULT_PLUS_PAYMENT_METHOD;
|
let currentPlusPaymentMethod = DEFAULT_PLUS_PAYMENT_METHOD;
|
||||||
let currentSignupMethod = DEFAULT_SIGNUP_METHOD;
|
let currentSignupMethod = DEFAULT_SIGNUP_METHOD;
|
||||||
let currentPhoneSignupReloginAfterBindEmailEnabled = DEFAULT_PHONE_SIGNUP_RELOGIN_AFTER_BIND_EMAIL_ENABLED;
|
let currentPhoneSignupReloginAfterBindEmailEnabled = DEFAULT_PHONE_SIGNUP_RELOGIN_AFTER_BIND_EMAIL_ENABLED;
|
||||||
|
let currentStepDefinitionFlowId = DEFAULT_ACTIVE_FLOW_ID;
|
||||||
let phoneSignupReuseUiWasLocked = false;
|
let phoneSignupReuseUiWasLocked = false;
|
||||||
let lastConfirmedOperationDelayEnabled = true;
|
let lastConfirmedOperationDelayEnabled = true;
|
||||||
let heroSmsCountrySelectionOrder = [];
|
let heroSmsCountrySelectionOrder = [];
|
||||||
@@ -924,6 +938,7 @@ function getStepIdByNodeIdForCurrentMode(nodeId = '') {
|
|||||||
|
|
||||||
function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
||||||
currentPlusModeEnabled = Boolean(plusModeEnabled);
|
currentPlusModeEnabled = Boolean(plusModeEnabled);
|
||||||
|
const defaultFlowId = typeof DEFAULT_ACTIVE_FLOW_ID !== 'undefined' ? DEFAULT_ACTIVE_FLOW_ID : 'openai';
|
||||||
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal';
|
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal';
|
||||||
const rawPaymentMethod = typeof options === 'string'
|
const rawPaymentMethod = typeof options === 'string'
|
||||||
? options
|
? options
|
||||||
@@ -937,15 +952,23 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
|||||||
currentPlusPaymentMethod = normalizePlusPaymentMethod(rawPaymentMethod);
|
currentPlusPaymentMethod = normalizePlusPaymentMethod(rawPaymentMethod);
|
||||||
currentSignupMethod = normalizeSignupMethod(rawSignupMethod);
|
currentSignupMethod = normalizeSignupMethod(rawSignupMethod);
|
||||||
currentPhoneSignupReloginAfterBindEmailEnabled = phoneSignupReloginAfterBindEmailEnabled;
|
currentPhoneSignupReloginAfterBindEmailEnabled = phoneSignupReloginAfterBindEmailEnabled;
|
||||||
|
const nextActiveFlowId = String(
|
||||||
|
options?.activeFlowId
|
||||||
|
|| (typeof latestState !== 'undefined' ? latestState?.activeFlowId : '')
|
||||||
|
|| defaultFlowId
|
||||||
|
).trim().toLowerCase() || defaultFlowId;
|
||||||
|
if (typeof currentStepDefinitionFlowId !== 'undefined') {
|
||||||
|
currentStepDefinitionFlowId = nextActiveFlowId;
|
||||||
|
}
|
||||||
stepDefinitions = getStepDefinitionsForMode(currentPlusModeEnabled, {
|
stepDefinitions = getStepDefinitionsForMode(currentPlusModeEnabled, {
|
||||||
activeFlowId: options?.activeFlowId,
|
activeFlowId: nextActiveFlowId,
|
||||||
plusPaymentMethod: currentPlusPaymentMethod,
|
plusPaymentMethod: currentPlusPaymentMethod,
|
||||||
signupMethod: currentSignupMethod,
|
signupMethod: currentSignupMethod,
|
||||||
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
|
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
|
||||||
});
|
});
|
||||||
const nextWorkflowNodes = typeof getWorkflowNodesForMode === 'function'
|
const nextWorkflowNodes = typeof getWorkflowNodesForMode === 'function'
|
||||||
? getWorkflowNodesForMode(currentPlusModeEnabled, {
|
? getWorkflowNodesForMode(currentPlusModeEnabled, {
|
||||||
activeFlowId: options?.activeFlowId,
|
activeFlowId: nextActiveFlowId,
|
||||||
plusPaymentMethod: currentPlusPaymentMethod,
|
plusPaymentMethod: currentPlusPaymentMethod,
|
||||||
signupMethod: currentSignupMethod,
|
signupMethod: currentSignupMethod,
|
||||||
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
|
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
|
||||||
@@ -1991,7 +2014,9 @@ function shouldPromptNewUserGuide() {
|
|||||||
if (!btnContributionMode || btnContributionMode.disabled) {
|
if (!btnContributionMode || btnContributionMode.disabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (latestState?.contributionMode) {
|
if (typeof isContributionModeActiveForFlow === 'function'
|
||||||
|
? isContributionModeActiveForFlow(latestState)
|
||||||
|
: Boolean(latestState?.contributionMode)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -2218,7 +2243,9 @@ async function openAutoRunFallbackRiskConfirmModal(totalRuns) {
|
|||||||
|
|
||||||
function updateConfigMenuControls() {
|
function updateConfigMenuControls() {
|
||||||
const disabled = configActionInFlight || settingsSaveInFlight;
|
const disabled = configActionInFlight || settingsSaveInFlight;
|
||||||
const contributionModeEnabled = Boolean(latestState?.contributionMode);
|
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||||
|
? isContributionModeActiveForFlow(latestState)
|
||||||
|
: Boolean(latestState?.contributionMode);
|
||||||
if (contributionModeEnabled && configMenuOpen) {
|
if (contributionModeEnabled && configMenuOpen) {
|
||||||
configMenuOpen = false;
|
configMenuOpen = false;
|
||||||
}
|
}
|
||||||
@@ -2579,6 +2606,16 @@ function syncLatestState(nextState) {
|
|||||||
renderAccountRecords(latestState);
|
renderAccountRecords(latestState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isContributionModeActiveForFlow(state = latestState, flowId = undefined) {
|
||||||
|
const rawFlowId = flowId !== undefined
|
||||||
|
? flowId
|
||||||
|
: (state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID);
|
||||||
|
const normalizedFlowId = typeof normalizeFlowId === 'function'
|
||||||
|
? normalizeFlowId(rawFlowId, DEFAULT_ACTIVE_FLOW_ID)
|
||||||
|
: (String(rawFlowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID);
|
||||||
|
return normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID && Boolean(state?.contributionMode);
|
||||||
|
}
|
||||||
|
|
||||||
let accountRunHistoryRefreshTimer = null;
|
let accountRunHistoryRefreshTimer = null;
|
||||||
|
|
||||||
function scheduleAccountRunHistoryRefresh(delayMs = 150) {
|
function scheduleAccountRunHistoryRefresh(delayMs = 150) {
|
||||||
@@ -3478,7 +3515,23 @@ function collectSettingsPayload() {
|
|||||||
const normalizeCloudMailDomainInput = typeof normalizeCloudMailDomainValue === 'function'
|
const normalizeCloudMailDomainInput = typeof normalizeCloudMailDomainValue === 'function'
|
||||||
? normalizeCloudMailDomainValue
|
? normalizeCloudMailDomainValue
|
||||||
: normalizeCloudflareTempEmailDomainValue;
|
: normalizeCloudflareTempEmailDomainValue;
|
||||||
const contributionModeEnabled = Boolean(latestState?.contributionMode);
|
const defaultFlowId = typeof DEFAULT_ACTIVE_FLOW_ID !== 'undefined'
|
||||||
|
? DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
: 'openai';
|
||||||
|
const activeFlowId = typeof getSelectedFlowId === 'function'
|
||||||
|
? getSelectedFlowId(latestState)
|
||||||
|
: (() => {
|
||||||
|
const normalized = String(
|
||||||
|
latestState?.activeFlowId || latestState?.flowId || defaultFlowId
|
||||||
|
).trim().toLowerCase();
|
||||||
|
if (normalized === 'codex') {
|
||||||
|
return defaultFlowId;
|
||||||
|
}
|
||||||
|
return normalized || defaultFlowId;
|
||||||
|
})();
|
||||||
|
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||||
|
? isContributionModeActiveForFlow(latestState, activeFlowId)
|
||||||
|
: (activeFlowId === defaultFlowId && Boolean(latestState?.contributionMode));
|
||||||
const icloudFetchModeRawValue = typeof selectIcloudFetchMode !== 'undefined'
|
const icloudFetchModeRawValue = typeof selectIcloudFetchMode !== 'undefined'
|
||||||
? String(selectIcloudFetchMode?.value || '')
|
? String(selectIcloudFetchMode?.value || '')
|
||||||
: '';
|
: '';
|
||||||
@@ -3985,17 +4038,27 @@ 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'
|
||||||
|
? getSelectedSourceId(activeFlowId)
|
||||||
|
: (activeFlowId === defaultFlowId
|
||||||
|
? rawPanelMode
|
||||||
|
: String(selectPanelMode?.value || latestState?.kiroSourceId || '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);
|
||||||
const rawPhoneVerificationEnabled = Boolean(inputPhoneVerificationEnabled?.checked);
|
const rawPhoneVerificationEnabled = Boolean(inputPhoneVerificationEnabled?.checked);
|
||||||
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
? resolveCurrentSidepanelCapabilities({
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
activeFlowId,
|
||||||
|
sourceId: selectedSourceId,
|
||||||
panelMode: rawPanelMode,
|
panelMode: rawPanelMode,
|
||||||
signupMethod: selectedSignupMethod,
|
signupMethod: selectedSignupMethod,
|
||||||
state: {
|
state: {
|
||||||
...(latestState || {}),
|
...(latestState || {}),
|
||||||
panelMode: rawPanelMode,
|
activeFlowId,
|
||||||
|
...(activeFlowId === defaultFlowId
|
||||||
|
? { panelMode: rawPanelMode }
|
||||||
|
: { kiroSourceId: selectedSourceId }),
|
||||||
plusModeEnabled: rawPlusModeEnabled,
|
plusModeEnabled: rawPlusModeEnabled,
|
||||||
phoneVerificationEnabled: rawPhoneVerificationEnabled,
|
phoneVerificationEnabled: rawPhoneVerificationEnabled,
|
||||||
signupMethod: selectedSignupMethod,
|
signupMethod: selectedSignupMethod,
|
||||||
@@ -4008,12 +4071,15 @@ function collectSettingsPayload() {
|
|||||||
}) || null;
|
}) || null;
|
||||||
return registry?.resolveSidepanelCapabilities
|
return registry?.resolveSidepanelCapabilities
|
||||||
? registry.resolveSidepanelCapabilities({
|
? registry.resolveSidepanelCapabilities({
|
||||||
activeFlowId: latestState?.activeFlowId,
|
activeFlowId,
|
||||||
panelMode: rawPanelMode,
|
panelMode: rawPanelMode,
|
||||||
signupMethod: selectedSignupMethod,
|
signupMethod: selectedSignupMethod,
|
||||||
state: {
|
state: {
|
||||||
...(latestState || {}),
|
...(latestState || {}),
|
||||||
panelMode: rawPanelMode,
|
activeFlowId,
|
||||||
|
...(activeFlowId === defaultFlowId
|
||||||
|
? { panelMode: rawPanelMode }
|
||||||
|
: { kiroSourceId: selectedSourceId }),
|
||||||
plusModeEnabled: rawPlusModeEnabled,
|
plusModeEnabled: rawPlusModeEnabled,
|
||||||
phoneVerificationEnabled: rawPhoneVerificationEnabled,
|
phoneVerificationEnabled: rawPhoneVerificationEnabled,
|
||||||
signupMethod: selectedSignupMethod,
|
signupMethod: selectedSignupMethod,
|
||||||
@@ -4022,6 +4088,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 effectivePlusModeEnabled = capabilityState
|
const effectivePlusModeEnabled = capabilityState
|
||||||
? Boolean(capabilityState.runtimeLocks?.plusModeEnabled)
|
? Boolean(capabilityState.runtimeLocks?.plusModeEnabled)
|
||||||
: rawPlusModeEnabled;
|
: rawPlusModeEnabled;
|
||||||
@@ -4084,10 +4151,46 @@ function collectSettingsPayload() {
|
|||||||
const numeric = Number(String(value ?? '').trim());
|
const numeric = Number(String(value ?? '').trim());
|
||||||
return Number.isSafeInteger(numeric) && numeric >= 1 ? numeric : 1;
|
return Number.isSafeInteger(numeric) && numeric >= 1 ? numeric : 1;
|
||||||
});
|
});
|
||||||
|
const flowRegistryApi = typeof getFlowRegistry === 'function' ? getFlowRegistry() : null;
|
||||||
|
const defaultKiroRsUrl = String(
|
||||||
|
flowRegistryApi?.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin'
|
||||||
|
).trim() || 'https://kiro.leftcode.xyz/admin';
|
||||||
|
const defaultKiroRegion = String(
|
||||||
|
flowRegistryApi?.DEFAULT_KIRO_REGION || 'us-east-1'
|
||||||
|
).trim() || 'us-east-1';
|
||||||
|
const normalizeKiroSourceIdSafe = typeof normalizeSourceIdForFlow === 'function'
|
||||||
|
? normalizeSourceIdForFlow
|
||||||
|
: ((_flowId, sourceId = '', fallback = 'kiro-rs') => {
|
||||||
|
const normalized = String(sourceId || '').trim().toLowerCase();
|
||||||
|
return normalized || String(fallback || '').trim().toLowerCase() || 'kiro-rs';
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
|
activeFlowId,
|
||||||
...(contributionModeEnabled ? {} : {
|
...(contributionModeEnabled ? {} : {
|
||||||
panelMode: effectivePanelMode,
|
...(activeFlowId === defaultFlowId ? { panelMode: effectivePanelMode } : {}),
|
||||||
}),
|
}),
|
||||||
|
kiroSourceId: normalizeKiroSourceIdSafe(
|
||||||
|
'kiro',
|
||||||
|
activeFlowId === 'kiro'
|
||||||
|
? effectiveSourceId
|
||||||
|
: (latestState?.kiroSourceId || 'kiro-rs'),
|
||||||
|
'kiro-rs'
|
||||||
|
),
|
||||||
|
kiroRsUrl: String(
|
||||||
|
(typeof inputKiroRsUrl !== 'undefined' && inputKiroRsUrl ? inputKiroRsUrl.value : '')
|
||||||
|
|| latestState?.kiroRsUrl
|
||||||
|
|| defaultKiroRsUrl
|
||||||
|
).trim() || defaultKiroRsUrl,
|
||||||
|
kiroRsKey: String(
|
||||||
|
(typeof inputKiroRsKey !== 'undefined' && inputKiroRsKey ? inputKiroRsKey.value : '')
|
||||||
|
|| latestState?.kiroRsKey
|
||||||
|
|| ''
|
||||||
|
),
|
||||||
|
kiroRegion: String(
|
||||||
|
(typeof inputKiroRegion !== 'undefined' && inputKiroRegion ? inputKiroRegion.value : '')
|
||||||
|
|| latestState?.kiroRegion
|
||||||
|
|| defaultKiroRegion
|
||||||
|
).trim() || defaultKiroRegion,
|
||||||
vpsUrl: inputVpsUrl.value.trim(),
|
vpsUrl: inputVpsUrl.value.trim(),
|
||||||
vpsPassword: inputVpsPassword.value,
|
vpsPassword: inputVpsPassword.value,
|
||||||
localCpaStep9Mode: getSelectedLocalCpaStep9Mode(),
|
localCpaStep9Mode: getSelectedLocalCpaStep9Mode(),
|
||||||
@@ -7999,8 +8102,227 @@ function normalizePanelMode(value = '') {
|
|||||||
return 'cpa';
|
return 'cpa';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let flowRegistry = null;
|
||||||
|
let settingsSchema = null;
|
||||||
let flowCapabilityRegistry = null;
|
let flowCapabilityRegistry = null;
|
||||||
|
|
||||||
|
function getFlowRegistry() {
|
||||||
|
if (flowRegistry) {
|
||||||
|
return flowRegistry;
|
||||||
|
}
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
flowRegistry = rootScope.MultiPageFlowRegistry || null;
|
||||||
|
return flowRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSettingsSchema() {
|
||||||
|
if (settingsSchema) {
|
||||||
|
return settingsSchema;
|
||||||
|
}
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
const registry = getFlowRegistry();
|
||||||
|
settingsSchema = rootScope.MultiPageSettingsSchema?.createSettingsSchema?.({
|
||||||
|
defaultFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
flowRegistry: registry || undefined,
|
||||||
|
}) || null;
|
||||||
|
return settingsSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFlowId(value = '', fallback = DEFAULT_ACTIVE_FLOW_ID) {
|
||||||
|
const registry = getFlowRegistry();
|
||||||
|
if (registry?.normalizeFlowId) {
|
||||||
|
return registry.normalizeFlowId(value, fallback);
|
||||||
|
}
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === 'codex') {
|
||||||
|
return DEFAULT_ACTIVE_FLOW_ID;
|
||||||
|
}
|
||||||
|
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||||
|
return normalized || fallbackValue || DEFAULT_ACTIVE_FLOW_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultSourceIdForFlow(flowId = DEFAULT_ACTIVE_FLOW_ID) {
|
||||||
|
const registry = getFlowRegistry();
|
||||||
|
if (registry?.getDefaultSourceId) {
|
||||||
|
return registry.getDefaultSourceId(normalizeFlowId(flowId));
|
||||||
|
}
|
||||||
|
return normalizeFlowId(flowId) === 'kiro' ? 'kiro-rs' : 'cpa';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSourceIdForFlow(flowId = DEFAULT_ACTIVE_FLOW_ID, sourceId = '', fallback = '') {
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId);
|
||||||
|
const registry = getFlowRegistry();
|
||||||
|
const fallbackSourceId = fallback || getDefaultSourceIdForFlow(normalizedFlowId);
|
||||||
|
if (registry?.normalizeSourceId) {
|
||||||
|
return registry.normalizeSourceId(normalizedFlowId, sourceId, fallbackSourceId);
|
||||||
|
}
|
||||||
|
if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) {
|
||||||
|
return normalizePanelMode(sourceId || fallbackSourceId);
|
||||||
|
}
|
||||||
|
const normalized = String(sourceId || '').trim().toLowerCase();
|
||||||
|
return normalized || String(fallbackSourceId || '').trim().toLowerCase() || 'kiro-rs';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedFlowId(state = latestState) {
|
||||||
|
const selectedValue = typeof selectFlow !== 'undefined' && selectFlow
|
||||||
|
? selectFlow.value
|
||||||
|
: '';
|
||||||
|
return normalizeFlowId(
|
||||||
|
selectedValue || state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedSourceIdForState(state = latestState, flowId = getSelectedFlowId(state)) {
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId);
|
||||||
|
const schema = getSettingsSchema();
|
||||||
|
if (schema?.getSelectedSourceId) {
|
||||||
|
return schema.getSelectedSourceId(state || {}, normalizedFlowId);
|
||||||
|
}
|
||||||
|
if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) {
|
||||||
|
return normalizePanelMode(state?.panelMode || getDefaultSourceIdForFlow(normalizedFlowId));
|
||||||
|
}
|
||||||
|
return normalizeSourceIdForFlow(
|
||||||
|
normalizedFlowId,
|
||||||
|
state?.kiroSourceId || '',
|
||||||
|
getDefaultSourceIdForFlow(normalizedFlowId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedSourceId(flowId = getSelectedFlowId()) {
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId);
|
||||||
|
const selectedValue = typeof selectPanelMode !== 'undefined' && selectPanelMode
|
||||||
|
? selectPanelMode.value
|
||||||
|
: '';
|
||||||
|
if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) {
|
||||||
|
return normalizePanelMode(
|
||||||
|
selectedValue || latestState?.panelMode || getDefaultSourceIdForFlow(normalizedFlowId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return normalizeSourceIdForFlow(
|
||||||
|
normalizedFlowId,
|
||||||
|
selectedValue || latestState?.kiroSourceId || '',
|
||||||
|
getDefaultSourceIdForFlow(normalizedFlowId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFlowSelectorOptions(selectedFlowId = getSelectedFlowId()) {
|
||||||
|
if (!selectFlow) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const registry = getFlowRegistry();
|
||||||
|
const flowIds = Array.isArray(registry?.getRegisteredFlowIds?.())
|
||||||
|
? registry.getRegisteredFlowIds()
|
||||||
|
: [DEFAULT_ACTIVE_FLOW_ID];
|
||||||
|
const normalizedSelectedFlowId = normalizeFlowId(selectedFlowId);
|
||||||
|
selectFlow.innerHTML = '';
|
||||||
|
flowIds.forEach((flowId) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = flowId;
|
||||||
|
option.textContent = registry?.getFlowLabel?.(flowId) || flowId;
|
||||||
|
selectFlow.appendChild(option);
|
||||||
|
});
|
||||||
|
selectFlow.value = normalizedSelectedFlowId;
|
||||||
|
return flowIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSourceSelectorOptions(flowId = getSelectedFlowId(), selectedSourceId = '') {
|
||||||
|
if (!selectPanelMode) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const registry = getFlowRegistry();
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId);
|
||||||
|
const sourceOptions = Array.isArray(registry?.getSourceOptions?.(normalizedFlowId))
|
||||||
|
? registry.getSourceOptions(normalizedFlowId)
|
||||||
|
: [];
|
||||||
|
const normalizedSourceId = normalizeSourceIdForFlow(
|
||||||
|
normalizedFlowId,
|
||||||
|
selectedSourceId,
|
||||||
|
getDefaultSourceIdForFlow(normalizedFlowId)
|
||||||
|
);
|
||||||
|
selectPanelMode.innerHTML = '';
|
||||||
|
sourceOptions.forEach((sourceOption) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = sourceOption.id;
|
||||||
|
option.textContent = sourceOption.label || sourceOption.id;
|
||||||
|
selectPanelMode.appendChild(option);
|
||||||
|
});
|
||||||
|
if (labelSourceSelector) {
|
||||||
|
labelSourceSelector.textContent = '来源';
|
||||||
|
}
|
||||||
|
selectPanelMode.disabled = sourceOptions.length <= 1;
|
||||||
|
if (sourceOptions.length > 0) {
|
||||||
|
selectPanelMode.value = normalizedSourceId;
|
||||||
|
}
|
||||||
|
return sourceOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectVisibleSettingsTargets(visibleGroupIds = []) {
|
||||||
|
const registry = getFlowRegistry();
|
||||||
|
const visibleGroupIdSet = new Set(
|
||||||
|
Array.isArray(visibleGroupIds)
|
||||||
|
? visibleGroupIds.map((groupId) => String(groupId || '').trim()).filter(Boolean)
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
const groupDefinitions = registry?.getSettingsGroupDefinitions?.() || {};
|
||||||
|
const rowIds = new Set();
|
||||||
|
const sectionIds = new Set();
|
||||||
|
Object.entries(groupDefinitions).forEach(([groupId, definition]) => {
|
||||||
|
if (!visibleGroupIdSet.has(groupId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(definition?.rowIds || []).forEach((rowId) => rowIds.add(rowId));
|
||||||
|
(definition?.sectionIds || []).forEach((sectionId) => sectionIds.add(sectionId));
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
rowIds: Array.from(rowIds),
|
||||||
|
sectionIds: Array.from(sectionIds),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFlowSettingsGroupVisibility(visibleGroupIds = []) {
|
||||||
|
const registry = getFlowRegistry();
|
||||||
|
const groupDefinitions = registry?.getSettingsGroupDefinitions?.() || {};
|
||||||
|
const { rowIds: visibleRowIds, sectionIds: visibleSectionIds } = collectVisibleSettingsTargets(visibleGroupIds);
|
||||||
|
const visibleRowIdSet = new Set(visibleRowIds);
|
||||||
|
const visibleSectionIdSet = new Set(visibleSectionIds);
|
||||||
|
const allRowIds = new Set();
|
||||||
|
const allSectionIds = new Set();
|
||||||
|
Object.values(groupDefinitions).forEach((definition) => {
|
||||||
|
(definition?.rowIds || []).forEach((rowId) => allRowIds.add(rowId));
|
||||||
|
(definition?.sectionIds || []).forEach((sectionId) => allSectionIds.add(sectionId));
|
||||||
|
});
|
||||||
|
allRowIds.forEach((rowId) => {
|
||||||
|
const element = document.getElementById(rowId);
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.style.display = visibleRowIdSet.has(rowId) ? '' : 'none';
|
||||||
|
});
|
||||||
|
allSectionIds.forEach((sectionId) => {
|
||||||
|
const element = document.getElementById(sectionId);
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.style.display = visibleSectionIdSet.has(sectionId) ? '' : 'none';
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
rowIds: visibleRowIds,
|
||||||
|
sectionIds: visibleSectionIds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncFlowSelectorsFromState(state = latestState) {
|
||||||
|
const activeFlowId = normalizeFlowId(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID);
|
||||||
|
renderFlowSelectorOptions(activeFlowId);
|
||||||
|
const sourceId = getSelectedSourceIdForState(state, activeFlowId);
|
||||||
|
renderSourceSelectorOptions(activeFlowId, sourceId);
|
||||||
|
return {
|
||||||
|
activeFlowId,
|
||||||
|
sourceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getFlowCapabilityRegistry() {
|
function getFlowCapabilityRegistry() {
|
||||||
if (flowCapabilityRegistry) {
|
if (flowCapabilityRegistry) {
|
||||||
return flowCapabilityRegistry;
|
return flowCapabilityRegistry;
|
||||||
@@ -8017,13 +8339,37 @@ function resolveCurrentSidepanelCapabilities(options = {}) {
|
|||||||
if (!registry?.resolveSidepanelCapabilities) {
|
if (!registry?.resolveSidepanelCapabilities) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const activeFlowId = normalizeFlowId(
|
||||||
|
options?.activeFlowId
|
||||||
|
?? options?.state?.activeFlowId
|
||||||
|
?? latestState?.activeFlowId
|
||||||
|
?? latestState?.flowId
|
||||||
|
?? DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
);
|
||||||
const state = {
|
const state = {
|
||||||
...(latestState || {}),
|
...(latestState || {}),
|
||||||
...(options?.state || {}),
|
...(options?.state || {}),
|
||||||
|
activeFlowId,
|
||||||
};
|
};
|
||||||
|
const sourceId = options?.sourceId !== undefined
|
||||||
|
? options.sourceId
|
||||||
|
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
? (options?.panelMode ?? state?.panelMode)
|
||||||
|
: (options?.kiroSourceId ?? state?.kiroSourceId));
|
||||||
|
if (activeFlowId === DEFAULT_ACTIVE_FLOW_ID) {
|
||||||
|
state.panelMode = normalizePanelMode(
|
||||||
|
sourceId || state?.panelMode || getDefaultSourceIdForFlow(activeFlowId)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state.kiroSourceId = normalizeSourceIdForFlow(
|
||||||
|
activeFlowId,
|
||||||
|
sourceId || state?.kiroSourceId || '',
|
||||||
|
getDefaultSourceIdForFlow(activeFlowId)
|
||||||
|
);
|
||||||
|
}
|
||||||
return registry.resolveSidepanelCapabilities({
|
return registry.resolveSidepanelCapabilities({
|
||||||
activeFlowId: options?.activeFlowId ?? state?.activeFlowId,
|
activeFlowId,
|
||||||
panelMode: options?.panelMode ?? state?.panelMode,
|
panelMode: state?.panelMode,
|
||||||
signupMethod: options?.signupMethod ?? state?.signupMethod,
|
signupMethod: options?.signupMethod ?? state?.signupMethod,
|
||||||
state,
|
state,
|
||||||
});
|
});
|
||||||
@@ -8051,10 +8397,11 @@ function resolveStepDefinitionCapabilityState(state = latestState, options = {})
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedPanelMode() {
|
function getSelectedPanelMode() {
|
||||||
const selectedValue = typeof selectPanelMode !== 'undefined' && selectPanelMode
|
const resolvedPanelMode = normalizePanelMode(
|
||||||
|
typeof selectPanelMode !== 'undefined' && selectPanelMode
|
||||||
? selectPanelMode.value
|
? selectPanelMode.value
|
||||||
: (typeof latestState !== 'undefined' ? latestState?.panelMode : '');
|
: (typeof latestState !== 'undefined' ? latestState?.panelMode : '')
|
||||||
const resolvedPanelMode = normalizePanelMode(selectedValue || 'cpa');
|
);
|
||||||
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
? resolveCurrentSidepanelCapabilities({ panelMode: resolvedPanelMode })
|
? resolveCurrentSidepanelCapabilities({ panelMode: resolvedPanelMode })
|
||||||
: null;
|
: null;
|
||||||
@@ -8082,7 +8429,9 @@ function canSelectPhoneSignupMethod() {
|
|||||||
const plusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
const plusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||||
? Boolean(inputPlusModeEnabled.checked)
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
: Boolean(latestState?.plusModeEnabled);
|
: Boolean(latestState?.plusModeEnabled);
|
||||||
const contributionModeEnabled = Boolean(latestState?.contributionMode);
|
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||||
|
? isContributionModeActiveForFlow(latestState)
|
||||||
|
: Boolean(latestState?.contributionMode);
|
||||||
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
? resolveCurrentSidepanelCapabilities({
|
? resolveCurrentSidepanelCapabilities({
|
||||||
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode,
|
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode,
|
||||||
@@ -8136,6 +8485,9 @@ function updateSignupMethodUI(options = {}) {
|
|||||||
|
|
||||||
let selectedMethod = normalizeSignupMethod(getSelectedSignupMethod());
|
let selectedMethod = normalizeSignupMethod(getSelectedSignupMethod());
|
||||||
const phoneSelectable = canSelectPhoneSignupMethod();
|
const phoneSelectable = canSelectPhoneSignupMethod();
|
||||||
|
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||||
|
? isContributionModeActiveForFlow(latestState)
|
||||||
|
: Boolean(latestState?.contributionMode);
|
||||||
if (!phoneSelectable && selectedMethod === SIGNUP_METHOD_PHONE) {
|
if (!phoneSelectable && selectedMethod === SIGNUP_METHOD_PHONE) {
|
||||||
selectedMethod = setSignupMethod(SIGNUP_METHOD_EMAIL);
|
selectedMethod = setSignupMethod(SIGNUP_METHOD_EMAIL);
|
||||||
if (options.notify && typeof showToast === 'function') {
|
if (options.notify && typeof showToast === 'function') {
|
||||||
@@ -8156,7 +8508,7 @@ function updateSignupMethodUI(options = {}) {
|
|||||||
button.title = '开启接码后可选择手机号注册';
|
button.title = '开启接码后可选择手机号注册';
|
||||||
} else if (typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled?.checked) {
|
} else if (typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled?.checked) {
|
||||||
button.title = 'Plus 模式第一版暂不支持手机号注册';
|
button.title = 'Plus 模式第一版暂不支持手机号注册';
|
||||||
} else if (latestState?.contributionMode) {
|
} else if (contributionModeEnabled) {
|
||||||
button.title = '贡献模式第一版暂不支持手机号注册';
|
button.title = '贡献模式第一版暂不支持手机号注册';
|
||||||
} else if (locked) {
|
} else if (locked) {
|
||||||
button.title = '自动流程运行中不能切换注册方式';
|
button.title = '自动流程运行中不能切换注册方式';
|
||||||
@@ -9330,6 +9682,9 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr
|
|||||||
|| (typeof latestState !== 'undefined' ? latestState?.activeFlowId : '')
|
|| (typeof latestState !== 'undefined' ? latestState?.activeFlowId : '')
|
||||||
|| defaultFlowId
|
|| defaultFlowId
|
||||||
).trim().toLowerCase() || defaultFlowId;
|
).trim().toLowerCase() || defaultFlowId;
|
||||||
|
const currentFlowId = typeof currentStepDefinitionFlowId !== 'undefined'
|
||||||
|
? currentStepDefinitionFlowId
|
||||||
|
: defaultFlowId;
|
||||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
const currentPaymentStep = stepDefinitions.find((step) => step.key === 'paypal-approve');
|
const currentPaymentStep = stepDefinitions.find((step) => step.key === 'paypal-approve');
|
||||||
const nextPaymentTitle = rootScope.MultiPageStepDefinitions?.getPlusPaymentStepTitle?.({
|
const nextPaymentTitle = rootScope.MultiPageStepDefinitions?.getPlusPaymentStepTitle?.({
|
||||||
@@ -9345,6 +9700,7 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr
|
|||||||
|| nextPaymentMethod !== currentPlusPaymentMethod
|
|| nextPaymentMethod !== currentPlusPaymentMethod
|
||||||
|| nextSignupMethod !== currentSignupMethod
|
|| nextSignupMethod !== currentSignupMethod
|
||||||
|| nextPhoneSignupReloginAfterBindEmailEnabled !== currentPhoneSignupReloginAfterBindEmailEnabled
|
|| nextPhoneSignupReloginAfterBindEmailEnabled !== currentPhoneSignupReloginAfterBindEmailEnabled
|
||||||
|
|| nextActiveFlowId !== currentFlowId
|
||||||
|| paymentTitleChanged;
|
|| paymentTitleChanged;
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
return;
|
return;
|
||||||
@@ -9359,6 +9715,31 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr
|
|||||||
renderStepsList();
|
renderStepsList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncStepDefinitionsFromUiState(stateOverrides = {}) {
|
||||||
|
const nextState = {
|
||||||
|
...(latestState || {}),
|
||||||
|
...(stateOverrides || {}),
|
||||||
|
};
|
||||||
|
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
||||||
|
? resolveStepDefinitionCapabilityState(nextState, {
|
||||||
|
activeFlowId: nextState?.activeFlowId,
|
||||||
|
panelMode: nextState?.panelMode,
|
||||||
|
signupMethod: nextState?.signupMethod,
|
||||||
|
state: nextState,
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
plusModeEnabled: Boolean(nextState?.plusModeEnabled),
|
||||||
|
signupMethod: normalizeSignupMethod(nextState?.signupMethod || DEFAULT_SIGNUP_METHOD),
|
||||||
|
};
|
||||||
|
syncStepDefinitionsForMode(stepDefinitionState.plusModeEnabled, {
|
||||||
|
activeFlowId: nextState?.activeFlowId || nextState?.flowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
plusPaymentMethod: getSelectedPlusPaymentMethod(nextState),
|
||||||
|
signupMethod: stepDefinitionState.signupMethod,
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: Boolean(nextState?.phoneSignupReloginAfterBindEmailEnabled),
|
||||||
|
});
|
||||||
|
return stepDefinitionState;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// State Restore on load
|
// State Restore on load
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -9427,6 +9808,12 @@ function applySettingsState(state) {
|
|||||||
return Math.max(1, Math.min(1440, numeric));
|
return Math.max(1, Math.min(1440, numeric));
|
||||||
};
|
};
|
||||||
syncLatestState(state);
|
syncLatestState(state);
|
||||||
|
const appliedFlowSelection = typeof syncFlowSelectorsFromState === 'function'
|
||||||
|
? syncFlowSelectorsFromState(state)
|
||||||
|
: {
|
||||||
|
activeFlowId: String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
sourceId: String(state?.panelMode || 'cpa').trim().toLowerCase() || 'cpa',
|
||||||
|
};
|
||||||
if (typeof applyOperationDelayState === 'function') {
|
if (typeof applyOperationDelayState === 'function') {
|
||||||
applyOperationDelayState(state);
|
applyOperationDelayState(state);
|
||||||
}
|
}
|
||||||
@@ -9522,7 +9909,6 @@ function applySettingsState(state) {
|
|||||||
inputVpsUrl.value = state?.vpsUrl || '';
|
inputVpsUrl.value = state?.vpsUrl || '';
|
||||||
inputVpsPassword.value = state?.vpsPassword || '';
|
inputVpsPassword.value = state?.vpsPassword || '';
|
||||||
setLocalCpaStep9Mode(state?.localCpaStep9Mode);
|
setLocalCpaStep9Mode(state?.localCpaStep9Mode);
|
||||||
selectPanelMode.value = normalizePanelMode(state?.panelMode || 'cpa');
|
|
||||||
inputSub2ApiUrl.value = state?.sub2apiUrl || '';
|
inputSub2ApiUrl.value = state?.sub2apiUrl || '';
|
||||||
inputSub2ApiEmail.value = state?.sub2apiEmail || '';
|
inputSub2ApiEmail.value = state?.sub2apiEmail || '';
|
||||||
inputSub2ApiPassword.value = state?.sub2apiPassword || '';
|
inputSub2ApiPassword.value = state?.sub2apiPassword || '';
|
||||||
@@ -9531,6 +9917,47 @@ function applySettingsState(state) {
|
|||||||
inputSub2ApiAccountPriority.value = String(normalizeSub2ApiAccountPriorityValue(state?.sub2apiAccountPriority));
|
inputSub2ApiAccountPriority.value = String(normalizeSub2ApiAccountPriorityValue(state?.sub2apiAccountPriority));
|
||||||
}
|
}
|
||||||
inputSub2ApiDefaultProxy.value = state?.sub2apiDefaultProxyName || '';
|
inputSub2ApiDefaultProxy.value = state?.sub2apiDefaultProxyName || '';
|
||||||
|
if (typeof inputKiroRsUrl !== 'undefined' && inputKiroRsUrl) {
|
||||||
|
inputKiroRsUrl.value = String(
|
||||||
|
state?.kiroRsUrl
|
||||||
|
|| getFlowRegistry()?.DEFAULT_KIRO_RS_URL
|
||||||
|
|| 'https://kiro.leftcode.xyz/admin'
|
||||||
|
).trim();
|
||||||
|
}
|
||||||
|
if (typeof inputKiroRsKey !== 'undefined' && inputKiroRsKey) {
|
||||||
|
inputKiroRsKey.value = String(state?.kiroRsKey || '');
|
||||||
|
}
|
||||||
|
if (typeof inputKiroRegion !== 'undefined' && inputKiroRegion) {
|
||||||
|
inputKiroRegion.value = String(
|
||||||
|
state?.kiroRegion
|
||||||
|
|| getFlowRegistry()?.DEFAULT_KIRO_REGION
|
||||||
|
|| 'us-east-1'
|
||||||
|
).trim();
|
||||||
|
}
|
||||||
|
if (typeof displayKiroDeviceCode !== 'undefined' && displayKiroDeviceCode) {
|
||||||
|
const kiroDeviceCode = String(
|
||||||
|
state?.flows?.kiro?.auth?.deviceCode
|
||||||
|
|| state?.kiroDeviceCode
|
||||||
|
|| ''
|
||||||
|
).trim();
|
||||||
|
displayKiroDeviceCode.textContent = kiroDeviceCode || '未生成';
|
||||||
|
}
|
||||||
|
if (typeof displayKiroLoginUrl !== 'undefined' && displayKiroLoginUrl) {
|
||||||
|
const kiroLoginUrl = String(
|
||||||
|
state?.flows?.kiro?.auth?.loginUrl
|
||||||
|
|| state?.kiroLoginUrl
|
||||||
|
|| ''
|
||||||
|
).trim();
|
||||||
|
displayKiroLoginUrl.textContent = kiroLoginUrl || '未生成';
|
||||||
|
}
|
||||||
|
if (typeof displayKiroUploadStatus !== 'undefined' && displayKiroUploadStatus) {
|
||||||
|
const kiroUploadStatus = String(
|
||||||
|
state?.flows?.kiro?.upload?.status
|
||||||
|
|| state?.kiroUploadStatus
|
||||||
|
|| ''
|
||||||
|
).trim();
|
||||||
|
displayKiroUploadStatus.textContent = kiroUploadStatus || '未开始';
|
||||||
|
}
|
||||||
const normalizedIpProxyService = resolveIpProxyService(state?.ipProxyService);
|
const normalizedIpProxyService = resolveIpProxyService(state?.ipProxyService);
|
||||||
const normalizedIpProxyServiceProfiles = typeof normalizeIpProxyServiceProfiles === 'function'
|
const normalizedIpProxyServiceProfiles = typeof normalizeIpProxyServiceProfiles === 'function'
|
||||||
? normalizeIpProxyServiceProfiles(state?.ipProxyServiceProfiles || {}, state || {})
|
? normalizeIpProxyServiceProfiles(state?.ipProxyServiceProfiles || {}, state || {})
|
||||||
@@ -9616,6 +10043,12 @@ function applySettingsState(state) {
|
|||||||
if (typeof updateIpProxyUI === 'function') {
|
if (typeof updateIpProxyUI === 'function') {
|
||||||
updateIpProxyUI(latestState);
|
updateIpProxyUI(latestState);
|
||||||
}
|
}
|
||||||
|
if (selectFlow) {
|
||||||
|
selectFlow.value = appliedFlowSelection.activeFlowId;
|
||||||
|
}
|
||||||
|
if (selectPanelMode && appliedFlowSelection.sourceId) {
|
||||||
|
selectPanelMode.value = appliedFlowSelection.sourceId;
|
||||||
|
}
|
||||||
inputCodex2ApiUrl.value = state?.codex2apiUrl || '';
|
inputCodex2ApiUrl.value = state?.codex2apiUrl || '';
|
||||||
inputCodex2ApiAdminKey.value = state?.codex2apiAdminKey || '';
|
inputCodex2ApiAdminKey.value = state?.codex2apiAdminKey || '';
|
||||||
const yydsMailProvider = typeof YYDS_MAIL_PROVIDER === 'string'
|
const yydsMailProvider = typeof YYDS_MAIL_PROVIDER === 'string'
|
||||||
@@ -10348,7 +10781,9 @@ function shouldShowContributionUpdateHint(snapshot = currentContributionContentS
|
|||||||
if (promptVersion === getDismissedContributionContentPromptVersion()) {
|
if (promptVersion === getDismissedContributionContentPromptVersion()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (latestState?.contributionMode) {
|
if (typeof isContributionModeActiveForFlow === 'function'
|
||||||
|
? isContributionModeActiveForFlow(latestState)
|
||||||
|
: Boolean(latestState?.contributionMode)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !btnContributionMode.disabled;
|
return !btnContributionMode.disabled;
|
||||||
@@ -10411,7 +10846,10 @@ async function refreshContributionContentHint() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function syncPasswordField(state) {
|
function syncPasswordField(state) {
|
||||||
inputPassword.value = state?.contributionMode ? '' : (state.customPassword || state.password || '');
|
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||||
|
? isContributionModeActiveForFlow(state)
|
||||||
|
: Boolean(state?.contributionMode);
|
||||||
|
inputPassword.value = contributionModeEnabled ? '' : (state.customPassword || state.password || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCustomMailProvider(provider = selectMailProvider.value) {
|
function isCustomMailProvider(provider = selectMailProvider.value) {
|
||||||
@@ -11559,60 +11997,52 @@ async function handleDeleteSub2ApiGroup(groupName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updatePanelModeUI() {
|
function updatePanelModeUI() {
|
||||||
const rawPanelMode = normalizePanelMode(selectPanelMode?.value || latestState?.panelMode || 'cpa');
|
const activeFlowId = typeof getSelectedFlowId === 'function'
|
||||||
|
? getSelectedFlowId(latestState)
|
||||||
|
: normalizeFlowId(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID);
|
||||||
|
const sourceId = typeof getSelectedSourceId === 'function'
|
||||||
|
? getSelectedSourceId(activeFlowId)
|
||||||
|
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
? normalizePanelMode(selectPanelMode?.value || latestState?.panelMode || 'cpa')
|
||||||
|
: String(selectPanelMode?.value || latestState?.kiroSourceId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs');
|
||||||
|
const rawPanelMode = activeFlowId === DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
? normalizePanelMode(sourceId || latestState?.panelMode || 'cpa')
|
||||||
|
: normalizePanelMode(latestState?.panelMode || 'cpa');
|
||||||
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
? resolveCurrentSidepanelCapabilities({
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
activeFlowId,
|
||||||
|
sourceId,
|
||||||
panelMode: rawPanelMode,
|
panelMode: rawPanelMode,
|
||||||
state: {
|
state: {
|
||||||
...(latestState || {}),
|
...(latestState || {}),
|
||||||
panelMode: rawPanelMode,
|
activeFlowId,
|
||||||
|
...(activeFlowId === DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
? { panelMode: rawPanelMode }
|
||||||
|
: { kiroSourceId: sourceId }),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
const supportedPanelModes = Array.isArray(capabilityState?.supportedPanelModes)
|
const effectiveSourceId = capabilityState?.effectiveSourceId || sourceId;
|
||||||
? capabilityState.supportedPanelModes
|
renderFlowSelectorOptions(activeFlowId);
|
||||||
|
renderSourceSelectorOptions(activeFlowId, effectiveSourceId);
|
||||||
|
const visibleGroupIds = Array.isArray(capabilityState?.visibleGroupIds)
|
||||||
|
? capabilityState.visibleGroupIds
|
||||||
: [];
|
: [];
|
||||||
if (selectPanelMode?.options && supportedPanelModes.length) {
|
if (typeof applyFlowSettingsGroupVisibility === 'function') {
|
||||||
Array.from(selectPanelMode.options).forEach((option) => {
|
applyFlowSettingsGroupVisibility(visibleGroupIds);
|
||||||
if (!option) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const optionMode = normalizePanelMode(option.value || '');
|
const panelMode = capabilityState?.effectivePanelMode || capabilityState?.panelMode || rawPanelMode;
|
||||||
const enabled = supportedPanelModes.includes(optionMode);
|
if (selectFlow) {
|
||||||
option.disabled = !enabled;
|
selectFlow.value = activeFlowId;
|
||||||
option.hidden = !enabled;
|
|
||||||
});
|
|
||||||
} else if (selectPanelMode?.options) {
|
|
||||||
Array.from(selectPanelMode.options).forEach((option) => {
|
|
||||||
if (!option) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
option.disabled = false;
|
|
||||||
option.hidden = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const panelMode = capabilityState?.effectivePanelMode || capabilityState?.panelMode || getSelectedPanelMode();
|
|
||||||
if (selectPanelMode) {
|
if (selectPanelMode) {
|
||||||
selectPanelMode.value = panelMode;
|
selectPanelMode.value = effectiveSourceId;
|
||||||
}
|
}
|
||||||
const useSub2Api = panelMode === 'sub2api';
|
|
||||||
const useCodex2Api = panelMode === 'codex2api';
|
|
||||||
const useCpa = !useSub2Api && !useCodex2Api;
|
|
||||||
rowVpsUrl.style.display = useCpa ? '' : 'none';
|
|
||||||
rowVpsPassword.style.display = useCpa ? '' : 'none';
|
|
||||||
rowLocalCpaStep9Mode.style.display = useCpa ? '' : 'none';
|
|
||||||
rowSub2ApiUrl.style.display = useSub2Api ? '' : 'none';
|
|
||||||
rowSub2ApiEmail.style.display = useSub2Api ? '' : 'none';
|
|
||||||
rowSub2ApiPassword.style.display = useSub2Api ? '' : 'none';
|
|
||||||
rowSub2ApiGroup.style.display = useSub2Api ? '' : 'none';
|
|
||||||
rowSub2ApiAccountPriority.style.display = useSub2Api ? '' : 'none';
|
|
||||||
rowSub2ApiDefaultProxy.style.display = useSub2Api ? '' : 'none';
|
|
||||||
rowCodex2ApiUrl.style.display = useCodex2Api ? '' : 'none';
|
|
||||||
rowCodex2ApiAdminKey.style.display = useCodex2Api ? '' : 'none';
|
|
||||||
|
|
||||||
|
const useCodex2Api = panelMode === 'codex2api';
|
||||||
const step9Btn = document.querySelector('.step-btn[data-step-key="platform-verify"]');
|
const step9Btn = document.querySelector('.step-btn[data-step-key="platform-verify"]');
|
||||||
if (step9Btn) {
|
if (step9Btn && activeFlowId === DEFAULT_ACTIVE_FLOW_ID) {
|
||||||
step9Btn.textContent = useSub2Api
|
step9Btn.textContent = panelMode === 'sub2api'
|
||||||
? 'SUB2API 回调验证'
|
? 'SUB2API 回调验证'
|
||||||
: (useCodex2Api ? 'Codex2API 回调验证' : 'CPA 回调验证');
|
: (useCodex2Api ? 'Codex2API 回调验证' : 'CPA 回调验证');
|
||||||
}
|
}
|
||||||
@@ -13563,26 +13993,76 @@ checkboxAutoDeleteIcloud?.addEventListener('change', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
selectPanelMode.addEventListener('change', async () => {
|
selectPanelMode.addEventListener('change', async () => {
|
||||||
const previousPanelMode = normalizePanelMode(latestState?.panelMode || 'cpa');
|
const activeFlowId = typeof getSelectedFlowId === 'function'
|
||||||
const rawNextPanelMode = normalizePanelMode(selectPanelMode.value);
|
? getSelectedFlowId(latestState)
|
||||||
selectPanelMode.value = rawNextPanelMode;
|
: normalizeFlowId(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID);
|
||||||
const nextPanelMode = getSelectedPanelMode();
|
const defaultSourceId = typeof getDefaultSourceIdForFlow === 'function'
|
||||||
|
? getDefaultSourceIdForFlow(activeFlowId)
|
||||||
|
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID ? 'cpa' : 'kiro-rs');
|
||||||
|
const previousSourceId = typeof getSelectedSourceIdForState === 'function'
|
||||||
|
? getSelectedSourceIdForState(latestState, activeFlowId)
|
||||||
|
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
? normalizePanelMode(latestState?.panelMode || defaultSourceId)
|
||||||
|
: String(latestState?.kiroSourceId || defaultSourceId).trim().toLowerCase() || defaultSourceId);
|
||||||
|
let nextSourceId = typeof normalizeSourceIdForFlow === 'function'
|
||||||
|
? normalizeSourceIdForFlow(activeFlowId, selectPanelMode.value, defaultSourceId)
|
||||||
|
: (activeFlowId === DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
? normalizePanelMode(selectPanelMode.value)
|
||||||
|
: String(selectPanelMode.value || defaultSourceId).trim().toLowerCase() || defaultSourceId);
|
||||||
|
if (activeFlowId === DEFAULT_ACTIVE_FLOW_ID) {
|
||||||
|
const nextPanelMode = normalizePanelMode(nextSourceId);
|
||||||
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 = previousPanelMode;
|
selectPanelMode.value = previousSourceId;
|
||||||
updatePanelModeUI();
|
updatePanelModeUI();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
syncLatestState({ panelMode: nextPanelMode });
|
nextSourceId = nextPanelMode;
|
||||||
|
syncLatestState({
|
||||||
|
activeFlowId,
|
||||||
|
flowId: activeFlowId,
|
||||||
|
panelMode: nextPanelMode,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
syncLatestState({
|
||||||
|
activeFlowId,
|
||||||
|
flowId: activeFlowId,
|
||||||
|
kiroSourceId: nextSourceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
updatePanelModeUI();
|
updatePanelModeUI();
|
||||||
|
if (typeof syncStepDefinitionsFromUiState === 'function') {
|
||||||
|
syncStepDefinitionsFromUiState({
|
||||||
|
plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||||
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
|
: Boolean(latestState?.plusModeEnabled),
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: typeof inputPhoneSignupReloginAfterBindEmail !== 'undefined' && inputPhoneSignupReloginAfterBindEmail
|
||||||
|
? Boolean(inputPhoneSignupReloginAfterBindEmail.checked)
|
||||||
|
: Boolean(latestState?.phoneSignupReloginAfterBindEmailEnabled),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
applyStepExecutionRangeState(latestState);
|
||||||
|
renderStepStatuses(latestState);
|
||||||
|
updateButtonStates();
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[inputKiroRsUrl, inputKiroRsKey, inputKiroRegion].forEach((input) => {
|
||||||
|
input?.addEventListener('input', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
scheduleSettingsAutoSave();
|
||||||
|
});
|
||||||
|
input?.addEventListener('blur', () => {
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function syncCurrentIpProxyServiceProfileToLatestState() {
|
function syncCurrentIpProxyServiceProfileToLatestState() {
|
||||||
const selectedService = normalizeIpProxyService(
|
const selectedService = normalizeIpProxyService(
|
||||||
selectIpProxyService?.value || latestState?.ipProxyService || DEFAULT_IP_PROXY_SERVICE
|
selectIpProxyService?.value || latestState?.ipProxyService || DEFAULT_IP_PROXY_SERVICE
|
||||||
@@ -14347,9 +14827,50 @@ inputStepExecutionRangeEnabled?.addEventListener('change', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
selectFlow?.addEventListener('change', () => {
|
selectFlow?.addEventListener('change', () => {
|
||||||
|
const nextActiveFlowId = typeof normalizeFlowId === 'function'
|
||||||
|
? normalizeFlowId(selectFlow.value, latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID)
|
||||||
|
: (String(selectFlow.value || latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID);
|
||||||
|
const nextStateBase = {
|
||||||
|
...(latestState || {}),
|
||||||
|
activeFlowId: nextActiveFlowId,
|
||||||
|
flowId: nextActiveFlowId,
|
||||||
|
};
|
||||||
|
const defaultSourceId = typeof getDefaultSourceIdForFlow === 'function'
|
||||||
|
? getDefaultSourceIdForFlow(nextActiveFlowId)
|
||||||
|
: (nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID ? 'cpa' : 'kiro-rs');
|
||||||
|
const nextSourceId = typeof getSelectedSourceIdForState === 'function'
|
||||||
|
? getSelectedSourceIdForState(nextStateBase, nextActiveFlowId)
|
||||||
|
: (nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
? normalizePanelMode(nextStateBase?.panelMode || defaultSourceId)
|
||||||
|
: String(nextStateBase?.kiroSourceId || defaultSourceId).trim().toLowerCase() || defaultSourceId);
|
||||||
|
syncLatestState({
|
||||||
|
activeFlowId: nextActiveFlowId,
|
||||||
|
flowId: nextActiveFlowId,
|
||||||
|
...(nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID
|
||||||
|
? { panelMode: normalizePanelMode(nextSourceId || defaultSourceId) }
|
||||||
|
: {
|
||||||
|
kiroSourceId: typeof normalizeSourceIdForFlow === 'function'
|
||||||
|
? normalizeSourceIdForFlow(nextActiveFlowId, nextSourceId, defaultSourceId)
|
||||||
|
: (String(nextSourceId || defaultSourceId).trim().toLowerCase() || defaultSourceId),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
updatePanelModeUI();
|
||||||
|
if (typeof syncStepDefinitionsFromUiState === 'function') {
|
||||||
|
syncStepDefinitionsFromUiState({
|
||||||
|
plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||||
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
|
: Boolean(latestState?.plusModeEnabled),
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: typeof inputPhoneSignupReloginAfterBindEmail !== 'undefined' && inputPhoneSignupReloginAfterBindEmail
|
||||||
|
? Boolean(inputPhoneSignupReloginAfterBindEmail.checked)
|
||||||
|
: Boolean(latestState?.phoneSignupReloginAfterBindEmailEnabled),
|
||||||
|
});
|
||||||
|
}
|
||||||
applyStepExecutionRangeState(latestState);
|
applyStepExecutionRangeState(latestState);
|
||||||
renderStepStatuses(latestState);
|
renderStepStatuses(latestState);
|
||||||
updateButtonStates();
|
updateButtonStates();
|
||||||
|
markSettingsDirty(true);
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
[inputStepExecutionRangeFrom, inputStepExecutionRangeTo].forEach((input) => {
|
[inputStepExecutionRangeFrom, inputStepExecutionRangeTo].forEach((input) => {
|
||||||
@@ -15249,8 +15770,17 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
if (message.payload.localCpaStep9Mode !== undefined) {
|
if (message.payload.localCpaStep9Mode !== undefined) {
|
||||||
setLocalCpaStep9Mode(message.payload.localCpaStep9Mode);
|
setLocalCpaStep9Mode(message.payload.localCpaStep9Mode);
|
||||||
}
|
}
|
||||||
if (message.payload.panelMode !== undefined) {
|
if (
|
||||||
|
message.payload.panelMode !== undefined
|
||||||
|
|| message.payload.activeFlowId !== undefined
|
||||||
|
|| message.payload.flowId !== undefined
|
||||||
|
|| message.payload.kiroSourceId !== undefined
|
||||||
|
) {
|
||||||
|
if (typeof syncFlowSelectorsFromState === 'function') {
|
||||||
|
syncFlowSelectorsFromState(latestState);
|
||||||
|
} else if (message.payload.panelMode !== undefined) {
|
||||||
selectPanelMode.value = normalizePanelMode(message.payload.panelMode || 'cpa');
|
selectPanelMode.value = normalizePanelMode(message.payload.panelMode || 'cpa');
|
||||||
|
}
|
||||||
updatePanelModeUI();
|
updatePanelModeUI();
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO_SMS;
|
|||||||
const SIGNUP_METHOD_EMAIL = 'email';
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
const SIGNUP_METHOD_PHONE = 'phone';
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
||||||
|
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
@@ -132,6 +133,28 @@ const FIVE_SIM_OPERATOR = 'any';
|
|||||||
const FIVE_SIM_SUPPORTED_COUNTRY_ID_SET = new Set(['indonesia', 'thailand', 'vietnam']);
|
const FIVE_SIM_SUPPORTED_COUNTRY_ID_SET = new Set(['indonesia', 'thailand', 'vietnam']);
|
||||||
const HERO_SMS_SUPPORTED_COUNTRY_ID_SET = new Set(['6', '52', '10']);
|
const HERO_SMS_SUPPORTED_COUNTRY_ID_SET = new Set(['6', '52', '10']);
|
||||||
const self = {
|
const self = {
|
||||||
|
MultiPageFlowRegistry: {
|
||||||
|
DEFAULT_KIRO_REGION: 'us-east-1',
|
||||||
|
DEFAULT_KIRO_RS_URL: 'https://kiro.leftcode.xyz/admin',
|
||||||
|
normalizeFlowId(value, fallback = 'openai') {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === 'kiro') {
|
||||||
|
return 'kiro';
|
||||||
|
}
|
||||||
|
if (normalized === 'codex' || normalized === 'openai') {
|
||||||
|
return 'openai';
|
||||||
|
}
|
||||||
|
return String(fallback || 'openai').trim().toLowerCase() === 'kiro' ? 'kiro' : 'openai';
|
||||||
|
},
|
||||||
|
normalizeSourceId(flowId, sourceId, fallback = 'kiro-rs') {
|
||||||
|
const normalizedFlowId = this.normalizeFlowId(flowId);
|
||||||
|
if (normalizedFlowId !== 'kiro') {
|
||||||
|
return 'cpa';
|
||||||
|
}
|
||||||
|
const normalizedSourceId = String(sourceId || '').trim().toLowerCase();
|
||||||
|
return normalizedSourceId === 'kiro-rs' ? normalizedSourceId : fallback;
|
||||||
|
},
|
||||||
|
},
|
||||||
GoPayUtils: {
|
GoPayUtils: {
|
||||||
normalizeGoPayCountryCode(value) {
|
normalizeGoPayCountryCode(value) {
|
||||||
const digits = String(value || '').replace(/\\D/g, '');
|
const digits = String(value || '').replace(/\\D/g, '');
|
||||||
@@ -252,6 +275,12 @@ return {
|
|||||||
assert.equal(api.normalizePersistentSettingValue('heroSmsPreferredPrice', '0.051234'), '0.0512');
|
assert.equal(api.normalizePersistentSettingValue('heroSmsPreferredPrice', '0.051234'), '0.0512');
|
||||||
assert.equal(api.normalizePersistentSettingValue('signupMethod', 'phone'), 'phone');
|
assert.equal(api.normalizePersistentSettingValue('signupMethod', 'phone'), 'phone');
|
||||||
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', 'kiro'), 'kiro');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('kiroSourceId', 'unknown'), 'kiro-rs');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('kiroRsUrl', ''), 'https://kiro.leftcode.xyz/admin');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('kiroRegion', ''), 'us-east-1');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('kiroRsKey', ' key-1 '), ' key-1 ');
|
||||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', '5SIM'), '5sim');
|
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', '5SIM'), '5sim');
|
||||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'NEXSMS'), 'nexsms');
|
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'NEXSMS'), 'nexsms');
|
||||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'unknown'), 'hero-sms');
|
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'unknown'), 'hero-sms');
|
||||||
|
|||||||
@@ -0,0 +1,328 @@
|
|||||||
|
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), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 registers client, opens auth tab, and completes with runtime payload', async () => {
|
||||||
|
const api = loadKiroDeviceAuthApi();
|
||||||
|
const fetchCalls = [];
|
||||||
|
const stateUpdates = [];
|
||||||
|
const registerCalls = [];
|
||||||
|
const reuseCalls = [];
|
||||||
|
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',
|
||||||
|
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 () => ({
|
||||||
|
kiroRegion: 'eu-west-1',
|
||||||
|
}),
|
||||||
|
registerTab: async (source, tabId) => {
|
||||||
|
registerCalls.push({ source, tabId });
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async (source, url) => {
|
||||||
|
reuseCalls.push({ source, url });
|
||||||
|
return 88;
|
||||||
|
},
|
||||||
|
setState: async (updates) => {
|
||||||
|
stateUpdates.push(updates);
|
||||||
|
},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeKiroStartDeviceLogin({
|
||||||
|
nodeId: 'kiro-start-device-login',
|
||||||
|
kiroRegion: 'eu-west-1',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(fetchCalls.length, 2);
|
||||||
|
assert.equal(fetchCalls[0].url, 'https://oidc.eu-west-1.amazonaws.com/client/register');
|
||||||
|
assert.equal(fetchCalls[1].url, 'https://oidc.eu-west-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,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
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, 'eu-west-1');
|
||||||
|
assert.equal(finalState.kiroAuthIntervalSeconds, 7);
|
||||||
|
assert.equal(finalState.kiroAuthStatus, 'waiting_user');
|
||||||
|
assert.equal(finalState.kiroUploadStatus, 'waiting_login');
|
||||||
|
|
||||||
|
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 await device login polls until refresh token is captured', async () => {
|
||||||
|
const api = loadKiroDeviceAuthApi();
|
||||||
|
const fetchCalls = [];
|
||||||
|
const stateUpdates = [];
|
||||||
|
const sleepCalls = [];
|
||||||
|
const completeCalls = [];
|
||||||
|
|
||||||
|
let pollCount = 0;
|
||||||
|
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',
|
||||||
|
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 () => ({
|
||||||
|
kiroClientId: 'client-001',
|
||||||
|
kiroClientSecret: 'secret-001',
|
||||||
|
kiroDeviceAuthorizationCode: 'device-code-001',
|
||||||
|
kiroAuthRegion: 'us-east-1',
|
||||||
|
kiroAuthExpiresAt: Date.now() + 60000,
|
||||||
|
kiroAuthIntervalSeconds: 5,
|
||||||
|
}),
|
||||||
|
setState: async (updates) => {
|
||||||
|
stateUpdates.push(updates);
|
||||||
|
},
|
||||||
|
sleepWithStop: async (ms) => {
|
||||||
|
sleepCalls.push(ms);
|
||||||
|
},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeKiroAwaitDeviceLogin({
|
||||||
|
nodeId: 'kiro-await-device-login',
|
||||||
|
kiroClientId: 'client-001',
|
||||||
|
kiroClientSecret: 'secret-001',
|
||||||
|
kiroDeviceAuthorizationCode: 'device-code-001',
|
||||||
|
kiroAuthRegion: 'us-east-1',
|
||||||
|
kiroAuthExpiresAt: Date.now() + 60000,
|
||||||
|
kiroAuthIntervalSeconds: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
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-await-device-login');
|
||||||
|
assert.equal(completeCalls[0].payload.kiroRefreshToken, 'refresh-001');
|
||||||
|
});
|
||||||
|
|
||||||
|
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 connection ok (HTTP 200)');
|
||||||
|
assert.equal(finalState.kiroAuthorizedEmail, 'aws-user@example.com');
|
||||||
|
assert.equal(finalState.kiroCredentialId, 321);
|
||||||
|
assert.equal(finalState.kiroUploadStatus, 'uploaded');
|
||||||
|
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, 'uploaded');
|
||||||
|
});
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||||
|
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
|
||||||
|
const backgroundSource = fs.readFileSync('background.js', 'utf8');
|
||||||
|
|
||||||
|
function extractFunction(name) {
|
||||||
|
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||||
|
const start = markers
|
||||||
|
.map((marker) => backgroundSource.indexOf(marker))
|
||||||
|
.find((index) => index >= 0);
|
||||||
|
if (start < 0) {
|
||||||
|
throw new Error(`missing function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parenDepth = 0;
|
||||||
|
let signatureEnded = false;
|
||||||
|
let braceStart = -1;
|
||||||
|
for (let i = start; i < backgroundSource.length; i += 1) {
|
||||||
|
const ch = backgroundSource[i];
|
||||||
|
if (ch === '(') parenDepth += 1;
|
||||||
|
if (ch === ')') {
|
||||||
|
parenDepth -= 1;
|
||||||
|
if (parenDepth === 0) signatureEnded = true;
|
||||||
|
}
|
||||||
|
if (ch === '{' && signatureEnded) {
|
||||||
|
braceStart = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (braceStart < 0) {
|
||||||
|
throw new Error(`missing body for function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let depth = 0;
|
||||||
|
let end = braceStart;
|
||||||
|
for (; end < backgroundSource.length; end += 1) {
|
||||||
|
const ch = backgroundSource[end];
|
||||||
|
if (ch === '{') depth += 1;
|
||||||
|
if (ch === '}') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
end += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return backgroundSource.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHarness(extra = '') {
|
||||||
|
return new Function(`
|
||||||
|
const self = {};
|
||||||
|
${flowRegistrySource}
|
||||||
|
${settingsSchemaSource}
|
||||||
|
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||||
|
const DEFAULT_SUB2API_GROUP_NAMES = ['codex', 'openai-plus'];
|
||||||
|
const PERSISTED_SETTING_DEFAULTS = {
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
panelMode: 'cpa',
|
||||||
|
signupMethod: 'email',
|
||||||
|
plusModeEnabled: false,
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
phoneVerificationEnabled: false,
|
||||||
|
mailProvider: '163',
|
||||||
|
ipProxyEnabled: false,
|
||||||
|
ipProxyService: '711proxy',
|
||||||
|
ipProxyMode: 'account',
|
||||||
|
kiroSourceId: 'kiro-rs',
|
||||||
|
kiroRsUrl: 'https://kiro.leftcode.xyz/admin',
|
||||||
|
kiroRsKey: '',
|
||||||
|
kiroRegion: 'us-east-1',
|
||||||
|
stepExecutionRangeByFlow: {},
|
||||||
|
};
|
||||||
|
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||||
|
const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState'];
|
||||||
|
const LEGACY_AUTO_STEP_DELAY_KEYS = [];
|
||||||
|
const LEGACY_VERIFICATION_RESEND_COUNT_KEYS = [];
|
||||||
|
function normalizePanelMode(value = '') {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa';
|
||||||
|
}
|
||||||
|
function normalizeSignupMethod(value = '') {
|
||||||
|
return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
|
||||||
|
}
|
||||||
|
function normalizePlusPaymentMethod(value = '') {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
return normalized === 'gopay' || normalized === 'gpc-helper' ? normalized : 'paypal';
|
||||||
|
}
|
||||||
|
function normalizeSub2ApiGroupNames(value) {
|
||||||
|
return Array.isArray(value) ? value.map((entry) => String(entry || '').trim()).filter(Boolean) : [];
|
||||||
|
}
|
||||||
|
function normalizeCloudflareDomains(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function normalizeCloudflareTempEmailDomains(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function normalizeCloudMailDomains(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function normalizeMailProvider(value = '') { return String(value || '163').trim().toLowerCase() || '163'; }
|
||||||
|
function normalizeStepExecutionRangeByFlow(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }
|
||||||
|
function normalizeIpProxyProviderValue(value) { return String(value || '711proxy').trim() || '711proxy'; }
|
||||||
|
function normalizeIpProxyMode(value) { return String(value || 'account').trim() || 'account'; }
|
||||||
|
function normalizeIpProxyServiceProfiles(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }
|
||||||
|
function buildIpProxyServiceProfileFromState() {
|
||||||
|
return {
|
||||||
|
mode: 'account',
|
||||||
|
apiUrl: '',
|
||||||
|
accountList: '',
|
||||||
|
accountSessionPrefix: '',
|
||||||
|
accountLifeMinutes: '',
|
||||||
|
poolTargetCount: '20',
|
||||||
|
host: '',
|
||||||
|
port: '',
|
||||||
|
protocol: 'http',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
region: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function normalizeIpProxyAccountList(value) { return String(value || ''); }
|
||||||
|
function normalizeIpProxyAccountSessionPrefix(value) { return String(value || ''); }
|
||||||
|
function normalizeIpProxyAccountLifeMinutes(value) { return String(value || ''); }
|
||||||
|
function normalizeIpProxyPoolTargetCount(value) { return String(value || '20'); }
|
||||||
|
function normalizeIpProxyPort(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeIpProxyProtocol(value) { return String(value || 'http').trim() || 'http'; }
|
||||||
|
function resolveSignupMethod(state = {}) {
|
||||||
|
const activeFlowId = String(state?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
|
||||||
|
if (activeFlowId === 'kiro') {
|
||||||
|
return 'email';
|
||||||
|
}
|
||||||
|
return String(state?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
|
||||||
|
}
|
||||||
|
function resolveLegacyAutoStepDelaySeconds() { return undefined; }
|
||||||
|
${extractFunction('normalizePersistentSettingValue')}
|
||||||
|
${extractFunction('getSettingsSchemaApi')}
|
||||||
|
${extractFunction('buildPersistentSettingsPayload')}
|
||||||
|
${extractFunction('getPersistedSettings')}
|
||||||
|
${extractFunction('setPersistentSettings')}
|
||||||
|
${extra}
|
||||||
|
return {
|
||||||
|
buildPersistentSettingsPayload,
|
||||||
|
getPersistedSettings,
|
||||||
|
setPersistentSettings,
|
||||||
|
getRequestedKeys: typeof getRequestedKeys === 'function' ? getRequestedKeys : () => [],
|
||||||
|
getPersistedWrites: typeof getPersistedWrites === 'function' ? getPersistedWrites : () => [],
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
}
|
||||||
|
|
||||||
|
test('buildPersistentSettingsPayload writes canonical settings schema into persisted payloads when defaults are materialized', () => {
|
||||||
|
const api = buildHarness();
|
||||||
|
|
||||||
|
const payload = api.buildPersistentSettingsPayload({
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||||
|
kiroRsKey: 'secret-key',
|
||||||
|
kiroRegion: 'eu-west-1',
|
||||||
|
}, { fillDefaults: true });
|
||||||
|
|
||||||
|
assert.equal(payload.activeFlowId, 'kiro');
|
||||||
|
assert.equal(payload.kiroSourceId, 'kiro-rs');
|
||||||
|
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||||
|
assert.equal(payload.kiroRsKey, 'secret-key');
|
||||||
|
assert.equal(payload.kiroRegion, 'eu-west-1');
|
||||||
|
assert.equal(payload.settingsSchemaVersion, 3);
|
||||||
|
assert.equal(payload.settingsState.activeFlowId, 'kiro');
|
||||||
|
assert.equal(payload.settingsState.flows.kiro.source.selected, 'kiro-rs');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildPersistentSettingsPayload accepts schema-only input when requireKnownKeys is enabled', () => {
|
||||||
|
const api = buildHarness();
|
||||||
|
|
||||||
|
const payload = api.buildPersistentSettingsPayload({
|
||||||
|
settingsSchemaVersion: 3,
|
||||||
|
settingsState: {
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
services: {
|
||||||
|
email: { provider: '163' },
|
||||||
|
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
|
||||||
|
},
|
||||||
|
flows: {
|
||||||
|
openai: {
|
||||||
|
source: { selected: 'cpa', entries: {} },
|
||||||
|
account: { customPassword: '' },
|
||||||
|
signup: {
|
||||||
|
signupMethod: 'email',
|
||||||
|
phoneVerificationEnabled: false,
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||||
|
},
|
||||||
|
plus: {
|
||||||
|
plusModeEnabled: false,
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kiro: {
|
||||||
|
source: {
|
||||||
|
selected: 'kiro-rs',
|
||||||
|
entries: {
|
||||||
|
'kiro-rs': {
|
||||||
|
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||||
|
kiroRsKey: 'schema-only-key',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
kiroRegion: 'eu-west-1',
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 3 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, { requireKnownKeys: true });
|
||||||
|
|
||||||
|
assert.equal(payload.activeFlowId, 'kiro');
|
||||||
|
assert.equal(payload.kiroSourceId, 'kiro-rs');
|
||||||
|
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||||
|
assert.equal(payload.kiroRsKey, 'schema-only-key');
|
||||||
|
assert.equal(payload.kiroRegion, 'eu-west-1');
|
||||||
|
assert.equal(payload.settingsSchemaVersion, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getPersistedSettings reads schema keys alongside legacy flat settings keys', async () => {
|
||||||
|
const api = buildHarness(`
|
||||||
|
let requestedKeys = [];
|
||||||
|
const chrome = {
|
||||||
|
storage: {
|
||||||
|
local: {
|
||||||
|
async get(keys) {
|
||||||
|
requestedKeys = Array.isArray(keys) ? [...keys] : [];
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
function getRequestedKeys() {
|
||||||
|
return requestedKeys;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const state = await api.getPersistedSettings();
|
||||||
|
|
||||||
|
assert.ok(api.getRequestedKeys().includes('settingsSchemaVersion'));
|
||||||
|
assert.ok(api.getRequestedKeys().includes('settingsState'));
|
||||||
|
assert.equal(state.settingsSchemaVersion, 3);
|
||||||
|
assert.equal(state.settingsState.activeFlowId, 'openai');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getPersistedSettings can project schema-only storage back into legacy flat settings', async () => {
|
||||||
|
const api = buildHarness(`
|
||||||
|
const chrome = {
|
||||||
|
storage: {
|
||||||
|
local: {
|
||||||
|
async get() {
|
||||||
|
return {
|
||||||
|
settingsSchemaVersion: 3,
|
||||||
|
settingsState: {
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
services: {
|
||||||
|
email: { provider: 'hotmail' },
|
||||||
|
proxy: { enabled: true, provider: '711proxy', mode: 'account' },
|
||||||
|
},
|
||||||
|
flows: {
|
||||||
|
openai: {
|
||||||
|
source: {
|
||||||
|
selected: 'sub2api',
|
||||||
|
entries: {},
|
||||||
|
},
|
||||||
|
account: { customPassword: '' },
|
||||||
|
signup: {
|
||||||
|
signupMethod: 'email',
|
||||||
|
phoneVerificationEnabled: false,
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||||
|
},
|
||||||
|
plus: {
|
||||||
|
plusModeEnabled: false,
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kiro: {
|
||||||
|
source: {
|
||||||
|
selected: 'kiro-rs',
|
||||||
|
entries: {
|
||||||
|
'kiro-rs': {
|
||||||
|
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||||
|
kiroRsKey: 'stored-key',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
kiroRegion: 'ap-southeast-1',
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 3 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const state = await api.getPersistedSettings();
|
||||||
|
|
||||||
|
assert.equal(state.activeFlowId, 'kiro');
|
||||||
|
assert.equal(state.panelMode, 'sub2api');
|
||||||
|
assert.equal(state.mailProvider, 'hotmail');
|
||||||
|
assert.equal(state.ipProxyEnabled, true);
|
||||||
|
assert.equal(state.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||||
|
assert.equal(state.kiroRsKey, 'stored-key');
|
||||||
|
assert.equal(state.kiroRegion, 'ap-southeast-1');
|
||||||
|
assert.deepEqual(state.stepExecutionRangeByFlow.kiro, {
|
||||||
|
enabled: true,
|
||||||
|
fromStep: 1,
|
||||||
|
toStep: 3,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setPersistentSettings materializes canonical schema keys for schema-only updates', async () => {
|
||||||
|
const api = buildHarness(`
|
||||||
|
const persistedWrites = [];
|
||||||
|
const chrome = {
|
||||||
|
storage: {
|
||||||
|
local: {
|
||||||
|
async get() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
async set(payload) {
|
||||||
|
persistedWrites.push(JSON.parse(JSON.stringify(payload)));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
function getPersistedWrites() {
|
||||||
|
return persistedWrites;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const persisted = await api.setPersistentSettings({
|
||||||
|
settingsSchemaVersion: 3,
|
||||||
|
settingsState: {
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
services: {
|
||||||
|
email: { provider: '163' },
|
||||||
|
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
|
||||||
|
},
|
||||||
|
flows: {
|
||||||
|
openai: {
|
||||||
|
source: { selected: 'cpa', entries: {} },
|
||||||
|
account: { customPassword: '' },
|
||||||
|
signup: {
|
||||||
|
signupMethod: 'email',
|
||||||
|
phoneVerificationEnabled: false,
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||||
|
},
|
||||||
|
plus: {
|
||||||
|
plusModeEnabled: false,
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kiro: {
|
||||||
|
source: {
|
||||||
|
selected: 'kiro-rs',
|
||||||
|
entries: {
|
||||||
|
'kiro-rs': {
|
||||||
|
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||||
|
kiroRsKey: 'nested-only-key',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
kiroRegion: 'us-west-2',
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 3 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const write = api.getPersistedWrites().at(-1);
|
||||||
|
|
||||||
|
assert.equal(persisted.activeFlowId, 'kiro');
|
||||||
|
assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||||
|
assert.equal(persisted.kiroRsKey, 'nested-only-key');
|
||||||
|
assert.equal(persisted.kiroRegion, 'us-west-2');
|
||||||
|
assert.equal(persisted.settingsSchemaVersion, 3);
|
||||||
|
assert.equal(write.activeFlowId, 'kiro');
|
||||||
|
assert.equal(write.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||||
|
assert.equal(write.kiroRsKey, 'nested-only-key');
|
||||||
|
assert.equal(write.kiroRegion, 'us-west-2');
|
||||||
|
assert.equal(write.settingsSchemaVersion, 3);
|
||||||
|
assert.equal(write.settingsState.activeFlowId, 'kiro');
|
||||||
|
});
|
||||||
@@ -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 step 1~10 modules', () => {
|
test('background imports workflow step modules including Kiro device auth', () => {
|
||||||
const source = fs.readFileSync('background.js', 'utf8');
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
|
|
||||||
[
|
[
|
||||||
@@ -16,6 +16,7 @@ test('background imports step 1~10 modules', () => {
|
|||||||
'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',
|
||||||
].forEach((path) => {
|
].forEach((path) => {
|
||||||
assert.match(source, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
assert.match(source, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
test('background node registry preserves node metadata even before an executor is registered', () => {
|
||||||
|
const source = fs.readFileSync('background/steps/registry.js', 'utf8');
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundStepRegistry;`)({});
|
||||||
|
const registry = api.createNodeRegistry([
|
||||||
|
{
|
||||||
|
flowId: 'kiro',
|
||||||
|
legacyStepId: 1,
|
||||||
|
nodeId: 'kiro-start-device-login',
|
||||||
|
displayOrder: 10,
|
||||||
|
executeKey: 'kiro-start-device-login',
|
||||||
|
title: 'Start device login',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const node = registry.getNodeDefinition('kiro-start-device-login');
|
||||||
|
|
||||||
|
assert.equal(node.flowId, 'kiro');
|
||||||
|
assert.equal(node.legacyStepId, 1);
|
||||||
|
assert.equal(node.title, 'Start device login');
|
||||||
|
assert.throws(
|
||||||
|
() => registry.executeNode('kiro-start-device-login', {}),
|
||||||
|
/Missing node executor: kiro-start-device-login/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('background node registry executes registered nodes in display order', async () => {
|
||||||
|
const source = fs.readFileSync('background/steps/registry.js', 'utf8');
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundStepRegistry;`)({});
|
||||||
|
const events = [];
|
||||||
|
const registry = api.createNodeRegistry([
|
||||||
|
{
|
||||||
|
flowId: 'openai',
|
||||||
|
legacyStepId: 2,
|
||||||
|
nodeId: 'submit-signup-email',
|
||||||
|
displayOrder: 20,
|
||||||
|
executeKey: 'submit-signup-email',
|
||||||
|
title: 'Submit signup email',
|
||||||
|
execute: async (state) => {
|
||||||
|
events.push({ type: 'execute', state });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flowId: 'openai',
|
||||||
|
legacyStepId: 1,
|
||||||
|
nodeId: 'open-chatgpt',
|
||||||
|
displayOrder: 10,
|
||||||
|
executeKey: 'open-chatgpt',
|
||||||
|
title: 'Open ChatGPT',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
registry.getOrderedNodes().map((node) => node.nodeId),
|
||||||
|
['open-chatgpt', 'submit-signup-email']
|
||||||
|
);
|
||||||
|
|
||||||
|
await registry.executeNode('submit-signup-email', { activeFlowId: 'openai' });
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events, [{ type: 'execute', state: { activeFlowId: 'openai' } }]);
|
||||||
|
});
|
||||||
@@ -8,37 +8,10 @@ test('background imports node registry and shared workflow definitions', () => {
|
|||||||
assert.match(source, /data\/step-definitions\.js/);
|
assert.match(source, /data\/step-definitions\.js/);
|
||||||
assert.match(source, /background\/workflow-engine\.js/);
|
assert.match(source, /background\/workflow-engine\.js/);
|
||||||
assert.match(source, /MultiPageStepDefinitions\?\.getNodes/);
|
assert.match(source, /MultiPageStepDefinitions\?\.getNodes/);
|
||||||
assert.match(source, /getStepRegistryForState\(state\)/);
|
|
||||||
assert.match(source, /buildNodeRegistry\(definitions/);
|
assert.match(source, /buildNodeRegistry\(definitions/);
|
||||||
assert.match(source, /PLUS_PAYPAL_STEP_DEFINITIONS/);
|
assert.match(source, /const stepRegistryCache = new Map\(\);/);
|
||||||
assert.match(source, /PLUS_GOPAY_STEP_DEFINITIONS/);
|
assert.match(source, /const definitions = getNodeDefinitionsForState\(state\);/);
|
||||||
assert.match(source, /NORMAL_PHONE_STEP_DEFINITIONS/);
|
assert.match(source, /stepRegistryCache\.set\(cacheKey, buildStepRegistry\(definitions\)\)/);
|
||||||
assert.match(source, /NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS/);
|
|
||||||
assert.match(source, /PLUS_PAYPAL_PHONE_STEP_DEFINITIONS/);
|
|
||||||
assert.match(source, /PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS/);
|
|
||||||
assert.match(source, /PLUS_GOPAY_PHONE_STEP_DEFINITIONS/);
|
|
||||||
assert.match(source, /PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS/);
|
|
||||||
assert.match(source, /PLUS_GPC_PHONE_STEP_DEFINITIONS/);
|
|
||||||
assert.match(source, /PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS/);
|
|
||||||
assert.match(source, /plusPayPalStepRegistry/);
|
|
||||||
assert.match(source, /plusGoPayStepRegistry/);
|
|
||||||
assert.match(source, /normalPhoneStepRegistry/);
|
|
||||||
assert.match(source, /normalPhoneBoundEmailReloginStepRegistry/);
|
|
||||||
assert.match(source, /plusPayPalPhoneStepRegistry/);
|
|
||||||
assert.match(source, /plusPayPalPhoneBoundEmailReloginStepRegistry/);
|
|
||||||
assert.match(source, /plusGoPayPhoneStepRegistry/);
|
|
||||||
assert.match(source, /plusGoPayPhoneBoundEmailReloginStepRegistry/);
|
|
||||||
assert.match(source, /plusGpcPhoneStepRegistry/);
|
|
||||||
assert.match(source, /plusGpcPhoneBoundEmailReloginStepRegistry/);
|
|
||||||
assert.match(source, /const signupMethod = getSignupMethodForStepDefinitions\(state\);/);
|
|
||||||
assert.match(source, /const useBoundEmailRelogin = signupMethod === SIGNUP_METHOD_PHONE/);
|
|
||||||
assert.match(source, /useBoundEmailRelogin \? normalPhoneBoundEmailReloginStepRegistry : normalPhoneStepRegistry/);
|
|
||||||
assert.match(source, /const paymentMethod = normalizePlusPaymentMethod\(state\?\.plusPaymentMethod\);/);
|
|
||||||
assert.match(source, /paymentMethod === PLUS_PAYMENT_METHOD_GOPAY/);
|
|
||||||
assert.match(source, /useBoundEmailRelogin \? plusGoPayPhoneBoundEmailReloginStepRegistry : plusGoPayPhoneStepRegistry/);
|
|
||||||
assert.match(source, /useBoundEmailRelogin \? plusPayPalPhoneBoundEmailReloginStepRegistry : plusPayPalPhoneStepRegistry/);
|
|
||||||
assert.match(source, /useBoundEmailRelogin \? plusGpcPhoneBoundEmailReloginStepRegistry : plusGpcPhoneStepRegistry/);
|
|
||||||
assert.match(source, /activeStepRegistry\.executeNode\(normalizedNodeId,\s*\{/);
|
|
||||||
assert.match(source, /'bind-email': \(state\) => step8Executor\.executeBindEmail\(state\)/);
|
assert.match(source, /'bind-email': \(state\) => step8Executor\.executeBindEmail\(state\)/);
|
||||||
assert.match(source, /'fetch-bind-email-code': \(state\) => step8Executor\.executeFetchBindEmailCode\(state\)/);
|
assert.match(source, /'fetch-bind-email-code': \(state\) => step8Executor\.executeFetchBindEmailCode\(state\)/);
|
||||||
assert.match(source, /'relogin-bound-email': \(state\) => executeReloginBoundEmail\(state\)/);
|
assert.match(source, /'relogin-bound-email': \(state\) => executeReloginBoundEmail\(state\)/);
|
||||||
@@ -51,9 +24,14 @@ test('background imports node registry and shared workflow definitions', () => {
|
|||||||
assert.match(source, /background\/steps\/paypal-approve\.js/);
|
assert.match(source, /background\/steps\/paypal-approve\.js/);
|
||||||
assert.match(source, /background\/steps\/gopay-approve\.js/);
|
assert.match(source, /background\/steps\/gopay-approve\.js/);
|
||||||
assert.match(source, /background\/steps\/plus-return-confirm\.js/);
|
assert.match(source, /background\/steps\/plus-return-confirm\.js/);
|
||||||
|
assert.match(source, /background\/steps\/kiro-device-auth\.js/);
|
||||||
|
assert.match(source, /const kiroDeviceAuthExecutor = self\.MultiPageBackgroundKiroDeviceAuth\?\.createKiroDeviceAuthExecutor\(/);
|
||||||
|
assert.match(source, /'kiro-start-device-login': \(state\) => kiroDeviceAuthExecutor\.executeKiroStartDeviceLogin\(state\)/);
|
||||||
|
assert.match(source, /'kiro-await-device-login': \(state\) => kiroDeviceAuthExecutor\.executeKiroAwaitDeviceLogin\(state\)/);
|
||||||
|
assert.match(source, /'kiro-upload-credential': \(state\) => kiroDeviceAuthExecutor\.executeKiroUploadCredential\(state\)/);
|
||||||
|
assert.match(source, /'kiro-start-device-login',[\s\S]*'kiro-await-device-login',[\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', () => {
|
||||||
const source = fs.readFileSync('background.js', 'utf8');
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
assert.match(source, /createGoPayApproveExecutor\(\{[\s\S]*clickWithDebugger[\s\S]*requestGoPayOtpInput[\s\S]*\}\)/);
|
assert.match(source, /createGoPayApproveExecutor\(\{[\s\S]*clickWithDebugger[\s\S]*requestGoPayOtpInput[\s\S]*\}\)/);
|
||||||
|
|||||||
@@ -2,11 +2,16 @@ 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');
|
||||||
|
|
||||||
|
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||||
|
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
|
||||||
const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8');
|
const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8');
|
||||||
|
|
||||||
function loadApi() {
|
function loadApi() {
|
||||||
const scope = {};
|
const scope = {};
|
||||||
return new Function('self', `${source}; return self.MultiPageFlowCapabilities;`)(scope);
|
return new Function(
|
||||||
|
'self',
|
||||||
|
`${flowRegistrySource}; ${settingsSchemaSource}; ${source}; return self.MultiPageFlowCapabilities;`
|
||||||
|
)(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
test('flow capability registry keeps OpenAI phone signup available only when runtime locks allow it', () => {
|
test('flow capability registry keeps OpenAI phone signup available only when runtime locks allow it', () => {
|
||||||
@@ -71,6 +76,32 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
|
|||||||
assert.deepEqual(capabilityState.supportedPanelModes, []);
|
assert.deepEqual(capabilityState.supportedPanelModes, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('flow capability registry exposes Kiro as an independent flow with its own visible groups', () => {
|
||||||
|
const api = loadApi();
|
||||||
|
const registry = api.createFlowCapabilityRegistry();
|
||||||
|
|
||||||
|
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||||
|
state: {
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
kiroSourceId: 'kiro-rs',
|
||||||
|
panelMode: 'sub2api',
|
||||||
|
signupMethod: 'phone',
|
||||||
|
plusModeEnabled: true,
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(capabilityState.activeFlowId, 'kiro');
|
||||||
|
assert.equal(capabilityState.canShowPhoneSettings, false);
|
||||||
|
assert.equal(capabilityState.canShowPlusSettings, false);
|
||||||
|
assert.equal(capabilityState.effectiveSignupMethod, 'email');
|
||||||
|
assert.equal(capabilityState.effectiveSourceId, 'kiro-rs');
|
||||||
|
assert.deepEqual(
|
||||||
|
capabilityState.visibleGroupIds,
|
||||||
|
['kiro-runtime-status', 'kiro-source-kiro-rs', 'service-email', 'service-proxy']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('flow capability registry exposes shared auto-run validation for phone locks and panel support', () => {
|
test('flow capability registry exposes shared auto-run validation for phone locks and panel support', () => {
|
||||||
const api = loadApi();
|
const api = loadApi();
|
||||||
const registry = api.createFlowCapabilityRegistry({
|
const registry = api.createFlowCapabilityRegistry({
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||||
|
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
|
||||||
|
|
||||||
|
function loadApis() {
|
||||||
|
const scope = {};
|
||||||
|
return new Function('self', `${flowRegistrySource}; ${settingsSchemaSource}; return {
|
||||||
|
flowRegistry: self.MultiPageFlowRegistry,
|
||||||
|
settingsSchema: self.MultiPageSettingsSchema,
|
||||||
|
};`)(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('flow registry exposes openai and kiro with canonical source metadata', () => {
|
||||||
|
const { flowRegistry } = loadApis();
|
||||||
|
|
||||||
|
assert.deepEqual(flowRegistry.getRegisteredFlowIds(), ['openai', 'kiro']);
|
||||||
|
assert.equal(flowRegistry.getFlowLabel('codex'), 'Codex / OpenAI');
|
||||||
|
assert.equal(flowRegistry.normalizeFlowId('codex'), 'openai');
|
||||||
|
assert.equal(flowRegistry.normalizeSourceId('openai', 'sub2api'), 'sub2api');
|
||||||
|
assert.equal(flowRegistry.normalizeSourceId('kiro', 'anything-else'), 'kiro-rs');
|
||||||
|
assert.deepEqual(
|
||||||
|
flowRegistry.getVisibleGroupIds('kiro', 'kiro-rs'),
|
||||||
|
['kiro-runtime-status', 'kiro-source-kiro-rs', 'service-email', 'service-proxy']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('settings schema normalizes flat input into canonical flow and service namespaces', () => {
|
||||||
|
const { settingsSchema } = loadApis();
|
||||||
|
const schema = settingsSchema.createSettingsSchema();
|
||||||
|
|
||||||
|
const normalized = schema.normalizeSettingsState({
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
panelMode: 'sub2api',
|
||||||
|
mailProvider: 'hotmail',
|
||||||
|
ipProxyEnabled: true,
|
||||||
|
ipProxyService: '711proxy',
|
||||||
|
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||||
|
kiroRsKey: 'secret-key',
|
||||||
|
kiroRegion: 'eu-west-1',
|
||||||
|
stepExecutionRangeByFlow: {
|
||||||
|
openai: { enabled: true, fromStep: 2, toStep: 9 },
|
||||||
|
kiro: { enabled: true, fromStep: 1, toStep: 3 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(normalized.activeFlowId, 'kiro');
|
||||||
|
assert.equal(normalized.services.email.provider, 'hotmail');
|
||||||
|
assert.equal(normalized.services.proxy.enabled, true);
|
||||||
|
assert.equal(normalized.flows.openai.source.selected, 'sub2api');
|
||||||
|
assert.equal(normalized.flows.kiro.source.selected, 'kiro-rs');
|
||||||
|
assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsUrl, 'https://kiro.example.com/admin');
|
||||||
|
assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsKey, 'secret-key');
|
||||||
|
assert.equal(normalized.flows.kiro.options.kiroRegion, 'eu-west-1');
|
||||||
|
assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, {
|
||||||
|
enabled: true,
|
||||||
|
fromStep: 1,
|
||||||
|
toStep: 3,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('settings schema can project canonical state back to legacy payload without losing flow selection', () => {
|
||||||
|
const { settingsSchema } = loadApis();
|
||||||
|
const schema = settingsSchema.createSettingsSchema();
|
||||||
|
const payload = schema.buildLegacySettingsPayload(schema.normalizeSettingsState({
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
kiroSourceId: 'kiro-rs',
|
||||||
|
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||||
|
kiroRsKey: 'key-123',
|
||||||
|
kiroRegion: 'ap-southeast-1',
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert.equal(payload.activeFlowId, 'kiro');
|
||||||
|
assert.equal(payload.panelMode, 'cpa');
|
||||||
|
assert.equal(payload.kiroSourceId, 'kiro-rs');
|
||||||
|
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||||
|
assert.equal(payload.kiroRsKey, 'key-123');
|
||||||
|
assert.equal(payload.kiroRegion, 'ap-southeast-1');
|
||||||
|
assert.equal(payload.settingsSchemaVersion, 3);
|
||||||
|
assert.equal(payload.settingsState.activeFlowId, 'kiro');
|
||||||
|
});
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const vm = require('node:vm');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('sidepanel/contribution-mode.js', 'utf8');
|
||||||
|
|
||||||
|
function createElement() {
|
||||||
|
return {
|
||||||
|
hidden: false,
|
||||||
|
disabled: false,
|
||||||
|
title: '',
|
||||||
|
textContent: '',
|
||||||
|
value: '',
|
||||||
|
classList: {
|
||||||
|
hiddenState: false,
|
||||||
|
toggle(_className, hidden) {
|
||||||
|
this.hiddenState = Boolean(hidden);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setAttribute() {},
|
||||||
|
addEventListener() {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('contribution mode manager does not project openai-only ui state into kiro flow', () => {
|
||||||
|
const context = {
|
||||||
|
window: {},
|
||||||
|
document: { activeElement: null },
|
||||||
|
console,
|
||||||
|
setTimeout,
|
||||||
|
clearTimeout,
|
||||||
|
};
|
||||||
|
vm.runInNewContext(source, context);
|
||||||
|
|
||||||
|
const createContributionModeManager = context.window.SidepanelContributionMode.createContributionModeManager;
|
||||||
|
const rowVpsUrl = createElement();
|
||||||
|
const dom = {
|
||||||
|
btnContributionMode: createElement(),
|
||||||
|
contributionModePanel: createElement(),
|
||||||
|
contributionModeText: createElement(),
|
||||||
|
contributionModeBadge: createElement(),
|
||||||
|
contributionOauthStatus: createElement(),
|
||||||
|
contributionCallbackStatus: createElement(),
|
||||||
|
contributionModeSummary: createElement(),
|
||||||
|
inputContributionNickname: createElement(),
|
||||||
|
inputContributionQq: createElement(),
|
||||||
|
btnStartContribution: createElement(),
|
||||||
|
btnOpenContributionUpload: createElement(),
|
||||||
|
btnExitContributionMode: createElement(),
|
||||||
|
btnOpenAccountRecords: createElement(),
|
||||||
|
selectPanelMode: createElement(),
|
||||||
|
rowVpsUrl,
|
||||||
|
};
|
||||||
|
const manager = createContributionModeManager({
|
||||||
|
state: {
|
||||||
|
getLatestState: () => ({
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
flowId: 'kiro',
|
||||||
|
contributionMode: true,
|
||||||
|
contributionSource: 'cpa',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
dom,
|
||||||
|
helpers: {
|
||||||
|
updatePanelModeUI() {},
|
||||||
|
updateAccountRunHistorySettingsUI() {},
|
||||||
|
updateConfigMenuControls() {},
|
||||||
|
closeConfigMenu() {},
|
||||||
|
closeAccountRecordsPanel() {},
|
||||||
|
isModeSwitchBlocked() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
sendMessage: async () => ({}),
|
||||||
|
},
|
||||||
|
constants: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.render();
|
||||||
|
|
||||||
|
assert.equal(dom.contributionModePanel.hidden, true);
|
||||||
|
assert.equal(dom.selectPanelMode.disabled, false);
|
||||||
|
assert.equal(dom.btnContributionMode.disabled, true);
|
||||||
|
assert.equal(dom.btnContributionMode.title, '当前 flow 不支持贡献模式');
|
||||||
|
assert.equal(dom.btnStartContribution.disabled, true);
|
||||||
|
assert.equal(dom.btnOpenContributionUpload.disabled, true);
|
||||||
|
assert.equal(rowVpsUrl.classList.hiddenState, false);
|
||||||
|
});
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
|
||||||
|
const sidepanelHtml = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||||
|
|
||||||
|
function extractFunction(source, name) {
|
||||||
|
const asyncStart = source.indexOf(`async function ${name}`);
|
||||||
|
const normalStart = source.indexOf(`function ${name}`);
|
||||||
|
const start = asyncStart !== -1
|
||||||
|
? asyncStart
|
||||||
|
: normalStart;
|
||||||
|
if (start === -1) {
|
||||||
|
throw new Error(`Function ${name} not found`);
|
||||||
|
}
|
||||||
|
const signatureEnd = source.indexOf(')', start);
|
||||||
|
const bodyStart = source.indexOf('{', signatureEnd);
|
||||||
|
let depth = 0;
|
||||||
|
let end = bodyStart;
|
||||||
|
for (; end < source.length; end += 1) {
|
||||||
|
const char = source[end];
|
||||||
|
if (char === '{') {
|
||||||
|
depth += 1;
|
||||||
|
} else if (char === '}') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
end += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return source.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('sidepanel html exposes flow selector and kiro source fields', () => {
|
||||||
|
[
|
||||||
|
'id="select-flow"',
|
||||||
|
'id="label-source-selector"',
|
||||||
|
'id="row-kiro-rs-url"',
|
||||||
|
'id="row-kiro-rs-key"',
|
||||||
|
'id="row-kiro-region"',
|
||||||
|
'id="row-kiro-device-code"',
|
||||||
|
'id="row-kiro-login-url"',
|
||||||
|
'id="row-kiro-upload-status"',
|
||||||
|
].forEach((snippet) => {
|
||||||
|
assert.match(sidepanelHtml, new RegExp(snippet.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sidepanel step definitions rerender when active flow changes even if plus/signup settings stay the same', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction(sidepanelSource, 'normalizeSignupMethod'),
|
||||||
|
extractFunction(sidepanelSource, 'normalizePlusPaymentMethod'),
|
||||||
|
extractFunction(sidepanelSource, 'getStepDefinitionsForMode'),
|
||||||
|
extractFunction(sidepanelSource, 'rebuildStepDefinitionState'),
|
||||||
|
extractFunction(sidepanelSource, 'syncStepDefinitionsForMode'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const calls = [];
|
||||||
|
const window = {
|
||||||
|
MultiPageStepDefinitions: {
|
||||||
|
getSteps(options) {
|
||||||
|
calls.push({ type: 'getSteps', options });
|
||||||
|
return [{ id: options.activeFlowId === 'kiro' ? 88 : 6, order: 1, key: options.activeFlowId }];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let latestState = { activeFlowId: 'openai' };
|
||||||
|
let currentPlusModeEnabled = false;
|
||||||
|
let currentPlusPaymentMethod = 'paypal';
|
||||||
|
let currentSignupMethod = 'email';
|
||||||
|
let currentPhoneSignupReloginAfterBindEmailEnabled = false;
|
||||||
|
let currentStepDefinitionFlowId = 'openai';
|
||||||
|
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||||
|
const DEFAULT_SIGNUP_METHOD = 'email';
|
||||||
|
const DEFAULT_PLUS_PAYMENT_METHOD = 'paypal';
|
||||||
|
let stepDefinitions = [{ id: 6, key: 'openai' }];
|
||||||
|
let STEP_IDS = [6];
|
||||||
|
let STEP_DEFAULT_STATUSES = { 6: 'pending' };
|
||||||
|
let SKIPPABLE_STEPS = new Set([6]);
|
||||||
|
function renderStepsList() {
|
||||||
|
calls.push({ type: 'render', stepIds: [...STEP_IDS] });
|
||||||
|
}
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
calls,
|
||||||
|
syncStepDefinitionsForMode,
|
||||||
|
getStepIds: () => [...STEP_IDS],
|
||||||
|
getCurrentFlowId: () => currentStepDefinitionFlowId,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
api.syncStepDefinitionsForMode(false, {
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
signupMethod: 'email',
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(api.getCurrentFlowId(), 'kiro');
|
||||||
|
assert.deepEqual(api.getStepIds(), [88]);
|
||||||
|
assert.deepEqual(api.calls[0], {
|
||||||
|
type: 'getSteps',
|
||||||
|
options: {
|
||||||
|
activeFlowId: 'kiro',
|
||||||
|
plusModeEnabled: false,
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
signupMethod: 'email',
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [88] });
|
||||||
|
});
|
||||||
@@ -4,6 +4,8 @@ const fs = require('node:fs');
|
|||||||
|
|
||||||
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\/settings-schema\.js/);
|
||||||
assert.match(source, /shared\/source-registry\.js/);
|
assert.match(source, /shared\/source-registry\.js/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -21,8 +23,9 @@ test('manifest loads shared source registry before content utils in static bundl
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('shared source registry exposes canonical source, alias, detection, and ready policies', () => {
|
test('shared source registry exposes canonical source, alias, detection, and ready policies', () => {
|
||||||
|
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||||
const source = fs.readFileSync('shared/source-registry.js', 'utf8');
|
const source = fs.readFileSync('shared/source-registry.js', 'utf8');
|
||||||
const api = new Function('self', `${source}; return self.MultiPageSourceRegistry;`)({});
|
const api = new Function('self', `${flowRegistrySource}; ${source}; return self.MultiPageSourceRegistry;`)({});
|
||||||
const registry = api.createSourceRegistry();
|
const registry = api.createSourceRegistry();
|
||||||
|
|
||||||
assert.equal(registry.resolveCanonicalSource('signup-page'), 'openai-auth');
|
assert.equal(registry.resolveCanonicalSource('signup-page'), 'openai-auth');
|
||||||
@@ -50,6 +53,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.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('unknown-source', false), false);
|
||||||
assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth');
|
assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth');
|
||||||
@@ -59,4 +66,5 @@ test('shared source registry exposes canonical source, alias, detection, and rea
|
|||||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'fetch-bind-email-code'), 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('background/kiro-device-auth', 'kiro-start-device-login'), true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
});
|
});
|
||||||
const goPaySteps = api.getSteps({ plusModeEnabled: true, plusPaymentMethod: 'gopay' });
|
const goPaySteps = api.getSteps({ plusModeEnabled: true, plusPaymentMethod: 'gopay' });
|
||||||
const gpcSteps = api.getSteps({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' });
|
const gpcSteps = api.getSteps({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' });
|
||||||
|
const kiroSteps = api.getSteps({ activeFlowId: 'kiro' });
|
||||||
|
|
||||||
assert.equal(Array.isArray(steps), true);
|
assert.equal(Array.isArray(steps), true);
|
||||||
assert.equal(steps.length, 11);
|
assert.equal(steps.length, 11);
|
||||||
@@ -158,10 +159,28 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, signupMethod: 'phone', phoneSignupReloginAfterBindEmailEnabled: true }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]);
|
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, signupMethod: 'phone', phoneSignupReloginAfterBindEmailEnabled: true }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]);
|
||||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, signupMethod: 'phone', phoneSignupReloginAfterBindEmailEnabled: true }), 18);
|
assert.equal(api.getLastStepId({ plusModeEnabled: true, signupMethod: 'phone', phoneSignupReloginAfterBindEmailEnabled: true }), 18);
|
||||||
assert.equal(api.hasFlow('openai'), true);
|
assert.equal(api.hasFlow('openai'), true);
|
||||||
|
assert.equal(api.hasFlow('kiro'), true);
|
||||||
assert.equal(api.hasFlow('site-a'), false);
|
assert.equal(api.hasFlow('site-a'), false);
|
||||||
assert.deepStrictEqual(api.getRegisteredFlowIds(), ['openai']);
|
assert.deepStrictEqual(api.getRegisteredFlowIds(), ['openai', 'kiro']);
|
||||||
assert.deepStrictEqual(api.getSteps({ activeFlowId: 'site-a' }), []);
|
assert.deepStrictEqual(api.getSteps({ activeFlowId: 'site-a' }), []);
|
||||||
assert.equal(api.getStepById(2, { activeFlowId: 'site-a' }), null);
|
assert.equal(api.getStepById(2, { activeFlowId: 'site-a' }), null);
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
kiroSteps.map((step) => step.key),
|
||||||
|
[
|
||||||
|
'kiro-start-device-login',
|
||||||
|
'kiro-await-device-login',
|
||||||
|
'kiro-upload-credential',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert.equal(kiroSteps.every((step) => step.flowId === 'kiro'), true);
|
||||||
|
assert.equal(kiroSteps[0].driverId, 'background/kiro-device-auth');
|
||||||
|
assert.equal(kiroSteps[2].sourceId, 'kiro-rs-admin');
|
||||||
|
assert.deepStrictEqual(api.getStepIds({ activeFlowId: 'kiro' }), [1, 2, 3]);
|
||||||
|
assert.equal(api.getLastStepId({ activeFlowId: 'kiro' }), 3);
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
api.getNodes({ activeFlowId: 'kiro' }).map((node) => node.next),
|
||||||
|
[['kiro-await-device-login'], ['kiro-upload-credential'], []]
|
||||||
|
);
|
||||||
assert.equal(plusSteps[5].title, '创建 Plus Checkout');
|
assert.equal(plusSteps[5].title, '创建 Plus Checkout');
|
||||||
assert.equal(plusSteps[7].title, 'PayPal 登录与授权');
|
assert.equal(plusSteps[7].title, 'PayPal 登录与授权');
|
||||||
|
|
||||||
|
|||||||
+68
-6
@@ -10,7 +10,10 @@
|
|||||||
|
|
||||||
## 1. 项目目标
|
## 1. 项目目标
|
||||||
|
|
||||||
这是一个 Chrome 扩展,用于自动执行一整套 OpenAI / ChatGPT OAuth 注册与登录流程。
|
这是一个 Chrome 扩展,用于承载多套注册与认证自动化 flow。当前已注册两条主 flow:
|
||||||
|
|
||||||
|
- `openai`:OpenAI / ChatGPT OAuth 注册、登录、平台绑定与 Plus 扩展链路
|
||||||
|
- `kiro`:Builder ID device auth + `kiro.rs` 凭据上传链路
|
||||||
|
|
||||||
它的核心价值不是“打开一个页面点几个按钮”,而是把下面这些环节串成一条完整可恢复的自动化链路:
|
它的核心价值不是“打开一个页面点几个按钮”,而是把下面这些环节串成一条完整可恢复的自动化链路:
|
||||||
|
|
||||||
@@ -25,6 +28,8 @@
|
|||||||
- 自动确认 OAuth 同意页
|
- 自动确认 OAuth 同意页
|
||||||
- 把 localhost 回调提交到 CPA、SUB2API 或 Codex2API
|
- 把 localhost 回调提交到 CPA、SUB2API 或 Codex2API
|
||||||
|
|
||||||
|
同时,这套扩展现在不再把“来源、步骤、侧边栏显隐、运行态字段”硬编码在单一 OpenAI 链路里,而是统一挂到 `flow registry + source registry + settings schema + step definitions` 这一层。Kiro 这类新 flow 可以复用共享邮箱服务与 IP 代理服务,但不会复用 OpenAI 的接码、Plus、平台绑定和贡献模式逻辑。
|
||||||
|
|
||||||
## 2. 核心运行参与者
|
## 2. 核心运行参与者
|
||||||
|
|
||||||
### 2.1 Sidepanel
|
### 2.1 Sidepanel
|
||||||
@@ -38,6 +43,8 @@
|
|||||||
- 向后台发送命令
|
- 向后台发送命令
|
||||||
- 接收后台广播并更新 UI
|
- 接收后台广播并更新 UI
|
||||||
- 动态渲染步骤列表
|
- 动态渲染步骤列表
|
||||||
|
- 维护 `activeFlowId + sourceId` 的双层选择;`openai` flow 继续把 `panelMode` 作为 legacy 来源映射,`kiro` flow 则改用 `kiroSourceId`
|
||||||
|
- 按当前 flow capability 决定显隐分组;Kiro flow 只显示来源下拉、`kiro.rs URL / API Key / region`、共享邮箱服务、共享 IP 代理与 Kiro 运行状态,不显示 OpenAI 接码 / Plus / 平台绑定配置
|
||||||
- 管理顶部“贡献”按钮与贡献模式主面板;贡献模式本身是 sidepanel 的运行态 UI 模式,不是新的 `panelMode` 来源
|
- 管理顶部“贡献”按钮与贡献模式主面板;贡献模式本身是 sidepanel 的运行态 UI 模式,不是新的 `panelMode` 来源
|
||||||
- 在贡献模式下复用同一套主自动流程启动,并在面板内展示贡献链路的 `OAUTH / 回调 / 总状态` 三块实时状态
|
- 在贡献模式下复用同一套主自动流程启动,并在面板内展示贡献链路的 `OAUTH / 回调 / 总状态` 三块实时状态
|
||||||
- 在顶部“贡献/使用”按钮下方展示一个非强制的内容更新轻提示;提示来源于 `flowpilot.qlhazycoder.top` 的公开公告 / 教程摘要,用户关闭后仅对当前 `promptVersion` 静默,下次内容版本变化后会重新出现
|
- 在顶部“贡献/使用”按钮下方展示一个非强制的内容更新轻提示;提示来源于 `flowpilot.qlhazycoder.top` 的公开公告 / 教程摘要,用户关闭后仅对当前 `promptVersion` 静默,下次内容版本变化后会重新出现
|
||||||
@@ -110,18 +117,32 @@
|
|||||||
- 极少量保留函数
|
- 极少量保留函数
|
||||||
- Chrome 事件挂接入口
|
- Chrome 事件挂接入口
|
||||||
|
|
||||||
### 3.3 步骤注册
|
### 3.3 Flow / Source 注册
|
||||||
|
|
||||||
[data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js) 提供共享步骤元数据,并会按 `plusPaymentMethod / signupMethod` 解析当前运行态应显示的步骤标题。
|
[shared/flow-registry.js](c:/Users/projectf/Downloads/codex注册扩展/shared/flow-registry.js) 定义 flow、来源、可见设置分组、runtime source 与 driver。
|
||||||
[background/steps/registry.js](c:/Users/projectf/Downloads/codex注册扩展/background/steps/registry.js) 负责把“步骤元数据”映射到“步骤执行器”。
|
[shared/source-registry.js](c:/Users/projectf/Downloads/codex注册扩展/shared/source-registry.js) 负责把 flow runtime source 与共享 mail source 合并成统一来源视图。
|
||||||
|
[shared/flow-capabilities.js](c:/Users/projectf/Downloads/codex注册扩展/shared/flow-capabilities.js) 负责把当前 flow/source 解析成 sidepanel 的能力矩阵。
|
||||||
|
[shared/settings-schema.js](c:/Users/projectf/Downloads/codex注册扩展/shared/settings-schema.js) 负责把旧平铺字段与新的 `flows.*` 配置结构互转。
|
||||||
|
|
||||||
|
这意味着:
|
||||||
|
|
||||||
|
- flow 是第一层边界,source 是第二层边界
|
||||||
|
- “显示什么配置”“允许什么步骤”“哪些运行态字段属于哪个 flow” 都不再由 sidepanel 或 background 临时硬编码
|
||||||
|
- 共享服务目前主要是 `email` 与 `proxy`
|
||||||
|
|
||||||
|
### 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。
|
||||||
|
[background/steps/registry.js](c:/Users/projectf/Downloads/codex注册扩展/background/steps/registry.js) 负责把“workflow node 元数据”映射到“步骤执行器”,同时保留 `flowId / nodeId / displayOrder / executeKey` 等稳定节点元数据。
|
||||||
|
|
||||||
这意味着:
|
这意味着:
|
||||||
|
|
||||||
- 步骤顺序靠 `order`
|
- 步骤顺序靠 `order`
|
||||||
- 步骤文件名靠语义
|
- 步骤文件名靠语义
|
||||||
- 新增步骤时不需要重命名后续文件
|
- 新增步骤时不需要重命名后续文件
|
||||||
|
- 非 OpenAI flow 不需要复用旧的数字 step registry;直接按 node workflow 注册即可
|
||||||
|
|
||||||
### 3.4 日志步骤号链路
|
### 3.5 日志步骤号链路
|
||||||
|
|
||||||
日志步骤号不再从日志正文里提取,也不再兼容旧的 `步骤 X` / `Step X` 文本解析。
|
日志步骤号不再从日志正文里提取,也不再兼容旧的 `步骤 X` / `Step X` 文本解析。
|
||||||
|
|
||||||
@@ -137,6 +158,7 @@
|
|||||||
|
|
||||||
保存运行态:
|
保存运行态:
|
||||||
|
|
||||||
|
- 当前 flow 选择:`activeFlowId / flowId`
|
||||||
- 当前步骤状态
|
- 当前步骤状态
|
||||||
- 当前 sidepanel UI 模式 `contributionMode`
|
- 当前 sidepanel UI 模式 `contributionMode`
|
||||||
- 贡献 OAuth 会话字段:
|
- 贡献 OAuth 会话字段:
|
||||||
@@ -148,6 +170,7 @@
|
|||||||
- `contributionStatusMessage`
|
- `contributionStatusMessage`
|
||||||
- `contributionLastPollAt`
|
- `contributionLastPollAt`
|
||||||
- OAuth 链接
|
- OAuth 链接
|
||||||
|
- Kiro 运行态:`kiroDeviceCode / kiroLoginUrl / kiroAuthStatus / kiroRefreshToken / kiroUploadStatus / kiroAuthorizedEmail / kiroCredentialId`
|
||||||
- 当前轮冻结后的注册方式 `resolvedSignupMethod`
|
- 当前轮冻结后的注册方式 `resolvedSignupMethod`
|
||||||
- 当前统一账号标识 `accountIdentifierType / accountIdentifier`
|
- 当前统一账号标识 `accountIdentifierType / accountIdentifier`
|
||||||
- 当前邮箱 / 密码
|
- 当前邮箱 / 密码
|
||||||
@@ -184,6 +207,8 @@
|
|||||||
|
|
||||||
保存持久配置与账号运行历史:
|
保存持久配置与账号运行历史:
|
||||||
|
|
||||||
|
- 当前 flow 与来源配置:`activeFlowId`、OpenAI 的 `panelMode`、Kiro 的 `kiroSourceId`
|
||||||
|
- flow-aware 配置树:`flows.openai.*`、`flows.kiro.*`
|
||||||
- CPA / SUB2API 配置
|
- CPA / SUB2API 配置
|
||||||
- Codex2API 配置
|
- Codex2API 配置
|
||||||
- IP 代理持久配置:`ipProxyEnabled`、服务商、模式、API 地址、服务商配置快照、账号列表、固定 Host / Port / Protocol / Username / Password、地区参数、session 与自动切换阈值
|
- IP 代理持久配置:`ipProxyEnabled`、服务商、模式、API 地址、服务商配置快照、账号列表、固定 Host / Port / Protocol / Username / Password、地区参数、session 与自动切换阈值
|
||||||
@@ -202,6 +227,7 @@
|
|||||||
- 接码开关、接码平台 `phoneSmsProvider`,以及 HeroSMS / 5sim 各自的 API Key、国家/地区、价格上限和平台扩展设置
|
- 接码开关、接码平台 `phoneSmsProvider`,以及 HeroSMS / 5sim 各自的 API Key、国家/地区、价格上限和平台扩展设置
|
||||||
- iCloud 相关偏好
|
- iCloud 相关偏好
|
||||||
- LuckMail API 配置
|
- LuckMail API 配置
|
||||||
|
- Kiro 来源配置:`kiroRsUrl / kiroRsKey / kiroRegion / kiroRsPriority / kiroRsEndpoint / kiroRsAuthRegion / kiroRsApiRegion`
|
||||||
- 自动运行默认配置
|
- 自动运行默认配置
|
||||||
- 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。
|
||||||
@@ -211,7 +237,7 @@
|
|||||||
注意:
|
注意:
|
||||||
|
|
||||||
- `contributionMode` 不属于持久配置,也不参与导入/导出;它只存在于运行态
|
- `contributionMode` 不属于持久配置,也不参与导入/导出;它只存在于运行态
|
||||||
- `panelMode` 当前表示 `cpa | sub2api | codex2api` 三个来源;贡献模式仍不是新的来源枚举
|
- `panelMode` 现在只代表 `openai` flow 下的 `cpa | sub2api | codex2api` 三个 legacy 来源;`kiro` flow 使用独立的 `kiroSourceId`
|
||||||
|
|
||||||
账号运行历史会默认通过 [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 的接码模式。
|
||||||
@@ -591,6 +617,40 @@ Plus 模式可见步骤:
|
|||||||
等待模型:
|
等待模型:
|
||||||
|
|
||||||
- Plus Checkout 和 PayPal 专用步骤使用“无限等待但可停止”的后台等待 helper。
|
- Plus Checkout 和 PayPal 专用步骤使用“无限等待但可停止”的后台等待 helper。
|
||||||
|
|
||||||
|
## 6.2 Kiro Flow 链路
|
||||||
|
|
||||||
|
Kiro flow 的目标不是复用 OpenAI 注册链,而是单独完成“拿 Builder ID refresh token -> 上传到 `kiro.rs`”这条链路。
|
||||||
|
|
||||||
|
当前节点只有 3 个:
|
||||||
|
|
||||||
|
1. `kiro-start-device-login`
|
||||||
|
2. `kiro-await-device-login`
|
||||||
|
3. `kiro-upload-credential`
|
||||||
|
|
||||||
|
链路如下:
|
||||||
|
|
||||||
|
1. sidepanel 把当前 flow 切到 `kiro`,来源固定为 `kiro-rs`
|
||||||
|
2. 用户填写 `kiro.rs URL / API Key / region`
|
||||||
|
3. 步骤 1 调用 AWS Builder ID OIDC:
|
||||||
|
- 注册 public client
|
||||||
|
- 请求 `device_authorization`
|
||||||
|
- 打开 `verificationUriComplete`
|
||||||
|
- 保存 `kiroDeviceCode / kiroLoginUrl / kiroClientId / kiroClientSecret`
|
||||||
|
4. 步骤 2 轮询 token 接口,直到用户完成 device login:
|
||||||
|
- 成功后保存 `kiroRefreshToken`
|
||||||
|
- 状态进入 `ready_to_upload`
|
||||||
|
5. 步骤 3 调用 `kiro.rs` Admin API:
|
||||||
|
- 先探活 `/api/admin/credentials`
|
||||||
|
- 再用 `x-api-key` 上传 Builder ID 凭据
|
||||||
|
- payload 会带上 `refreshToken / clientId / clientSecret / region`
|
||||||
|
- 如果当前启用了共享 IP 代理,也会把 `proxyUrl / proxyUsername / proxyPassword` 一并上传
|
||||||
|
|
||||||
|
这条链路的关键边界是:
|
||||||
|
|
||||||
|
- Kiro 只复用共享邮箱服务和共享 IP 代理服务,不复用 OpenAI 接码 / Plus / 平台绑定 / 贡献模式链路
|
||||||
|
- Kiro 自动运行不再走 OpenAI 专用步骤文案和重开逻辑,而是走通用 linear node runner
|
||||||
|
- Kiro 节点完成回写直接按 `flowId=kiro` 写运行态,不会再误入 OpenAI 的旧 `step` 分支
|
||||||
- 每次页面加载完成后固定等待 1 秒,再继续下一次输入、点击或状态判断。
|
- 每次页面加载完成后固定等待 1 秒,再继续下一次输入、点击或状态判断。
|
||||||
- 第一版不实时抓取外部地址网站;地址自动化只依赖本地 seed query 和 checkout 页面内置 Google 地址推荐。
|
- 第一版不实时抓取外部地址网站;地址自动化只依赖本地 seed query 和 checkout 页面内置 Google 地址推荐。
|
||||||
|
|
||||||
@@ -944,6 +1004,7 @@ Hide My Email 获取与管理链路:
|
|||||||
- 如果当前生成方式是 `custom-pool`,会先按当前目标轮次把邮箱池中的对应邮箱写回运行态
|
- 如果当前生成方式是 `custom-pool`,会先按当前目标轮次把邮箱池中的对应邮箱写回运行态
|
||||||
- fresh-attempt reset 会保留 `stepExecutionRangeByFlow`,避免重置运行态时丢失用户设置的执行窗口
|
- fresh-attempt reset 会保留 `stepExecutionRangeByFlow`,避免重置运行态时丢失用户设置的执行窗口
|
||||||
5. 执行 `runAutoSequenceFromStep`
|
5. 执行 `runAutoSequenceFromStep`
|
||||||
|
- 如果当前 `activeFlowId !== openai`,后台会切到通用 linear node runner,按当前 flow 的 workflow node 顺序执行,并复用统一的完成状态与 idle watchdog
|
||||||
- 如果 `stepExecutionRangeByFlow` 已启用,自动运行只会遍历允许范围内的 workflow node;范围外节点会被跳过,`getFirstUnfinishedNodeId` 与保存进度判断也只统计允许节点
|
- 如果 `stepExecutionRangeByFlow` 已启用,自动运行只会遍历允许范围内的 workflow node;范围外节点会被跳过,`getFirstUnfinishedNodeId` 与保存进度判断也只统计允许节点
|
||||||
- 手动执行、手动跳过、completion signal 执行和 `executeNodeAndWait` 都会在后台校验执行范围,范围外节点即使被外部消息或旧状态触发,也会直接报错,不会真正执行
|
- 手动执行、手动跳过、completion signal 执行和 `executeNodeAndWait` 都会在后台校验执行范围,范围外节点即使被外部消息或旧状态触发,也会直接报错,不会真正执行
|
||||||
- 步骤 7 内部仍保留登录态恢复的有限重试,但 `add-phone / 手机号页` 属于立即跳出的不可重试错误
|
- 步骤 7 内部仍保留登录态恢复的有限重试,但 `add-phone / 手机号页` 属于立即跳出的不可重试错误
|
||||||
@@ -971,6 +1032,7 @@ Hide My Email 获取与管理链路:
|
|||||||
1. [data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js)
|
1. [data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js)
|
||||||
2. [background/steps](c:/Users/projectf/Downloads/codex注册扩展/background/steps)
|
2. [background/steps](c:/Users/projectf/Downloads/codex注册扩展/background/steps)
|
||||||
3. [background/steps/registry.js](c:/Users/projectf/Downloads/codex注册扩展/background/steps/registry.js)
|
3. [background/steps/registry.js](c:/Users/projectf/Downloads/codex注册扩展/background/steps/registry.js)
|
||||||
|
4. 如果步骤属于新 flow,还必须同步检查 [shared/flow-registry.js](c:/Users/projectf/Downloads/codex注册扩展/shared/flow-registry.js) 与 [shared/settings-schema.js](c:/Users/projectf/Downloads/codex注册扩展/shared/settings-schema.js)
|
||||||
4. 自动运行链路是否需要纳入
|
4. 自动运行链路是否需要纳入
|
||||||
5. Step 状态传播和侧边栏展示是否需要适配
|
5. Step 状态传播和侧边栏展示是否需要适配
|
||||||
6. 测试是否要补
|
6. 测试是否要补
|
||||||
|
|||||||
+21
-5
@@ -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 入口壳,负责模块装配、初始化、全局常量、少量保留的领域函数与运行入口;当前承接 `oauthFlowTimeoutEnabled` 持久化配置与 OAuth 授权后链总预算开关,同时新增通用 `stepExecutionRangeByFlow` 持久配置,按当前 flow 的 workflow node 解析执行范围,后台手动执行、自动执行、手动跳过与 completion signal 路径都会拦截范围外节点。
|
- `background.js`:扩展后台 Service Worker 入口壳,负责模块装配、初始化、全局常量、少量保留的领域函数与运行入口;当前已按 `activeFlowId` 装配 flow-aware 的步骤定义、执行注册表、自动运行与状态同步,`openai` flow 继续承接 `oauthFlowTimeoutEnabled` 与 `stepExecutionRangeByFlow`,`kiro` flow 则走独立的 device auth 与 `kiro.rs` 上传链路。
|
||||||
- `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 与余额响应解析。
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
- `background/steps/paypal-approve.js`:Plus 模式第 8 步实现,负责驱动 PayPal 登录、关闭可见通行密钥提示、点击“同意并继续”,并等待授权流程离开 PayPal 页面。
|
- `background/steps/paypal-approve.js`:Plus 模式第 8 步实现,负责驱动 PayPal 登录、关闭可见通行密钥提示、点击“同意并继续”,并等待授权流程离开 PayPal 页面。
|
||||||
- `background/steps/platform-verify.js`:平台回调验证实现,普通模式为步骤 10,Plus 模式为可见步骤 13;负责 CPA 页面回调验证、SUB2API 管理接口 callback code/state 交换与账号创建,以及 Codex2API 的协议式 callback code/state 交换,所有平台验证日志和完成信号都按当前可见步骤上报。
|
- `background/steps/platform-verify.js`:平台回调验证实现,普通模式为步骤 10,Plus 模式为可见步骤 13;负责 CPA 页面回调验证、SUB2API 管理接口 callback code/state 交换与账号创建,以及 Codex2API 的协议式 callback code/state 交换,所有平台验证日志和完成信号都按当前可见步骤上报。
|
||||||
- `background/steps/plus-return-confirm.js`:Plus 模式第 9 步实现,负责等待 PayPal 授权后回跳到 ChatGPT / OpenAI 页面,页面加载完成后固定等待 1 秒再完成。
|
- `background/steps/plus-return-confirm.js`:Plus 模式第 9 步实现,负责等待 PayPal 授权后回跳到 ChatGPT / OpenAI 页面,页面加载完成后固定等待 1 秒再完成。
|
||||||
- `background/steps/registry.js`:步骤注册表工厂,负责用稳定的步骤元数据映射到执行器。
|
- `background/steps/registry.js`:节点注册表工厂,负责保留 `flowId / nodeId / displayOrder / executeKey` 等稳定节点元数据,并把 flow-specific workflow node 映射到后台执行器。
|
||||||
- `background/steps/submit-signup-email.js`:步骤 2 实现,负责按 `signupMethod` 在邮箱注册与手机号注册之间分发;切回已有注册页标签时会先等待加载完成并额外稳定 3 秒,再检查注册入口状态;邮箱分支会先点击官网“免费注册”再提交邮箱并清理旧手机号注册运行态,手机号分支会先点击官网“免费注册”并切到手机号注册入口,确认手机号输入页就绪后优先复用已有 signup activation,其次使用手动运行态手机号,最后才申请接码平台号码并提交,最后根据落地页判断是否跳过后续密码步骤。
|
- `background/steps/submit-signup-email.js`:步骤 2 实现,负责按 `signupMethod` 在邮箱注册与手机号注册之间分发;切回已有注册页标签时会先等待加载完成并额外稳定 3 秒,再检查注册入口状态;邮箱分支会先点击官网“免费注册”再提交邮箱并清理旧手机号注册运行态,手机号分支会先点击官网“免费注册”并切到手机号注册入口,确认手机号输入页就绪后优先复用已有 signup activation,其次使用手动运行态手机号,最后才申请接码平台号码并提交,最后根据落地页判断是否跳过后续密码步骤。
|
||||||
|
|
||||||
## `content/`
|
## `content/`
|
||||||
@@ -109,7 +109,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`:共享步骤元数据,前后台共同使用,用于动态渲染和步骤注册;当前同时提供普通 10 步定义与 Plus 模式 13 步定义,并会按 `plusPaymentMethod / signupMethod` 动态解析实际步骤标题。
|
- `data/step-definitions.js`:共享步骤元数据,前后台共同使用,用于动态渲染、workflow 生成和步骤注册;当前 `openai` flow 会按 `plusPaymentMethod / signupMethod` 动态生成普通/Plus workflow,`kiro` flow 则提供独立的 3 节点 device auth workflow。
|
||||||
|
|
||||||
## `docs/`
|
## `docs/`
|
||||||
|
|
||||||
@@ -139,6 +139,13 @@
|
|||||||
- `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` 会消费本次返回的验证码记录,避免下次重复读取。
|
||||||
- `scripts/hotmail_helper.py`:本地 helper 服务,负责通过本地接口协助 Hotmail 获取邮件和验证码,并提供账号记录 JSON 快照同步接口;旧的文本追加接口仍保留作兼容。
|
- `scripts/hotmail_helper.py`:本地 helper 服务,负责通过本地接口协助 Hotmail 获取邮件和验证码,并提供账号记录 JSON 快照同步接口;旧的文本追加接口仍保留作兼容。
|
||||||
|
|
||||||
|
## `shared/`
|
||||||
|
|
||||||
|
- `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/settings-schema.js`:统一设置 schema 与归一化层,负责把旧平铺字段与新 `flows.*` 结构互转,并维护 `activeFlowId`、`kiroSourceId`、`kiroRs*`、`stepExecutionRangeByFlow` 等跨 flow 配置。
|
||||||
|
- `shared/source-registry.js`:运行时来源注册表,负责合并 flow runtime source 与共享 mail source,统一 source family、driver、URL 归属和 cleanup owner 判定。
|
||||||
|
|
||||||
## `sidepanel/`
|
## `sidepanel/`
|
||||||
|
|
||||||
- `sidepanel/account-pool-ui.js`:侧边栏号池表单共享 UI helper,负责 Hotmail / 2925 账号池新增表单的显隐、头部“添加账号/取消添加”按钮文案切换、清空表单与首字段聚焦。
|
- `sidepanel/account-pool-ui.js`:侧边栏号池表单共享 UI helper,负责 Hotmail / 2925 账号池新增表单的显隐、头部“添加账号/取消添加”按钮文案切换、清空表单与首字段聚焦。
|
||||||
@@ -178,6 +185,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 / region` 专属字段,并只显示 Kiro 运行状态与共享邮箱/IP 代理配置。
|
||||||
|
|
||||||
## `tests/`
|
## `tests/`
|
||||||
|
|
||||||
@@ -193,6 +201,7 @@
|
|||||||
- `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-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 在保留手机号身份场景下的共享持久化传参。
|
||||||
@@ -214,13 +223,15 @@
|
|||||||
- `tests/background-registration-email-state.test.js`:测试注册邮箱运行态共享模块对 `registrationEmailState` 的维护,覆盖清空当前邮箱时保留上一比较基线、Duck 生成前优先使用侧栏当前可见邮箱作为基线,以及流程邮箱写入时保留手机号身份的共享纯函数。
|
- `tests/background-registration-email-state.test.js`:测试注册邮箱运行态共享模块对 `registrationEmailState` 的维护,覆盖清空当前邮箱时保留上一比较基线、Duck 生成前优先使用侧栏当前可见邮箱作为基线,以及流程邮箱写入时保留手机号身份的共享纯函数。
|
||||||
- `tests/background-signup-flow-module.test.js`:测试注册页辅助模块已接入且导出工厂。
|
- `tests/background-signup-flow-module.test.js`:测试注册页辅助模块已接入且导出工厂。
|
||||||
- `tests/background-skip-step-linking.test.js`:测试手动跳过步骤 1 时,会级联跳过步骤 2~6,并仅跳过其中未完成、未运行且仍在当前执行范围内的步骤。
|
- `tests/background-skip-step-linking.test.js`:测试手动跳过步骤 1 时,会级联跳过步骤 2~6,并仅跳过其中未完成、未运行且仍在当前执行范围内的步骤。
|
||||||
|
- `tests/background-settings-schema-persistence.test.js`:测试后台设置持久化已接入统一 settings schema,并覆盖 flow-aware 配置与旧字段兼容回写。
|
||||||
- `tests/background-step-execution-range.test.js`:测试 `stepExecutionRangeByFlow` 对 codex/openai flow 的归一化、节点执行范围过滤、后台执行禁用拦截,以及保存进度只统计允许执行节点。
|
- `tests/background-step-execution-range.test.js`:测试 `stepExecutionRangeByFlow` 对 codex/openai flow 的归一化、节点执行范围过滤、后台执行禁用拦截,以及保存进度只统计允许执行节点。
|
||||||
- `tests/background-signup-step2-branching.test.js`:测试在 Gmail / 2925 模式下,已有兼容别名邮箱时应直接复用,不应再次重生成,并覆盖生成阶段已落库邮箱不会被流程层重复覆盖,以及 Step 8 `add-email` 复用已有邮箱时会委托共享注册邮箱状态持久化保留手机号身份。
|
- `tests/background-signup-step2-branching.test.js`:测试在 Gmail / 2925 模式下,已有兼容别名邮箱时应直接复用,不应再次重生成,并覆盖生成阶段已落库邮箱不会被流程层重复覆盖,以及 Step 8 `add-email` 复用已有邮箱时会委托共享注册邮箱状态持久化保留手机号身份。
|
||||||
- `tests/background-step-modules.test.js`:测试步骤模块文件都已由后台入口加载。
|
- `tests/background-step-modules.test.js`:测试步骤模块文件都已由后台入口加载。
|
||||||
|
- `tests/background-step-node-registry-module.test.js`:测试节点注册表模块已接入且导出工厂,并覆盖节点元数据保留与 display order 执行顺序。
|
||||||
- `tests/background-step5-submit-short-circuit.test.js`:测试步骤 5 会把生成好的资料直接转发给内容脚本,并依赖完成信号收尾。
|
- `tests/background-step5-submit-short-circuit.test.js`:测试步骤 5 会把生成好的资料直接转发给内容脚本,并依赖完成信号收尾。
|
||||||
- `tests/background-step6-retry-limit.test.js`:测试步骤 6 的注册成功等待、可选 cookies 清理开关,以及步骤 7 的有限重试上限与 `add-phone` 命中后的立即跳出行为。
|
- `tests/background-step6-retry-limit.test.js`:测试步骤 6 的注册成功等待、可选 cookies 清理开关,以及步骤 7 的有限重试上限与 `add-phone` 命中后的立即跳出行为。
|
||||||
- `tests/background-step7-recovery.test.js`:测试步骤 8 获取登录验证码后直接提交(不再回放步骤 7),覆盖邮箱验证码页、真实手机验证码页、`add-email` 绑定邮箱后收码、`email_in_use / max_check_attempts` 当前步骤恢复、2925 固定回看窗口与关闭重发间隔。
|
- `tests/background-step7-recovery.test.js`:测试步骤 8 获取登录验证码后直接提交(不再回放步骤 7),覆盖邮箱验证码页、真实手机验证码页、`add-email` 绑定邮箱后收码、`email_in_use / max_check_attempts` 当前步骤恢复、2925 固定回看窗口与关闭重发间隔。
|
||||||
- `tests/background-step-registry.test.js`:测试后台步骤注册表和共享步骤定义已接入。
|
- `tests/background-step-registry.test.js`:测试后台步骤注册表、Kiro 执行器映射与共享 workflow 定义已接入。
|
||||||
- `tests/background-tab-runtime-module.test.js`:测试标签运行时模块已接入且导出工厂,并覆盖等待标签完成、等待标签稳定完成,以及等待过程中的 Stop 中断行为。
|
- `tests/background-tab-runtime-module.test.js`:测试标签运行时模块已接入且导出工厂,并覆盖等待标签完成、等待标签稳定完成,以及等待过程中的 Stop 中断行为。
|
||||||
- `tests/background-verification-flow-module.test.js`:测试验证码流程模块已接入且导出工厂。
|
- `tests/background-verification-flow-module.test.js`:测试验证码流程模块已接入且导出工厂。
|
||||||
- `tests/phone-auth-country-match.test.js`:测试认证页手机号国家选择在页面本地化时,仍能用 HeroSMS 的英文国家名匹配对应国家选项。
|
- `tests/phone-auth-country-match.test.js`:测试认证页手机号国家选择在页面本地化时,仍能用 HeroSMS 的英文国家名匹配对应国家选项。
|
||||||
@@ -253,8 +264,12 @@
|
|||||||
- `tests/sidepanel-contribution-button.test.js`:测试侧边栏顶部 `贡献` 按钮的 HTML 接线、更新提示气泡接线,以及相关脚本加载顺序。
|
- `tests/sidepanel-contribution-button.test.js`:测试侧边栏顶部 `贡献` 按钮的 HTML 接线、更新提示气泡接线,以及相关脚本加载顺序。
|
||||||
- `tests/sidepanel-account-records-manager.test.js`:测试侧边栏账号记录覆盖层的 HTML 接入、helper 地址归一化与 manager 渲染逻辑。
|
- `tests/sidepanel-account-records-manager.test.js`:测试侧边栏账号记录覆盖层的 HTML 接入、helper 地址归一化与 manager 渲染逻辑。
|
||||||
- `tests/contribution-content-update-service.test.js`:测试贡献内容更新服务对公开摘要接口的归一化、版本提取与失败回退缓存逻辑。
|
- `tests/contribution-content-update-service.test.js`:测试贡献内容更新服务对公开摘要接口的归一化、版本提取与失败回退缓存逻辑。
|
||||||
|
- `tests/flow-capabilities-module.test.js`:测试 flow 能力矩阵模块对来源作用域、显隐能力和贡献模式边界的归一化。
|
||||||
|
- `tests/flow-registry-settings-schema.test.js`:测试 flow registry 与 settings schema 之间的默认值、来源映射和 flow-specific 配置结构保持一致。
|
||||||
- `tests/sidepanel-custom-email-pool.test.js`:测试侧边栏自定义邮箱池、自定义邮箱服务号池的 HTML 接线,以及邮箱池长度与自动轮数之间的联动规则。
|
- `tests/sidepanel-custom-email-pool.test.js`:测试侧边栏自定义邮箱池、自定义邮箱服务号池的 HTML 接线,以及邮箱池长度与自动轮数之间的联动规则。
|
||||||
- `tests/sidepanel-contribution-mode.test.js`:测试侧边栏贡献模式的 HTML 接线、runtime-only 设置保护,以及贡献模式 manager 复用主自动流启动、状态轮询和退出清理逻辑。
|
- `tests/sidepanel-contribution-mode.test.js`:测试侧边栏贡献模式的 HTML 接线、runtime-only 设置保护,以及贡献模式 manager 复用主自动流启动、状态轮询和退出清理逻辑。
|
||||||
|
- `tests/sidepanel-contribution-mode-flow-scope.test.js`:测试贡献模式只在 `openai` flow 内生效,不会污染 Kiro flow 的来源与面板作用域。
|
||||||
|
- `tests/sidepanel-flow-source-registry.test.js`:测试 sidepanel 的 flow/source 选择器、Kiro 来源切换与显隐分组接线。
|
||||||
- `tests/sidepanel-auto-run-content-refresh.test.js`:测试点击“自动”时会先冻结当前轮数并刷新贡献内容更新摘要,且刷新失败或启动前状态回灌不会阻塞或改写自动流程启动目标。
|
- `tests/sidepanel-auto-run-content-refresh.test.js`:测试点击“自动”时会先冻结当前轮数并刷新贡献内容更新摘要,且刷新失败或启动前状态回灌不会阻塞或改写自动流程启动目标。
|
||||||
- `tests/sidepanel-icloud-manager.test.js`:测试侧边栏 iCloud 管理器模块接线与空态渲染。
|
- `tests/sidepanel-icloud-manager.test.js`:测试侧边栏 iCloud 管理器模块接线与空态渲染。
|
||||||
- `tests/sidepanel-icloud-provider.test.js`:测试侧边栏 iCloud 登录地址解析、目标邮箱类型 / 转发邮箱 provider 保存、回显和显隐联动。
|
- `tests/sidepanel-icloud-provider.test.js`:测试侧边栏 iCloud 登录地址解析、目标邮箱类型 / 转发邮箱 provider 保存、回显和显隐联动。
|
||||||
@@ -265,7 +280,8 @@
|
|||||||
- `tests/signup-entry-diagnostics.test.js`:测试注册入口与密码页诊断快照输出,包括密码页错误文案字段。
|
- `tests/signup-entry-diagnostics.test.js`:测试注册入口与密码页诊断快照输出,包括密码页错误文案字段。
|
||||||
- `tests/signup-step2-email-switch.test.js`:测试 Step 2 在手机号输入模式下切回邮箱输入模式、本地化邮箱输入框识别、官网注册入口点击等待与最多 5 次重试,以及手机号注册时国家下拉框可视区号同步。
|
- `tests/signup-step2-email-switch.test.js`:测试 Step 2 在手机号输入模式下切回邮箱输入模式、本地化邮箱输入框识别、官网注册入口点击等待与最多 5 次重试,以及手机号注册时国家下拉框可视区号同步。
|
||||||
- `tests/signup-page-tab-cleanup.test.js`:测试注册页来源标签的冲突清理逻辑。
|
- `tests/signup-page-tab-cleanup.test.js`:测试注册页来源标签的冲突清理逻辑。
|
||||||
- `tests/step-definitions-module.test.js`:测试共享步骤定义模块及侧边栏脚本加载顺序。
|
- `tests/source-registry-module.test.js`:测试共享来源注册表模块已接入且导出工厂,并覆盖 flow runtime source 与共享 mail source 的合并。
|
||||||
|
- `tests/step-definitions-module.test.js`:测试共享步骤定义模块、flow-specific workflow 输出与侧边栏脚本加载顺序。
|
||||||
- `tests/step3-direct-complete.test.js`:测试步骤 3 在提交前先完成上报,并保留延迟提交回调的可执行性验证。
|
- `tests/step3-direct-complete.test.js`:测试步骤 3 在提交前先完成上报,并保留延迟提交回调的可执行性验证。
|
||||||
- `tests/step5-age-consent.test.js`:测试步骤 5 在年龄页会先勾选顶部“我同意以下所有各项”复选框,再点击提交。
|
- `tests/step5-age-consent.test.js`:测试步骤 5 在年龄页会先勾选顶部“我同意以下所有各项”复选框,再点击提交。
|
||||||
- `tests/step5-direct-complete.test.js`:测试步骤 5 在资料页点击提交后立即完成当前步骤。
|
- `tests/step5-direct-complete.test.js`:测试步骤 5 在资料页点击提交后立即完成当前步骤。
|
||||||
|
|||||||
Reference in New Issue
Block a user