refactor flows into canonical runtime architecture

This commit is contained in:
QLHazyCoder
2026-05-21 07:21:34 +08:00
parent e2485d2e64
commit a7b35ee11a
167 changed files with 9034 additions and 3032 deletions
+626
View File
@@ -0,0 +1,626 @@
(function attachMultiPageFlowCapabilities(root, factory) {
root.MultiPageFlowCapabilities = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createFlowCapabilitiesModule() {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
const contributionRegistryApi = rootScope.MultiPageContributionRegistry || {};
const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {};
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
const DEFAULT_OPENAI_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_TARGET_ID || 'cpa';
const SIGNUP_METHOD_EMAIL = 'email';
const SIGNUP_METHOD_PHONE = 'phone';
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
const VALID_OPENAI_TARGET_IDS = typeof flowRegistryApi.getTargetDefinitions === 'function'
? Object.keys(flowRegistryApi.getTargetDefinitions('openai') || {})
: (Array.isArray(flowRegistryApi.OPENAI_TARGET_IDS)
? flowRegistryApi.OPENAI_TARGET_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({
supportsEmailSignup: true,
supportsPhoneSignup: false,
supportsPhoneVerificationSettings: false,
supportsPlusMode: false,
supportsContributionMode: false,
supportsAccountContribution: false,
supportsOpenAiOAuthContribution: false,
contributionAdapterIds: [],
supportedTargetIds: [],
supportsLuckmail: false,
supportsOauthTimeoutBudget: false,
canSwitchFlow: true,
stepDefinitionMode: 'default',
targetSelectorLabel: '来源',
});
const FLOW_CAPABILITIES = Object.freeze(
Object.fromEntries(
(typeof flowRegistryApi.getRegisteredFlowIds === 'function'
? flowRegistryApi.getRegisteredFlowIds()
: [DEFAULT_FLOW_ID]
).map((flowId) => [
flowId,
Object.freeze({
...DEFAULT_FLOW_CAPABILITIES,
...(typeof flowRegistryApi.getFlowCapabilities === 'function'
? flowRegistryApi.getFlowCapabilities(flowId)
: {}),
}),
])
)
);
const DEFAULT_TARGET_CAPABILITIES = Object.freeze({
supportsPhoneSignup: true,
requiresPhoneSignupWarning: false,
supportedPlusAccountAccessStrategies: Object.freeze([PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]),
});
const MODE_SWITCH_RELEVANT_KEYS = Object.freeze([
'activeFlowId',
'accountContributionEnabled',
'phoneVerificationEnabled',
'plusModeEnabled',
'signupMethod',
'plusAccountAccessStrategy',
'targetId',
]);
const OPENAI_TARGET_CAPABILITIES = Object.freeze(
typeof flowRegistryApi.getTargetCapabilityDefinitions === 'function'
? (flowRegistryApi.getTargetCapabilityDefinitions('openai') || {})
: {}
);
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
if (typeof flowRegistryApi.normalizeFlowId === 'function') {
return flowRegistryApi.normalizeFlowId(value, fallback);
}
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) {
return normalized;
}
return normalizeFlowId(fallback, DEFAULT_FLOW_ID);
}
function isRegisteredFlowId(flowId = '') {
const normalized = String(flowId || '').trim().toLowerCase();
return Boolean(normalized) && REGISTERED_FLOW_ID_SET.has(normalized);
}
function normalizeOpenAiTargetId(value = '', fallback = DEFAULT_OPENAI_TARGET_ID) {
const normalized = String(value || '').trim().toLowerCase();
if (VALID_OPENAI_TARGET_IDS.includes(normalized)) {
return normalized;
}
const fallbackValue = String(fallback || '').trim().toLowerCase();
return VALID_OPENAI_TARGET_IDS.includes(fallbackValue)
? fallbackValue
: DEFAULT_OPENAI_TARGET_ID;
}
function normalizeSignupMethod(value = '') {
return String(value || '').trim().toLowerCase() === SIGNUP_METHOD_PHONE
? SIGNUP_METHOD_PHONE
: SIGNUP_METHOD_EMAIL;
}
function normalizePlusAccountAccessStrategy(value = '') {
const normalized = String(value || '').trim().toLowerCase();
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
}
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION;
}
return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
}
function getPlusAccountSessionStrategyForTarget(targetId = '') {
const normalizedTargetId = String(targetId || '').trim().toLowerCase();
if (normalizedTargetId === 'sub2api') {
return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
}
if (normalizedTargetId === 'cpa') {
return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION;
}
return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
}
function normalizePlusAccountAccessStrategyForTarget(value = '', targetId = '') {
const normalizedStrategy = normalizePlusAccountAccessStrategy(value);
if (
normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|| normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION
) {
return getPlusAccountSessionStrategyForTarget(targetId);
}
return normalizedStrategy;
}
function normalizeOpenAiTargetList(values = []) {
if (!Array.isArray(values)) {
return [];
}
const seen = new Set();
const normalized = [];
values.forEach((value) => {
const targetId = normalizeOpenAiTargetId(value, '');
if (!targetId || seen.has(targetId)) {
return;
}
seen.add(targetId);
normalized.push(targetId);
});
return normalized;
}
function getTargetLabel(flowId = DEFAULT_FLOW_ID, targetId = '') {
if (
isRegisteredFlowId(flowId)
&& typeof flowRegistryApi.getTargetLabel === 'function'
) {
return flowRegistryApi.getTargetLabel(flowId, targetId);
}
const normalized = String(targetId || '').trim().toLowerCase();
if (normalized === 'sub2api') {
return 'SUB2API';
}
if (normalized === 'codex2api') {
return 'Codex2API';
}
if (normalized === 'cpa') {
return 'CPA';
}
return normalized || String(targetId || '').trim();
}
function createFlowCapabilityRegistry(deps = {}) {
const {
defaultFlowCapabilities = DEFAULT_FLOW_CAPABILITIES,
defaultFlowId = DEFAULT_FLOW_ID,
defaultTargetCapabilities = DEFAULT_TARGET_CAPABILITIES,
flowCapabilities = FLOW_CAPABILITIES,
targetCapabilities = OPENAI_TARGET_CAPABILITIES,
} = deps;
const settingsSchema = settingsSchemaApi.createSettingsSchema
? settingsSchemaApi.createSettingsSchema({
defaultFlowId,
})
: null;
function getFlowCapabilities(flowId) {
const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId);
const entry = flowCapabilities[normalizedFlowId] || null;
const registryAdapterIds = typeof contributionRegistryApi.getContributionAdapterIds === 'function'
? contributionRegistryApi.getContributionAdapterIds(normalizedFlowId)
: [];
const contributionAdapterIds = registryAdapterIds.length
? registryAdapterIds
: (Array.isArray(entry?.contributionAdapterIds)
? entry.contributionAdapterIds.map((value) => String(value || '').trim()).filter(Boolean)
: []);
const supportedTargetIds = normalizedFlowId === 'openai'
? normalizeOpenAiTargetList(
entry?.supportedTargetIds || defaultFlowCapabilities.supportedTargetIds
)
: (Array.isArray(entry?.supportedTargetIds)
? entry.supportedTargetIds.map((value) => String(value || '').trim().toLowerCase()).filter(Boolean)
: []);
return {
...defaultFlowCapabilities,
...(entry || {}),
supportedTargetIds,
contributionAdapterIds,
supportsAccountContribution: Boolean(entry?.supportsAccountContribution || contributionAdapterIds.length > 0),
};
}
function getOpenAiTargetCapabilities(targetId) {
const normalizedTargetId = normalizeOpenAiTargetId(targetId);
return {
...defaultTargetCapabilities,
...(targetCapabilities[normalizedTargetId] || {}),
};
}
function normalizeRequestedTargetId(activeFlowId, state = {}, options = {}) {
const schemaTargetId = settingsSchema?.getSelectedTargetId
? settingsSchema.getSelectedTargetId({
...state,
activeFlowId,
}, activeFlowId)
: '';
const rawTargetId = options?.targetId
?? options?.selectedTargetId
?? state?.targetId
?? schemaTargetId
?? flowRegistryApi.getDefaultTargetId?.(activeFlowId)
?? '';
if (
isRegisteredFlowId(activeFlowId)
&& typeof flowRegistryApi.normalizeTargetId === 'function'
) {
return flowRegistryApi.normalizeTargetId(
activeFlowId,
rawTargetId,
flowRegistryApi.getDefaultTargetId?.(activeFlowId)
);
}
return String(rawTargetId || '').trim().toLowerCase();
}
function normalizeChangedKeys(values = []) {
const list = Array.isArray(values) ? values : [];
const seen = new Set();
const normalized = [];
list.forEach((value) => {
const key = String(value || '').trim();
if (!key || seen.has(key)) {
return;
}
seen.add(key);
normalized.push(key);
});
return normalized;
}
function resolveEffectiveTargetId(activeFlowId, state = {}, requestedTargetId = DEFAULT_OPENAI_TARGET_ID) {
return normalizeRequestedTargetId(activeFlowId, state, {
targetId: requestedTargetId,
});
}
function resolveSidepanelCapabilities(options = {}) {
const state = options?.state || {};
const activeFlowId = normalizeCapabilityFlowId(
options?.activeFlowId ?? state?.activeFlowId,
defaultFlowId
);
const flowState = getFlowCapabilities(activeFlowId);
const requestedTargetId = normalizeRequestedTargetId(
activeFlowId,
state,
options
);
const supportedTargetIds = activeFlowId === 'openai'
? normalizeOpenAiTargetList(flowState.supportedTargetIds)
: (Array.isArray(flowState.supportedTargetIds)
? flowState.supportedTargetIds.slice()
: []);
const targetSupported = supportedTargetIds.length === 0
? true
: supportedTargetIds.includes(requestedTargetId);
const effectiveTargetId = targetSupported
? requestedTargetId
: (supportedTargetIds[0] || requestedTargetId);
const targetState = activeFlowId === 'openai'
? getOpenAiTargetCapabilities(effectiveTargetId)
: defaultTargetCapabilities;
const runtimeLocks = {
autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked),
accountContribution: Boolean(flowState.supportsAccountContribution) && Boolean(state?.accountContributionEnabled),
phoneVerificationEnabled: activeFlowId === 'openai' && flowState.supportsPhoneVerificationSettings && Boolean(state?.phoneVerificationEnabled),
plusModeEnabled: activeFlowId === 'openai' && flowState.supportsPlusMode && Boolean(state?.plusModeEnabled),
settingsMenuLocked: Boolean(options?.settingsMenuLocked ?? state?.settingsMenuLocked),
};
const effectiveSignupMethods = [];
if (flowState.supportsEmailSignup !== false) {
effectiveSignupMethods.push(SIGNUP_METHOD_EMAIL);
}
const canSelectPhoneSignup = activeFlowId === 'openai'
&& Boolean(flowState.supportsPhoneSignup)
&& Boolean(targetState.supportsPhoneSignup)
&& runtimeLocks.phoneVerificationEnabled
&& !runtimeLocks.plusModeEnabled
&& !runtimeLocks.accountContribution;
if (canSelectPhoneSignup) {
effectiveSignupMethods.push(SIGNUP_METHOD_PHONE);
}
if (!effectiveSignupMethods.length) {
effectiveSignupMethods.push(SIGNUP_METHOD_EMAIL);
}
const requestedSignupMethod = normalizeSignupMethod(
options?.signupMethod ?? state?.signupMethod
);
const effectiveSignupMethod = requestedSignupMethod === SIGNUP_METHOD_PHONE && canSelectPhoneSignup
? SIGNUP_METHOD_PHONE
: (effectiveSignupMethods.includes(SIGNUP_METHOD_EMAIL)
? SIGNUP_METHOD_EMAIL
: effectiveSignupMethods[0]);
const requestedPlusAccountAccessStrategy = normalizePlusAccountAccessStrategyForTarget(
options?.plusAccountAccessStrategy ?? state?.plusAccountAccessStrategy,
effectiveTargetId
);
const targetPlusAccountAccessStrategies = (Array.isArray(targetState.supportedPlusAccountAccessStrategies)
&& targetState.supportedPlusAccountAccessStrategies.length > 0
? targetState.supportedPlusAccountAccessStrategies
: [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH])
.map(normalizePlusAccountAccessStrategy)
.filter((strategy, index, strategies) => strategy && strategies.indexOf(strategy) === index);
const availablePlusAccountAccessStrategies = activeFlowId === 'openai'
&& Boolean(flowState.supportsPlusMode)
&& Boolean(runtimeLocks.plusModeEnabled)
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
? (runtimeLocks.accountContribution
? [PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION]
: targetPlusAccountAccessStrategies)
: [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH];
const effectivePlusAccountAccessStrategy = runtimeLocks.accountContribution
&& runtimeLocks.plusModeEnabled
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
: availablePlusAccountAccessStrategies.includes(requestedPlusAccountAccessStrategy)
? requestedPlusAccountAccessStrategy
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
const canEditPlusAccountAccessStrategy = activeFlowId === 'openai'
&& Boolean(flowState.supportsPlusMode)
&& Boolean(runtimeLocks.plusModeEnabled)
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
&& !runtimeLocks.accountContribution
&& availablePlusAccountAccessStrategies.length > 1;
const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function'
&& isRegisteredFlowId(activeFlowId)
? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveTargetId)
: [];
return {
activeFlowId,
canShowContributionMode: Boolean(flowState.supportsAccountContribution),
canShowLuckmail: activeFlowId === 'openai' && Boolean(flowState.supportsLuckmail),
canShowPhoneSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPhoneVerificationSettings),
canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode),
canSwitchFlow: Boolean(flowState.canSwitchFlow),
canEditPlusAccountAccessStrategy,
canUsePhoneSignup: canSelectPhoneSignup,
canUseSelectedTarget: targetSupported,
effectivePlusAccountAccessStrategy,
effectiveSignupMethod,
effectiveSignupMethods,
effectiveTargetId,
flowCapabilities: flowState,
panelCapabilities: targetState,
requestedPlusAccountAccessStrategy,
requestedSignupMethod,
requestedTargetId,
runtimeLocks,
availablePlusAccountAccessStrategies,
shouldWarnCpaPhoneSignup: effectiveSignupMethod === SIGNUP_METHOD_PHONE
&& Boolean(targetState.requiresPhoneSignupWarning),
stepDefinitionOptions: {
activeFlowId,
targetId: effectiveTargetId,
plusAccountAccessStrategy: effectivePlusAccountAccessStrategy,
plusModeEnabled: runtimeLocks.plusModeEnabled,
signupMethod: effectiveSignupMethod,
},
supportedPanelModes: supportedTargetIds,
supportedTargetIds,
targetCapabilities: targetState,
targetId: effectiveTargetId,
visibleGroupIds,
};
}
function buildPhoneSignupValidationError(capabilityState = {}) {
const flowState = capabilityState.flowCapabilities || {};
const targetState = capabilityState.targetCapabilities || {};
const runtimeLocks = capabilityState.runtimeLocks || {};
if (!flowState.supportsPhoneSignup) {
return {
code: 'phone_signup_flow_unsupported',
message: '当前 flow 不支持手机号注册。',
};
}
if (!targetState.supportsPhoneSignup) {
return {
code: 'phone_signup_panel_unsupported',
message: `当前来源 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 不支持手机号注册。`,
};
}
if (!runtimeLocks.phoneVerificationEnabled) {
return {
code: 'phone_signup_phone_verification_disabled',
message: '请先开启接码设置后再使用手机号注册。',
};
}
if (runtimeLocks.plusModeEnabled) {
return {
code: 'phone_signup_plus_mode_locked',
message: 'Plus 模式开启时不能使用手机号注册。',
};
}
if (runtimeLocks.accountContribution) {
return {
code: 'phone_signup_contribution_mode_locked',
message: '贡献模式开启时不能使用手机号注册。',
};
}
return {
code: 'phone_signup_unavailable',
message: '当前设置暂不支持手机号注册。',
};
}
function validateAutoRunStart(options = {}) {
const state = options?.state || {};
const capabilityState = resolveSidepanelCapabilities(options);
const errors = [];
if (
Array.isArray(capabilityState.supportedTargetIds)
&& capabilityState.supportedTargetIds.length > 0
&& capabilityState.canUseSelectedTarget === false
) {
errors.push({
code: 'panel_mode_unsupported',
message: `当前 flow 不支持 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 来源。`,
});
}
if (Boolean(state?.plusModeEnabled) && !capabilityState.flowCapabilities?.supportsPlusMode) {
errors.push({
code: 'plus_mode_unsupported',
message: '当前 flow 不支持 Plus 模式。',
});
}
if (Boolean(state?.accountContributionEnabled) && !capabilityState.flowCapabilities?.supportsAccountContribution) {
errors.push({
code: 'contribution_mode_unsupported',
message: '当前 flow 不支持贡献模式。',
});
}
if (
capabilityState.requestedSignupMethod === SIGNUP_METHOD_PHONE
&& capabilityState.effectiveSignupMethod !== SIGNUP_METHOD_PHONE
) {
errors.push(buildPhoneSignupValidationError(capabilityState));
}
return {
ok: errors.length === 0,
errors,
capabilityState,
};
}
function validateModeSwitch(options = {}) {
const state = options?.state || {};
const changedKeys = normalizeChangedKeys(
options?.changedKeys !== undefined
? options.changedKeys
: Object.keys(state || {})
);
const changedKeySet = new Set(changedKeys);
const capabilityState = resolveSidepanelCapabilities(options);
const errors = [];
const normalizedUpdates = {};
const flowState = capabilityState.flowCapabilities || {};
const requestedPhoneSignup = capabilityState.requestedSignupMethod === SIGNUP_METHOD_PHONE;
const shouldReconcileSignupMethod = MODE_SWITCH_RELEVANT_KEYS.some((key) => changedKeySet.has(key));
if (
changedKeySet.has('targetId')
&& Array.isArray(capabilityState.supportedTargetIds)
&& capabilityState.supportedTargetIds.length > 0
&& capabilityState.canUseSelectedTarget === false
) {
normalizedUpdates.targetId = capabilityState.effectiveTargetId;
errors.push({
code: 'panel_mode_unsupported',
message: `当前 flow 不支持 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 来源。`,
});
}
if (changedKeySet.has('plusModeEnabled') && Boolean(state?.plusModeEnabled) && !flowState.supportsPlusMode) {
normalizedUpdates.plusModeEnabled = false;
errors.push({
code: 'plus_mode_unsupported',
message: '当前 flow 不支持 Plus 模式。',
});
}
if (
changedKeySet.has('accountContributionEnabled')
&& Boolean(state?.accountContributionEnabled)
&& !flowState.supportsAccountContribution
) {
normalizedUpdates.accountContributionEnabled = false;
errors.push({
code: 'contribution_mode_unsupported',
message: '当前 flow 不支持贡献模式。',
});
}
if (
changedKeySet.has('phoneVerificationEnabled')
&& Boolean(state?.phoneVerificationEnabled)
&& !flowState.supportsPhoneVerificationSettings
) {
normalizedUpdates.phoneVerificationEnabled = false;
errors.push({
code: 'phone_verification_unsupported',
message: '当前 flow 不支持接码设置。',
});
}
if (
shouldReconcileSignupMethod
&& requestedPhoneSignup
&& capabilityState.effectiveSignupMethod !== SIGNUP_METHOD_PHONE
) {
normalizedUpdates.signupMethod = capabilityState.effectiveSignupMethod;
errors.push(buildPhoneSignupValidationError(capabilityState));
}
return {
ok: errors.length === 0,
changedKeys,
capabilityState,
errors,
normalizedUpdates,
};
}
function canUsePhoneSignup(state = {}) {
return resolveSidepanelCapabilities({ state }).canUsePhoneSignup;
}
function resolveSignupMethod(state = {}, signupMethod = undefined) {
return resolveSidepanelCapabilities({
signupMethod,
state,
}).effectiveSignupMethod;
}
return {
canUsePhoneSignup,
getFlowCapabilities,
getOpenAiTargetCapabilities,
normalizeFlowId,
normalizeOpenAiTargetId,
normalizePlusAccountAccessStrategy,
normalizeSignupMethod,
resolveSidepanelCapabilities,
resolveSignupMethod,
validateAutoRunStart,
validateModeSwitch,
};
}
return {
createFlowCapabilityRegistry,
DEFAULT_FLOW_CAPABILITIES,
DEFAULT_FLOW_ID,
DEFAULT_TARGET_CAPABILITIES,
DEFAULT_OPENAI_TARGET_ID,
FLOW_CAPABILITIES,
OPENAI_TARGET_CAPABILITIES,
PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH,
PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION,
SIGNUP_METHOD_EMAIL,
SIGNUP_METHOD_PHONE,
VALID_OPENAI_TARGET_IDS,
normalizeFlowId,
normalizeOpenAiTargetId,
normalizePlusAccountAccessStrategy,
normalizeSignupMethod,
};
});
+300
View File
@@ -0,0 +1,300 @@
(function attachMultiPageFlowRegistry(root, factory) {
root.MultiPageFlowRegistry = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
const flowsIndexApi = rootScope.MultiPageFlowsIndex || {};
const DEFAULT_FLOW_ID = 'openai';
const SHARED_SERVICE_IDS = Object.freeze(['account', 'email', 'proxy']);
const DEFAULT_FLOW_CAPABILITIES = Object.freeze({
supportsEmailSignup: true,
supportsPhoneSignup: false,
supportsPhoneVerificationSettings: false,
supportsPlusMode: false,
supportsContributionMode: false,
supportsAccountContribution: false,
supportsOpenAiOAuthContribution: false,
contributionAdapterIds: [],
supportedTargetIds: [],
supportsLuckmail: false,
supportsOauthTimeoutBudget: false,
canSwitchFlow: true,
stepDefinitionMode: 'default',
targetSelectorLabel: '\u6765\u6e90',
});
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 SHARED_SETTINGS_GROUP_DEFINITIONS = freezeDeep({
'service-account': {
id: 'service-account',
label: '\u8d26\u6237',
rowIds: ['row-custom-password'],
},
'service-email': {
id: 'service-email',
label: '\u90ae\u7bb1\u670d\u52a1',
},
'service-proxy': {
id: 'service-proxy',
label: 'IP \u4ee3\u7406',
sectionIds: ['ip-proxy-section'],
},
});
function buildFlowDefinitions() {
if (typeof flowsIndexApi.getFlowDefinitions !== 'function') {
return {};
}
return flowsIndexApi.getFlowDefinitions() || {};
}
const FLOW_DEFINITIONS = freezeDeep(buildFlowDefinitions());
const REGISTERED_FLOW_IDS = Object.freeze(Object.keys(FLOW_DEFINITIONS));
function buildSettingsGroupDefinitions() {
const next = {
...SHARED_SETTINGS_GROUP_DEFINITIONS,
};
Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => {
Object.assign(next, flowDefinition?.settingsGroups || {});
});
return next;
}
const SETTINGS_GROUP_DEFINITIONS = freezeDeep(buildSettingsGroupDefinitions());
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
const normalized = String(value || '').trim().toLowerCase();
if (normalized && Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, normalized)) {
return normalized;
}
const fallbackValue = String(fallback || '').trim().toLowerCase();
if (fallbackValue && Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, fallbackValue)) {
return fallbackValue;
}
if (Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, DEFAULT_FLOW_ID)) {
return DEFAULT_FLOW_ID;
}
return REGISTERED_FLOW_IDS[0] || DEFAULT_FLOW_ID;
}
function getRegisteredFlowIds() {
return REGISTERED_FLOW_IDS.slice();
}
function getFlowDefinition(flowId) {
const normalizedFlowId = normalizeFlowId(flowId);
return FLOW_DEFINITIONS[normalizedFlowId] || null;
}
function getFlowLabel(flowId) {
return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId);
}
function getDefaultTargetId(flowId) {
const flowDefinition = getFlowDefinition(flowId);
return String(flowDefinition?.defaultTargetId || '').trim().toLowerCase();
}
function normalizeTargetId(flowId, targetId = '', fallback = undefined) {
const normalizedFlowId = normalizeFlowId(flowId);
const targetDefinitions = getFlowDefinition(normalizedFlowId)?.targets || {};
const targetKeys = Object.keys(targetDefinitions);
if (!targetKeys.length) {
return String(targetId || fallback || '').trim().toLowerCase();
}
const normalizedTargetId = String(targetId || '').trim().toLowerCase();
if (normalizedTargetId && Object.prototype.hasOwnProperty.call(targetDefinitions, normalizedTargetId)) {
return normalizedTargetId;
}
const fallbackValue = String(fallback || '').trim().toLowerCase();
if (fallbackValue && Object.prototype.hasOwnProperty.call(targetDefinitions, fallbackValue)) {
return fallbackValue;
}
const defaultTargetId = getDefaultTargetId(normalizedFlowId);
if (defaultTargetId && Object.prototype.hasOwnProperty.call(targetDefinitions, defaultTargetId)) {
return defaultTargetId;
}
return targetKeys[0];
}
function normalizeOpenAiTargetId(value = '', fallback = undefined) {
return normalizeTargetId('openai', value, fallback);
}
function normalizeKiroTargetId(value = '', fallback = undefined) {
return normalizeTargetId('kiro', value, fallback);
}
function getTargetDefinitions(flowId) {
return getFlowDefinition(flowId)?.targets || {};
}
function getTargetDefinition(flowId, targetId) {
const normalizedFlowId = normalizeFlowId(flowId);
const normalizedTargetId = normalizeTargetId(
normalizedFlowId,
targetId,
getDefaultTargetId(normalizedFlowId)
);
return getTargetDefinitions(normalizedFlowId)[normalizedTargetId] || null;
}
function getTargetOptions(flowId) {
return Object.values(getTargetDefinitions(flowId));
}
function getTargetLabel(flowId, targetId) {
return getTargetDefinition(flowId, targetId)?.label
|| normalizeTargetId(flowId, targetId);
}
function getPublicationTargetDefinitions(flowId) {
return getFlowDefinition(flowId)?.publicationTargets || {};
}
function getPublicationTargetDefinition(flowId, publicationTargetId) {
const normalizedFlowId = normalizeFlowId(flowId);
const flowDefinition = getFlowDefinition(normalizedFlowId);
const normalizedPublicationTargetId = String(
publicationTargetId || flowDefinition?.defaultPublicationTargetId || ''
).trim().toLowerCase();
return getPublicationTargetDefinitions(normalizedFlowId)[normalizedPublicationTargetId] || null;
}
function getFlowCapabilities(flowId) {
return {
...DEFAULT_FLOW_CAPABILITIES,
...(getFlowDefinition(flowId)?.capabilities || {}),
};
}
function getTargetCapabilityDefinitions(flowId) {
return getFlowDefinition(flowId)?.targetCapabilities || {};
}
function getTargetCapabilities(flowId, targetId) {
const normalizedFlowId = normalizeFlowId(flowId);
const normalizedTargetId = normalizeTargetId(
normalizedFlowId,
targetId,
getDefaultTargetId(normalizedFlowId)
);
return getTargetCapabilityDefinitions(normalizedFlowId)[normalizedTargetId] || null;
}
function getVisibleGroupIds(flowId, targetId, options = {}) {
const normalizedFlowId = normalizeFlowId(flowId);
const flowDefinition = getFlowDefinition(normalizedFlowId);
const normalizedTargetId = normalizeTargetId(
normalizedFlowId,
targetId,
getDefaultTargetId(normalizedFlowId)
);
const targetDefinition = getTargetDefinition(normalizedFlowId, normalizedTargetId);
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(targetDefinition?.groups) ? targetDefinition.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;
}
function getSourceAliases() {
const next = {};
Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => {
Object.assign(next, flowDefinition?.sourceAliases || {});
});
return next;
}
const OPENAI_TARGET_IDS = Object.freeze(Object.keys(getTargetDefinitions('openai')));
const DEFAULT_OPENAI_TARGET_ID = String(getDefaultTargetId('openai') || OPENAI_TARGET_IDS[0] || 'cpa');
const DEFAULT_KIRO_TARGET_ID = String(getDefaultTargetId('kiro') || 'kiro-rs');
const DEFAULT_KIRO_PUBLICATION_TARGET_ID = String(
getFlowDefinition('kiro')?.defaultPublicationTargetId || DEFAULT_KIRO_TARGET_ID
);
const DEFAULT_KIRO_RS_URL = String(
getFlowDefinition('kiro')?.defaultTargetState?.baseUrl || ''
).trim();
return {
DEFAULT_FLOW_CAPABILITIES,
DEFAULT_FLOW_ID,
DEFAULT_KIRO_PUBLICATION_TARGET_ID,
DEFAULT_KIRO_RS_URL,
DEFAULT_KIRO_TARGET_ID,
DEFAULT_OPENAI_TARGET_ID,
FLOW_DEFINITIONS,
OPENAI_TARGET_IDS,
SETTINGS_GROUP_DEFINITIONS,
SHARED_SERVICE_IDS,
getDriverDefinitions,
getDefaultTargetId,
getFlowCapabilities,
getFlowDefinition,
getFlowLabel,
getPublicationTargetDefinition,
getPublicationTargetDefinitions,
getRegisteredFlowIds,
getRuntimeSourceDefinitions,
getSettingsGroupDefinition,
getSettingsGroupDefinitions,
getSourceAliases,
getTargetCapabilities,
getTargetCapabilityDefinitions,
getTargetDefinition,
getTargetDefinitions,
getTargetLabel,
getTargetOptions,
getVisibleGroupIds,
normalizeFlowId,
normalizeKiroTargetId,
normalizeOpenAiTargetId,
normalizeTargetId,
};
});
+257
View File
@@ -0,0 +1,257 @@
(function attachBackgroundLoggingStatus(root, factory) {
root.MultiPageBackgroundLoggingStatus = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundLoggingStatusModule() {
function createLoggingStatus(deps = {}) {
const {
chrome,
DEFAULT_STATE,
getStepIdByNodeIdForState,
getState,
isRecoverableStep9AuthFailure,
LOG_PREFIX,
setState,
sourceRegistry = null,
STOP_ERROR_MESSAGE,
} = deps;
function getSourceLabel(source) {
if (sourceRegistry?.getSourceLabel) {
return sourceRegistry.getSourceLabel(source);
}
const labels = {
'openai-auth': '认证页',
'gmail-mail': 'Gmail 邮箱',
'sidepanel': '侧边栏',
'vps-panel': 'CPA 面板',
'sub2api-panel': 'SUB2API 后台',
'codex2api-panel': 'Codex2API 后台',
'qq-mail': 'QQ 邮箱',
'mail-163': '163 邮箱',
'mail-2925': '2925 邮箱',
'inbucket-mail': 'Inbucket 邮箱',
'duck-mail': 'Duck 邮箱',
'hotmail-api': 'HotmailAPI对接/本地助手)',
'luckmail-api': 'LuckMailAPI 购邮)',
'cloudflare-temp-email': 'Cloudflare Temp Email',
'cloudmail': 'Cloud Mail',
'plus-checkout': 'Plus Checkout',
'paypal-flow': 'PayPal 授权页',
'gopay-flow': 'GoPay 授权页',
'unknown-source': '未知来源',
};
return labels[source] || source || '未知来源';
}
function normalizeLogStep(value) {
const step = Math.floor(Number(value) || 0);
return step > 0 ? step : null;
}
function buildLogEntry(message, level = 'info', options = {}) {
const normalizedOptions = options && typeof options === 'object' ? options : {};
const step = normalizeLogStep(normalizedOptions.step);
const stepKey = String(normalizedOptions.stepKey || '').trim();
const nodeId = String(normalizedOptions.nodeId || normalizedOptions.nodeKey || stepKey || '').trim();
return {
message: String(message || ''),
level,
timestamp: Date.now(),
step,
stepKey,
nodeId,
};
}
async function addLog(message, level = 'info', options = {}) {
const state = await getState();
const logs = state.logs || [];
const entry = buildLogEntry(message, level, options);
logs.push(entry);
if (logs.length > 500) logs.splice(0, logs.length - 500);
await setState({ logs });
chrome.runtime.sendMessage({ type: 'LOG_ENTRY', payload: entry }).catch(() => { });
}
async function setNodeStatus(nodeId, status) {
const normalizedNodeId = String(nodeId || '').trim();
if (!normalizedNodeId) {
throw new Error('setNodeStatus 缺少 nodeId。');
}
const state = await getState();
const nodeStatuses = { ...(state.nodeStatuses || {}) };
nodeStatuses[normalizedNodeId] = status;
await setState({
nodeStatuses,
currentNodeId: normalizedNodeId,
});
chrome.runtime.sendMessage({
type: 'NODE_STATUS_CHANGED',
payload: { nodeId: normalizedNodeId, status },
}).catch(() => { });
}
function getErrorMessage(error) {
return String(typeof error === 'string' ? error : error?.message || '')
.replace(/^GPC_TASK_ENDED::/i, '')
.replace(/^AUTO_RUN_STEP_IDLE_RESTART::/i, '');
}
function isVerificationMailPollingError(error) {
const message = getErrorMessage(error);
return /未在 .*邮箱中找到新的匹配邮件|未在 Hotmail 收件箱中找到新的匹配验证码|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|did not respond in \d+s|405\s+method\s+not\s+allowed|route\s+error.*405|did\s+not\s+provide\s+an?\s+[`'"]?action|post\s+request\s+to\s+["']?\/(?:email|phone)-verification/i.test(message);
}
function isAddPhoneAuthFailure(error) {
const message = getErrorMessage(error);
if (/\u624b\u673a\u53f7\u8f93\u5165\u6a21\u5f0f|phone\s+entry/i.test(message)) {
return false;
}
return /https:\/\/auth\.openai\.com\/(?:add-phone|phone-verification)(?:[/?#]|$)|\badd-phone\b|phone-verification|\u6dfb\u52a0\u624b\u673a\u53f7|\u624b\u673a\u53f7\u7801|\u624b\u673a\u9a8c\u8bc1\u7801\u9875|\u624b\u673a\u9a8c\u8bc1\u9875|\u8fdb\u5165\u624b\u673a\u53f7\u9875\u9762|\u624b\u673a\u53f7\u9875|\u624b\u673a\u53f7\u9875\u9762|phone\s+number|telephone/i.test(message);
}
function getLoginAuthStateLabel(state) {
switch (state) {
case 'verification_page':
return '登录验证码页';
case 'password_page':
return '密码页';
case 'email_page':
return '邮箱输入页';
case 'login_timeout_error_page':
return '登录超时报错页';
case 'oauth_consent_page':
return 'OAuth 授权页';
case 'add_phone_page':
return '手机号页';
case 'add_email_page':
return '添加邮箱页';
case 'phone_verification_page':
return '手机验证码页';
default:
return '未知页面';
}
}
function isRestartCurrentAttemptError(error) {
const message = String(typeof error === 'string' ? error : error?.message || '');
return /当前邮箱已存在,需要重新开始新一轮|SIGNUP_PHONE_PASSWORD_MISMATCH::/i.test(message);
}
function isSignupUserAlreadyExistsFailure(error) {
const message = getErrorMessage(error);
return /SIGNUP_USER_ALREADY_EXISTS::|user_already_exists/i.test(message);
}
function isKiroProxyFailure(error) {
const message = getErrorMessage(error);
return /Kiro\s*(?:注册页|桌面授权页).*(?:CloudFront\s*拒绝请求|AWS\s*请求异常)|(?:当前代理\s*IP|出口区域异常).*(?:切换代理|更换代理)|AWS\s*风控.*(?:切换代理|更换代理)/i.test(message);
}
function isStep9RecoverableAuthError(error) {
const message = String(typeof error === 'string' ? error : error?.message || '');
return /STEP9_OAUTH_RETRY::/i.test(message)
|| isRecoverableStep9AuthFailure(message);
}
function isLegacyStep9RecoverableAuthError(error) {
const message = String(typeof error === 'string' ? error : error?.message || '');
return /STEP9_OAUTH_TIMEOUT::|认证失败:\s*(?:Timeout waiting for OAuth callback|timeout of \d+ms exceeded)/i.test(message);
}
function isStepDoneStatus(status) {
return status === 'completed' || status === 'manual_completed' || status === 'skipped';
}
function getFirstUnfinishedStep(statuses = {}) {
const nodeStatuses = statuses && typeof statuses === 'object' ? statuses : {};
const nodeIds = Object.keys(DEFAULT_STATE.nodeStatuses || {});
for (const nodeId of nodeIds) {
if (!isStepDoneStatus(nodeStatuses[nodeId] || 'pending')) {
return typeof getStepIdByNodeIdForState === 'function'
? getStepIdByNodeIdForState(nodeId, {})
: null;
}
}
return null;
}
function hasSavedProgress(statuses = {}) {
return Object.values({ ...DEFAULT_STATE.nodeStatuses, ...statuses }).some((status) => status !== 'pending');
}
function getRunningSteps(statuses = {}) {
return Object.entries({ ...DEFAULT_STATE.nodeStatuses, ...statuses })
.filter(([, status]) => status === 'running')
.map(([nodeId]) => (typeof getStepIdByNodeIdForState === 'function' ? getStepIdByNodeIdForState(nodeId, {}) : null))
.filter((step) => Number.isInteger(step) && step > 0)
.sort((a, b) => a - b);
}
function getFirstUnfinishedNode(statuses = {}) {
const nodeIds = Object.keys(DEFAULT_STATE.nodeStatuses || {});
for (const nodeId of nodeIds) {
if (!isStepDoneStatus(statuses[nodeId] || 'pending')) {
return nodeId;
}
}
return '';
}
function hasSavedNodeProgress(statuses = {}) {
return Object.values({ ...DEFAULT_STATE.nodeStatuses, ...statuses }).some((status) => status !== 'pending');
}
function getRunningNodes(statuses = {}) {
return Object.entries({ ...DEFAULT_STATE.nodeStatuses, ...statuses })
.filter(([, status]) => status === 'running')
.map(([nodeId]) => nodeId);
}
function getAutoRunStatusPayload(phase, payload = {}) {
return {
autoRunning: phase === 'scheduled'
|| phase === 'running'
|| phase === 'waiting_step'
|| phase === 'waiting_email'
|| phase === 'retrying'
|| phase === 'waiting_interval',
autoRunPhase: phase,
autoRunCurrentRun: payload.currentRun ?? 0,
autoRunTotalRuns: payload.totalRuns ?? 1,
autoRunAttemptRun: payload.attemptRun ?? 0,
autoRunSessionId: Math.max(0, Math.floor(Number(payload.sessionId ?? payload.autoRunSessionId) || 0)),
scheduledAutoRunAt: Number.isFinite(Number(payload.scheduledAt)) ? Number(payload.scheduledAt) : null,
autoRunCountdownAt: Number.isFinite(Number(payload.countdownAt)) ? Number(payload.countdownAt) : null,
autoRunCountdownTitle: payload.countdownTitle === undefined ? '' : String(payload.countdownTitle || ''),
autoRunCountdownNote: payload.countdownNote === undefined ? '' : String(payload.countdownNote || ''),
};
}
return {
addLog,
getAutoRunStatusPayload,
getFirstUnfinishedNode,
isAddPhoneAuthFailure,
getErrorMessage,
getFirstUnfinishedStep,
getLoginAuthStateLabel,
getRunningNodes,
getRunningSteps,
getSourceLabel,
hasSavedNodeProgress,
hasSavedProgress,
isKiroProxyFailure,
isLegacyStep9RecoverableAuthError,
isRestartCurrentAttemptError,
isSignupUserAlreadyExistsFailure,
isStep9RecoverableAuthError,
isStepDoneStatus,
isVerificationMailPollingError,
setNodeStatus,
};
}
return {
createLoggingStatus,
};
});
+579
View File
@@ -0,0 +1,579 @@
(function attachBackgroundRuntimeState(root, factory) {
root.MultiPageBackgroundRuntimeState = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundRuntimeStateModule() {
function createRuntimeStateHelpers(deps = {}) {
const {
DEFAULT_ACTIVE_FLOW_ID = 'openai',
defaultNodeStatuses = {},
} = deps;
const RUNTIME_SHARED_FIELDS = Object.freeze([
'automationWindowId',
'tabRegistry',
'sourceLastUrls',
'flowStartTime',
]);
const RUNTIME_PROXY_FIELDS = Object.freeze([
'ipProxyApiPool',
'ipProxyApiCurrentIndex',
'ipProxyApiCurrent',
'ipProxyAccountPool',
'ipProxyAccountCurrentIndex',
'ipProxyAccountCurrent',
'ipProxyPool',
'ipProxyCurrentIndex',
'ipProxyCurrent',
]);
const OPENAI_FLOW_FIELD_GROUPS = Object.freeze({
auth: Object.freeze([
'oauthUrl',
'localhostUrl',
]),
platformBinding: Object.freeze([
'cpaOAuthState',
'cpaManagementOrigin',
'sub2apiSessionId',
'sub2apiOAuthState',
'sub2apiGroupId',
'sub2apiDraftName',
'sub2apiProxyId',
'sub2apiGroupIds',
'codex2apiSessionId',
'codex2apiOAuthState',
]),
plus: Object.freeze([
'plusCheckoutTabId',
'plusCheckoutUrl',
'plusCheckoutCountry',
'plusCheckoutCurrency',
'plusCheckoutSource',
'plusBillingCountryText',
'plusBillingAddress',
'plusPaypalApprovedAt',
'plusGoPayApprovedAt',
'plusReturnUrl',
'plusManualConfirmationPending',
'plusManualConfirmationRequestId',
'plusManualConfirmationStep',
'plusManualConfirmationMethod',
'plusManualConfirmationTitle',
'plusManualConfirmationMessage',
'gopayHelperReferenceId',
'gopayHelperGoPayGuid',
'gopayHelperRedirectUrl',
'gopayHelperNextAction',
'gopayHelperFlowId',
'gopayHelperChallengeId',
'gopayHelperStartPayload',
'gopayHelperTaskId',
'gopayHelperTaskStatus',
'gopayHelperStatusText',
'gopayHelperRemoteStage',
'gopayHelperApiWaitingFor',
'gopayHelperApiInputDeadlineAt',
'gopayHelperApiInputWaitSeconds',
'gopayHelperLastInputError',
'gopayHelperOtpInvalidCount',
'gopayHelperFailureStage',
'gopayHelperFailureDetail',
'gopayHelperTaskPayload',
'gopayHelperOrderCreatedAt',
'gopayHelperTaskProgressSignature',
'gopayHelperTaskProgressAt',
'gopayHelperTaskProgressTaskId',
'gopayHelperPinPayload',
'gopayHelperResolvedOtp',
'gopayHelperOtpRequestId',
'gopayHelperOtpReferenceId',
]),
phoneVerification: Object.freeze([
'currentPhoneActivation',
'phoneNumber',
'currentPhoneVerificationCode',
'currentPhoneVerificationCountdownEndsAt',
'currentPhoneVerificationCountdownWindowIndex',
'currentPhoneVerificationCountdownWindowTotal',
'reusablePhoneActivation',
'freeReusablePhoneActivation',
'phoneReusableActivationPool',
'signupPhoneNumber',
'signupPhoneActivation',
'signupPhoneCompletedActivation',
'signupPhoneVerificationRequestedAt',
'signupPhoneVerificationPurpose',
]),
luckmail: Object.freeze([
'currentLuckmailPurchase',
'currentLuckmailMailCursor',
'currentYydsMailInbox',
]),
identity: Object.freeze([
'resolvedSignupMethod',
'accountIdentifierType',
'accountIdentifier',
'registrationEmailState',
'email',
'password',
'lastEmailTimestamp',
'lastSignupCode',
'lastLoginCode',
'step8VerificationTargetEmail',
]),
});
const FLOW_FIELD_GROUPS = Object.freeze({
openai: OPENAI_FLOW_FIELD_GROUPS,
});
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cloneValue(value) {
if (Array.isArray(value)) {
return value.map((item) => cloneValue(item));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [key, cloneValue(entryValue)])
);
}
return value;
}
function deepMerge(baseValue, patchValue) {
if (Array.isArray(patchValue)) {
return patchValue.map((item) => cloneValue(item));
}
if (!isPlainObject(patchValue)) {
return patchValue === undefined ? cloneValue(baseValue) : patchValue;
}
const baseObject = isPlainObject(baseValue) ? baseValue : {};
const next = {
...cloneValue(baseObject),
};
Object.entries(patchValue).forEach(([key, value]) => {
next[key] = deepMerge(baseObject[key], value);
});
return next;
}
function normalizePlainObject(value) {
return isPlainObject(value) ? value : {};
}
function normalizeFlowId(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized || DEFAULT_ACTIVE_FLOW_ID;
}
function normalizeRunId(value = '') {
return String(value || '').trim();
}
function normalizeNodeStatus(value = '') {
const normalized = String(value || '').trim().toLowerCase();
if (!normalized) {
return 'pending';
}
return normalized;
}
function buildDefaultNodeStatuses() {
return Object.fromEntries(
Object.entries(normalizePlainObject(defaultNodeStatuses)).map(([key, value]) => [
String(key),
normalizeNodeStatus(value),
])
);
}
function normalizeNodeStatuses(value) {
const base = buildDefaultNodeStatuses();
if (!isPlainObject(value)) {
return base;
}
const next = { ...base };
for (const [key, status] of Object.entries(value)) {
const nodeId = String(key || '').trim();
if (!nodeId) continue;
next[nodeId] = normalizeNodeStatus(status);
}
return next;
}
function pickDefinedFields(state = {}, fields = []) {
const next = {};
for (const field of fields) {
if (Object.prototype.hasOwnProperty.call(state, field)) {
next[field] = cloneValue(state[field]);
}
}
return next;
}
function buildSharedState(baseValue = {}, state = {}) {
return {
...cloneValue(normalizePlainObject(baseValue)),
...pickDefinedFields(state, RUNTIME_SHARED_FIELDS),
};
}
function buildServiceState(baseValue = {}, state = {}) {
const base = cloneValue(normalizePlainObject(baseValue));
return {
...base,
proxy: {
...cloneValue(normalizePlainObject(base.proxy)),
...pickDefinedFields(state, RUNTIME_PROXY_FIELDS),
},
};
}
function buildScopedFlowState(baseFlowState = {}, state = {}, flowId = 'openai') {
const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {};
const baseScopedState = cloneValue(normalizePlainObject(baseFlowState[flowId]));
const scopedState = {
...baseScopedState,
};
for (const [groupKey, fields] of Object.entries(fieldGroups)) {
scopedState[groupKey] = {
...cloneValue(normalizePlainObject(baseScopedState[groupKey])),
...pickDefinedFields(state, fields),
};
}
return scopedState;
}
function buildFlowState(baseValue = {}, state = {}) {
const baseFlowState = cloneValue(normalizePlainObject(baseValue));
return {
...baseFlowState,
openai: buildScopedFlowState(baseFlowState, state, 'openai'),
};
}
function listFlowFieldNames() {
const fields = [];
for (const flowGroups of Object.values(FLOW_FIELD_GROUPS)) {
for (const groupFields of Object.values(normalizePlainObject(flowGroups))) {
for (const field of Array.isArray(groupFields) ? groupFields : []) {
const normalizedField = String(field || '').trim();
if (normalizedField) {
fields.push(normalizedField);
}
}
}
}
return Array.from(new Set(fields));
}
const FLOW_RUNTIME_FIELDS = Object.freeze(listFlowFieldNames());
const RUNTIME_TOP_LEVEL_FIELDS = Object.freeze([
...RUNTIME_SHARED_FIELDS,
...RUNTIME_PROXY_FIELDS,
...FLOW_RUNTIME_FIELDS,
]);
const RUNTIME_TOP_LEVEL_FIELD_SET = new Set(RUNTIME_TOP_LEVEL_FIELDS);
const RUNTIME_PATCH_IGNORED_KEYS = new Set([
'runtimeState',
'flowState',
'sharedState',
'serviceState',
'flows',
'shared',
'services',
'currentStep',
'stepStatuses',
'legacyStepCompat',
'flowId',
'runId',
'activeFlowId',
'activeRunId',
'currentNodeId',
'nodeStatuses',
'kiroRuntime',
...RUNTIME_TOP_LEVEL_FIELDS,
]);
function projectScopedFlowFields(flowState = {}) {
const next = {};
for (const [flowId, fieldGroups] of Object.entries(FLOW_FIELD_GROUPS)) {
const scopedState = normalizePlainObject(normalizePlainObject(flowState)[flowId]);
for (const [groupKey, fields] of Object.entries(fieldGroups)) {
const group = normalizePlainObject(scopedState[groupKey]);
Object.assign(next, pickDefinedFields(group, fields));
}
}
return next;
}
function projectRuntimeViewFields(runtimeState = {}) {
const normalizedRuntimeState = normalizePlainObject(runtimeState);
return {
...pickDefinedFields(
normalizePlainObject(normalizedRuntimeState.sharedState),
RUNTIME_SHARED_FIELDS
),
...pickDefinedFields(
normalizePlainObject(normalizePlainObject(normalizedRuntimeState.serviceState).proxy),
RUNTIME_PROXY_FIELDS
),
...projectScopedFlowFields(normalizedRuntimeState.flowState),
};
}
function buildRuntimeInputFromPatch(updates = {}) {
const normalizedUpdates = normalizePlainObject(updates);
const runtimeStatePatch = normalizePlainObject(normalizedUpdates.runtimeState);
const next = {
...pickDefinedFields(normalizedUpdates, [
'flowId',
'runId',
'activeFlowId',
'activeRunId',
'currentNodeId',
'nodeStatuses',
]),
...pickDefinedFields(runtimeStatePatch, [
'flowId',
'runId',
'activeFlowId',
'activeRunId',
'currentNodeId',
'nodeStatuses',
]),
...projectRuntimeViewFields(runtimeStatePatch),
...pickDefinedFields(normalizedUpdates, RUNTIME_TOP_LEVEL_FIELDS),
};
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'sharedState')) {
Object.assign(
next,
pickDefinedFields(normalizePlainObject(normalizedUpdates.sharedState), RUNTIME_SHARED_FIELDS)
);
}
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'serviceState')) {
Object.assign(
next,
pickDefinedFields(
normalizePlainObject(normalizePlainObject(normalizedUpdates.serviceState).proxy),
RUNTIME_PROXY_FIELDS
)
);
}
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'flowState')) {
Object.assign(next, projectScopedFlowFields(normalizedUpdates.flowState));
}
if (!Object.prototype.hasOwnProperty.call(next, 'flowId') && Object.prototype.hasOwnProperty.call(next, 'activeFlowId')) {
next.flowId = next.activeFlowId;
}
if (!Object.prototype.hasOwnProperty.call(next, 'runId') && Object.prototype.hasOwnProperty.call(next, 'activeRunId')) {
next.runId = next.activeRunId;
}
return next;
}
function buildRuntimeStateSeed(currentRuntimeState = {}, updates = {}) {
const normalizedUpdates = normalizePlainObject(updates);
let nextRuntimeState = deepMerge(currentRuntimeState, normalizePlainObject(normalizedUpdates.runtimeState));
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'sharedState')) {
nextRuntimeState = deepMerge(nextRuntimeState, {
sharedState: normalizePlainObject(normalizedUpdates.sharedState),
});
}
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'shared')) {
nextRuntimeState = deepMerge(nextRuntimeState, {
sharedState: normalizePlainObject(normalizedUpdates.shared),
});
}
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'serviceState')) {
nextRuntimeState = deepMerge(nextRuntimeState, {
serviceState: normalizePlainObject(normalizedUpdates.serviceState),
});
}
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'services')) {
nextRuntimeState = deepMerge(nextRuntimeState, {
serviceState: normalizePlainObject(normalizedUpdates.services),
});
}
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'flowState')) {
nextRuntimeState = deepMerge(nextRuntimeState, {
flowState: normalizePlainObject(normalizedUpdates.flowState),
});
}
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'flows')) {
nextRuntimeState = deepMerge(nextRuntimeState, {
flowState: normalizePlainObject(normalizedUpdates.flows),
});
}
return nextRuntimeState;
}
function buildRuntimeStateDefault() {
return {
flowId: DEFAULT_ACTIVE_FLOW_ID,
runId: '',
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
activeRunId: '',
currentNodeId: '',
nodeStatuses: {},
sharedState: {},
serviceState: {
proxy: {},
},
flowState: {
openai: {
auth: {},
platformBinding: {},
plus: {},
phoneVerification: {},
luckmail: {},
identity: {},
},
},
};
}
function ensureRuntimeState(state = {}) {
const baseRuntimeState = {
...buildRuntimeStateDefault(),
...cloneValue(normalizePlainObject(state.runtimeState)),
};
const projectedKiroRuntime = isPlainObject(normalizePlainObject(state.flowState).kiro)
? normalizePlainObject(state.flowState.kiro)
: {};
const canonicalKiroRuntime = deepMerge(
normalizePlainObject(normalizePlainObject(baseRuntimeState.flowState).kiro),
projectedKiroRuntime
);
if (Object.keys(canonicalKiroRuntime).length > 0) {
baseRuntimeState.flowState = {
...cloneValue(normalizePlainObject(baseRuntimeState.flowState)),
kiro: canonicalKiroRuntime,
};
}
const activeFlowId = normalizeFlowId(
Object.prototype.hasOwnProperty.call(state, 'activeFlowId')
? state.activeFlowId
: Object.prototype.hasOwnProperty.call(state, 'flowId')
? state.flowId
: Object.prototype.hasOwnProperty.call(baseRuntimeState, 'activeFlowId')
? baseRuntimeState.activeFlowId
: baseRuntimeState.flowId
);
const currentNodeId = String(
Object.prototype.hasOwnProperty.call(state, 'currentNodeId')
? state.currentNodeId
: baseRuntimeState.currentNodeId
).trim();
const nodeStatuses = normalizeNodeStatuses(
Object.prototype.hasOwnProperty.call(state, 'nodeStatuses')
? state.nodeStatuses
: baseRuntimeState.nodeStatuses
);
return {
...baseRuntimeState,
flowId: activeFlowId,
activeFlowId,
runId: normalizeRunId(
Object.prototype.hasOwnProperty.call(state, 'runId')
? state.runId
: Object.prototype.hasOwnProperty.call(state, 'activeRunId')
? state.activeRunId
: Object.prototype.hasOwnProperty.call(baseRuntimeState, 'runId')
? baseRuntimeState.runId
: baseRuntimeState.activeRunId
),
activeRunId: normalizeRunId(
Object.prototype.hasOwnProperty.call(state, 'runId')
? state.runId
: Object.prototype.hasOwnProperty.call(state, 'activeRunId')
? state.activeRunId
: Object.prototype.hasOwnProperty.call(baseRuntimeState, 'runId')
? baseRuntimeState.runId
: baseRuntimeState.activeRunId
),
currentNodeId,
nodeStatuses,
sharedState: buildSharedState(baseRuntimeState.sharedState, state),
serviceState: buildServiceState(baseRuntimeState.serviceState, state),
flowState: buildFlowState(baseRuntimeState.flowState, state),
};
}
function buildPersistentPatchPayload(updates = {}) {
const next = {};
for (const [key, value] of Object.entries(normalizePlainObject(updates))) {
if (RUNTIME_PATCH_IGNORED_KEYS.has(key)) {
continue;
}
next[key] = cloneValue(value);
}
return next;
}
function buildStateView(state = {}) {
const runtimeState = ensureRuntimeState(state);
return {
...state,
...projectRuntimeViewFields(runtimeState),
flowId: runtimeState.flowId,
runId: runtimeState.runId,
activeFlowId: runtimeState.activeFlowId,
activeRunId: runtimeState.activeRunId,
currentNodeId: runtimeState.currentNodeId,
nodeStatuses: cloneValue(runtimeState.nodeStatuses),
flowState: cloneValue(runtimeState.flowState),
sharedState: cloneValue(runtimeState.sharedState),
serviceState: cloneValue(runtimeState.serviceState),
flows: cloneValue(runtimeState.flowState),
shared: cloneValue(runtimeState.sharedState),
services: cloneValue(runtimeState.serviceState),
runtimeState,
};
}
function buildSessionStatePatch(currentState = {}, updates = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
const runtimeStateSeed = buildRuntimeStateSeed(currentRuntimeState, updates);
const runtimeState = ensureRuntimeState({
runtimeState: runtimeStateSeed,
...projectRuntimeViewFields(runtimeStateSeed),
...buildRuntimeInputFromPatch(updates),
});
return {
...buildPersistentPatchPayload(updates),
flowId: runtimeState.flowId,
runId: runtimeState.runId,
activeFlowId: runtimeState.activeFlowId,
activeRunId: runtimeState.activeRunId,
currentNodeId: runtimeState.currentNodeId,
nodeStatuses: cloneValue(runtimeState.nodeStatuses),
runtimeState,
};
}
return {
DEFAULT_ACTIVE_FLOW_ID,
FLOW_FIELD_GROUPS,
OPENAI_FLOW_FIELD_GROUPS,
RUNTIME_PROXY_FIELDS,
RUNTIME_SHARED_FIELDS,
buildDefaultRuntimeState: buildRuntimeStateDefault,
buildSessionStatePatch,
buildStateView,
ensureRuntimeState,
};
}
return {
createRuntimeStateHelpers,
};
});
+569
View File
@@ -0,0 +1,569 @@
(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 defaultOpenAiTargetId = flowRegistry.DEFAULT_OPENAI_TARGET_ID || 'cpa';
const defaultKiroTargetId = flowRegistry.DEFAULT_KIRO_TARGET_ID || 'kiro-rs';
const defaultKiroRsUrl = String(flowRegistry.DEFAULT_KIRO_RS_URL || '').trim();
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 normalizeTargetId = typeof flowRegistry.normalizeTargetId === 'function'
? flowRegistry.normalizeTargetId
: ((_flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase());
const normalizePlusAccountAccessStrategy = (value = '') => {
const normalized = String(value || '').trim().toLowerCase();
if (normalized === 'sub2api_codex_session') {
return 'sub2api_codex_session';
}
if (normalized === 'cpa_codex_session') {
return 'cpa_codex_session';
}
return 'oauth';
};
function buildDefaultSettingsState() {
return {
schemaVersion: 5,
activeFlowId: defaultFlowId,
services: {
account: {
customPassword: '',
},
email: {
provider: '163',
},
proxy: {
enabled: false,
provider: '711proxy',
mode: 'account',
},
},
flows: {
openai: {
selectedTargetId: defaultOpenAiTargetId,
targets: {
cpa: {
vpsUrl: '',
vpsPassword: '',
localCpaStep9Mode: 'submit',
},
sub2api: {
sub2apiUrl: '',
sub2apiEmail: '',
sub2apiPassword: '',
sub2apiGroupName: 'codex',
sub2apiGroupNames: ['codex', 'openai-plus'],
sub2apiAccountPriority: 1,
sub2apiDefaultProxyName: '',
},
codex2api: {
codex2apiUrl: '',
codex2apiAdminKey: '',
},
},
signup: {
signupMethod: 'email',
phoneVerificationEnabled: false,
phoneSignupReloginAfterBindEmailEnabled: false,
},
plus: {
plusModeEnabled: false,
plusPaymentMethod: 'paypal-hosted',
plusAccountAccessStrategy: 'oauth',
hostedCheckoutVerificationUrl: '',
hostedCheckoutPhoneNumber: '',
plusHostedCheckoutOauthDelaySeconds: 3,
},
autoRun: {
stepExecutionRange: {
enabled: false,
fromStep: 1,
toStep: 11,
},
},
},
kiro: {
selectedTargetId: defaultKiroTargetId,
targets: {
'kiro-rs': {
baseUrl: defaultKiroRsUrl,
apiKey: '',
},
},
autoRun: {
stepExecutionRange: {
enabled: false,
fromStep: 1,
toStep: 9,
},
},
},
},
};
}
function getTargetValue(settingsState, pathGetter, legacyPathGetter = null, fallback = {}) {
const sourceState = isPlainObject(settingsState) ? settingsState : {};
const resolvedValue = pathGetter(sourceState)
|| (typeof legacyPathGetter === 'function' ? legacyPathGetter(sourceState) : null)
|| fallback;
return cloneValue(resolvedValue);
}
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 openaiSelectedTargetId = normalizeTargetId(
'openai',
nested?.flows?.openai?.selectedTargetId
?? nested?.flows?.openai?.integrationTargetId
?? (activeFlowId === 'openai'
? (input?.selectedTargetId ?? input?.targetId)
: undefined)
?? defaults.flows.openai.selectedTargetId,
defaults.flows.openai.selectedTargetId
);
const kiroSelectedTargetId = normalizeTargetId(
'kiro',
nested?.flows?.kiro?.selectedTargetId
?? nested?.flows?.kiro?.targetId
?? (activeFlowId === 'kiro'
? (input?.selectedTargetId ?? input?.targetId)
: undefined)
?? defaults.flows.kiro.selectedTargetId,
defaults.flows.kiro.selectedTargetId
);
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,
},
account: {
customPassword: String(
input?.customPassword
?? nested?.services?.account?.customPassword
?? defaults.services.account.customPassword
).trim(),
},
},
flows: {
openai: {
selectedTargetId: openaiSelectedTargetId,
targets: {
cpa: {
...defaults.flows.openai.targets.cpa,
...getTargetValue(
nested,
(state) => state.flows?.openai?.targets?.cpa,
(state) => state.flows?.openai?.integrationTargets?.cpa
),
vpsUrl: String(
input?.vpsUrl
?? nested?.flows?.openai?.targets?.cpa?.vpsUrl
?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsUrl
?? ''
).trim(),
vpsPassword: String(
input?.vpsPassword
?? nested?.flows?.openai?.targets?.cpa?.vpsPassword
?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsPassword
?? ''
),
localCpaStep9Mode: String(
input?.localCpaStep9Mode
?? nested?.flows?.openai?.targets?.cpa?.localCpaStep9Mode
?? nested?.flows?.openai?.integrationTargets?.cpa?.localCpaStep9Mode
?? defaults.flows.openai.targets.cpa.localCpaStep9Mode
).trim() || defaults.flows.openai.targets.cpa.localCpaStep9Mode,
},
sub2api: {
...defaults.flows.openai.targets.sub2api,
...getTargetValue(
nested,
(state) => state.flows?.openai?.targets?.sub2api,
(state) => state.flows?.openai?.integrationTargets?.sub2api
),
sub2apiUrl: String(
input?.sub2apiUrl
?? nested?.flows?.openai?.targets?.sub2api?.sub2apiUrl
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiUrl
?? ''
).trim(),
sub2apiEmail: String(
input?.sub2apiEmail
?? nested?.flows?.openai?.targets?.sub2api?.sub2apiEmail
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiEmail
?? ''
).trim(),
sub2apiPassword: String(
input?.sub2apiPassword
?? nested?.flows?.openai?.targets?.sub2api?.sub2apiPassword
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiPassword
?? ''
),
sub2apiGroupName: String(
input?.sub2apiGroupName
?? nested?.flows?.openai?.targets?.sub2api?.sub2apiGroupName
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiGroupName
?? defaults.flows.openai.targets.sub2api.sub2apiGroupName
).trim() || defaults.flows.openai.targets.sub2api.sub2apiGroupName,
sub2apiGroupNames: Array.isArray(input?.sub2apiGroupNames)
? input.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean)
: (Array.isArray(nested?.flows?.openai?.targets?.sub2api?.sub2apiGroupNames)
? nested.flows.openai.targets.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean)
: (Array.isArray(nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiGroupNames)
? nested.flows.openai.integrationTargets.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean)
: [...defaults.flows.openai.targets.sub2api.sub2apiGroupNames])),
sub2apiAccountPriority: Math.max(1, Number(
input?.sub2apiAccountPriority
?? nested?.flows?.openai?.targets?.sub2api?.sub2apiAccountPriority
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiAccountPriority
?? defaults.flows.openai.targets.sub2api.sub2apiAccountPriority
) || defaults.flows.openai.targets.sub2api.sub2apiAccountPriority),
sub2apiDefaultProxyName: String(
input?.sub2apiDefaultProxyName
?? nested?.flows?.openai?.targets?.sub2api?.sub2apiDefaultProxyName
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiDefaultProxyName
?? ''
).trim(),
},
codex2api: {
...defaults.flows.openai.targets.codex2api,
...getTargetValue(
nested,
(state) => state.flows?.openai?.targets?.codex2api,
(state) => state.flows?.openai?.integrationTargets?.codex2api
),
codex2apiUrl: String(
input?.codex2apiUrl
?? nested?.flows?.openai?.targets?.codex2api?.codex2apiUrl
?? nested?.flows?.openai?.integrationTargets?.codex2api?.codex2apiUrl
?? ''
).trim(),
codex2apiAdminKey: String(
input?.codex2apiAdminKey
?? nested?.flows?.openai?.targets?.codex2api?.codex2apiAdminKey
?? nested?.flows?.openai?.integrationTargets?.codex2api?.codex2apiAdminKey
?? ''
).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,
plusAccountAccessStrategy: normalizePlusAccountAccessStrategy(
input?.plusAccountAccessStrategy
?? nested?.flows?.openai?.plus?.plusAccountAccessStrategy
?? defaults.flows.openai.plus.plusAccountAccessStrategy
),
hostedCheckoutVerificationUrl: String(
input?.hostedCheckoutVerificationUrl
?? nested?.flows?.openai?.plus?.hostedCheckoutVerificationUrl
?? defaults.flows.openai.plus.hostedCheckoutVerificationUrl
).trim(),
hostedCheckoutPhoneNumber: String(
input?.hostedCheckoutPhoneNumber
?? nested?.flows?.openai?.plus?.hostedCheckoutPhoneNumber
?? defaults.flows.openai.plus.hostedCheckoutPhoneNumber
).trim(),
plusHostedCheckoutOauthDelaySeconds: (() => {
const numeric = Number(
input?.plusHostedCheckoutOauthDelaySeconds
?? nested?.flows?.openai?.plus?.plusHostedCheckoutOauthDelaySeconds
?? defaults.flows.openai.plus.plusHostedCheckoutOauthDelaySeconds
);
return Math.min(120, Math.max(0, Math.floor(Number.isFinite(numeric) ? numeric : defaults.flows.openai.plus.plusHostedCheckoutOauthDelaySeconds)));
})(),
},
autoRun: {
stepExecutionRange: normalizeStepExecutionRangeEntry(
stepExecutionRangeByFlow.openai
?? nested?.flows?.openai?.autoRun?.stepExecutionRange
?? {},
defaults.flows.openai.autoRun.stepExecutionRange
),
},
},
kiro: {
selectedTargetId: kiroSelectedTargetId,
targets: {
'kiro-rs': {
...defaults.flows.kiro.targets['kiro-rs'],
...getTargetValue(
nested,
(state) => state.flows?.kiro?.targets?.['kiro-rs']
),
baseUrl: String(
input?.kiroRsUrl
?? input?.kiroRsBaseUrl
?? nested?.flows?.kiro?.targets?.['kiro-rs']?.baseUrl
?? defaults.flows.kiro.targets['kiro-rs'].baseUrl
).trim() || defaults.flows.kiro.targets['kiro-rs'].baseUrl,
apiKey: String(
input?.kiroRsKey
?? input?.kiroRsApiKey
?? nested?.flows?.kiro?.targets?.['kiro-rs']?.apiKey
?? defaults.flows.kiro.targets['kiro-rs'].apiKey
),
},
},
autoRun: {
stepExecutionRange: normalizeStepExecutionRangeEntry(
stepExecutionRangeByFlow.kiro
?? nested?.flows?.kiro?.autoRun?.stepExecutionRange
?? {},
defaults.flows.kiro.autoRun.stepExecutionRange
),
},
},
},
};
}
function mergeSettingsState(baseValue = {}, patchValue = {}) {
const baseSettingsState = normalizeSettingsState(baseValue);
const patchSettingsState = normalizeSettingsState({
settingsState: patchValue,
activeFlowId: patchValue?.activeFlowId ?? baseSettingsState.activeFlowId,
});
function mergeRecursive(baseNode, patchNode) {
if (Array.isArray(patchNode)) {
return patchNode.map((entry) => cloneValue(entry));
}
if (!isPlainObject(patchNode)) {
return patchNode === undefined ? cloneValue(baseNode) : patchNode;
}
const next = {
...cloneValue(isPlainObject(baseNode) ? baseNode : {}),
};
Object.entries(patchNode).forEach(([key, value]) => {
next[key] = mergeRecursive(baseNode?.[key], value);
});
return next;
}
return normalizeSettingsState({
settingsState: mergeRecursive(baseSettingsState, patchSettingsState),
});
}
function getFlowSettings(settingsState = {}, flowId) {
const normalizedState = normalizeSettingsState(settingsState);
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
return cloneValue(normalizedState?.flows?.[normalizedFlowId] || {});
}
function getSelectedTargetId(settingsState = {}, flowId) {
const normalizedState = normalizeSettingsState(settingsState);
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
const flowSettings = normalizedState?.flows?.[normalizedFlowId] || {};
return normalizeTargetId(
normalizedFlowId,
flowSettings?.selectedTargetId,
normalizedFlowId === 'kiro' ? defaultKiroTargetId : defaultOpenAiTargetId
);
}
function getTargetSettings(settingsState = {}, flowId, targetId = '') {
const normalizedState = normalizeSettingsState(settingsState);
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
const resolvedTargetId = normalizeTargetId(
normalizedFlowId,
targetId || getSelectedTargetId(normalizedState, normalizedFlowId),
getSelectedTargetId(normalizedState, normalizedFlowId)
);
return cloneValue(normalizedState?.flows?.[normalizedFlowId]?.targets?.[resolvedTargetId] || {});
}
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 buildSettingsView(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.targetId = getSelectedTargetId(normalizedState, normalizedState.activeFlowId);
next.vpsUrl = openaiState.targets.cpa.vpsUrl;
next.vpsPassword = openaiState.targets.cpa.vpsPassword;
next.localCpaStep9Mode = openaiState.targets.cpa.localCpaStep9Mode;
next.sub2apiUrl = openaiState.targets.sub2api.sub2apiUrl;
next.sub2apiEmail = openaiState.targets.sub2api.sub2apiEmail;
next.sub2apiPassword = openaiState.targets.sub2api.sub2apiPassword;
next.sub2apiGroupName = openaiState.targets.sub2api.sub2apiGroupName;
next.sub2apiGroupNames = cloneValue(openaiState.targets.sub2api.sub2apiGroupNames);
next.sub2apiAccountPriority = openaiState.targets.sub2api.sub2apiAccountPriority;
next.sub2apiDefaultProxyName = openaiState.targets.sub2api.sub2apiDefaultProxyName;
next.codex2apiUrl = openaiState.targets.codex2api.codex2apiUrl;
next.codex2apiAdminKey = openaiState.targets.codex2api.codex2apiAdminKey;
next.customPassword = normalizedState.services.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.plusAccountAccessStrategy = openaiState.plus.plusAccountAccessStrategy;
next.hostedCheckoutVerificationUrl = openaiState.plus.hostedCheckoutVerificationUrl;
next.hostedCheckoutPhoneNumber = openaiState.plus.hostedCheckoutPhoneNumber;
next.plusHostedCheckoutOauthDelaySeconds = openaiState.plus.plusHostedCheckoutOauthDelaySeconds;
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.targets['kiro-rs'].baseUrl;
next.kiroRsKey = kiroState.targets['kiro-rs'].apiKey;
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);
const targetId = getSelectedTargetId(normalizedState, normalizedFlowId);
if (normalizedFlowId === 'kiro') {
const targetSettings = getTargetSettings(normalizedState, normalizedFlowId, targetId);
return {
activeFlowId: normalizedFlowId,
targetId,
kiroRsUrl: targetSettings.baseUrl || '',
kiroRsKey: targetSettings.apiKey || '',
};
}
return {
activeFlowId: normalizedFlowId,
targetId,
};
}
return {
buildDefaultSettingsState,
buildSettingsView,
buildStepExecutionRangeByFlow,
getFlowInputState,
getFlowSettings,
getSelectedTargetId,
getTargetSettings,
mergeSettingsState,
normalizeSettingsState,
};
}
return {
createSettingsSchema,
};
});
+486
View File
@@ -0,0 +1,486 @@
(function attachMultiPageSourceRegistry(root, factory) {
root.MultiPageSourceRegistry = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createSourceRegistryModule() {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
const SHARED_SOURCE_DEFINITIONS = Object.freeze({
'qq-mail': {
flowId: null,
kind: 'mail-provider',
label: 'QQ \u90ae\u7bb1',
readyPolicy: 'top-frame-only',
family: 'qq-mail-family',
driverId: 'content/qq-mail',
cleanupScopes: [],
detectionMatchers: [
{ hostnames: ['mail.qq.com', 'wx.mail.qq.com'] },
],
familyMatchers: [
{ hostnames: ['mail.qq.com', 'wx.mail.qq.com'] },
],
},
'mail-163': {
flowId: null,
kind: 'mail-provider',
label: '163 \u90ae\u7bb1',
readyPolicy: 'top-frame-only',
family: 'mail-163-family',
driverId: 'content/mail-163',
cleanupScopes: [],
detectionMatchers: [
{
hostnames: ['mail.163.com', 'mail.126.com', 'webmail.vip.163.com'],
hostnameEndsWith: ['.mail.163.com', '.mail.126.com'],
matchMode: 'any',
},
],
familyMatchers: [
{
hostnames: ['mail.163.com', 'mail.126.com', 'webmail.vip.163.com'],
hostnameEndsWith: ['.mail.163.com', '.mail.126.com'],
matchMode: 'any',
},
],
},
'gmail-mail': {
flowId: null,
kind: 'mail-provider',
label: 'Gmail \u90ae\u7bb1',
readyPolicy: 'top-frame-only',
family: 'gmail-mail-family',
driverId: 'content/gmail-mail',
cleanupScopes: [],
detectionMatchers: [
{ hostnames: ['mail.google.com'] },
],
familyMatchers: [
{ hostnames: ['mail.google.com'] },
],
},
'icloud-mail': {
flowId: null,
kind: 'mail-provider',
label: 'iCloud \u90ae\u7bb1',
readyPolicy: 'allow-child-frame',
family: 'icloud-mail-family',
driverId: 'content/icloud-mail',
cleanupScopes: [],
detectionMatchers: [
{ hostnames: ['www.icloud.com', 'www.icloud.com.cn'] },
],
familyMatchers: [
{ hostnames: ['www.icloud.com', 'www.icloud.com.cn'] },
],
},
'inbucket-mail': {
flowId: null,
kind: 'mail-provider',
label: 'Inbucket \u90ae\u7bb1',
readyPolicy: 'top-frame-only',
family: 'inbucket-mail-family',
driverId: 'content/inbucket-mail',
cleanupScopes: [],
familyMatchers: [
{ originEqualsReference: true, pathPrefixes: ['/m/'] },
],
},
'mail-2925': {
flowId: null,
kind: 'mail-provider',
label: '2925 \u90ae\u7bb1',
readyPolicy: 'top-frame-only',
family: 'mail-2925-family',
driverId: 'content/mail-2925',
cleanupScopes: [],
detectionMatchers: [
{ hostnames: ['2925.com', 'www.2925.com'] },
],
familyMatchers: [
{ hostnames: ['2925.com', 'www.2925.com'] },
],
},
'duck-mail': {
flowId: null,
kind: 'mail-provider',
label: 'Duck \u90ae\u7bb1',
readyPolicy: 'allow-child-frame',
family: 'duck-mail-family',
driverId: 'content/duck-mail',
cleanupScopes: [],
detectionMatchers: [
{ urlIncludes: 'duckduckgo.com/email/settings/autofill' },
],
familyMatchers: [
{ hostnames: ['duckduckgo.com'], pathPrefixes: ['/email/'] },
],
},
'unknown-source': {
flowId: null,
kind: 'unknown',
label: '\u672a\u77e5\u6765\u6e90',
readyPolicy: 'disabled',
family: 'unknown-family',
driverId: null,
cleanupScopes: [],
},
});
const SHARED_DRIVER_DEFINITIONS = Object.freeze({
'content/qq-mail': {
sourceId: 'qq-mail',
commands: ['POLL_EMAIL'],
},
'content/mail-163': {
sourceId: 'mail-163',
commands: ['POLL_EMAIL'],
},
'content/gmail-mail': {
sourceId: 'gmail-mail',
commands: ['POLL_EMAIL'],
},
'content/icloud-mail': {
sourceId: 'icloud-mail',
commands: ['POLL_EMAIL'],
},
'content/mail-2925': {
sourceId: 'mail-2925',
commands: ['POLL_EMAIL'],
},
'content/duck-mail': {
sourceId: 'duck-mail',
commands: ['FETCH_ALIAS_EMAIL'],
},
});
function normalizeHostname(hostname = '') {
return String(hostname || '').trim().toLowerCase();
}
function matchesNamedHostFamily(hostname = '', family = '') {
const normalizedHost = normalizeHostname(hostname);
const normalizedFamily = normalizeHostname(family);
if (!normalizedHost || !normalizedFamily) {
return false;
}
return normalizedHost === normalizedFamily
|| normalizedHost.endsWith(`.${normalizedFamily}`)
|| normalizedHost.startsWith(`${normalizedFamily}.`)
|| normalizedHost.includes(`.${normalizedFamily}.`);
}
function parseUrlSafely(rawUrl) {
if (!rawUrl) {
return null;
}
try {
return new URL(rawUrl);
} catch {
return null;
}
}
function buildCandidateUrl(url = '', hostname = '') {
const candidate = parseUrlSafely(url);
if (candidate) {
return candidate;
}
const normalizedHostname = normalizeHostname(hostname);
if (!normalizedHostname) {
return null;
}
return parseUrlSafely(`https://${normalizedHostname}/`);
}
function normalizeSourceId(source) {
return String(source || '').trim();
}
function normalizeStringArray(values = []) {
if (!Array.isArray(values)) {
return [];
}
return values
.map((value) => String(value || '').trim())
.filter(Boolean);
}
function hostnameMatchesSuffix(hostname = '', suffix = '') {
const normalizedHost = normalizeHostname(hostname);
const normalizedSuffix = normalizeHostname(suffix);
if (!normalizedHost || !normalizedSuffix) {
return false;
}
if (normalizedHost === normalizedSuffix) {
return true;
}
const dottedSuffix = normalizedSuffix.startsWith('.')
? normalizedSuffix
: `.${normalizedSuffix}`;
return normalizedHost.endsWith(dottedSuffix);
}
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 getSourceAliases() {
return {
...(typeof flowRegistryApi.getSourceAliases === 'function'
? flowRegistryApi.getSourceAliases()
: {}),
};
}
function createSourceRegistry() {
const SOURCE_DEFINITIONS = getRuntimeSourceDefinitions();
const DRIVER_DEFINITIONS = getDriverDefinitions();
const SOURCE_ALIASES = getSourceAliases();
function resolveCanonicalSource(source) {
const normalized = normalizeSourceId(source);
if (!normalized) {
return '';
}
return SOURCE_ALIASES[normalized] || normalized;
}
function getAliasKeysForCanonicalSource(source) {
const canonical = resolveCanonicalSource(source);
return Object.keys(SOURCE_ALIASES).filter((alias) => SOURCE_ALIASES[alias] === canonical);
}
function getSourceKeys(source) {
const normalized = normalizeSourceId(source);
const canonical = resolveCanonicalSource(normalized);
return Array.from(new Set([
canonical,
...getAliasKeysForCanonicalSource(canonical),
normalized,
].filter(Boolean)));
}
function getSourceMeta(source) {
const canonical = resolveCanonicalSource(source);
const definition = SOURCE_DEFINITIONS[canonical];
if (!definition) {
return null;
}
return {
id: canonical,
aliases: getAliasKeysForCanonicalSource(canonical),
...definition,
};
}
function getSourceLabel(source) {
return getSourceMeta(source)?.label || normalizeSourceId(source) || '\u672a\u77e5\u6765\u6e90';
}
function getDriverIdForSource(source) {
return getSourceMeta(source)?.driverId || null;
}
function getDriverMeta(sourceOrDriverId) {
const directDriverId = normalizeSourceId(sourceOrDriverId);
const driverId = Object.prototype.hasOwnProperty.call(DRIVER_DEFINITIONS, directDriverId)
? directDriverId
: getDriverIdForSource(sourceOrDriverId);
if (!driverId || !Object.prototype.hasOwnProperty.call(DRIVER_DEFINITIONS, driverId)) {
return null;
}
return {
id: driverId,
...DRIVER_DEFINITIONS[driverId],
};
}
function driverAcceptsCommand(sourceOrDriverId, command) {
const normalizedCommand = normalizeSourceId(command);
if (!normalizedCommand) {
return false;
}
const driver = getDriverMeta(sourceOrDriverId);
return Array.isArray(driver?.commands) && driver.commands.includes(normalizedCommand);
}
function matchesUrlRule(rule = {}, candidate, reference) {
if (!candidate) {
return false;
}
const matchMode = String(rule?.matchMode || 'all').trim().toLowerCase() === 'any'
? 'any'
: 'all';
const conditions = [];
const hostnames = normalizeStringArray(rule.hostnames);
if (hostnames.length) {
conditions.push(hostnames.includes(candidate.hostname));
}
const hostnameFamilies = normalizeStringArray(rule.hostnameFamilies);
if (hostnameFamilies.length) {
conditions.push(hostnameFamilies.some((family) => matchesNamedHostFamily(candidate.hostname, family)));
}
const hostnameEndsWith = normalizeStringArray(rule.hostnameEndsWith);
if (hostnameEndsWith.length) {
conditions.push(hostnameEndsWith.some((suffix) => hostnameMatchesSuffix(candidate.hostname, suffix)));
}
if (rule.hostnameRegex) {
conditions.push(new RegExp(rule.hostnameRegex, rule.hostnameRegexFlags || '').test(candidate.hostname));
}
if (rule.urlIncludes) {
conditions.push(String(candidate.href || '').includes(String(rule.urlIncludes)));
}
const pathPrefixes = normalizeStringArray(rule.pathPrefixes);
if (pathPrefixes.length) {
conditions.push(pathPrefixes.some((prefix) => candidate.pathname.startsWith(prefix)));
}
if (rule.pathEquals) {
conditions.push(candidate.pathname === String(rule.pathEquals));
}
const pathEqualsOneOf = normalizeStringArray(rule.pathEqualsOneOf);
if (pathEqualsOneOf.length) {
conditions.push(pathEqualsOneOf.includes(candidate.pathname));
}
if (rule.originEqualsReference) {
conditions.push(Boolean(reference) && candidate.origin === reference.origin);
}
if (rule.pathEqualsReference) {
conditions.push(Boolean(reference) && candidate.pathname === reference.pathname);
}
if (!conditions.length) {
return false;
}
return matchMode === 'any'
? conditions.some(Boolean)
: conditions.every(Boolean);
}
function matchesSourceDetection(source, url = '', hostname = '') {
const candidate = buildCandidateUrl(url, hostname);
if (!candidate) {
return false;
}
const canonical = resolveCanonicalSource(source);
const detectionMatchers = getSourceMeta(canonical)?.detectionMatchers || [];
return detectionMatchers.some((rule) => matchesUrlRule(rule, candidate, null));
}
function isSignupPageHost(hostname = '') {
return matchesSourceDetection('openai-auth', '', hostname);
}
function isSignupEntryHost(hostname = '') {
return matchesSourceDetection('chatgpt', '', hostname);
}
function is163MailHost(hostname = '') {
return matchesSourceDetection('mail-163', '', hostname);
}
function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
const candidate = parseUrlSafely(candidateUrl);
if (!candidate) {
return false;
}
const canonical = resolveCanonicalSource(source);
const familyMatchers = getSourceMeta(canonical)?.familyMatchers || [];
const reference = parseUrlSafely(referenceUrl);
return familyMatchers.some((rule) => matchesUrlRule(rule, candidate, reference));
}
function detectSourceFromLocation({
injectedSource,
url = '',
hostname = '',
} = {}) {
if (injectedSource) {
return resolveCanonicalSource(injectedSource);
}
const candidate = buildCandidateUrl(url, hostname);
if (!candidate) {
return 'unknown-source';
}
const detectionSourceIds = Object.keys(SOURCE_DEFINITIONS).filter((sourceId) => sourceId !== 'unknown-source');
for (const sourceId of detectionSourceIds) {
if (matchesSourceDetection(sourceId, candidate.href, candidate.hostname)) {
return sourceId;
}
}
return 'unknown-source';
}
function shouldReportReadyForFrame(source, isChildFrame) {
const canonical = resolveCanonicalSource(source);
const readyPolicy = getSourceMeta(canonical)?.readyPolicy || 'allow-child-frame';
if (readyPolicy === 'disabled') {
return false;
}
if (!isChildFrame) {
return true;
}
return readyPolicy === 'allow-child-frame';
}
function getCleanupOwnerSource(cleanupScope) {
const normalizedCleanupScope = String(cleanupScope || '').trim();
const owner = Object.keys(SOURCE_DEFINITIONS).find((sourceId) => {
const cleanupScopes = Array.isArray(SOURCE_DEFINITIONS[sourceId]?.cleanupScopes)
? SOURCE_DEFINITIONS[sourceId].cleanupScopes
: [];
return cleanupScopes.includes(normalizedCleanupScope);
});
return resolveCanonicalSource(owner || '');
}
return {
detectSourceFromLocation,
driverAcceptsCommand,
getCleanupOwnerSource,
getDriverIdForSource,
getDriverMeta,
getSourceKeys,
getSourceLabel,
getSourceMeta,
is163MailHost,
isSignupEntryHost,
isSignupPageHost,
matchesSourceUrlFamily,
parseUrlSafely,
resolveCanonicalSource,
shouldReportReadyForFrame,
};
}
return {
createSourceRegistry,
};
});
+53
View File
@@ -0,0 +1,53 @@
(function attachBackgroundStepRegistry(root, factory) {
root.MultiPageBackgroundStepRegistry = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStepRegistryModule() {
function createNodeRegistry(definitions = []) {
const ordered = (Array.isArray(definitions) ? definitions : [])
.map((definition) => ({
flowId: String(definition?.flowId || '').trim(),
nodeId: String(definition?.nodeId || definition?.key || '').trim(),
displayOrder: Number(definition?.displayOrder ?? definition?.order ?? definition?.id),
executeKey: String(definition?.executeKey || definition?.key || definition?.nodeId || '').trim(),
title: String(definition?.title || '').trim(),
execute: definition?.execute,
}))
.filter((definition) => definition.nodeId)
.sort((left, right) => {
const leftOrder = Number.isFinite(left.displayOrder) ? left.displayOrder : 0;
const rightOrder = Number.isFinite(right.displayOrder) ? right.displayOrder : 0;
if (leftOrder !== rightOrder) return leftOrder - rightOrder;
return left.nodeId.localeCompare(right.nodeId);
});
const byId = new Map(ordered.map((definition) => [definition.nodeId, definition]));
function getNodeDefinition(nodeId) {
return byId.get(String(nodeId || '').trim()) || null;
}
function getOrderedNodes() {
return ordered.slice();
}
function executeNode(nodeId, state) {
const definition = getNodeDefinition(nodeId);
if (!definition) {
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 {
executeNode,
getNodeDefinition,
getOrderedNodes,
};
}
return {
createNodeRegistry,
};
});
File diff suppressed because it is too large Load Diff
+157
View File
@@ -0,0 +1,157 @@
(function attachBackgroundWorkflowEngine(root, factory) {
root.MultiPageBackgroundWorkflowEngine = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundWorkflowEngineModule() {
function createWorkflowEngine(deps = {}) {
const {
defaultFlowId = 'openai',
workflowDefinitions = null,
} = deps;
function normalizeFlowId(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized || defaultFlowId;
}
function normalizeNodeId(value = '') {
return String(value || '').trim();
}
function resolveStateFlowId(state = {}) {
return normalizeFlowId(state?.flowId || state?.activeFlowId || defaultFlowId);
}
function getWorkflow(options = {}) {
const flowId = normalizeFlowId(options?.flowId || options?.activeFlowId || defaultFlowId);
if (workflowDefinitions?.getWorkflow) {
return workflowDefinitions.getWorkflow({
...options,
activeFlowId: flowId,
flowId,
});
}
return {
flowId,
workflowVersion: 1,
nodes: [],
nodeIds: [],
};
}
function getWorkflowForState(state = {}) {
return getWorkflow({
...state,
flowId: resolveStateFlowId(state),
activeFlowId: resolveStateFlowId(state),
});
}
function getNodesForState(state = {}) {
return Array.isArray(getWorkflowForState(state).nodes)
? getWorkflowForState(state).nodes
: [];
}
function getNodeIdsForState(state = {}) {
return getNodesForState(state).map((node) => node.nodeId).filter(Boolean);
}
function getNodeById(nodeId, state = {}) {
const normalizedNodeId = normalizeNodeId(nodeId);
if (!normalizedNodeId) {
return null;
}
return getNodesForState(state).find((node) => node.nodeId === normalizedNodeId) || null;
}
function getDisplayOrderForNode(nodeId, state = {}) {
const node = getNodeById(nodeId, state);
return Number.isFinite(Number(node?.displayOrder)) ? Number(node.displayOrder) : null;
}
function getNodeTitle(nodeId, state = {}) {
return getNodeById(nodeId, state)?.title || nodeId || '';
}
function normalizeNodeStatus(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized || 'pending';
}
function buildDefaultNodeStatuses(state = {}) {
return Object.fromEntries(getNodeIdsForState(state).map((nodeId) => [nodeId, 'pending']));
}
function normalizeNodeStatuses(statuses = {}, state = {}) {
const defaults = buildDefaultNodeStatuses(state);
const next = { ...defaults };
if (!statuses || typeof statuses !== 'object' || Array.isArray(statuses)) {
return next;
}
for (const [rawNodeId, rawStatus] of Object.entries(statuses)) {
const nodeId = normalizeNodeId(rawNodeId);
if (!nodeId || !Object.prototype.hasOwnProperty.call(defaults, nodeId)) {
continue;
}
next[nodeId] = normalizeNodeStatus(rawStatus);
}
return next;
}
function isNodeDoneStatus(status = '') {
return ['completed', 'manual_completed', 'skipped'].includes(normalizeNodeStatus(status));
}
function isNodeTerminalStatus(status = '') {
return ['completed', 'manual_completed', 'skipped', 'failed', 'stopped'].includes(normalizeNodeStatus(status));
}
function getRunningNodeIds(statuses = {}, state = {}) {
const normalizedStatuses = normalizeNodeStatuses(statuses, state);
return getNodeIdsForState(state).filter((nodeId) => normalizedStatuses[nodeId] === 'running');
}
function getFirstUnfinishedNodeId(statuses = {}, state = {}) {
const normalizedStatuses = normalizeNodeStatuses(statuses, state);
return getNodeIdsForState(state).find((nodeId) => !isNodeDoneStatus(normalizedStatuses[nodeId])) || '';
}
function hasSavedProgress(statuses = {}, state = {}) {
const normalizedStatuses = normalizeNodeStatuses(statuses, state);
return getNodeIdsForState(state).some((nodeId) => normalizeNodeStatus(normalizedStatuses[nodeId]) !== 'pending');
}
function getNextNodeIds(nodeId, state = {}) {
const node = getNodeById(nodeId, state);
if (!node) {
return [];
}
return Array.isArray(node.next) ? node.next.map(normalizeNodeId).filter(Boolean) : [];
}
return {
buildDefaultNodeStatuses,
getDisplayOrderForNode,
getFirstUnfinishedNodeId,
getNextNodeIds,
getNodeById,
getNodeIdsForState,
getNodesForState,
getNodeTitle,
getRunningNodeIds,
getWorkflow,
getWorkflowForState,
hasSavedProgress,
isNodeDoneStatus,
isNodeTerminalStatus,
normalizeFlowId,
normalizeNodeId,
normalizeNodeStatuses,
normalizeNodeStatus,
resolveStateFlowId,
};
}
return {
createWorkflowEngine,
};
});