Add Plus SUB2API session access strategy
This commit is contained in:
+177
-18
@@ -55,6 +55,7 @@ importScripts(
|
||||
'background/steps/paypal-approve.js',
|
||||
'background/steps/gopay-approve.js',
|
||||
'background/steps/plus-return-confirm.js',
|
||||
'background/steps/sub2api-session-import.js',
|
||||
'background/steps/oauth-login.js',
|
||||
'background/steps/fetch-login-code.js',
|
||||
'background/steps/confirm-oauth.js',
|
||||
@@ -94,6 +95,12 @@ const PLUS_PAYPAL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
}) || NORMAL_STEP_DEFINITIONS;
|
||||
const PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||
}) || PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||
const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
plusModeEnabled: true,
|
||||
@@ -112,6 +119,12 @@ const PLUS_GOPAY_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'gopay',
|
||||
}) || PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||
const PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'gopay',
|
||||
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||
}) || PLUS_GOPAY_STEP_DEFINITIONS;
|
||||
const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
plusModeEnabled: true,
|
||||
@@ -130,6 +143,12 @@ const PLUS_GPC_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
}) || PLUS_GOPAY_STEP_DEFINITIONS;
|
||||
const PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||
}) || PLUS_GPC_STEP_DEFINITIONS;
|
||||
const PLUS_GPC_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
plusModeEnabled: true,
|
||||
@@ -165,12 +184,15 @@ const ALL_STEP_DEFINITIONS = (() => {
|
||||
...NORMAL_PHONE_STEP_DEFINITIONS,
|
||||
...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
...PLUS_PAYPAL_STEP_DEFINITIONS,
|
||||
...PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
...PLUS_GOPAY_STEP_DEFINITIONS,
|
||||
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||
...PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
||||
...PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
...PLUS_GPC_STEP_DEFINITIONS,
|
||||
...PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||
...PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
||||
...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
];
|
||||
@@ -632,6 +654,8 @@ const FIVE_SIM_OPERATOR = DEFAULT_FIVE_SIM_OPERATOR;
|
||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
||||
const DISPLAY_TIMEZONE = 'Asia/Shanghai';
|
||||
const MICROSOFT_TOKEN_DNR_RULE_ID = 1001;
|
||||
@@ -722,34 +746,108 @@ function getSignupMethodForStepDefinitions(state = {}) {
|
||||
return normalizeSignupMethod(state?.resolvedSignupMethod || state?.signupMethod);
|
||||
}
|
||||
|
||||
function buildResolvedStepDefinitionState(state = {}) {
|
||||
const defaultFlowId = typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai';
|
||||
const requestedActiveFlowId = String(state?.activeFlowId || state?.flowId || '').trim().toLowerCase() || defaultFlowId;
|
||||
const requestedSignupMethod = getSignupMethodForStepDefinitions(state);
|
||||
const plusModeEnabled = isPlusModeState(state);
|
||||
const plusPaymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod);
|
||||
const capabilityState = typeof resolveCurrentFlowCapabilities === 'function'
|
||||
? resolveCurrentFlowCapabilities({
|
||||
...state,
|
||||
activeFlowId: requestedActiveFlowId,
|
||||
flowId: requestedActiveFlowId,
|
||||
plusModeEnabled,
|
||||
plusPaymentMethod,
|
||||
signupMethod: requestedSignupMethod,
|
||||
}, {
|
||||
activeFlowId: requestedActiveFlowId,
|
||||
panelMode: state?.panelMode,
|
||||
signupMethod: requestedSignupMethod,
|
||||
})
|
||||
: null;
|
||||
const stepDefinitionOptions = capabilityState?.stepDefinitionOptions || {};
|
||||
const resolvedActiveFlowId = String(stepDefinitionOptions.activeFlowId || requestedActiveFlowId).trim().toLowerCase() || defaultFlowId;
|
||||
const resolvedSignupMethod = normalizeSignupMethod(
|
||||
stepDefinitionOptions.signupMethod
|
||||
|| capabilityState?.effectiveSignupMethod
|
||||
|| requestedSignupMethod
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeFlowId: resolvedActiveFlowId,
|
||||
flowId: resolvedActiveFlowId,
|
||||
panelMode: stepDefinitionOptions.panelMode || capabilityState?.effectivePanelMode || state?.panelMode,
|
||||
targetId: stepDefinitionOptions.targetId || capabilityState?.effectiveTargetId || state?.targetId,
|
||||
plusModeEnabled: stepDefinitionOptions.plusModeEnabled === undefined
|
||||
? plusModeEnabled
|
||||
: Boolean(stepDefinitionOptions.plusModeEnabled),
|
||||
plusPaymentMethod,
|
||||
plusAccountAccessStrategy: normalizePlusAccountAccessStrategy(
|
||||
stepDefinitionOptions.plusAccountAccessStrategy
|
||||
?? capabilityState?.effectivePlusAccountAccessStrategy
|
||||
?? state?.plusAccountAccessStrategy
|
||||
),
|
||||
signupMethod: resolvedSignupMethod,
|
||||
resolvedSignupMethod: resolvedSignupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: Boolean(state?.phoneSignupReloginAfterBindEmailEnabled),
|
||||
};
|
||||
}
|
||||
|
||||
function getStepDefinitionsForState(state = {}) {
|
||||
const resolvedState = buildResolvedStepDefinitionState(state);
|
||||
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||
if (rootScope.MultiPageStepDefinitions?.getSteps) {
|
||||
const defaultFlowId = typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai';
|
||||
const activeFlowId = String(state?.activeFlowId || '').trim().toLowerCase() || defaultFlowId;
|
||||
const activeFlowId = String(resolvedState?.activeFlowId || '').trim().toLowerCase() || defaultFlowId;
|
||||
const definitions = rootScope.MultiPageStepDefinitions.getSteps({
|
||||
activeFlowId,
|
||||
plusModeEnabled: isPlusModeState(state),
|
||||
plusPaymentMethod: normalizePlusPaymentMethod(state?.plusPaymentMethod),
|
||||
signupMethod: getSignupMethodForStepDefinitions(state),
|
||||
phoneSignupReloginAfterBindEmailEnabled: Boolean(state?.phoneSignupReloginAfterBindEmailEnabled),
|
||||
plusModeEnabled: Boolean(resolvedState?.plusModeEnabled),
|
||||
plusPaymentMethod: normalizePlusPaymentMethod(resolvedState?.plusPaymentMethod),
|
||||
plusAccountAccessStrategy: normalizePlusAccountAccessStrategy(resolvedState?.plusAccountAccessStrategy),
|
||||
signupMethod: getSignupMethodForStepDefinitions(resolvedState),
|
||||
phoneSignupReloginAfterBindEmailEnabled: Boolean(resolvedState?.phoneSignupReloginAfterBindEmailEnabled),
|
||||
});
|
||||
if (Array.isArray(definitions)) {
|
||||
return definitions;
|
||||
}
|
||||
}
|
||||
const activeFlowId = String(state?.activeFlowId || '').trim().toLowerCase();
|
||||
const activeFlowId = String(resolvedState?.activeFlowId || '').trim().toLowerCase();
|
||||
if (activeFlowId && activeFlowId !== DEFAULT_ACTIVE_FLOW_ID) {
|
||||
return [];
|
||||
}
|
||||
if (!isPlusModeState(state)) {
|
||||
if (!Boolean(resolvedState?.plusModeEnabled)) {
|
||||
return NORMAL_STEP_DEFINITIONS;
|
||||
}
|
||||
const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod);
|
||||
const paymentMethod = normalizePlusPaymentMethod(resolvedState?.plusPaymentMethod);
|
||||
const signupMethod = getSignupMethodForStepDefinitions(resolvedState);
|
||||
const plusAccountAccessStrategy = normalizePlusAccountAccessStrategy(resolvedState?.plusAccountAccessStrategy);
|
||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
||||
if (
|
||||
signupMethod === SIGNUP_METHOD_EMAIL
|
||||
&& plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
) {
|
||||
return PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||
}
|
||||
return PLUS_GPC_STEP_DEFINITIONS;
|
||||
}
|
||||
return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_GOPAY_STEP_DEFINITIONS : PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) {
|
||||
if (
|
||||
signupMethod === SIGNUP_METHOD_EMAIL
|
||||
&& plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
) {
|
||||
return PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||
}
|
||||
return PLUS_GOPAY_STEP_DEFINITIONS;
|
||||
}
|
||||
if (
|
||||
signupMethod === SIGNUP_METHOD_EMAIL
|
||||
&& plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
) {
|
||||
return PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||
}
|
||||
return PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||
}
|
||||
|
||||
function getStepIdsForState(state = {}) {
|
||||
@@ -806,17 +904,18 @@ function getStepIdByKeyForState(stepKey, state = {}) {
|
||||
}
|
||||
|
||||
function getNodeDefinitionsForState(state = {}) {
|
||||
const resolvedState = buildResolvedStepDefinitionState(state);
|
||||
if (workflowEngine?.getNodesForState) {
|
||||
return workflowEngine.getNodesForState(state);
|
||||
return workflowEngine.getNodesForState(resolvedState);
|
||||
}
|
||||
if (self.MultiPageStepDefinitions?.getNodes) {
|
||||
return self.MultiPageStepDefinitions.getNodes({
|
||||
...state,
|
||||
activeFlowId: state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||
flowId: state?.flowId || state?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||
...resolvedState,
|
||||
activeFlowId: resolvedState?.activeFlowId || resolvedState?.flowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||
flowId: resolvedState?.flowId || resolvedState?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||
});
|
||||
}
|
||||
return getStepDefinitionsForState(state)
|
||||
return getStepDefinitionsForState(resolvedState)
|
||||
.map((definition) => ({
|
||||
nodeId: String(definition?.key || '').trim(),
|
||||
displayOrder: Number.isFinite(Number(definition?.id)) ? Number(definition.id) : Number(definition?.order),
|
||||
@@ -827,19 +926,21 @@ function getNodeDefinitionsForState(state = {}) {
|
||||
}
|
||||
|
||||
function getNodeIdsForState(state = {}) {
|
||||
const resolvedState = buildResolvedStepDefinitionState(state);
|
||||
if (workflowEngine?.getNodeIdsForState) {
|
||||
return workflowEngine.getNodeIdsForState(state);
|
||||
return workflowEngine.getNodeIdsForState(resolvedState);
|
||||
}
|
||||
return getNodeDefinitionsForState(state).map((definition) => definition.nodeId).filter(Boolean);
|
||||
return getNodeDefinitionsForState(resolvedState).map((definition) => definition.nodeId).filter(Boolean);
|
||||
}
|
||||
|
||||
function getNodeDefinitionForState(nodeId, state = {}) {
|
||||
const normalizedNodeId = String(nodeId || '').trim();
|
||||
if (!normalizedNodeId) return null;
|
||||
const resolvedState = buildResolvedStepDefinitionState(state);
|
||||
if (workflowEngine?.getNodeById) {
|
||||
return workflowEngine.getNodeById(normalizedNodeId, state);
|
||||
return workflowEngine.getNodeById(normalizedNodeId, resolvedState);
|
||||
}
|
||||
return getNodeDefinitionsForState(state).find((definition) => definition.nodeId === normalizedNodeId) || null;
|
||||
return getNodeDefinitionsForState(resolvedState).find((definition) => definition.nodeId === normalizedNodeId) || null;
|
||||
}
|
||||
|
||||
function getLastNodeIdForState(state = {}) {
|
||||
@@ -947,6 +1048,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
customPassword: '',
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: DEFAULT_PLUS_PAYMENT_METHOD,
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
paypalEmail: '',
|
||||
paypalPassword: '',
|
||||
currentPayPalAccountId: '',
|
||||
@@ -1120,6 +1222,7 @@ const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
|
||||
'phoneSignupReloginAfterBindEmailEnabled',
|
||||
'plusModeEnabled',
|
||||
'plusPaymentMethod',
|
||||
'plusAccountAccessStrategy',
|
||||
'mailProvider',
|
||||
'ipProxyEnabled',
|
||||
'ipProxyService',
|
||||
@@ -1693,6 +1796,13 @@ function normalizePlusPaymentMethod(value = '') {
|
||||
return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL;
|
||||
}
|
||||
|
||||
function normalizePlusAccountAccessStrategy(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
}
|
||||
|
||||
function normalizeFiveSimCountryId(value, fallback = FIVE_SIM_COUNTRY_ID) {
|
||||
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||
const rawNormalized = rootScope.PhoneSmsFiveSimProvider?.normalizeFiveSimCountryId
|
||||
@@ -2866,6 +2976,10 @@ function normalizePersistentSettingValue(key, value) {
|
||||
return normalizeSignupMethod(value);
|
||||
case 'plusPaymentMethod':
|
||||
return normalizePlusPaymentMethod(value);
|
||||
case 'plusAccountAccessStrategy':
|
||||
return String(value || '').trim().toLowerCase() === 'sub2api_codex_session'
|
||||
? 'sub2api_codex_session'
|
||||
: 'oauth';
|
||||
case 'paypalEmail':
|
||||
return String(value || '').trim();
|
||||
case 'paypalPassword':
|
||||
@@ -10241,6 +10355,7 @@ const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([
|
||||
'plus-checkout-billing',
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'sub2api-session-import',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'post-login-phone-verification',
|
||||
@@ -11339,6 +11454,7 @@ const AUTO_RUN_NODE_DELAYS = Object.freeze({
|
||||
'gopay-subscription-confirm': 2000,
|
||||
'paypal-approve': 2000,
|
||||
'plus-checkout-return': 1000,
|
||||
'sub2api-session-import': 0,
|
||||
'oauth-login': 2000,
|
||||
'fetch-login-code': 2000,
|
||||
'confirm-oauth': 1000,
|
||||
@@ -13096,6 +13212,7 @@ const goPayManualConfirmExecutor = self.MultiPageBackgroundGoPayManualConfirm?.c
|
||||
broadcastDataUpdate,
|
||||
chrome,
|
||||
getTabId,
|
||||
getNodeIdsForState,
|
||||
isTabAlive,
|
||||
registerTab,
|
||||
createAutomationTab,
|
||||
@@ -13143,6 +13260,21 @@ const plusReturnConfirmExecutor = self.MultiPageBackgroundPlusReturnConfirm?.cre
|
||||
sleepWithStop,
|
||||
waitForTabUrlMatchUntilStopped,
|
||||
});
|
||||
const sub2ApiSessionImportExecutor = self.MultiPageBackgroundSub2ApiSessionImport?.createSub2ApiSessionImportExecutor({
|
||||
addLog,
|
||||
chrome,
|
||||
completeNodeFromBackground,
|
||||
ensureContentScriptReadyOnTabUntilStopped,
|
||||
getTabId,
|
||||
isTabAlive,
|
||||
normalizeSub2ApiUrl,
|
||||
registerTab,
|
||||
sendTabMessageUntilStopped,
|
||||
sleepWithStop,
|
||||
throwIfStopped,
|
||||
waitForTabCompleteUntilStopped,
|
||||
DEFAULT_SUB2API_GROUP_NAME,
|
||||
});
|
||||
const kiroRegisterRunner = self.MultiPageBackgroundKiroRegisterRunner?.createKiroRegisterRunner({
|
||||
addLog,
|
||||
chrome,
|
||||
@@ -13290,6 +13422,7 @@ const stepExecutorsByKey = {
|
||||
? goPayApproveExecutor.executeGoPayApprove(state)
|
||||
: payPalApproveExecutor.executePayPalApprove(state),
|
||||
'plus-checkout-return': (state) => plusReturnConfirmExecutor.executePlusReturnConfirm(state),
|
||||
'sub2api-session-import': (state) => sub2ApiSessionImportExecutor.executeSub2ApiSessionImport(state),
|
||||
'oauth-login': (state) => step7Executor.executeStep7(state),
|
||||
'fetch-login-code': (state) => step8Executor.executeStep8(state),
|
||||
'post-login-phone-verification': (state) => step8Executor.executePostLoginPhoneVerification(state),
|
||||
@@ -13956,6 +14089,7 @@ async function getPostStep6AutoRestartDecision(step, error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
const shouldForceRestartFromStep7 = /restart step 7 with a new number/i.test(errorMessage);
|
||||
const latestState = await getState();
|
||||
const explicitAuthChainStartStep = findStepIdByKeyForState('oauth-login', latestState);
|
||||
const authChainStartStep = typeof getAuthChainStartStepId === 'function'
|
||||
? getAuthChainStartStepId(latestState)
|
||||
: FINAL_OAUTH_CHAIN_START_STEP;
|
||||
@@ -13963,6 +14097,20 @@ async function getPostStep6AutoRestartDecision(step, error) {
|
||||
? getLastStepIdForState(latestState)
|
||||
: (typeof LAST_STEP_ID === 'number' ? LAST_STEP_ID : 10);
|
||||
const currentNodeKey = resolveStepKey(normalizedStep, latestState);
|
||||
const currentNodeIsAuthChain = typeof isAuthChainNode === 'function'
|
||||
? isAuthChainNode(currentNodeKey)
|
||||
: [
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'post-login-phone-verification',
|
||||
'bind-email',
|
||||
'fetch-bind-email-code',
|
||||
'relogin-bound-email',
|
||||
'fetch-bound-email-login-code',
|
||||
'post-bound-email-phone-verification',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
].includes(currentNodeKey);
|
||||
const confirmOauthStep = findStepIdByKeyForState('confirm-oauth', latestState);
|
||||
const boundEmailReloginStep = findStepIdByKeyForState('relogin-bound-email', latestState);
|
||||
const isBoundEmailReloginTailStep = [
|
||||
@@ -13991,6 +14139,17 @@ async function getPostStep6AutoRestartDecision(step, error) {
|
||||
};
|
||||
}
|
||||
|
||||
if (!Number.isFinite(explicitAuthChainStartStep) || explicitAuthChainStartStep <= 0 || !currentNodeIsAuthChain) {
|
||||
return {
|
||||
shouldRestart: false,
|
||||
blockedByAddPhone: false,
|
||||
forcedByPhoneVerificationTimeout: false,
|
||||
restartStep: authChainStartStep,
|
||||
errorMessage,
|
||||
authState: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (!Number.isFinite(normalizedStep) || normalizedStep < authChainStartStep || normalizedStep > lastStepId) {
|
||||
return {
|
||||
shouldRestart: false,
|
||||
|
||||
@@ -592,6 +592,18 @@
|
||||
return method === 'gopay' ? 'GoPay' : 'PayPal';
|
||||
}
|
||||
|
||||
function normalizePlusAccountAccessStrategyForDisplay(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'sub2api_codex_session'
|
||||
? 'sub2api_codex_session'
|
||||
: 'oauth';
|
||||
}
|
||||
|
||||
function getPlusAccountAccessStrategyLabel(value = '') {
|
||||
return normalizePlusAccountAccessStrategyForDisplay(value) === 'sub2api_codex_session'
|
||||
? '导入当前 ChatGPT 会话到 SUB2API'
|
||||
: 'OAuth';
|
||||
}
|
||||
|
||||
async function handlePlatformVerifyStepData(payload) {
|
||||
if (payload.localhostUrl) {
|
||||
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
||||
@@ -1421,6 +1433,9 @@
|
||||
const plusPaymentChanged = Object.prototype.hasOwnProperty.call(updates, 'plusPaymentMethod')
|
||||
&& normalizePlusPaymentMethodForDisplay(currentState?.plusPaymentMethod || 'paypal')
|
||||
!== normalizePlusPaymentMethodForDisplay(updates.plusPaymentMethod || 'paypal');
|
||||
const plusAccountAccessStrategyChanged = Object.prototype.hasOwnProperty.call(updates, 'plusAccountAccessStrategy')
|
||||
&& normalizePlusAccountAccessStrategyForDisplay(currentState?.plusAccountAccessStrategy || 'oauth')
|
||||
!== normalizePlusAccountAccessStrategyForDisplay(updates.plusAccountAccessStrategy || 'oauth');
|
||||
const phoneSignupReloginAfterBindEmailChanged = Object.prototype.hasOwnProperty.call(updates, 'phoneSignupReloginAfterBindEmailEnabled')
|
||||
&& Boolean(currentState?.phoneSignupReloginAfterBindEmailEnabled) !== Boolean(updates.phoneSignupReloginAfterBindEmailEnabled);
|
||||
const nextPlusModeEnabled = Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled')
|
||||
@@ -1428,6 +1443,7 @@
|
||||
: Boolean(currentState?.plusModeEnabled);
|
||||
const stepModeChanged = modeChanged
|
||||
|| (nextPlusModeEnabled && plusPaymentChanged)
|
||||
|| (nextPlusModeEnabled && plusAccountAccessStrategyChanged)
|
||||
|| phoneSignupReloginAfterBindEmailChanged;
|
||||
const oauthFlowTimeoutDisabled = Object.prototype.hasOwnProperty.call(updates, 'oauthFlowTimeoutEnabled')
|
||||
&& updates.oauthFlowTimeoutEnabled === false;
|
||||
@@ -1450,11 +1466,45 @@
|
||||
? nextHostPreference
|
||||
: '';
|
||||
}
|
||||
if (stepModeChanged && typeof getStepIdsForState === 'function') {
|
||||
const nextStateForSteps = { ...currentState, ...stateUpdates };
|
||||
const nextNodeIds = typeof getNodeIdsForState === 'function'
|
||||
? getNodeIdsForState(nextStateForSteps)
|
||||
: getStepIdsForState(nextStateForSteps).map((stepId) => getStepKeyForState(stepId, nextStateForSteps)).filter(Boolean);
|
||||
const currentNodeIds = typeof getNodeIdsForState === 'function'
|
||||
? getNodeIdsForState(currentState)
|
||||
: (typeof getStepIdsForState === 'function'
|
||||
? getStepIdsForState(currentState).map((stepId) => getStepKeyForState(stepId, currentState)).filter(Boolean)
|
||||
: []);
|
||||
const nextStateForSteps = { ...currentState, ...stateUpdates };
|
||||
const nextNodeIds = typeof getNodeIdsForState === 'function'
|
||||
? getNodeIdsForState(nextStateForSteps)
|
||||
: (typeof getStepIdsForState === 'function'
|
||||
? getStepIdsForState(nextStateForSteps).map((stepId) => getStepKeyForState(stepId, nextStateForSteps)).filter(Boolean)
|
||||
: []);
|
||||
const nodeTopologyChanged = currentNodeIds.length !== nextNodeIds.length
|
||||
|| currentNodeIds.some((nodeId, index) => nodeId !== nextNodeIds[index]);
|
||||
const shouldRebuildNodeStatuses = stepModeChanged || nodeTopologyChanged;
|
||||
if (shouldRebuildNodeStatuses && nextNodeIds.length > 0) {
|
||||
Object.assign(stateUpdates, {
|
||||
oauthUrl: null,
|
||||
localhostUrl: null,
|
||||
oauthFlowDeadlineAt: null,
|
||||
oauthFlowDeadlineSourceUrl: null,
|
||||
cpaOAuthState: null,
|
||||
cpaManagementOrigin: null,
|
||||
sub2apiSessionId: null,
|
||||
sub2apiOAuthState: null,
|
||||
sub2apiGroupId: null,
|
||||
sub2apiGroupIds: [],
|
||||
sub2apiDraftName: null,
|
||||
sub2apiProxyId: null,
|
||||
codex2apiSessionId: null,
|
||||
codex2apiOAuthState: null,
|
||||
plusManualConfirmationPending: false,
|
||||
plusManualConfirmationRequestId: '',
|
||||
plusManualConfirmationStep: 0,
|
||||
plusManualConfirmationMethod: '',
|
||||
plusManualConfirmationTitle: '',
|
||||
plusManualConfirmationMessage: '',
|
||||
});
|
||||
}
|
||||
if (shouldRebuildNodeStatuses && nextNodeIds.length > 0) {
|
||||
stateUpdates.nodeStatuses = Object.fromEntries(nextNodeIds.map((nodeId) => [nodeId, 'pending']));
|
||||
stateUpdates.currentNodeId = '';
|
||||
}
|
||||
@@ -1510,9 +1560,12 @@
|
||||
const selectedPlusPaymentMethod = getPlusPaymentMethodLabel(
|
||||
stateUpdates.plusPaymentMethod ?? currentState?.plusPaymentMethod ?? 'paypal'
|
||||
);
|
||||
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
|
||||
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth'
|
||||
);
|
||||
await addLog(
|
||||
Boolean(updates.plusModeEnabled)
|
||||
? `Plus 模式已开启,已切换为 Plus Checkout 步骤,当前支付方式:${selectedPlusPaymentMethod}。`
|
||||
? `Plus 模式已开启,已切换为 Plus Checkout 步骤,当前支付方式:${selectedPlusPaymentMethod},账号接入策略:${selectedPlusAccountAccessStrategy}。`
|
||||
: 'Plus 模式已关闭,已恢复普通注册授权步骤。',
|
||||
'info'
|
||||
);
|
||||
@@ -1521,6 +1574,11 @@
|
||||
stateUpdates.plusPaymentMethod ?? currentState?.plusPaymentMethod ?? 'paypal'
|
||||
);
|
||||
await addLog(`Plus 支付方式已切换为 ${selectedPlusPaymentMethod},已更新对应的 Plus 步骤。`, 'info');
|
||||
} else if (plusAccountAccessStrategyChanged && nextPlusModeEnabled) {
|
||||
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
|
||||
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth'
|
||||
);
|
||||
await addLog(`Plus 账号接入策略已切换为 ${selectedPlusAccountAccessStrategy},已更新对应的 Plus 尾链。`, 'info');
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
|
||||
@@ -2,8 +2,42 @@
|
||||
root.MultiPageBackgroundGoPayManualConfirm = factory();
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundGoPayManualConfirmModule() {
|
||||
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
|
||||
const GOPAY_CONFIRM_NODE_ID = 'gopay-subscription-confirm';
|
||||
const SUB2API_SESSION_IMPORT_NODE_ID = 'sub2api-session-import';
|
||||
const DEFAULT_CONFIRM_TITLE = 'GoPay 订阅确认';
|
||||
const DEFAULT_CONFIRM_MESSAGE = 'GoPay 订阅页已打开。请先手动完成订阅,完成后确认继续 OAuth 登录。';
|
||||
const OAUTH_CONTINUATION_LABEL = 'OAuth 登录';
|
||||
const SUB2API_SESSION_CONTINUATION_LABEL = '导入当前 ChatGPT 会话到 SUB2API';
|
||||
|
||||
function normalizeString(value = '') {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function getContinuationActionLabel(state = {}, options = {}) {
|
||||
const { getNodeIdsForState = null } = options;
|
||||
if (typeof getNodeIdsForState === 'function') {
|
||||
const nodeIds = getNodeIdsForState(state)
|
||||
.map((nodeId) => normalizeString(nodeId))
|
||||
.filter(Boolean);
|
||||
const currentNodeId = normalizeString(state?.nodeId || GOPAY_CONFIRM_NODE_ID) || GOPAY_CONFIRM_NODE_ID;
|
||||
const currentNodeIndex = nodeIds.indexOf(currentNodeId);
|
||||
const nextNodeId = currentNodeIndex >= 0
|
||||
? normalizeString(nodeIds[currentNodeIndex + 1])
|
||||
: '';
|
||||
|
||||
if (
|
||||
nextNodeId === SUB2API_SESSION_IMPORT_NODE_ID
|
||||
|| (currentNodeIndex < 0 && nodeIds.includes(SUB2API_SESSION_IMPORT_NODE_ID))
|
||||
) {
|
||||
return SUB2API_SESSION_CONTINUATION_LABEL;
|
||||
}
|
||||
}
|
||||
return OAUTH_CONTINUATION_LABEL;
|
||||
}
|
||||
|
||||
function buildDefaultConfirmMessage(state = {}, options = {}) {
|
||||
const continuationActionLabel = getContinuationActionLabel(state, options);
|
||||
return `GoPay 订阅页已打开。请先手动完成订阅,完成后确认继续${continuationActionLabel}。`;
|
||||
}
|
||||
|
||||
function createGoPayManualConfirmExecutor(deps = {}) {
|
||||
const {
|
||||
@@ -12,6 +46,7 @@
|
||||
chrome,
|
||||
createAutomationTab = null,
|
||||
getTabId,
|
||||
getNodeIdsForState = null,
|
||||
isTabAlive,
|
||||
registerTab,
|
||||
setState,
|
||||
@@ -40,7 +75,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
const checkoutUrl = String(state?.plusCheckoutUrl || '').trim();
|
||||
const checkoutUrl = normalizeString(state?.plusCheckoutUrl);
|
||||
if (!checkoutUrl) {
|
||||
throw new Error('步骤 7:未检测到 GoPay 订阅页,请先执行步骤 6。');
|
||||
}
|
||||
@@ -63,19 +98,21 @@
|
||||
}
|
||||
|
||||
async function executeGoPayManualConfirm(state = {}) {
|
||||
const visibleStep = Number(state?.visibleStep) || 7;
|
||||
const tabId = await resolveCheckoutTabId(state);
|
||||
if (chrome?.tabs?.update && tabId) {
|
||||
await chrome.tabs.update(tabId, { active: true }).catch(() => {});
|
||||
}
|
||||
|
||||
const continuationActionLabel = getContinuationActionLabel(state, { getNodeIdsForState });
|
||||
const payload = {
|
||||
plusCheckoutTabId: tabId,
|
||||
plusManualConfirmationPending: true,
|
||||
plusManualConfirmationRequestId: buildRequestId(),
|
||||
plusManualConfirmationStep: 7,
|
||||
plusManualConfirmationStep: visibleStep,
|
||||
plusManualConfirmationMethod: 'gopay',
|
||||
plusManualConfirmationTitle: DEFAULT_CONFIRM_TITLE,
|
||||
plusManualConfirmationMessage: DEFAULT_CONFIRM_MESSAGE,
|
||||
plusManualConfirmationMessage: buildDefaultConfirmMessage(state, { getNodeIdsForState }),
|
||||
};
|
||||
|
||||
await setState(payload);
|
||||
@@ -83,7 +120,10 @@
|
||||
broadcastDataUpdate(payload);
|
||||
}
|
||||
|
||||
await addLog('步骤 7:正在等待手动完成 GoPay 订阅,确认后继续 OAuth 登录。', 'info');
|
||||
await addLog(
|
||||
`步骤 ${visibleStep}:正在等待手动完成 GoPay 订阅,确认后继续${continuationActionLabel}。`,
|
||||
'info'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
(function attachBackgroundSub2ApiSessionImport(root, factory) {
|
||||
root.MultiPageBackgroundSub2ApiSessionImport = factory();
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundSub2ApiSessionImportModule() {
|
||||
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
|
||||
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js'];
|
||||
|
||||
function createSub2ApiSessionImportExecutor(deps = {}) {
|
||||
const {
|
||||
addLog: rawAddLog = async () => {},
|
||||
chrome,
|
||||
completeNodeFromBackground,
|
||||
ensureContentScriptReadyOnTabUntilStopped,
|
||||
getTabId,
|
||||
isTabAlive,
|
||||
normalizeSub2ApiUrl = (value) => value,
|
||||
registerTab,
|
||||
sendTabMessageUntilStopped,
|
||||
sleepWithStop = async () => {},
|
||||
throwIfStopped = () => {},
|
||||
waitForTabCompleteUntilStopped = async () => {},
|
||||
DEFAULT_SUB2API_GROUP_NAME = 'codex',
|
||||
} = deps;
|
||||
|
||||
let sub2ApiApi = null;
|
||||
|
||||
function addStepLog(step, message, level = 'info') {
|
||||
return rawAddLog(message, level, {
|
||||
step,
|
||||
stepKey: 'sub2api-session-import',
|
||||
});
|
||||
}
|
||||
|
||||
function getSub2ApiApi() {
|
||||
if (sub2ApiApi) {
|
||||
return sub2ApiApi;
|
||||
}
|
||||
const factory = deps.createSub2ApiApi
|
||||
|| self.MultiPageBackgroundSub2ApiApi?.createSub2ApiApi;
|
||||
if (typeof factory !== 'function') {
|
||||
throw new Error('SUB2API 接口模块未加载,无法导入当前 ChatGPT 会话。');
|
||||
}
|
||||
sub2ApiApi = factory({
|
||||
addLog: rawAddLog,
|
||||
normalizeSub2ApiUrl,
|
||||
DEFAULT_SUB2API_GROUP_NAME,
|
||||
});
|
||||
return sub2ApiApi;
|
||||
}
|
||||
|
||||
function normalizeString(value = '') {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function resolveVisibleStep(state = {}) {
|
||||
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||||
return visibleStep > 0 ? visibleStep : 10;
|
||||
}
|
||||
|
||||
function isSupportedChatGptSessionUrl(url = '') {
|
||||
try {
|
||||
const parsed = new URL(String(url || ''));
|
||||
if (!/^https?:$/i.test(parsed.protocol)) {
|
||||
return false;
|
||||
}
|
||||
const hostname = String(parsed.hostname || '').trim().toLowerCase();
|
||||
return /(^|\.)chatgpt\.com$/.test(hostname)
|
||||
|| hostname === 'chat.openai.com'
|
||||
|| /(^|\.)openai\.com$/.test(hostname);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveSessionTabId(state = {}) {
|
||||
const registeredTabId = typeof getTabId === 'function'
|
||||
? await getTabId(PLUS_CHECKOUT_SOURCE)
|
||||
: null;
|
||||
if (registeredTabId && typeof isTabAlive === 'function' && await isTabAlive(PLUS_CHECKOUT_SOURCE)) {
|
||||
return Number(registeredTabId) || 0;
|
||||
}
|
||||
|
||||
const storedTabId = Number(state?.plusCheckoutTabId) || 0;
|
||||
if (storedTabId && chrome?.tabs?.get) {
|
||||
const tab = await chrome.tabs.get(storedTabId).catch(() => null);
|
||||
if (tab?.id) {
|
||||
if (typeof registerTab === 'function') {
|
||||
await registerTab(PLUS_CHECKOUT_SOURCE, tab.id);
|
||||
}
|
||||
return tab.id;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('未找到可读取 ChatGPT 会话的 Plus 标签页,请先完成当前 Plus 支付链路。');
|
||||
}
|
||||
|
||||
async function getResolvedSessionTab(tabId, visibleStep) {
|
||||
const tab = await chrome?.tabs?.get?.(tabId).catch(() => null);
|
||||
if (!tab?.id) {
|
||||
throw new Error(`步骤 ${visibleStep}:Plus 会话标签页不存在或已关闭,无法继续导入 SUB2API。`);
|
||||
}
|
||||
if (!isSupportedChatGptSessionUrl(tab.url)) {
|
||||
throw new Error(`步骤 ${visibleStep}:当前标签页不在 ChatGPT / OpenAI 页面,无法读取当前登录会话。`);
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
|
||||
async function readCurrentChatGptSession(tabId, visibleStep) {
|
||||
await waitForTabCompleteUntilStopped(tabId);
|
||||
await sleepWithStop(1000);
|
||||
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
|
||||
inject: PLUS_CHECKOUT_INJECT_FILES,
|
||||
injectSource: PLUS_CHECKOUT_SOURCE,
|
||||
logMessage: `步骤 ${visibleStep}:正在等待 ChatGPT 会话页完成加载,再继续读取当前登录会话...`,
|
||||
});
|
||||
|
||||
const sessionResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
|
||||
type: 'PLUS_CHECKOUT_GET_STATE',
|
||||
source: 'background',
|
||||
payload: {
|
||||
includeSession: true,
|
||||
includeAccessToken: true,
|
||||
},
|
||||
});
|
||||
if (sessionResult?.error) {
|
||||
throw new Error(sessionResult.error);
|
||||
}
|
||||
|
||||
const session = sessionResult?.session && typeof sessionResult.session === 'object' && !Array.isArray(sessionResult.session)
|
||||
? sessionResult.session
|
||||
: null;
|
||||
const accessToken = normalizeString(
|
||||
sessionResult?.accessToken
|
||||
|| session?.accessToken
|
||||
);
|
||||
if (!session && !accessToken) {
|
||||
throw new Error(`步骤 ${visibleStep}:未读取到有效的 ChatGPT 会话或 accessToken,请确认当前标签页仍处于已登录状态。`);
|
||||
}
|
||||
|
||||
return {
|
||||
session,
|
||||
accessToken,
|
||||
};
|
||||
}
|
||||
|
||||
async function executeSub2ApiSessionImport(state = {}) {
|
||||
throwIfStopped();
|
||||
const visibleStep = resolveVisibleStep(state);
|
||||
const api = getSub2ApiApi();
|
||||
|
||||
await addStepLog(visibleStep, '正在定位当前 Plus 会话页并准备导入 SUB2API...', 'info');
|
||||
const tabId = await resolveSessionTabId(state);
|
||||
const tab = await getResolvedSessionTab(tabId, visibleStep);
|
||||
if (chrome?.tabs?.update) {
|
||||
await chrome.tabs.update(tab.id, { active: true }).catch(() => {});
|
||||
}
|
||||
|
||||
await addStepLog(visibleStep, '正在读取当前 ChatGPT 登录会话...', 'info');
|
||||
const sessionState = await readCurrentChatGptSession(tab.id, visibleStep);
|
||||
throwIfStopped();
|
||||
|
||||
const result = await api.importCurrentChatGptSession({
|
||||
...state,
|
||||
session: sessionState.session,
|
||||
accessToken: sessionState.accessToken,
|
||||
}, {
|
||||
visibleStep,
|
||||
logLabel: `步骤 ${visibleStep}`,
|
||||
logOptions: { step: visibleStep, stepKey: 'sub2api-session-import' },
|
||||
timeoutMs: 120000,
|
||||
importTimeoutMs: 120000,
|
||||
});
|
||||
|
||||
await completeNodeFromBackground(state?.nodeId || 'sub2api-session-import', result);
|
||||
}
|
||||
|
||||
return {
|
||||
executeSub2ApiSessionImport,
|
||||
isSupportedChatGptSessionUrl,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
createSub2ApiSessionImportExecutor,
|
||||
};
|
||||
});
|
||||
@@ -345,6 +345,83 @@
|
||||
return `${prefix}-${stamp}-${random}`;
|
||||
}
|
||||
|
||||
function normalizeCodexSessionObject(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
||||
}
|
||||
|
||||
function buildCodexSessionImportContent(session, accessToken = '') {
|
||||
const normalizedAccessToken = normalizeString(accessToken);
|
||||
const sessionObject = normalizeCodexSessionObject(session);
|
||||
|
||||
if (sessionObject) {
|
||||
const contentObject = normalizedAccessToken
|
||||
? {
|
||||
...sessionObject,
|
||||
accessToken: normalizedAccessToken,
|
||||
}
|
||||
: sessionObject;
|
||||
return JSON.stringify(contentObject);
|
||||
}
|
||||
|
||||
if (normalizedAccessToken) {
|
||||
return normalizedAccessToken;
|
||||
}
|
||||
|
||||
throw new Error('未读取到可导入的 ChatGPT 会话或 accessToken。');
|
||||
}
|
||||
|
||||
function resolveCodexSessionImportExpiresAt(session) {
|
||||
const sessionObject = normalizeCodexSessionObject(session);
|
||||
const expiresValue = normalizeString(sessionObject?.expires);
|
||||
if (!expiresValue) {
|
||||
return null;
|
||||
}
|
||||
const expiresAtMs = Date.parse(expiresValue);
|
||||
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.floor(expiresAtMs / 1000);
|
||||
}
|
||||
|
||||
function normalizeCodexSessionImportMessages(messages) {
|
||||
return (Array.isArray(messages) ? messages : [])
|
||||
.map((item, index) => ({
|
||||
index: Number(item?.index) || index + 1,
|
||||
name: normalizeString(item?.name),
|
||||
message: normalizeString(item?.message),
|
||||
}))
|
||||
.filter((item) => item.message);
|
||||
}
|
||||
|
||||
function normalizeCodexSessionImportResult(result) {
|
||||
return {
|
||||
total: Math.max(0, Number(result?.total) || 0),
|
||||
created: Math.max(0, Number(result?.created) || 0),
|
||||
updated: Math.max(0, Number(result?.updated) || 0),
|
||||
skipped: Math.max(0, Number(result?.skipped) || 0),
|
||||
failed: Math.max(0, Number(result?.failed) || 0),
|
||||
items: Array.isArray(result?.items) ? result.items : [],
|
||||
warnings: normalizeCodexSessionImportMessages(result?.warnings),
|
||||
errors: normalizeCodexSessionImportMessages(result?.errors),
|
||||
};
|
||||
}
|
||||
|
||||
function buildCodexSessionImportSummary(result) {
|
||||
const normalized = normalizeCodexSessionImportResult(result);
|
||||
return `SUB2API 会话导入完成:新建 ${normalized.created},更新 ${normalized.updated},跳过 ${normalized.skipped},失败 ${normalized.failed}`;
|
||||
}
|
||||
|
||||
function getCodexSessionImportFailureMessage(result) {
|
||||
const normalized = normalizeCodexSessionImportResult(result);
|
||||
const detail = normalized.errors.map((item) => item.message).find(Boolean)
|
||||
|| normalized.warnings.map((item) => item.message).find(Boolean)
|
||||
|| normalized.items
|
||||
.map((item) => normalizeString(item?.message))
|
||||
.find(Boolean)
|
||||
|| buildCodexSessionImportSummary(normalized);
|
||||
return detail || 'SUB2API 会话导入失败。';
|
||||
}
|
||||
|
||||
function parseLocalhostCallback(rawUrl, visibleStep = 10) {
|
||||
let parsed;
|
||||
try {
|
||||
@@ -580,14 +657,93 @@
|
||||
};
|
||||
}
|
||||
|
||||
async function importCurrentChatGptSession(state = {}, options = {}) {
|
||||
const logLabel = normalizeString(options.logLabel) || 'SUB2API 会话导入';
|
||||
const session = normalizeCodexSessionObject(state?.session);
|
||||
const accessToken = normalizeString(
|
||||
state?.accessToken
|
||||
|| session?.accessToken
|
||||
);
|
||||
const importContent = buildCodexSessionImportContent(session, accessToken);
|
||||
const importExpiresAt = resolveCodexSessionImportExpiresAt(session);
|
||||
|
||||
await logWithOptions(`${logLabel}:正在通过 SUB2API 管理接口登录并准备导入当前 ChatGPT 会话...`, 'info', options);
|
||||
const { origin, token } = await loginSub2Api(state, options);
|
||||
const groupNames = state.sub2apiGroupName || DEFAULT_SUB2API_GROUP_NAME;
|
||||
const groups = await getGroupsByNames(origin, token, groupNames, options);
|
||||
const groupLabel = groups.map((item) => `${item.name}(${item.id})`).join('、');
|
||||
const proxyPreference = resolveSub2ApiProxyPreference(state);
|
||||
const proxy = proxyPreference ? await resolveSub2ApiProxy(origin, token, proxyPreference, options) : null;
|
||||
const proxyId = normalizeProxyId(proxy?.id);
|
||||
const accountPriority = resolveSub2ApiAccountPriority(state);
|
||||
|
||||
await logWithOptions(`${logLabel}:已登录 SUB2API,使用分组 ${groupLabel}。`, 'info', options);
|
||||
if (proxy) {
|
||||
await logWithOptions(`${logLabel}:已选择 SUB2API 默认代理 ${buildProxyDisplayName(proxy)}。`, 'info', options);
|
||||
} else {
|
||||
await logWithOptions(`${logLabel}:未配置 SUB2API 默认代理,本次将不使用代理。`, 'info', options);
|
||||
}
|
||||
|
||||
const importPayload = {
|
||||
content: importContent,
|
||||
group_ids: groups
|
||||
.map((group) => Number(group?.id))
|
||||
.filter((id) => Number.isFinite(id) && id > 0),
|
||||
priority: accountPriority,
|
||||
auto_pause_on_expired: true,
|
||||
update_existing: true,
|
||||
};
|
||||
if (!importPayload.group_ids.length) {
|
||||
throw new Error('SUB2API 返回的目标分组 ID 无效。');
|
||||
}
|
||||
if (proxyId) {
|
||||
importPayload.proxy_id = proxyId;
|
||||
}
|
||||
if (importExpiresAt) {
|
||||
importPayload.expires_at = importExpiresAt;
|
||||
}
|
||||
|
||||
await logWithOptions(`${logLabel}:正在导入当前 ChatGPT 会话到 SUB2API...`, 'info', options);
|
||||
const importResult = normalizeCodexSessionImportResult(await requestJson(origin, '/api/v1/admin/accounts/import/codex-session', {
|
||||
method: 'POST',
|
||||
token,
|
||||
timeoutMs: options.importTimeoutMs || options.timeoutMs,
|
||||
body: importPayload,
|
||||
}));
|
||||
|
||||
for (const warning of importResult.warnings) {
|
||||
await logWithOptions(`${logLabel}:${warning.message}`, 'warn', options);
|
||||
}
|
||||
|
||||
if (importResult.failed > 0) {
|
||||
throw new Error(getCodexSessionImportFailureMessage(importResult));
|
||||
}
|
||||
if (importResult.created <= 0 && importResult.updated <= 0) {
|
||||
throw new Error(getCodexSessionImportFailureMessage(importResult));
|
||||
}
|
||||
|
||||
const verifiedStatus = buildCodexSessionImportSummary(importResult);
|
||||
await logWithOptions(verifiedStatus, 'ok', options);
|
||||
return {
|
||||
verifiedStatus,
|
||||
sub2apiImportTotal: importResult.total,
|
||||
sub2apiImportCreated: importResult.created,
|
||||
sub2apiImportUpdated: importResult.updated,
|
||||
sub2apiImportSkipped: importResult.skipped,
|
||||
sub2apiImportFailed: importResult.failed,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
buildDraftAccountName,
|
||||
buildCodexSessionImportContent,
|
||||
buildOpenAiCredentials,
|
||||
buildOpenAiExtra,
|
||||
buildProxyDisplayName,
|
||||
extractStateFromAuthUrl,
|
||||
generateOpenAiAuthUrl,
|
||||
getGroupsByNames,
|
||||
importCurrentChatGptSession,
|
||||
loginSub2Api,
|
||||
normalizeProxyId,
|
||||
normalizeRedirectUri,
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_PAYMENT_STEP_KEY = 'paypal-approve';
|
||||
const SIGNUP_METHOD_EMAIL = 'email';
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
@@ -92,10 +94,41 @@
|
||||
];
|
||||
}
|
||||
|
||||
function createSub2ApiSessionImportTail(startId, startOrder) {
|
||||
const id = Number(startId) || 10;
|
||||
const order = Number(startOrder) || id * 10;
|
||||
return [
|
||||
{
|
||||
id,
|
||||
order,
|
||||
key: 'sub2api-session-import',
|
||||
title: '导入当前 ChatGPT 会话到 SUB2API',
|
||||
sourceId: 'sub2api-panel',
|
||||
driverId: 'background/sub2api-session-import',
|
||||
command: 'sub2api-session-import',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function normalizePlusAccountAccessStrategy(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
}
|
||||
|
||||
function shouldUseSub2ApiSessionImportTail(options = {}, signupMethod = SIGNUP_METHOD_EMAIL) {
|
||||
return signupMethod === SIGNUP_METHOD_EMAIL
|
||||
&& normalizePlusAccountAccessStrategy(options?.plusAccountAccessStrategy)
|
||||
=== PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
|
||||
}
|
||||
|
||||
function createOpenAiSteps(prefixSteps, startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) {
|
||||
const tailSteps = shouldUseSub2ApiSessionImportTail(options, signupMethod)
|
||||
? createSub2ApiSessionImportTail(startId, startOrder)
|
||||
: createOpenAiAuthTail(startId, startOrder, signupMethod, options);
|
||||
return [
|
||||
...prefixSteps,
|
||||
...createOpenAiAuthTail(startId, startOrder, signupMethod, options),
|
||||
...tailSteps,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -103,12 +136,33 @@
|
||||
const NORMAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE);
|
||||
const NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
||||
const PLUS_PAYPAL_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||
const PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS = createOpenAiSteps(
|
||||
PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS,
|
||||
10,
|
||||
100,
|
||||
SIGNUP_METHOD_EMAIL,
|
||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
||||
);
|
||||
const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
||||
const PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
||||
const PLUS_GOPAY_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||
const PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS = createOpenAiSteps(
|
||||
PLUS_GOPAY_PREFIX_STEP_DEFINITIONS,
|
||||
10,
|
||||
100,
|
||||
SIGNUP_METHOD_EMAIL,
|
||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
||||
);
|
||||
const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
||||
const PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
||||
const PLUS_GPC_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||
const PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS = createOpenAiSteps(
|
||||
PLUS_GPC_PREFIX_STEP_DEFINITIONS,
|
||||
10,
|
||||
100,
|
||||
SIGNUP_METHOD_EMAIL,
|
||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
||||
);
|
||||
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 KIRO_STEP_DEFINITIONS = [
|
||||
@@ -244,12 +298,16 @@
|
||||
return NORMAL_STEP_DEFINITIONS;
|
||||
}
|
||||
const paymentMethod = normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod);
|
||||
const plusAccountAccessStrategy = normalizePlusAccountAccessStrategy(options?.plusAccountAccessStrategy);
|
||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
||||
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
||||
return reloginAfterBindEmail
|
||||
? PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS
|
||||
: PLUS_GPC_PHONE_STEP_DEFINITIONS;
|
||||
}
|
||||
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||
return PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||
}
|
||||
return PLUS_GPC_STEP_DEFINITIONS;
|
||||
}
|
||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) {
|
||||
@@ -258,6 +316,9 @@
|
||||
? PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS
|
||||
: PLUS_GOPAY_PHONE_STEP_DEFINITIONS;
|
||||
}
|
||||
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||
return PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||
}
|
||||
return PLUS_GOPAY_STEP_DEFINITIONS;
|
||||
}
|
||||
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
||||
@@ -265,6 +326,9 @@
|
||||
? PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS
|
||||
: PLUS_PAYPAL_PHONE_STEP_DEFINITIONS;
|
||||
}
|
||||
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||
return PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||
}
|
||||
return PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||
}
|
||||
|
||||
@@ -299,12 +363,15 @@
|
||||
...NORMAL_PHONE_STEP_DEFINITIONS,
|
||||
...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
...PLUS_PAYPAL_STEP_DEFINITIONS,
|
||||
...PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
...PLUS_GOPAY_STEP_DEFINITIONS,
|
||||
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||
...PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
||||
...PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
...PLUS_GPC_STEP_DEFINITIONS,
|
||||
...PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||
...PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
||||
...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
]) {
|
||||
@@ -497,12 +564,15 @@
|
||||
NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
PLUS_STEP_DEFINITIONS: PLUS_PAYPAL_STEP_DEFINITIONS,
|
||||
PLUS_PAYPAL_STEP_DEFINITIONS,
|
||||
PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||
PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||
PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
PLUS_GOPAY_STEP_DEFINITIONS,
|
||||
PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||
PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
||||
PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
PLUS_GPC_STEP_DEFINITIONS,
|
||||
PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||
PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
||||
PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||
SIGNUP_METHOD_EMAIL,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
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 VALID_OPENAI_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_TARGET_IDS)
|
||||
? flowRegistryApi.OPENAI_TARGET_IDS.slice()
|
||||
: ['cpa', 'sub2api', 'codex2api'];
|
||||
@@ -50,6 +52,7 @@
|
||||
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([
|
||||
@@ -59,6 +62,7 @@
|
||||
'phoneVerificationEnabled',
|
||||
'plusModeEnabled',
|
||||
'signupMethod',
|
||||
'plusAccountAccessStrategy',
|
||||
'openaiIntegrationTargetId',
|
||||
'kiroTargetId',
|
||||
]);
|
||||
@@ -67,14 +71,20 @@
|
||||
cpa: Object.freeze({
|
||||
supportsPhoneSignup: true,
|
||||
requiresPhoneSignupWarning: true,
|
||||
supportedPlusAccountAccessStrategies: Object.freeze([PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]),
|
||||
}),
|
||||
sub2api: Object.freeze({
|
||||
supportsPhoneSignup: true,
|
||||
requiresPhoneSignupWarning: false,
|
||||
supportedPlusAccountAccessStrategies: Object.freeze([
|
||||
PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH,
|
||||
PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||
]),
|
||||
}),
|
||||
codex2api: Object.freeze({
|
||||
supportsPhoneSignup: true,
|
||||
requiresPhoneSignupWarning: false,
|
||||
supportedPlusAccountAccessStrategies: Object.freeze([PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -116,6 +126,12 @@
|
||||
: SIGNUP_METHOD_EMAIL;
|
||||
}
|
||||
|
||||
function normalizePlusAccountAccessStrategy(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
}
|
||||
|
||||
function normalizeOpenAiTargetList(values = []) {
|
||||
if (!Array.isArray(values)) {
|
||||
return [];
|
||||
@@ -336,6 +352,28 @@
|
||||
: (effectiveSignupMethods.includes(SIGNUP_METHOD_EMAIL)
|
||||
? SIGNUP_METHOD_EMAIL
|
||||
: effectiveSignupMethods[0]);
|
||||
const requestedPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(
|
||||
options?.plusAccountAccessStrategy ?? state?.plusAccountAccessStrategy
|
||||
);
|
||||
const availablePlusAccountAccessStrategies = activeFlowId === 'openai'
|
||||
&& Boolean(flowState.supportsPlusMode)
|
||||
&& Boolean(runtimeLocks.plusModeEnabled)
|
||||
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
|
||||
? (
|
||||
Array.isArray(targetState.supportedPlusAccountAccessStrategies)
|
||||
&& targetState.supportedPlusAccountAccessStrategies.length > 0
|
||||
? targetState.supportedPlusAccountAccessStrategies.slice()
|
||||
: [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]
|
||||
)
|
||||
: [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH];
|
||||
const effectivePlusAccountAccessStrategy = availablePlusAccountAccessStrategies.includes(requestedPlusAccountAccessStrategy)
|
||||
? requestedPlusAccountAccessStrategy
|
||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const canEditPlusAccountAccessStrategy = activeFlowId === 'openai'
|
||||
&& Boolean(flowState.supportsPlusMode)
|
||||
&& Boolean(runtimeLocks.plusModeEnabled)
|
||||
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
|
||||
&& availablePlusAccountAccessStrategies.length > 1;
|
||||
const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function'
|
||||
&& isRegisteredFlowId(activeFlowId)
|
||||
? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveTargetId)
|
||||
@@ -348,8 +386,10 @@
|
||||
canShowPhoneSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPhoneVerificationSettings),
|
||||
canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode),
|
||||
canSwitchFlow: Boolean(flowState.canSwitchFlow),
|
||||
canEditPlusAccountAccessStrategy,
|
||||
canUsePhoneSignup: canSelectPhoneSignup,
|
||||
canUseSelectedTarget: targetSupported,
|
||||
effectivePlusAccountAccessStrategy,
|
||||
effectivePanelMode: effectiveTargetId,
|
||||
effectiveSignupMethod,
|
||||
effectiveSignupMethods,
|
||||
@@ -357,9 +397,11 @@
|
||||
flowCapabilities: flowState,
|
||||
panelCapabilities: targetState,
|
||||
panelMode: effectiveTargetId,
|
||||
requestedPlusAccountAccessStrategy,
|
||||
requestedSignupMethod,
|
||||
requestedTargetId,
|
||||
runtimeLocks,
|
||||
availablePlusAccountAccessStrategies,
|
||||
shouldWarnCpaPhoneSignup: effectiveSignupMethod === SIGNUP_METHOD_PHONE
|
||||
&& Boolean(targetState.requiresPhoneSignupWarning),
|
||||
stepDefinitionOptions: {
|
||||
@@ -367,6 +409,7 @@
|
||||
integrationTargetId: effectiveTargetId,
|
||||
panelMode: effectiveTargetId,
|
||||
targetId: effectiveTargetId,
|
||||
plusAccountAccessStrategy: effectivePlusAccountAccessStrategy,
|
||||
plusModeEnabled: runtimeLocks.plusModeEnabled,
|
||||
signupMethod: effectiveSignupMethod,
|
||||
},
|
||||
@@ -556,6 +599,7 @@
|
||||
getOpenAiTargetCapabilities,
|
||||
normalizeFlowId,
|
||||
normalizeOpenAiTargetId,
|
||||
normalizePlusAccountAccessStrategy,
|
||||
normalizeSignupMethod,
|
||||
resolveSidepanelCapabilities,
|
||||
resolveSignupMethod,
|
||||
@@ -572,11 +616,14 @@
|
||||
DEFAULT_OPENAI_TARGET_ID,
|
||||
FLOW_CAPABILITIES,
|
||||
OPENAI_TARGET_CAPABILITIES,
|
||||
PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH,
|
||||
PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||
SIGNUP_METHOD_EMAIL,
|
||||
SIGNUP_METHOD_PHONE,
|
||||
VALID_OPENAI_TARGET_IDS,
|
||||
normalizeFlowId,
|
||||
normalizeOpenAiTargetId,
|
||||
normalizePlusAccountAccessStrategy,
|
||||
normalizeSignupMethod,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -336,7 +336,7 @@
|
||||
'openai-plus': {
|
||||
id: 'openai-plus',
|
||||
label: 'Plus',
|
||||
rowIds: ['row-plus-mode'],
|
||||
rowIds: ['row-plus-mode', 'row-plus-payment-method', 'row-plus-account-access-strategy'],
|
||||
},
|
||||
'openai-phone': {
|
||||
id: 'openai-phone',
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
const normalizeTargetId = typeof flowRegistry.normalizeTargetId === 'function'
|
||||
? flowRegistry.normalizeTargetId
|
||||
: ((_flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase());
|
||||
const normalizePlusAccountAccessStrategy = (value = '') => (
|
||||
String(value || '').trim().toLowerCase() === 'sub2api_codex_session'
|
||||
? 'sub2api_codex_session'
|
||||
: 'oauth'
|
||||
);
|
||||
|
||||
function buildDefaultSettingsState() {
|
||||
return {
|
||||
@@ -96,6 +101,7 @@
|
||||
plus: {
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: {
|
||||
@@ -301,6 +307,11 @@
|
||||
?? 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
|
||||
),
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: normalizeStepExecutionRangeEntry(
|
||||
@@ -439,6 +450,7 @@
|
||||
next.phoneSignupReloginAfterBindEmailEnabled = openaiState.signup.phoneSignupReloginAfterBindEmailEnabled;
|
||||
next.plusModeEnabled = openaiState.plus.plusModeEnabled;
|
||||
next.plusPaymentMethod = openaiState.plus.plusPaymentMethod;
|
||||
next.plusAccountAccessStrategy = openaiState.plus.plusAccountAccessStrategy;
|
||||
next.mailProvider = normalizedState.services.email.provider;
|
||||
next.ipProxyEnabled = normalizedState.services.proxy.enabled;
|
||||
next.ipProxyService = normalizedState.services.proxy.provider;
|
||||
|
||||
@@ -313,6 +313,16 @@
|
||||
<span class="setting-caption" id="plus-payment-method-caption">PayPal 订阅链路</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-plus-account-access-strategy" style="display:none;">
|
||||
<span class="data-label">账号接入策略</span>
|
||||
<div class="data-inline">
|
||||
<select id="select-plus-account-access-strategy" class="data-select">
|
||||
<option value="oauth">OAuth</option>
|
||||
<option value="sub2api_codex_session">导入当前 ChatGPT 会话到 SUB2API</option>
|
||||
</select>
|
||||
<span class="setting-caption" id="plus-account-access-strategy-caption">当前来源仅支持 OAuth</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-paypal-account" style="display:none;">
|
||||
<span class="data-label">PayPal 账号</span>
|
||||
<div class="data-inline">
|
||||
|
||||
+193
-4
@@ -192,6 +192,9 @@ const rowPlusPaymentMethod = document.getElementById('row-plus-payment-method');
|
||||
const selectPlusPaymentMethod = document.getElementById('select-plus-payment-method');
|
||||
const btnGpcCardKeyPurchase = document.getElementById('btn-gpc-card-key-purchase');
|
||||
const plusPaymentMethodCaption = document.getElementById('plus-payment-method-caption');
|
||||
const rowPlusAccountAccessStrategy = document.getElementById('row-plus-account-access-strategy');
|
||||
const selectPlusAccountAccessStrategy = document.getElementById('select-plus-account-access-strategy');
|
||||
const plusAccountAccessStrategyCaption = document.getElementById('plus-account-access-strategy-caption');
|
||||
const rowPayPalAccount = document.getElementById('row-paypal-account');
|
||||
const selectPayPalAccount = document.getElementById('select-paypal-account');
|
||||
const payPalAccountPickerRoot = document.getElementById('paypal-account-picker');
|
||||
@@ -542,6 +545,9 @@ const GPC_HELPER_PORTAL_URL = 'https://gpc.qlhazycoder.top/';
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const SIGNUP_METHOD_EMAIL = 'email';
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
||||
@@ -551,6 +557,7 @@ const PHONE_SIGNUP_REUSE_LOCK_TITLE = '手机号注册流程不使用号码复
|
||||
let latestState = null;
|
||||
let currentPlusModeEnabled = false;
|
||||
let currentPlusPaymentMethod = DEFAULT_PLUS_PAYMENT_METHOD;
|
||||
let currentPlusAccountAccessStrategy = DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY;
|
||||
let currentSignupMethod = DEFAULT_SIGNUP_METHOD;
|
||||
let currentPhoneSignupReloginAfterBindEmailEnabled = DEFAULT_PHONE_SIGNUP_RELOGIN_AFTER_BIND_EMAIL_ENABLED;
|
||||
let currentStepDefinitionFlowId = DEFAULT_ACTIVE_FLOW_ID;
|
||||
@@ -573,11 +580,13 @@ let nexSmsCountryMenuSearchKeyword = '';
|
||||
const nexSmsCountrySearchTextById = new Map();
|
||||
let stepDefinitions = getStepDefinitionsForMode(false, {
|
||||
plusPaymentMethod: currentPlusPaymentMethod,
|
||||
plusAccountAccessStrategy: currentPlusAccountAccessStrategy,
|
||||
signupMethod: currentSignupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
|
||||
});
|
||||
let workflowNodes = getWorkflowNodesForMode(false, {
|
||||
plusPaymentMethod: currentPlusPaymentMethod,
|
||||
plusAccountAccessStrategy: currentPlusAccountAccessStrategy,
|
||||
signupMethod: currentSignupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
|
||||
});
|
||||
@@ -838,9 +847,13 @@ function initPhoneVerificationSectionExpandedState() {
|
||||
function getStepDefinitionsForMode(plusModeEnabled = false, options = {}) {
|
||||
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 defaultStrategy = typeof DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY !== 'undefined' ? DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY : 'oauth';
|
||||
const rawPaymentMethod = typeof options === 'string'
|
||||
? options
|
||||
: (options.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod);
|
||||
const rawPlusAccountAccessStrategy = typeof options === 'string'
|
||||
? currentPlusAccountAccessStrategy
|
||||
: (options.plusAccountAccessStrategy || currentPlusAccountAccessStrategy || defaultStrategy);
|
||||
const rawSignupMethod = typeof options === 'string'
|
||||
? currentSignupMethod
|
||||
: (options.signupMethod || currentSignupMethod || DEFAULT_SIGNUP_METHOD);
|
||||
@@ -854,6 +867,7 @@ function getStepDefinitionsForMode(plusModeEnabled = false, options = {}) {
|
||||
activeFlowId: String(activeFlowId || '').trim().toLowerCase() || defaultFlowId,
|
||||
plusModeEnabled,
|
||||
plusPaymentMethod: normalizePlusPaymentMethod(rawPaymentMethod),
|
||||
plusAccountAccessStrategy: normalizePlusAccountAccessStrategy(rawPlusAccountAccessStrategy),
|
||||
signupMethod: normalizeSignupMethod(rawSignupMethod),
|
||||
phoneSignupReloginAfterBindEmailEnabled,
|
||||
}) || [])
|
||||
@@ -868,9 +882,13 @@ function getStepDefinitionsForMode(plusModeEnabled = false, options = {}) {
|
||||
function getWorkflowNodesForMode(plusModeEnabled = false, options = {}) {
|
||||
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 defaultStrategy = typeof DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY !== 'undefined' ? DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY : 'oauth';
|
||||
const rawPaymentMethod = typeof options === 'string'
|
||||
? options
|
||||
: (options.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod);
|
||||
const rawPlusAccountAccessStrategy = typeof options === 'string'
|
||||
? currentPlusAccountAccessStrategy
|
||||
: (options.plusAccountAccessStrategy || currentPlusAccountAccessStrategy || defaultStrategy);
|
||||
const rawSignupMethod = typeof options === 'string'
|
||||
? currentSignupMethod
|
||||
: (options.signupMethod || currentSignupMethod || DEFAULT_SIGNUP_METHOD);
|
||||
@@ -884,6 +902,7 @@ function getWorkflowNodesForMode(plusModeEnabled = false, options = {}) {
|
||||
activeFlowId: String(activeFlowId || '').trim().toLowerCase() || defaultFlowId,
|
||||
plusModeEnabled,
|
||||
plusPaymentMethod: normalizePlusPaymentMethod(rawPaymentMethod),
|
||||
plusAccountAccessStrategy: normalizePlusAccountAccessStrategy(rawPlusAccountAccessStrategy),
|
||||
signupMethod: normalizeSignupMethod(rawSignupMethod),
|
||||
phoneSignupReloginAfterBindEmailEnabled,
|
||||
});
|
||||
@@ -940,9 +959,13 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
||||
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 defaultStrategy = typeof DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY !== 'undefined' ? DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY : 'oauth';
|
||||
const rawPaymentMethod = typeof options === 'string'
|
||||
? options
|
||||
: (options.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod);
|
||||
const rawPlusAccountAccessStrategy = typeof options === 'string'
|
||||
? currentPlusAccountAccessStrategy
|
||||
: (options.plusAccountAccessStrategy || currentPlusAccountAccessStrategy || defaultStrategy);
|
||||
const rawSignupMethod = typeof options === 'string'
|
||||
? currentSignupMethod
|
||||
: (options.signupMethod || currentSignupMethod || DEFAULT_SIGNUP_METHOD);
|
||||
@@ -950,6 +973,7 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
||||
? currentPhoneSignupReloginAfterBindEmailEnabled
|
||||
: Boolean(options.phoneSignupReloginAfterBindEmailEnabled ?? currentPhoneSignupReloginAfterBindEmailEnabled);
|
||||
currentPlusPaymentMethod = normalizePlusPaymentMethod(rawPaymentMethod);
|
||||
currentPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(rawPlusAccountAccessStrategy);
|
||||
currentSignupMethod = normalizeSignupMethod(rawSignupMethod);
|
||||
currentPhoneSignupReloginAfterBindEmailEnabled = phoneSignupReloginAfterBindEmailEnabled;
|
||||
const nextActiveFlowId = String(
|
||||
@@ -963,6 +987,7 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
||||
stepDefinitions = getStepDefinitionsForMode(currentPlusModeEnabled, {
|
||||
activeFlowId: nextActiveFlowId,
|
||||
plusPaymentMethod: currentPlusPaymentMethod,
|
||||
plusAccountAccessStrategy: currentPlusAccountAccessStrategy,
|
||||
signupMethod: currentSignupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
|
||||
});
|
||||
@@ -970,6 +995,7 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
||||
? getWorkflowNodesForMode(currentPlusModeEnabled, {
|
||||
activeFlowId: nextActiveFlowId,
|
||||
plusPaymentMethod: currentPlusPaymentMethod,
|
||||
plusAccountAccessStrategy: currentPlusAccountAccessStrategy,
|
||||
signupMethod: currentSignupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
|
||||
})
|
||||
@@ -1899,6 +1925,19 @@ async function openConfirmModalWithOption({
|
||||
|
||||
async function openPlusManualConfirmationDialog(options = {}) {
|
||||
const method = String(options.method || '').trim().toLowerCase();
|
||||
const activeFlowId = String(latestState?.activeFlowId || latestState?.flowId || 'openai').trim().toLowerCase();
|
||||
const panelMode = String(latestState?.panelMode || latestState?.openaiIntegrationTargetId || '').trim().toLowerCase();
|
||||
const signupMethod = String(latestState?.resolvedSignupMethod || latestState?.signupMethod || 'email').trim().toLowerCase();
|
||||
const plusModeEnabled = latestState?.plusModeEnabled === undefined ? true : Boolean(latestState.plusModeEnabled);
|
||||
const plusAccountAccessStrategy = String(latestState?.plusAccountAccessStrategy || 'oauth').trim().toLowerCase();
|
||||
const useSub2ApiSessionImport = plusModeEnabled
|
||||
&& activeFlowId === 'openai'
|
||||
&& panelMode === 'sub2api'
|
||||
&& signupMethod === 'email'
|
||||
&& plusAccountAccessStrategy === 'sub2api_codex_session';
|
||||
const continuationActionLabel = useSub2ApiSessionImport
|
||||
? '导入当前 ChatGPT 会话到 SUB2API'
|
||||
: 'OAuth 登录';
|
||||
const title = String(options.title || '').trim() || (method === 'gopay' ? 'GoPay 订阅确认' : '手动确认');
|
||||
const message = String(options.message || '').trim()
|
||||
|| (method === 'gopay'
|
||||
@@ -1912,7 +1951,7 @@ async function openPlusManualConfirmationDialog(options = {}) {
|
||||
{ id: 'confirm', label: '我已完成订阅', variant: 'btn-primary' },
|
||||
],
|
||||
alert: method === 'gopay'
|
||||
? { text: '确认后流程会直接继续到 Plus 模式第 10 步 OAuth 登录。', tone: 'info' }
|
||||
? { text: `确认后流程会直接继续到 Plus 模式后续的${continuationActionLabel}。`, tone: 'info' }
|
||||
: null,
|
||||
});
|
||||
}
|
||||
@@ -1926,6 +1965,19 @@ async function syncPlusManualConfirmationDialog() {
|
||||
|
||||
const step = Number(latestState?.plusManualConfirmationStep) || 0;
|
||||
const method = String(latestState?.plusManualConfirmationMethod || '').trim().toLowerCase();
|
||||
const activeFlowId = String(latestState?.activeFlowId || latestState?.flowId || 'openai').trim().toLowerCase();
|
||||
const panelMode = String(latestState?.panelMode || latestState?.openaiIntegrationTargetId || '').trim().toLowerCase();
|
||||
const signupMethod = String(latestState?.resolvedSignupMethod || latestState?.signupMethod || 'email').trim().toLowerCase();
|
||||
const plusModeEnabled = latestState?.plusModeEnabled === undefined ? true : Boolean(latestState.plusModeEnabled);
|
||||
const plusAccountAccessStrategy = String(latestState?.plusAccountAccessStrategy || 'oauth').trim().toLowerCase();
|
||||
const useSub2ApiSessionImport = plusModeEnabled
|
||||
&& activeFlowId === 'openai'
|
||||
&& panelMode === 'sub2api'
|
||||
&& signupMethod === 'email'
|
||||
&& plusAccountAccessStrategy === 'sub2api_codex_session';
|
||||
const continuationActionLabel = useSub2ApiSessionImport
|
||||
? '导入当前 ChatGPT 会话到 SUB2API'
|
||||
: 'OAuth 登录';
|
||||
const title = latestState?.plusManualConfirmationTitle;
|
||||
const message = latestState?.plusManualConfirmationMessage;
|
||||
activePlusManualConfirmationRequestId = requestId;
|
||||
@@ -1963,7 +2015,7 @@ async function syncPlusManualConfirmationDialog() {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (confirmed) {
|
||||
showToast(method === 'gopay' ? 'GoPay 订阅已确认,正在继续 OAuth 登录...' : '已确认,流程继续执行中...', 'info', 2200);
|
||||
showToast(method === 'gopay' ? `GoPay 订阅已确认,正在继续${continuationActionLabel}...` : '已确认,流程继续执行中...', 'info', 2200);
|
||||
} else {
|
||||
showToast(method === 'gopay' ? '已取消 GoPay 订阅等待。' : '已取消当前手动确认。', 'warn', 2200);
|
||||
}
|
||||
@@ -2799,6 +2851,13 @@ function normalizePlusPaymentMethod(value = '') {
|
||||
return normalized === gopayValue ? gopayValue : paypalValue;
|
||||
}
|
||||
|
||||
function normalizePlusAccountAccessStrategy(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
}
|
||||
|
||||
function getSelectedPlusPaymentMethod(state = latestState) {
|
||||
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal';
|
||||
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod?.value) {
|
||||
@@ -2807,6 +2866,26 @@ function getSelectedPlusPaymentMethod(state = latestState) {
|
||||
return normalizePlusPaymentMethod(state?.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod);
|
||||
}
|
||||
|
||||
function getRequestedPlusAccountAccessStrategy(state = latestState) {
|
||||
const defaultStrategy = typeof DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY !== 'undefined'
|
||||
? DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY
|
||||
: 'oauth';
|
||||
const fallbackStrategy = normalizePlusAccountAccessStrategy(
|
||||
(typeof selectPlusAccountAccessStrategy !== 'undefined' && selectPlusAccountAccessStrategy?.dataset?.requestedValue)
|
||||
|| state?.plusAccountAccessStrategy
|
||||
|| currentPlusAccountAccessStrategy
|
||||
|| defaultStrategy
|
||||
);
|
||||
if (
|
||||
typeof selectPlusAccountAccessStrategy !== 'undefined'
|
||||
&& selectPlusAccountAccessStrategy
|
||||
&& !selectPlusAccountAccessStrategy.disabled
|
||||
) {
|
||||
return normalizePlusAccountAccessStrategy(selectPlusAccountAccessStrategy.value || fallbackStrategy);
|
||||
}
|
||||
return fallbackStrategy;
|
||||
}
|
||||
|
||||
function normalizeGpcHelperPhoneModeValue(value = '') {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
if (rootScope.GoPayUtils?.normalizeGpcHelperPhoneMode) {
|
||||
@@ -4150,6 +4229,9 @@ function collectSettingsPayload() {
|
||||
const rawPlusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||
? Boolean(inputPlusModeEnabled.checked)
|
||||
: Boolean(latestState?.plusModeEnabled);
|
||||
const requestedPlusAccountAccessStrategy = typeof getRequestedPlusAccountAccessStrategy === 'function'
|
||||
? getRequestedPlusAccountAccessStrategy(latestState)
|
||||
: normalizePlusAccountAccessStrategy(latestState?.plusAccountAccessStrategy || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY);
|
||||
const rawPhoneVerificationEnabled = Boolean(inputPhoneVerificationEnabled?.checked);
|
||||
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||
? resolveCurrentSidepanelCapabilities({
|
||||
@@ -4164,6 +4246,7 @@ function collectSettingsPayload() {
|
||||
? { panelMode: rawPanelMode }
|
||||
: { kiroTargetId: selectedTargetId }),
|
||||
plusModeEnabled: rawPlusModeEnabled,
|
||||
plusAccountAccessStrategy: requestedPlusAccountAccessStrategy,
|
||||
phoneVerificationEnabled: rawPhoneVerificationEnabled,
|
||||
signupMethod: selectedSignupMethod,
|
||||
},
|
||||
@@ -4186,6 +4269,7 @@ function collectSettingsPayload() {
|
||||
? { panelMode: rawPanelMode }
|
||||
: { kiroTargetId: selectedTargetId }),
|
||||
plusModeEnabled: rawPlusModeEnabled,
|
||||
plusAccountAccessStrategy: requestedPlusAccountAccessStrategy,
|
||||
phoneVerificationEnabled: rawPhoneVerificationEnabled,
|
||||
signupMethod: selectedSignupMethod,
|
||||
},
|
||||
@@ -4326,6 +4410,7 @@ function collectSettingsPayload() {
|
||||
codex2apiAdminKey: inputCodex2ApiAdminKey.value.trim(),
|
||||
plusModeEnabled: effectivePlusModeEnabled,
|
||||
plusPaymentMethod,
|
||||
plusAccountAccessStrategy: requestedPlusAccountAccessStrategy,
|
||||
paypalEmail: String(currentPayPalAccount?.email || latestState?.paypalEmail || '').trim(),
|
||||
paypalPassword: String(currentPayPalAccount?.password || latestState?.paypalPassword || ''),
|
||||
currentPayPalAccountId: String(latestState?.currentPayPalAccountId || '').trim(),
|
||||
@@ -8505,6 +8590,10 @@ function resolveStepDefinitionCapabilityState(state = latestState, options = {})
|
||||
plusModeEnabled: capabilityState
|
||||
? Boolean(capabilityState.runtimeLocks?.plusModeEnabled)
|
||||
: Boolean(nextState?.plusModeEnabled),
|
||||
plusAccountAccessStrategy: capabilityState?.effectivePlusAccountAccessStrategy
|
||||
|| normalizePlusAccountAccessStrategy(
|
||||
nextState?.plusAccountAccessStrategy || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY
|
||||
),
|
||||
signupMethod: capabilityState?.effectiveSignupMethod
|
||||
|| normalizeSignupMethod((options?.signupMethod ?? nextState?.signupMethod) || DEFAULT_SIGNUP_METHOD),
|
||||
};
|
||||
@@ -8905,7 +8994,16 @@ function updatePlusModeUI() {
|
||||
const paypalValue = typeof PLUS_PAYMENT_METHOD_PAYPAL !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL : 'paypal';
|
||||
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
||||
const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper';
|
||||
const oauthStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH !== 'undefined'
|
||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH
|
||||
: 'oauth';
|
||||
const sub2apiSessionStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION !== 'undefined'
|
||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||
: 'sub2api_codex_session';
|
||||
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : paypalValue;
|
||||
const requestedPlusAccountAccessStrategy = typeof getRequestedPlusAccountAccessStrategy === 'function'
|
||||
? getRequestedPlusAccountAccessStrategy(latestState)
|
||||
: normalizePlusAccountAccessStrategy(latestState?.plusAccountAccessStrategy || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY);
|
||||
const rawEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||
? Boolean(inputPlusModeEnabled.checked)
|
||||
: false;
|
||||
@@ -8915,6 +9013,7 @@ function updatePlusModeUI() {
|
||||
state: {
|
||||
...(latestState || {}),
|
||||
plusModeEnabled: rawEnabled,
|
||||
plusAccountAccessStrategy: requestedPlusAccountAccessStrategy,
|
||||
},
|
||||
})
|
||||
: (() => {
|
||||
@@ -8929,6 +9028,7 @@ function updatePlusModeUI() {
|
||||
state: {
|
||||
...(latestState || {}),
|
||||
plusModeEnabled: rawEnabled,
|
||||
plusAccountAccessStrategy: requestedPlusAccountAccessStrategy,
|
||||
},
|
||||
})
|
||||
: null;
|
||||
@@ -8937,6 +9037,10 @@ function updatePlusModeUI() {
|
||||
? Boolean(capabilityState.canShowPlusSettings)
|
||||
: true;
|
||||
const enabled = supportsPlusMode && rawEnabled;
|
||||
const canEditPlusAccountAccessStrategy = Boolean(capabilityState?.canEditPlusAccountAccessStrategy);
|
||||
const effectivePlusAccountAccessStrategy = capabilityState?.effectivePlusAccountAccessStrategy
|
||||
|| requestedPlusAccountAccessStrategy
|
||||
|| oauthStrategyValue;
|
||||
const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod;
|
||||
const gpcPhoneMode = normalizeGpcHelperPhoneModeValue(
|
||||
typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode
|
||||
@@ -8990,6 +9094,33 @@ function updatePlusModeUI() {
|
||||
}
|
||||
row.style.display = enabled ? '' : 'none';
|
||||
});
|
||||
[
|
||||
typeof rowPlusAccountAccessStrategy !== 'undefined' ? rowPlusAccountAccessStrategy : null,
|
||||
].forEach((row) => {
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
row.style.display = enabled ? '' : 'none';
|
||||
});
|
||||
if (typeof selectPlusAccountAccessStrategy !== 'undefined' && selectPlusAccountAccessStrategy) {
|
||||
selectPlusAccountAccessStrategy.dataset.requestedValue = requestedPlusAccountAccessStrategy;
|
||||
selectPlusAccountAccessStrategy.value = effectivePlusAccountAccessStrategy;
|
||||
selectPlusAccountAccessStrategy.disabled = !enabled || !canEditPlusAccountAccessStrategy;
|
||||
selectPlusAccountAccessStrategy.setAttribute('aria-disabled', String(selectPlusAccountAccessStrategy.disabled));
|
||||
}
|
||||
if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) {
|
||||
if (!enabled) {
|
||||
plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
|
||||
} else if (!canEditPlusAccountAccessStrategy) {
|
||||
plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
|
||||
} else if (effectivePlusAccountAccessStrategy === sub2apiSessionStrategyValue) {
|
||||
plusAccountAccessStrategyCaption.textContent = '复用当前 Plus 已登录会话,直接导入到 SUB2API';
|
||||
} else if (effectivePlusAccountAccessStrategy === oauthStrategyValue) {
|
||||
plusAccountAccessStrategyCaption.textContent = '通过 OAuth 回调创建 SUB2API 账号';
|
||||
} else {
|
||||
plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
|
||||
}
|
||||
}
|
||||
[
|
||||
typeof rowPayPalAccount !== 'undefined' ? rowPayPalAccount : null,
|
||||
].forEach((row) => {
|
||||
@@ -9362,6 +9493,19 @@ async function ensureGpcApiKeyReadyForStart(options = {}) {
|
||||
async function openPlusManualConfirmationDialog(options = {}) {
|
||||
const method = String(options.method || '').trim().toLowerCase();
|
||||
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
||||
const activeFlowId = String(latestState?.activeFlowId || latestState?.flowId || 'openai').trim().toLowerCase();
|
||||
const panelMode = String(latestState?.panelMode || latestState?.openaiIntegrationTargetId || '').trim().toLowerCase();
|
||||
const signupMethod = String(latestState?.resolvedSignupMethod || latestState?.signupMethod || 'email').trim().toLowerCase();
|
||||
const plusModeEnabled = latestState?.plusModeEnabled === undefined ? true : Boolean(latestState.plusModeEnabled);
|
||||
const plusAccountAccessStrategy = String(latestState?.plusAccountAccessStrategy || 'oauth').trim().toLowerCase();
|
||||
const useSub2ApiSessionImport = plusModeEnabled
|
||||
&& activeFlowId === 'openai'
|
||||
&& panelMode === 'sub2api'
|
||||
&& signupMethod === 'email'
|
||||
&& plusAccountAccessStrategy === 'sub2api_codex_session';
|
||||
const continuationActionLabel = useSub2ApiSessionImport
|
||||
? '导入当前 ChatGPT 会话到 SUB2API'
|
||||
: 'OAuth 登录';
|
||||
if (method === 'gopay-otp') {
|
||||
if (!sharedFormDialog?.open) {
|
||||
return null;
|
||||
@@ -9405,7 +9549,7 @@ async function openPlusManualConfirmationDialog(options = {}) {
|
||||
{ id: 'confirm', label: '我已完成订阅', variant: 'btn-primary' },
|
||||
],
|
||||
alert: method === gopayValue
|
||||
? { text: '确认后流程会直接继续到 Plus 模式第 10 步 OAuth 登录。', tone: 'info' }
|
||||
? { text: `确认后流程会直接继续到 Plus 模式后续的${continuationActionLabel}。`, tone: 'info' }
|
||||
: null,
|
||||
});
|
||||
}
|
||||
@@ -9420,6 +9564,19 @@ async function syncPlusManualConfirmationDialog() {
|
||||
|
||||
const step = Number(latestState?.plusManualConfirmationStep) || 0;
|
||||
const method = String(latestState?.plusManualConfirmationMethod || '').trim().toLowerCase();
|
||||
const activeFlowId = String(latestState?.activeFlowId || latestState?.flowId || 'openai').trim().toLowerCase();
|
||||
const panelMode = String(latestState?.panelMode || latestState?.openaiIntegrationTargetId || '').trim().toLowerCase();
|
||||
const signupMethod = String(latestState?.resolvedSignupMethod || latestState?.signupMethod || 'email').trim().toLowerCase();
|
||||
const plusModeEnabled = latestState?.plusModeEnabled === undefined ? true : Boolean(latestState.plusModeEnabled);
|
||||
const plusAccountAccessStrategy = String(latestState?.plusAccountAccessStrategy || 'oauth').trim().toLowerCase();
|
||||
const useSub2ApiSessionImport = plusModeEnabled
|
||||
&& activeFlowId === 'openai'
|
||||
&& panelMode === 'sub2api'
|
||||
&& signupMethod === 'email'
|
||||
&& plusAccountAccessStrategy === 'sub2api_codex_session';
|
||||
const continuationActionLabel = useSub2ApiSessionImport
|
||||
? '导入当前 ChatGPT 会话到 SUB2API'
|
||||
: 'OAuth 登录';
|
||||
const title = latestState?.plusManualConfirmationTitle;
|
||||
const message = latestState?.plusManualConfirmationMessage;
|
||||
activePlusManualConfirmationRequestId = requestId;
|
||||
@@ -9461,7 +9618,7 @@ async function syncPlusManualConfirmationDialog() {
|
||||
showToast(
|
||||
method === 'gopay-otp'
|
||||
? 'GPC OTP 已提交,正在继续验证...'
|
||||
: (method === gopayValue ? 'GoPay 订阅已确认,正在继续 OAuth 登录...' : '已确认,流程继续执行中...'),
|
||||
: (method === gopayValue ? `GoPay 订阅已确认,正在继续${continuationActionLabel}...` : '已确认,流程继续执行中...'),
|
||||
'info',
|
||||
2200
|
||||
);
|
||||
@@ -9776,6 +9933,7 @@ function renderStepsList() {
|
||||
|
||||
function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOrOptions = {}, maybeOptions = {}) {
|
||||
const defaultFlowId = typeof DEFAULT_ACTIVE_FLOW_ID !== 'undefined' ? DEFAULT_ACTIVE_FLOW_ID : 'openai';
|
||||
const defaultStrategy = typeof DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY !== 'undefined' ? DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY : 'oauth';
|
||||
const nextPlusModeEnabled = Boolean(plusModeEnabled);
|
||||
const options = typeof plusPaymentMethodOrOptions === 'string'
|
||||
? maybeOptions
|
||||
@@ -9783,6 +9941,11 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr
|
||||
const rawPaymentMethod = typeof plusPaymentMethodOrOptions === 'string'
|
||||
? plusPaymentMethodOrOptions
|
||||
: (options.plusPaymentMethod || getSelectedPlusPaymentMethod(latestState));
|
||||
const nextPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(
|
||||
options.plusAccountAccessStrategy
|
||||
|| currentPlusAccountAccessStrategy
|
||||
|| defaultStrategy
|
||||
);
|
||||
const nextSignupMethod = normalizeSignupMethod(options.signupMethod || currentSignupMethod || DEFAULT_SIGNUP_METHOD);
|
||||
const nextPhoneSignupReloginAfterBindEmailEnabled = Boolean(
|
||||
options.phoneSignupReloginAfterBindEmailEnabled
|
||||
@@ -9805,6 +9968,7 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr
|
||||
activeFlowId: nextActiveFlowId,
|
||||
plusModeEnabled: nextPlusModeEnabled,
|
||||
plusPaymentMethod: nextPaymentMethod,
|
||||
plusAccountAccessStrategy: nextPlusAccountAccessStrategy,
|
||||
signupMethod: nextSignupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: nextPhoneSignupReloginAfterBindEmailEnabled,
|
||||
});
|
||||
@@ -9812,6 +9976,7 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr
|
||||
const shouldRender = Boolean(options.render)
|
||||
|| nextPlusModeEnabled !== currentPlusModeEnabled
|
||||
|| nextPaymentMethod !== currentPlusPaymentMethod
|
||||
|| nextPlusAccountAccessStrategy !== currentPlusAccountAccessStrategy
|
||||
|| nextSignupMethod !== currentSignupMethod
|
||||
|| nextPhoneSignupReloginAfterBindEmailEnabled !== currentPhoneSignupReloginAfterBindEmailEnabled
|
||||
|| nextActiveFlowId !== currentFlowId
|
||||
@@ -9823,6 +9988,7 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr
|
||||
rebuildStepDefinitionState(nextPlusModeEnabled, {
|
||||
activeFlowId: nextActiveFlowId,
|
||||
plusPaymentMethod: nextPaymentMethod,
|
||||
plusAccountAccessStrategy: nextPlusAccountAccessStrategy,
|
||||
signupMethod: nextSignupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: nextPhoneSignupReloginAfterBindEmailEnabled,
|
||||
});
|
||||
@@ -9848,6 +10014,7 @@ function syncStepDefinitionsFromUiState(stateOverrides = {}) {
|
||||
syncStepDefinitionsForMode(stepDefinitionState.plusModeEnabled, {
|
||||
activeFlowId: nextState?.activeFlowId || nextState?.flowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||
plusPaymentMethod: getSelectedPlusPaymentMethod(nextState),
|
||||
plusAccountAccessStrategy: stepDefinitionState.plusAccountAccessStrategy,
|
||||
signupMethod: stepDefinitionState.signupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: Boolean(nextState?.phoneSignupReloginAfterBindEmailEnabled),
|
||||
});
|
||||
@@ -9949,6 +10116,13 @@ function applySettingsState(state) {
|
||||
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
|
||||
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(state?.plusPaymentMethod);
|
||||
}
|
||||
currentPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(
|
||||
state?.plusAccountAccessStrategy || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY
|
||||
);
|
||||
if (typeof selectPlusAccountAccessStrategy !== 'undefined' && selectPlusAccountAccessStrategy) {
|
||||
selectPlusAccountAccessStrategy.dataset.requestedValue = currentPlusAccountAccessStrategy;
|
||||
selectPlusAccountAccessStrategy.value = currentPlusAccountAccessStrategy;
|
||||
}
|
||||
if (typeof inputGpcHelperApi !== 'undefined' && inputGpcHelperApi) {
|
||||
const defaultGpcHelperApiUrl = typeof DEFAULT_GPC_HELPER_API_URL !== 'undefined'
|
||||
? DEFAULT_GPC_HELPER_API_URL
|
||||
@@ -14213,6 +14387,13 @@ selectPanelMode.addEventListener('change', async () => {
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
});
|
||||
|
||||
selectPlusAccountAccessStrategy?.addEventListener('change', () => {
|
||||
selectPlusAccountAccessStrategy.value = normalizePlusAccountAccessStrategy(selectPlusAccountAccessStrategy.value);
|
||||
updatePlusModeUI();
|
||||
markSettingsDirty(true);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
});
|
||||
|
||||
[inputKiroRsUrl, inputKiroRsKey].forEach((input) => {
|
||||
input?.addEventListener('input', () => {
|
||||
markSettingsDirty(true);
|
||||
@@ -16157,6 +16338,13 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
if (message.payload.plusPaymentMethod !== undefined && selectPlusPaymentMethod) {
|
||||
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(message.payload.plusPaymentMethod);
|
||||
}
|
||||
if (message.payload.plusAccountAccessStrategy !== undefined && selectPlusAccountAccessStrategy) {
|
||||
currentPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(message.payload.plusAccountAccessStrategy);
|
||||
selectPlusAccountAccessStrategy.dataset.requestedValue = currentPlusAccountAccessStrategy;
|
||||
if (!selectPlusAccountAccessStrategy.disabled) {
|
||||
selectPlusAccountAccessStrategy.value = currentPlusAccountAccessStrategy;
|
||||
}
|
||||
}
|
||||
if (message.payload.gopayHelperPhoneMode !== undefined && selectGpcHelperPhoneMode) {
|
||||
selectGpcHelperPhoneMode.value = normalizeGpcHelperPhoneModeValue(message.payload.gopayHelperPhoneMode);
|
||||
}
|
||||
@@ -16181,6 +16369,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
if (
|
||||
message.payload.plusModeEnabled !== undefined
|
||||
|| message.payload.plusPaymentMethod !== undefined
|
||||
|| message.payload.plusAccountAccessStrategy !== undefined
|
||||
|| message.payload.gopayHelperPhoneMode !== undefined
|
||||
|| message.payload.gopayHelperAutoModeEnabled !== undefined
|
||||
|| message.payload.gopayHelperOtpChannel !== undefined
|
||||
|
||||
@@ -173,8 +173,8 @@ const defaultStepDefinitions = {
|
||||
4: { key: 'verify-email' },
|
||||
5: { key: 'profile-basic' },
|
||||
6: { key: 'profile-finish' },
|
||||
7: { key: 'auth-login' },
|
||||
8: { key: 'auth-email-code' },
|
||||
7: { key: 'oauth-login' },
|
||||
8: { key: 'fetch-login-code' },
|
||||
9: { key: 'confirm-oauth' },
|
||||
10: { key: 'platform-verify' },
|
||||
};
|
||||
@@ -407,7 +407,7 @@ test('auto-run keeps restarting from step 7 after post-login failures without a
|
||||
7, 8, 9, 10,
|
||||
]
|
||||
);
|
||||
assert.ok(events.logs.some(({ message }) => /回到节点 auth-login 重新开始授权流程/.test(message)));
|
||||
assert.ok(events.logs.some(({ message }) => /回到节点 oauth-login 重新开始授权流程/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run restarts the current step after five minutes without new logs', async () => {
|
||||
@@ -964,3 +964,42 @@ test('auto-run does not restart GPC checkout when account already has a ChatGPT
|
||||
assert.equal(result.events.invalidations.length, 0);
|
||||
assert.ok(!result.events.logs.some(({ message }) => /回到步骤 6 重新创建 GPC 任务/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run does not reroute SUB2API session import failures into OAuth restarts', async () => {
|
||||
const plusSessionSteps = {
|
||||
6: { key: 'plus-checkout-create' },
|
||||
7: { key: 'plus-checkout-billing' },
|
||||
8: { key: 'paypal-approve' },
|
||||
9: { key: 'plus-checkout-return' },
|
||||
10: { key: 'sub2api-session-import' },
|
||||
};
|
||||
const harness = createHarness({
|
||||
startStep: 10,
|
||||
failureStep: 10,
|
||||
failureBudget: 1,
|
||||
failureMessage: '步骤 10:当前页面未读取到有效的 ChatGPT session。',
|
||||
stepDefinitions: plusSessionSteps,
|
||||
finalOAuthChainStartStep: 10,
|
||||
customState: {
|
||||
stepStatuses: {
|
||||
3: 'completed',
|
||||
6: 'completed',
|
||||
7: 'completed',
|
||||
8: 'completed',
|
||||
9: 'completed',
|
||||
},
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
panelMode: 'sub2api',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
},
|
||||
});
|
||||
|
||||
const result = await harness.runAndCaptureError();
|
||||
|
||||
assert.ok(result?.error);
|
||||
assert.match(result.error.message, /未读取到有效的 ChatGPT session/);
|
||||
assert.deepStrictEqual(result.events.steps, [10]);
|
||||
assert.equal(result.events.invalidations.length, 0);
|
||||
assert.ok(!result.events.logs.some(({ message }) => /回到节点 oauth-login|回到节点 confirm-oauth|重新开始授权流程/.test(message)));
|
||||
});
|
||||
|
||||
@@ -227,6 +227,8 @@ return {
|
||||
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'gpc-helper'), 'gpc-helper');
|
||||
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'paypal'), 'paypal');
|
||||
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'unknown'), 'paypal');
|
||||
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'sub2api_codex_session'), 'sub2api_codex_session');
|
||||
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'unknown'), 'oauth');
|
||||
assert.equal(
|
||||
api.normalizePersistentSettingValue('gopayHelperApiUrl', ' https://gpc.qlhazycoder.top/api/checkout/start '),
|
||||
'https://gpc.qlhazycoder.top'
|
||||
|
||||
@@ -48,6 +48,15 @@ function extractFunction(name) {
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
test('background auth chain set does not include SUB2API session import node', () => {
|
||||
const authChainStart = source.indexOf('const AUTH_CHAIN_NODE_IDS = new Set([');
|
||||
const authChainEnd = source.indexOf(']);', authChainStart);
|
||||
const authChainBlock = source.slice(authChainStart, authChainEnd);
|
||||
|
||||
assert.ok(authChainStart >= 0, 'expected AUTH_CHAIN_NODE_IDS block to exist');
|
||||
assert.doesNotMatch(authChainBlock, /sub2api-session-import/);
|
||||
});
|
||||
|
||||
const NODE_EXECUTE_COMPAT_HELPERS = `
|
||||
const AUTH_CHAIN_NODE_IDS = new Set(['oauth-login', 'fetch-login-code', 'confirm-oauth', 'platform-verify']);
|
||||
const STEP_NODE_IDS = {
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
const start = markers
|
||||
.map((marker) => source.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 < source.length; i += 1) {
|
||||
const ch = source[i];
|
||||
if (ch === '(') {
|
||||
parenDepth += 1;
|
||||
} else if (ch === ')') {
|
||||
parenDepth -= 1;
|
||||
if (parenDepth === 0) {
|
||||
signatureEnded = true;
|
||||
}
|
||||
} else 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 < source.length; end += 1) {
|
||||
const ch = source[end];
|
||||
if (ch === '{') depth += 1;
|
||||
if (ch === '}') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
end += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
function createHarness() {
|
||||
return new Function(`
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const workflowEngine = null;
|
||||
const self = {
|
||||
MultiPageStepDefinitions: {
|
||||
getSteps(options = {}) {
|
||||
return String(options.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
|
||||
? [
|
||||
{ id: 1, key: 'open-chatgpt' },
|
||||
{ id: 2, key: 'plus-checkout-create' },
|
||||
{ id: 3, key: 'plus-checkout-billing' },
|
||||
{ id: 4, key: 'paypal-approve' },
|
||||
{ id: 5, key: 'plus-checkout-return' },
|
||||
{ id: 6, key: 'sub2api-session-import' },
|
||||
]
|
||||
: [
|
||||
{ id: 1, key: 'open-chatgpt' },
|
||||
{ id: 2, key: 'plus-checkout-create' },
|
||||
{ id: 3, key: 'plus-checkout-billing' },
|
||||
{ id: 4, key: 'paypal-approve' },
|
||||
{ id: 5, key: 'plus-checkout-return' },
|
||||
{ id: 6, key: 'oauth-login' },
|
||||
{ id: 7, key: 'fetch-login-code' },
|
||||
{ id: 8, key: 'confirm-oauth' },
|
||||
{ id: 9, key: 'platform-verify' },
|
||||
];
|
||||
},
|
||||
getNodes(options = {}) {
|
||||
return this.getSteps(options).map((definition) => ({
|
||||
nodeId: String(definition.key || '').trim(),
|
||||
displayOrder: Number(definition.id) || 0,
|
||||
}));
|
||||
},
|
||||
},
|
||||
};
|
||||
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 normalizePlusAccountAccessStrategy(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'sub2api_codex_session'
|
||||
? 'sub2api_codex_session'
|
||||
: 'oauth';
|
||||
}
|
||||
function isPlusModeState(state = {}) {
|
||||
return Boolean(state?.plusModeEnabled);
|
||||
}
|
||||
function resolveCurrentFlowCapabilities(state = {}, options = {}) {
|
||||
const effectiveStrategy = String(options.panelMode || '').trim().toLowerCase() === 'sub2api'
|
||||
? normalizePlusAccountAccessStrategy(state.plusAccountAccessStrategy)
|
||||
: 'oauth';
|
||||
return {
|
||||
effectivePanelMode: options.panelMode,
|
||||
effectivePlusAccountAccessStrategy: effectiveStrategy,
|
||||
effectiveSignupMethod: 'email',
|
||||
stepDefinitionOptions: {
|
||||
activeFlowId: 'openai',
|
||||
panelMode: options.panelMode,
|
||||
plusModeEnabled: Boolean(state.plusModeEnabled),
|
||||
plusPaymentMethod: normalizePlusPaymentMethod(state.plusPaymentMethod),
|
||||
plusAccountAccessStrategy: effectiveStrategy,
|
||||
signupMethod: 'email',
|
||||
},
|
||||
};
|
||||
}
|
||||
${extractFunction('getSignupMethodForStepDefinitions')}
|
||||
${extractFunction('buildResolvedStepDefinitionState')}
|
||||
${extractFunction('getStepDefinitionsForState')}
|
||||
${extractFunction('getNodeDefinitionsForState')}
|
||||
${extractFunction('getNodeIdsForState')}
|
||||
return {
|
||||
buildResolvedStepDefinitionState,
|
||||
getNodeIdsForState,
|
||||
};
|
||||
`)();
|
||||
}
|
||||
|
||||
test('background step resolution keeps SUB2API session tail only when the effective Plus target supports it', () => {
|
||||
const api = createHarness();
|
||||
const state = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'sub2api',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
signupMethod: 'email',
|
||||
};
|
||||
|
||||
const resolvedState = api.buildResolvedStepDefinitionState(state);
|
||||
const nodeIds = api.getNodeIdsForState(state);
|
||||
|
||||
assert.equal(resolvedState.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.deepStrictEqual(nodeIds, [
|
||||
'open-chatgpt',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'sub2api-session-import',
|
||||
]);
|
||||
});
|
||||
|
||||
test('background step resolution falls back to OAuth tail when the requested session strategy is not effective for the current panel mode', () => {
|
||||
const api = createHarness();
|
||||
const state = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
signupMethod: 'email',
|
||||
};
|
||||
|
||||
const resolvedState = api.buildResolvedStepDefinitionState(state);
|
||||
const nodeIds = api.getNodeIdsForState(state);
|
||||
|
||||
assert.equal(resolvedState.plusAccountAccessStrategy, 'oauth');
|
||||
assert.equal(nodeIds.includes('sub2api-session-import'), false);
|
||||
assert.deepStrictEqual(nodeIds.slice(-4), [
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]);
|
||||
});
|
||||
@@ -297,6 +297,299 @@ test('SAVE_SETTING broadcasts operation delay setting without background success
|
||||
assert.equal(logs.length, 0);
|
||||
});
|
||||
|
||||
test('SAVE_SETTING rebuilds Plus node statuses when the account access strategy changes', async () => {
|
||||
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = { console };
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||
const broadcasts = [];
|
||||
let state = {
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
oauthUrl: 'https://oauth.example/current',
|
||||
localhostUrl: 'http://localhost:38080/callback',
|
||||
oauthFlowDeadlineAt: Date.now() + 60000,
|
||||
oauthFlowDeadlineSourceUrl: 'https://oauth.example/current',
|
||||
cpaOAuthState: 'cpa-state',
|
||||
cpaManagementOrigin: 'https://cpa.example.com',
|
||||
sub2apiSessionId: 'sub-session',
|
||||
sub2apiOAuthState: 'sub-oauth-state',
|
||||
sub2apiGroupId: 'group-id',
|
||||
sub2apiGroupIds: ['group-id'],
|
||||
sub2apiDraftName: 'draft-name',
|
||||
sub2apiProxyId: 'proxy-id',
|
||||
codex2apiSessionId: 'codex-session',
|
||||
codex2apiOAuthState: 'codex-oauth-state',
|
||||
plusManualConfirmationPending: true,
|
||||
plusManualConfirmationRequestId: 'gopay-req',
|
||||
plusManualConfirmationStep: 9,
|
||||
plusManualConfirmationMethod: 'gopay',
|
||||
plusManualConfirmationTitle: 'GoPay 订阅确认',
|
||||
plusManualConfirmationMessage: '完成后继续 OAuth 登录。',
|
||||
currentNodeId: 'confirm-oauth',
|
||||
nodeStatuses: {
|
||||
'open-chatgpt': 'completed',
|
||||
'plus-checkout-create': 'completed',
|
||||
'plus-checkout-billing': 'completed',
|
||||
'paypal-approve': 'completed',
|
||||
'plus-checkout-return': 'completed',
|
||||
'oauth-login': 'completed',
|
||||
'fetch-login-code': 'completed',
|
||||
'post-login-phone-verification': 'completed',
|
||||
'confirm-oauth': 'running',
|
||||
'platform-verify': 'pending',
|
||||
},
|
||||
};
|
||||
|
||||
const router = api.createMessageRouter({
|
||||
addLog: async () => {},
|
||||
buildLuckmailSessionSettingsPayload: () => ({}),
|
||||
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'plusAccountAccessStrategy')
|
||||
? { plusAccountAccessStrategy: input.plusAccountAccessStrategy }
|
||||
: {},
|
||||
broadcastDataUpdate: (payload) => broadcasts.push(payload),
|
||||
getNodeIdsForState: (nextState = {}) => (
|
||||
String(nextState.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
|
||||
? [
|
||||
'open-chatgpt',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'sub2api-session-import',
|
||||
]
|
||||
: [
|
||||
'open-chatgpt',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'post-login-phone-verification',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]
|
||||
),
|
||||
getState: async () => ({ ...state }),
|
||||
getStepIdsForState: () => [],
|
||||
setPersistentSettings: async (updates) => ({ ...updates }),
|
||||
setState: async (updates) => {
|
||||
state = { ...state, ...updates };
|
||||
},
|
||||
});
|
||||
|
||||
const response = await router.handleMessage({
|
||||
type: 'SAVE_SETTING',
|
||||
payload: {
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(state.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(state.currentNodeId, '');
|
||||
assert.equal(state.oauthUrl, null);
|
||||
assert.equal(state.localhostUrl, null);
|
||||
assert.equal(state.oauthFlowDeadlineAt, null);
|
||||
assert.equal(state.oauthFlowDeadlineSourceUrl, null);
|
||||
assert.equal(state.cpaOAuthState, null);
|
||||
assert.equal(state.cpaManagementOrigin, null);
|
||||
assert.equal(state.sub2apiSessionId, null);
|
||||
assert.equal(state.sub2apiOAuthState, null);
|
||||
assert.equal(state.sub2apiGroupId, null);
|
||||
assert.deepStrictEqual(state.sub2apiGroupIds, []);
|
||||
assert.equal(state.sub2apiDraftName, null);
|
||||
assert.equal(state.sub2apiProxyId, null);
|
||||
assert.equal(state.codex2apiSessionId, null);
|
||||
assert.equal(state.codex2apiOAuthState, null);
|
||||
assert.equal(state.plusManualConfirmationPending, false);
|
||||
assert.equal(state.plusManualConfirmationRequestId, '');
|
||||
assert.equal(state.plusManualConfirmationStep, 0);
|
||||
assert.equal(state.plusManualConfirmationMethod, '');
|
||||
assert.equal(state.plusManualConfirmationTitle, '');
|
||||
assert.equal(state.plusManualConfirmationMessage, '');
|
||||
assert.deepStrictEqual(state.nodeStatuses, {
|
||||
'open-chatgpt': 'pending',
|
||||
'plus-checkout-create': 'pending',
|
||||
'plus-checkout-billing': 'pending',
|
||||
'paypal-approve': 'pending',
|
||||
'plus-checkout-return': 'pending',
|
||||
'sub2api-session-import': 'pending',
|
||||
});
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(state.nodeStatuses, 'oauth-login'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(state.nodeStatuses, 'platform-verify'), false);
|
||||
assert.deepStrictEqual(broadcasts.at(-1), {
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
oauthUrl: null,
|
||||
localhostUrl: null,
|
||||
oauthFlowDeadlineAt: null,
|
||||
oauthFlowDeadlineSourceUrl: null,
|
||||
cpaOAuthState: null,
|
||||
cpaManagementOrigin: null,
|
||||
sub2apiSessionId: null,
|
||||
sub2apiOAuthState: null,
|
||||
sub2apiGroupId: null,
|
||||
sub2apiGroupIds: [],
|
||||
sub2apiDraftName: null,
|
||||
sub2apiProxyId: null,
|
||||
codex2apiSessionId: null,
|
||||
codex2apiOAuthState: null,
|
||||
plusManualConfirmationPending: false,
|
||||
plusManualConfirmationRequestId: '',
|
||||
plusManualConfirmationStep: 0,
|
||||
plusManualConfirmationMethod: '',
|
||||
plusManualConfirmationTitle: '',
|
||||
plusManualConfirmationMessage: '',
|
||||
nodeStatuses: {
|
||||
'open-chatgpt': 'pending',
|
||||
'plus-checkout-create': 'pending',
|
||||
'plus-checkout-billing': 'pending',
|
||||
'paypal-approve': 'pending',
|
||||
'plus-checkout-return': 'pending',
|
||||
'sub2api-session-import': 'pending',
|
||||
},
|
||||
currentNodeId: '',
|
||||
});
|
||||
});
|
||||
|
||||
test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effective strategy back to OAuth', async () => {
|
||||
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = { console };
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||
const broadcasts = [];
|
||||
let state = {
|
||||
panelMode: 'sub2api',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
oauthUrl: 'https://oauth.example/current',
|
||||
localhostUrl: 'http://localhost:38080/callback',
|
||||
sub2apiSessionId: 'sub-session',
|
||||
plusManualConfirmationPending: true,
|
||||
plusManualConfirmationRequestId: 'gopay-req',
|
||||
plusManualConfirmationStep: 9,
|
||||
plusManualConfirmationMethod: 'gopay',
|
||||
plusManualConfirmationTitle: 'GoPay 订阅确认',
|
||||
plusManualConfirmationMessage: '完成后继续导入当前 ChatGPT 会话到 SUB2API。',
|
||||
currentNodeId: 'sub2api-session-import',
|
||||
nodeStatuses: {
|
||||
'open-chatgpt': 'completed',
|
||||
'plus-checkout-create': 'completed',
|
||||
'plus-checkout-billing': 'completed',
|
||||
'paypal-approve': 'completed',
|
||||
'plus-checkout-return': 'completed',
|
||||
'sub2api-session-import': 'running',
|
||||
},
|
||||
};
|
||||
|
||||
const router = api.createMessageRouter({
|
||||
addLog: async () => {},
|
||||
buildLuckmailSessionSettingsPayload: () => ({}),
|
||||
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'panelMode')
|
||||
? { panelMode: input.panelMode }
|
||||
: {},
|
||||
broadcastDataUpdate: (payload) => broadcasts.push(payload),
|
||||
getNodeIdsForState: (nextState = {}) => (
|
||||
String(nextState.panelMode || '').trim() === 'sub2api'
|
||||
&& String(nextState.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
|
||||
? [
|
||||
'open-chatgpt',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'sub2api-session-import',
|
||||
]
|
||||
: [
|
||||
'open-chatgpt',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'post-login-phone-verification',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]
|
||||
),
|
||||
getState: async () => ({ ...state }),
|
||||
getStepIdsForState: () => [],
|
||||
setPersistentSettings: async (updates) => ({ ...updates }),
|
||||
setState: async (updates) => {
|
||||
state = { ...state, ...updates };
|
||||
},
|
||||
});
|
||||
|
||||
const response = await router.handleMessage({
|
||||
type: 'SAVE_SETTING',
|
||||
payload: {
|
||||
panelMode: 'cpa',
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(state.panelMode, 'cpa');
|
||||
assert.equal(state.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(state.currentNodeId, '');
|
||||
assert.equal(state.oauthUrl, null);
|
||||
assert.equal(state.localhostUrl, null);
|
||||
assert.equal(state.sub2apiSessionId, null);
|
||||
assert.equal(state.plusManualConfirmationPending, false);
|
||||
assert.equal(state.plusManualConfirmationMessage, '');
|
||||
assert.deepStrictEqual(state.nodeStatuses, {
|
||||
'open-chatgpt': 'pending',
|
||||
'plus-checkout-create': 'pending',
|
||||
'plus-checkout-billing': 'pending',
|
||||
'paypal-approve': 'pending',
|
||||
'plus-checkout-return': 'pending',
|
||||
'oauth-login': 'pending',
|
||||
'fetch-login-code': 'pending',
|
||||
'post-login-phone-verification': 'pending',
|
||||
'confirm-oauth': 'pending',
|
||||
'platform-verify': 'pending',
|
||||
});
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(state.nodeStatuses, 'sub2api-session-import'), false);
|
||||
assert.deepStrictEqual(broadcasts.at(-1), {
|
||||
panelMode: 'cpa',
|
||||
signupMethod: 'email',
|
||||
oauthUrl: null,
|
||||
localhostUrl: null,
|
||||
oauthFlowDeadlineAt: null,
|
||||
oauthFlowDeadlineSourceUrl: null,
|
||||
cpaOAuthState: null,
|
||||
cpaManagementOrigin: null,
|
||||
sub2apiSessionId: null,
|
||||
sub2apiOAuthState: null,
|
||||
sub2apiGroupId: null,
|
||||
sub2apiGroupIds: [],
|
||||
sub2apiDraftName: null,
|
||||
sub2apiProxyId: null,
|
||||
codex2apiSessionId: null,
|
||||
codex2apiOAuthState: null,
|
||||
plusManualConfirmationPending: false,
|
||||
plusManualConfirmationRequestId: '',
|
||||
plusManualConfirmationStep: 0,
|
||||
plusManualConfirmationMethod: '',
|
||||
plusManualConfirmationTitle: '',
|
||||
plusManualConfirmationMessage: '',
|
||||
nodeStatuses: {
|
||||
'open-chatgpt': 'pending',
|
||||
'plus-checkout-create': 'pending',
|
||||
'plus-checkout-billing': 'pending',
|
||||
'paypal-approve': 'pending',
|
||||
'plus-checkout-return': 'pending',
|
||||
'oauth-login': 'pending',
|
||||
'fetch-login-code': 'pending',
|
||||
'post-login-phone-verification': 'pending',
|
||||
'confirm-oauth': 'pending',
|
||||
'platform-verify': 'pending',
|
||||
},
|
||||
currentNodeId: '',
|
||||
});
|
||||
});
|
||||
|
||||
test('SAVE_SETTING mirrors activeFlowId into flowId when switching to kiro flow', async () => {
|
||||
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = { console };
|
||||
|
||||
@@ -6,8 +6,25 @@ const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||
|
||||
test('message router appends success record on Plus final step instead of hard-coded step 10', async () => {
|
||||
function createRouterWithFinalNode(options = {}) {
|
||||
const finalNodeId = String(options.finalNodeId || 'platform-verify').trim();
|
||||
const nodeIds = Array.isArray(options.nodeIds) ? options.nodeIds.slice() : [
|
||||
'open-chatgpt',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'confirm-oauth',
|
||||
finalNodeId,
|
||||
];
|
||||
const nodeStepMap = {
|
||||
'oauth-login': 10,
|
||||
'fetch-login-code': 11,
|
||||
'confirm-oauth': 12,
|
||||
'platform-verify': 13,
|
||||
'sub2api-session-import': 10,
|
||||
...(options.nodeStepMap || {}),
|
||||
};
|
||||
const appendCalls = [];
|
||||
|
||||
const router = api.createMessageRouter({
|
||||
addLog: async () => {},
|
||||
appendAccountRunRecord: async (...args) => {
|
||||
@@ -43,12 +60,15 @@ test('message router appends success record on Plus final step instead of hard-c
|
||||
getCurrentLuckmailPurchase: () => null,
|
||||
getPendingAutoRunTimerPlan: () => null,
|
||||
getSourceLabel: () => '',
|
||||
getState: async () => ({ plusModeEnabled: true, nodeStatuses: { 'platform-verify': 'pending' } }),
|
||||
getNodeIdsForState: () => ['open-chatgpt', 'oauth-login', 'fetch-login-code', 'confirm-oauth', 'platform-verify'],
|
||||
getStepIdByNodeIdForState: (nodeId) => ({ 'oauth-login': 10, 'fetch-login-code': 11, 'confirm-oauth': 12, 'platform-verify': 13 }[nodeId] || 0),
|
||||
getLastStepIdForState: () => 13,
|
||||
getStepDefinitionForState: (step) => ({ id: step, key: step === 10 ? 'oauth-login' : 'platform-verify' }),
|
||||
getStepIdsForState: () => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
|
||||
getState: async () => ({ plusModeEnabled: true, nodeStatuses: { [finalNodeId]: 'pending' } }),
|
||||
getNodeIdsForState: () => nodeIds.slice(),
|
||||
getStepIdByNodeIdForState: (nodeId) => nodeStepMap[nodeId] || 0,
|
||||
getLastStepIdForState: () => Math.max(...Object.values(nodeStepMap)),
|
||||
getStepDefinitionForState: (step) => ({
|
||||
id: step,
|
||||
key: Object.entries(nodeStepMap).find(([, mappedStep]) => mappedStep === step)?.[0] || finalNodeId,
|
||||
}),
|
||||
getStepIdsForState: () => Object.values(nodeStepMap),
|
||||
getTabId: async () => null,
|
||||
getStopRequested: () => false,
|
||||
handleAutoRunLoopUnhandledError: async () => {},
|
||||
@@ -100,8 +120,56 @@ test('message router appends success record on Plus final step instead of hard-c
|
||||
verifyHotmailAccount: async () => {},
|
||||
});
|
||||
|
||||
return {
|
||||
appendCalls,
|
||||
router,
|
||||
};
|
||||
}
|
||||
|
||||
test('message router appends success record on Plus final step instead of hard-coded step 10', async () => {
|
||||
const { appendCalls, router } = createRouterWithFinalNode({
|
||||
finalNodeId: 'platform-verify',
|
||||
nodeIds: ['open-chatgpt', 'oauth-login', 'fetch-login-code', 'confirm-oauth', 'platform-verify'],
|
||||
nodeStepMap: {
|
||||
'oauth-login': 10,
|
||||
'fetch-login-code': 11,
|
||||
'confirm-oauth': 12,
|
||||
'platform-verify': 13,
|
||||
},
|
||||
});
|
||||
|
||||
await router.handleMessage({ type: 'NODE_COMPLETE', nodeId: 'platform-verify', payload: { nodeId: 'platform-verify' } }, {});
|
||||
|
||||
assert.equal(appendCalls.length, 1);
|
||||
assert.equal(appendCalls[0][0], 'success');
|
||||
});
|
||||
|
||||
test('message router appends success record when SUB2API session import is the final Plus node', async () => {
|
||||
const { appendCalls, router } = createRouterWithFinalNode({
|
||||
finalNodeId: 'sub2api-session-import',
|
||||
nodeIds: [
|
||||
'open-chatgpt',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'sub2api-session-import',
|
||||
],
|
||||
nodeStepMap: {
|
||||
'plus-checkout-create': 6,
|
||||
'plus-checkout-billing': 7,
|
||||
'paypal-approve': 8,
|
||||
'plus-checkout-return': 9,
|
||||
'sub2api-session-import': 10,
|
||||
},
|
||||
});
|
||||
|
||||
await router.handleMessage({
|
||||
type: 'NODE_COMPLETE',
|
||||
nodeId: 'sub2api-session-import',
|
||||
payload: { nodeId: 'sub2api-session-import' },
|
||||
}, {});
|
||||
|
||||
assert.equal(appendCalls.length, 1);
|
||||
assert.equal(appendCalls[0][0], 'success');
|
||||
});
|
||||
|
||||
@@ -80,6 +80,7 @@ const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
|
||||
'phoneSignupReloginAfterBindEmailEnabled',
|
||||
'plusModeEnabled',
|
||||
'plusPaymentMethod',
|
||||
'plusAccountAccessStrategy',
|
||||
'mailProvider',
|
||||
'ipProxyEnabled',
|
||||
'ipProxyService',
|
||||
@@ -95,6 +96,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
signupMethod: 'email',
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
phoneVerificationEnabled: false,
|
||||
mailProvider: '163',
|
||||
ipProxyEnabled: false,
|
||||
@@ -249,6 +251,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
plus: {
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
|
||||
@@ -276,6 +279,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
assert.equal(payload.kiroRsKey, 'schema-only-key');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
|
||||
assert.equal(payload.settingsSchemaVersion, 4);
|
||||
assert.equal(payload.settingsState.flows.openai.plus.plusAccountAccessStrategy, 'oauth');
|
||||
});
|
||||
|
||||
test('getPersistedSettings reads schema keys alongside legacy flat settings keys', async () => {
|
||||
@@ -300,6 +304,7 @@ function getRequestedKeys() {
|
||||
|
||||
assert.ok(api.getRequestedKeys().includes('settingsSchemaVersion'));
|
||||
assert.ok(api.getRequestedKeys().includes('settingsState'));
|
||||
assert.ok(api.getRequestedKeys().includes('plusAccountAccessStrategy'));
|
||||
assert.equal(state.settingsSchemaVersion, 4);
|
||||
assert.equal(state.settingsState.activeFlowId, 'openai');
|
||||
});
|
||||
@@ -350,6 +355,7 @@ const chrome = {
|
||||
plus: {
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
|
||||
@@ -384,6 +390,7 @@ const chrome = {
|
||||
assert.equal(state.ipProxyEnabled, true);
|
||||
assert.equal(state.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(state.kiroRsKey, 'stored-key');
|
||||
assert.equal(state.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(state, 'kiroRegion'), false);
|
||||
assert.deepEqual(state.stepExecutionRangeByFlow.kiro, {
|
||||
enabled: true,
|
||||
@@ -459,6 +466,7 @@ function getRemovedKeys() {
|
||||
plus: {
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
|
||||
@@ -486,6 +494,7 @@ function getRemovedKeys() {
|
||||
assert.equal(persisted.kiroTargetId, 'kiro-rs');
|
||||
assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(persisted.kiroRsKey, 'nested-only-key');
|
||||
assert.equal(persisted.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false);
|
||||
assert.equal(persisted.settingsSchemaVersion, 4);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(write, 'activeFlowId'), false);
|
||||
@@ -494,6 +503,7 @@ function getRemovedKeys() {
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRegion'), false);
|
||||
assert.equal(write.settingsSchemaVersion, 4);
|
||||
assert.equal(write.settingsState.activeFlowId, 'kiro');
|
||||
assert.equal(write.settingsState.flows.openai.plus.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(write.settingsState.flows.kiro.targetId, 'kiro-rs');
|
||||
assert.ok(api.getRemovedKeys().includes('panelMode'));
|
||||
assert.ok(api.getRemovedKeys().includes('kiroRsUrl'));
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const test = require('node:test');
|
||||
|
||||
function createJsonResponse(payload, status = 200) {
|
||||
return {
|
||||
ok: status >= 200 && status < 300,
|
||||
status,
|
||||
text: async () => JSON.stringify(payload),
|
||||
};
|
||||
}
|
||||
|
||||
function loadSub2ApiApiModule() {
|
||||
const source = fs.readFileSync('background/sub2api-api.js', 'utf8');
|
||||
return new Function('self', `${source}; return self.MultiPageBackgroundSub2ApiApi;`)({});
|
||||
}
|
||||
|
||||
function loadSub2ApiSessionImportModule() {
|
||||
const source = fs.readFileSync('background/steps/sub2api-session-import.js', 'utf8');
|
||||
return new Function('self', `${source}; return self.MultiPageBackgroundSub2ApiSessionImport;`)({});
|
||||
}
|
||||
|
||||
test('sub2api api imports current ChatGPT session through codex-session endpoint', async () => {
|
||||
const apiModule = loadSub2ApiApiModule();
|
||||
const fetchCalls = [];
|
||||
const logs = [];
|
||||
const importExpiresAt = Math.floor(Date.parse('2026-05-20T12:34:56.000Z') / 1000);
|
||||
|
||||
const api = apiModule.createSub2ApiApi({
|
||||
addLog: async (message, level = 'info', options = {}) => {
|
||||
logs.push({ message, level, step: options.step, stepKey: options.stepKey });
|
||||
},
|
||||
normalizeSub2ApiUrl: (value) => value,
|
||||
DEFAULT_SUB2API_GROUP_NAME: 'codex',
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsed = new URL(url);
|
||||
const body = options.body ? JSON.parse(options.body) : null;
|
||||
fetchCalls.push({ path: parsed.pathname, search: parsed.search, method: options.method || 'GET', body });
|
||||
|
||||
if (parsed.pathname === '/api/v1/auth/login') {
|
||||
return createJsonResponse({
|
||||
code: 0,
|
||||
data: {
|
||||
access_token: 'admin-token',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (parsed.pathname === '/api/v1/admin/groups/all') {
|
||||
return createJsonResponse({
|
||||
code: 0,
|
||||
data: [
|
||||
{ id: 5, name: 'codex', platform: 'openai' },
|
||||
],
|
||||
});
|
||||
}
|
||||
if (parsed.pathname === '/api/v1/admin/proxies/all') {
|
||||
return createJsonResponse({
|
||||
code: 0,
|
||||
data: [{
|
||||
id: 7,
|
||||
name: 'shadowrocket',
|
||||
protocol: 'socks5',
|
||||
host: '127.0.0.1',
|
||||
port: 1080,
|
||||
status: 'active',
|
||||
}],
|
||||
});
|
||||
}
|
||||
if (parsed.pathname === '/api/v1/admin/accounts/import/codex-session') {
|
||||
const parsedContent = JSON.parse(body.content);
|
||||
assert.equal(parsedContent.accessToken, 'access-token-from-state');
|
||||
assert.equal(parsedContent.user?.email, 'flow@example.com');
|
||||
assert.equal(body.priority, 3);
|
||||
assert.equal(body.proxy_id, 7);
|
||||
assert.deepStrictEqual(body.group_ids, [5]);
|
||||
assert.equal(body.auto_pause_on_expired, true);
|
||||
assert.equal(body.update_existing, true);
|
||||
assert.equal(body.expires_at, importExpiresAt);
|
||||
return createJsonResponse({
|
||||
code: 0,
|
||||
data: {
|
||||
total: 1,
|
||||
created: 0,
|
||||
updated: 1,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
warnings: [{
|
||||
index: 1,
|
||||
message: '未包含 refresh_token,accessToken 过期后无法自动续期',
|
||||
}],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return createJsonResponse({ code: 1, message: `unexpected path ${parsed.pathname}` }, 404);
|
||||
},
|
||||
});
|
||||
|
||||
const result = await api.importCurrentChatGptSession({
|
||||
sub2apiUrl: 'https://sub.example/admin/accounts',
|
||||
sub2apiEmail: 'admin@example.com',
|
||||
sub2apiPassword: 'secret',
|
||||
sub2apiGroupName: 'codex',
|
||||
sub2apiDefaultProxyName: 'shadowrocket',
|
||||
sub2apiAccountPriority: 3,
|
||||
session: {
|
||||
accessToken: 'access-token-from-session',
|
||||
expires: '2026-05-20T12:34:56.000Z',
|
||||
user: {
|
||||
email: 'flow@example.com',
|
||||
},
|
||||
},
|
||||
accessToken: 'access-token-from-state',
|
||||
}, {
|
||||
logLabel: '步骤 10',
|
||||
logOptions: { step: 10, stepKey: 'sub2api-session-import' },
|
||||
timeoutMs: 120000,
|
||||
});
|
||||
|
||||
const importCall = fetchCalls.find((call) => call.path === '/api/v1/admin/accounts/import/codex-session');
|
||||
assert.ok(importCall, 'expected codex-session import call');
|
||||
assert.equal(result.verifiedStatus, 'SUB2API 会话导入完成:新建 0,更新 1,跳过 0,失败 0');
|
||||
assert.equal(result.sub2apiImportUpdated, 1);
|
||||
assert.equal(
|
||||
logs.some((entry) => entry.level === 'warn' && /refresh_token/.test(entry.message)),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('session import step reads current ChatGPT session and completes node', async () => {
|
||||
const moduleApi = loadSub2ApiSessionImportModule();
|
||||
const completed = [];
|
||||
const logs = [];
|
||||
const ensureCalls = [];
|
||||
const sentMessages = [];
|
||||
const importedPayloads = [];
|
||||
|
||||
const executor = moduleApi.createSub2ApiSessionImportExecutor({
|
||||
addLog: async (message, level = 'info', options = {}) => {
|
||||
logs.push({ message, level, step: options.step, stepKey: options.stepKey });
|
||||
},
|
||||
chrome: {
|
||||
tabs: {
|
||||
get: async (tabId) => ({
|
||||
id: tabId,
|
||||
url: 'https://chatgpt.com/?model=gpt-4o',
|
||||
}),
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (nodeId, payload) => {
|
||||
completed.push({ nodeId, payload });
|
||||
},
|
||||
createSub2ApiApi: () => ({
|
||||
importCurrentChatGptSession: async (state, options) => {
|
||||
importedPayloads.push({ state, options });
|
||||
return {
|
||||
verifiedStatus: 'SUB2API 会话导入完成:新建 1,更新 0,跳过 0,失败 0',
|
||||
sub2apiImportCreated: 1,
|
||||
sub2apiImportUpdated: 0,
|
||||
sub2apiImportSkipped: 0,
|
||||
sub2apiImportFailed: 0,
|
||||
};
|
||||
},
|
||||
}),
|
||||
ensureContentScriptReadyOnTabUntilStopped: async (source, tabId, options = {}) => {
|
||||
ensureCalls.push({ source, tabId, options });
|
||||
},
|
||||
getTabId: async () => null,
|
||||
isTabAlive: async () => false,
|
||||
normalizeSub2ApiUrl: (value) => value,
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async (tabId, source, message) => {
|
||||
sentMessages.push({ tabId, source, message });
|
||||
return {
|
||||
session: {
|
||||
accessToken: 'session-access-token',
|
||||
expires: '2026-05-20T12:34:56.000Z',
|
||||
user: {
|
||||
email: 'flow@example.com',
|
||||
},
|
||||
},
|
||||
accessToken: 'session-access-token',
|
||||
};
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
DEFAULT_SUB2API_GROUP_NAME: 'codex',
|
||||
});
|
||||
|
||||
await executor.executeSub2ApiSessionImport({
|
||||
nodeId: 'sub2api-session-import',
|
||||
visibleStep: 10,
|
||||
plusCheckoutTabId: 91,
|
||||
sub2apiUrl: 'https://sub.example/admin/accounts',
|
||||
sub2apiEmail: 'admin@example.com',
|
||||
sub2apiPassword: 'secret',
|
||||
sub2apiGroupName: 'codex',
|
||||
});
|
||||
|
||||
assert.equal(ensureCalls.length, 1);
|
||||
assert.equal(ensureCalls[0].source, 'plus-checkout');
|
||||
assert.deepStrictEqual(ensureCalls[0].options.inject, [
|
||||
'content/utils.js',
|
||||
'content/operation-delay.js',
|
||||
'content/plus-checkout.js',
|
||||
]);
|
||||
assert.deepStrictEqual(sentMessages, [{
|
||||
tabId: 91,
|
||||
source: 'plus-checkout',
|
||||
message: {
|
||||
type: 'PLUS_CHECKOUT_GET_STATE',
|
||||
source: 'background',
|
||||
payload: {
|
||||
includeSession: true,
|
||||
includeAccessToken: true,
|
||||
},
|
||||
},
|
||||
}]);
|
||||
assert.equal(importedPayloads.length, 1);
|
||||
assert.equal(importedPayloads[0].state.accessToken, 'session-access-token');
|
||||
assert.equal(importedPayloads[0].state.session.user.email, 'flow@example.com');
|
||||
assert.equal(completed.length, 1);
|
||||
assert.deepStrictEqual(completed[0], {
|
||||
nodeId: 'sub2api-session-import',
|
||||
payload: {
|
||||
verifiedStatus: 'SUB2API 会话导入完成:新建 1,更新 0,跳过 0,失败 0',
|
||||
sub2apiImportCreated: 1,
|
||||
sub2apiImportUpdated: 0,
|
||||
sub2apiImportSkipped: 0,
|
||||
sub2apiImportFailed: 0,
|
||||
},
|
||||
});
|
||||
assert.equal(
|
||||
logs.some((entry) => entry.stepKey === 'sub2api-session-import' && /读取当前 ChatGPT 登录会话/.test(entry.message)),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('session import step rejects unsupported non-chatgpt tabs before reading session', async () => {
|
||||
const moduleApi = loadSub2ApiSessionImportModule();
|
||||
let sendCalled = false;
|
||||
|
||||
const executor = moduleApi.createSub2ApiSessionImportExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
get: async (tabId) => ({
|
||||
id: tabId,
|
||||
url: 'https://example.com/not-chatgpt',
|
||||
}),
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async () => {},
|
||||
createSub2ApiApi: () => ({
|
||||
importCurrentChatGptSession: async () => ({}),
|
||||
}),
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
getTabId: async () => 91,
|
||||
isTabAlive: async () => true,
|
||||
normalizeSub2ApiUrl: (value) => value,
|
||||
sendTabMessageUntilStopped: async () => {
|
||||
sendCalled = true;
|
||||
return {};
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeSub2ApiSessionImport({
|
||||
nodeId: 'sub2api-session-import',
|
||||
visibleStep: 10,
|
||||
}),
|
||||
/当前标签页不在 ChatGPT \/ OpenAI 页面/
|
||||
);
|
||||
|
||||
assert.equal(sendCalled, false);
|
||||
});
|
||||
|
||||
test('background wires sub2api session import executor into the workflow runtime', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(source, /background\/steps\/sub2api-session-import\.js/);
|
||||
assert.match(source, /'sub2api-session-import': \(state\) => sub2ApiSessionImportExecutor\.executeSub2ApiSessionImport\(state\)/);
|
||||
assert.match(source, /'sub2api-session-import',\s*\n\s*'oauth-login'/);
|
||||
});
|
||||
@@ -191,3 +191,48 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('flow capability registry exposes editable Plus account access strategies for SUB2API', () => {
|
||||
const api = loadApi();
|
||||
const registry = api.createFlowCapabilityRegistry();
|
||||
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
signupMethod: 'email',
|
||||
plusModeEnabled: true,
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
capabilityState.availablePlusAccountAccessStrategies,
|
||||
['oauth', 'sub2api_codex_session']
|
||||
);
|
||||
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, true);
|
||||
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
});
|
||||
|
||||
test('flow capability registry preserves requested Plus account strategy while forcing OAuth on unsupported targets', () => {
|
||||
const api = loadApi();
|
||||
const registry = api.createFlowCapabilityRegistry();
|
||||
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
signupMethod: 'email',
|
||||
plusModeEnabled: true,
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(capabilityState.availablePlusAccountAccessStrategies, ['oauth']);
|
||||
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'oauth');
|
||||
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, false);
|
||||
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'oauth');
|
||||
});
|
||||
|
||||
@@ -34,6 +34,10 @@ test('flow registry exposes canonical flow and target metadata', () => {
|
||||
flowRegistry.getTargetOptions('openai').map((entry) => entry.id),
|
||||
['cpa', 'sub2api', 'codex2api']
|
||||
);
|
||||
assert.deepEqual(
|
||||
flowRegistry.getSettingsGroupDefinition('openai-plus')?.rowIds,
|
||||
['row-plus-mode', 'row-plus-payment-method', 'row-plus-account-access-strategy']
|
||||
);
|
||||
assert.equal(flowRegistry.getPublicationTargetDefinition('kiro', 'kiro-rs')?.label, 'kiro.rs');
|
||||
});
|
||||
|
||||
@@ -48,6 +52,7 @@ test('settings schema normalizes view input into canonical nested namespaces', (
|
||||
ipProxyEnabled: true,
|
||||
ipProxyService: '711proxy',
|
||||
customPassword: 'SharedSecret123!',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'secret-key',
|
||||
stepExecutionRangeByFlow: {
|
||||
@@ -61,6 +66,7 @@ test('settings schema normalizes view input into canonical nested namespaces', (
|
||||
assert.equal(normalized.services.proxy.enabled, true);
|
||||
assert.equal(normalized.services.account.customPassword, 'SharedSecret123!');
|
||||
assert.equal(normalized.flows.openai.integrationTargetId, 'sub2api');
|
||||
assert.equal(normalized.flows.openai.plus.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(normalized.flows.kiro.targetId, 'kiro-rs');
|
||||
assert.equal(normalized.flows.kiro.targets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(normalized.flows.kiro.targets['kiro-rs'].apiKey, 'secret-key');
|
||||
@@ -79,6 +85,7 @@ test('settings schema can project canonical state into a read view without legac
|
||||
kiroTargetId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'key-123',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
});
|
||||
const view = schema.buildSettingsView(normalized);
|
||||
|
||||
@@ -88,6 +95,7 @@ test('settings schema can project canonical state into a read view without legac
|
||||
assert.equal(view.panelMode, 'cpa');
|
||||
assert.equal(view.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(view.kiroRsKey, 'key-123');
|
||||
assert.equal(view.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(view.settingsSchemaVersion, 4);
|
||||
assert.equal(view.settingsState.activeFlowId, 'kiro');
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ const source = fs.readFileSync('background/steps/gopay-manual-confirm.js', 'utf8
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundGoPayManualConfirm;`)(globalScope);
|
||||
|
||||
test('GoPay manual confirm executor publishes pending manual confirmation state for step 7', async () => {
|
||||
test('GoPay manual confirm executor publishes pending manual confirmation state for OAuth continuation', async () => {
|
||||
const stateUpdates = [];
|
||||
const broadcasts = [];
|
||||
const events = [];
|
||||
@@ -24,6 +24,13 @@ test('GoPay manual confirm executor publishes pending manual confirmation state
|
||||
},
|
||||
},
|
||||
},
|
||||
getNodeIdsForState: () => [
|
||||
'open-chatgpt',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'gopay-subscription-confirm',
|
||||
'oauth-login',
|
||||
],
|
||||
getTabId: async () => 42,
|
||||
isTabAlive: async () => true,
|
||||
registerTab: async () => {},
|
||||
@@ -32,17 +39,70 @@ test('GoPay manual confirm executor publishes pending manual confirmation state
|
||||
},
|
||||
});
|
||||
|
||||
await executor.executeGoPayManualConfirm({ plusCheckoutTabId: 42, plusCheckoutUrl: 'https://chatgpt.com/checkout/openai_llc/session' });
|
||||
await executor.executeGoPayManualConfirm({
|
||||
nodeId: 'gopay-subscription-confirm',
|
||||
plusCheckoutTabId: 42,
|
||||
plusCheckoutUrl: 'https://chatgpt.com/checkout/openai_llc/session',
|
||||
});
|
||||
|
||||
assert.equal(stateUpdates.length, 1);
|
||||
assert.equal(stateUpdates[0].plusManualConfirmationPending, true);
|
||||
assert.equal(stateUpdates[0].plusManualConfirmationMethod, 'gopay');
|
||||
assert.equal(stateUpdates[0].plusManualConfirmationStep, 7);
|
||||
assert.match(String(stateUpdates[0].plusManualConfirmationRequestId || ''), /^gopay-/);
|
||||
assert.match(stateUpdates[0].plusManualConfirmationMessage, /OAuth 登录/);
|
||||
assert.equal(broadcasts.length, 1);
|
||||
assert.equal(broadcasts[0].plusManualConfirmationPending, true);
|
||||
assert.deepStrictEqual(
|
||||
events.find((event) => event.type === 'tab-update'),
|
||||
{ type: 'tab-update', tabId: 42, payload: { active: true } }
|
||||
);
|
||||
assert.match(
|
||||
events.find((event) => event.type === 'log')?.message || '',
|
||||
/确认后继续OAuth 登录/
|
||||
);
|
||||
});
|
||||
|
||||
test('GoPay manual confirm executor switches continuation copy to SUB2API session import when the effective tail is session-based', async () => {
|
||||
const stateUpdates = [];
|
||||
const events = [];
|
||||
const executor = api.createGoPayManualConfirmExecutor({
|
||||
addLog: async (message, level = 'info') => {
|
||||
events.push({ type: 'log', message, level });
|
||||
},
|
||||
broadcastDataUpdate: () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
getNodeIdsForState: () => [
|
||||
'open-chatgpt',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'gopay-subscription-confirm',
|
||||
'sub2api-session-import',
|
||||
],
|
||||
getTabId: async () => 42,
|
||||
isTabAlive: async () => true,
|
||||
registerTab: async () => {},
|
||||
setState: async (payload) => {
|
||||
stateUpdates.push(payload);
|
||||
},
|
||||
});
|
||||
|
||||
await executor.executeGoPayManualConfirm({
|
||||
nodeId: 'gopay-subscription-confirm',
|
||||
visibleStep: 9,
|
||||
plusCheckoutTabId: 42,
|
||||
plusCheckoutUrl: 'https://chatgpt.com/checkout/openai_llc/session',
|
||||
});
|
||||
|
||||
assert.equal(stateUpdates.length, 1);
|
||||
assert.equal(stateUpdates[0].plusManualConfirmationStep, 9);
|
||||
assert.match(stateUpdates[0].plusManualConfirmationMessage, /导入当前 ChatGPT 会话到 SUB2API/);
|
||||
assert.match(
|
||||
events.find((event) => event.type === 'log')?.message || '',
|
||||
/确认后继续导入当前 ChatGPT 会话到 SUB2API/
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
const start = markers
|
||||
.map((marker) => sidepanelSource.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 < sidepanelSource.length; i += 1) {
|
||||
const ch = sidepanelSource[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 < sidepanelSource.length; end += 1) {
|
||||
const ch = sidepanelSource[end];
|
||||
if (ch === '{') depth += 1;
|
||||
if (ch === '}') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
end += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sidepanelSource.slice(start, end);
|
||||
}
|
||||
|
||||
function buildHarness(capabilityStateSource, stateSource) {
|
||||
return new Function(`
|
||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||
const DEFAULT_PLUS_PAYMENT_METHOD = 'paypal';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = 'oauth';
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
${extractFunction('normalizePlusAccountAccessStrategy')}
|
||||
${extractFunction('getRequestedPlusAccountAccessStrategy')}
|
||||
${extractFunction('updatePlusModeUI')}
|
||||
function normalizePlusPaymentMethod(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized === 'gopay' || normalized === 'gpc-helper' ? normalized : 'paypal';
|
||||
}
|
||||
function getSelectedPlusPaymentMethod() {
|
||||
return normalizePlusPaymentMethod(selectPlusPaymentMethod.value || latestState?.plusPaymentMethod || currentPlusPaymentMethod || 'paypal');
|
||||
}
|
||||
function normalizeGpcHelperPhoneModeValue(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'auto' ? 'auto' : 'manual';
|
||||
}
|
||||
function normalizeGpcOtpChannelValue(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'sms' ? 'sms' : 'whatsapp';
|
||||
}
|
||||
function isGpcAutoModePermissionDenied() {
|
||||
return false;
|
||||
}
|
||||
function getSelectedPanelMode() {
|
||||
return latestState?.panelMode || 'cpa';
|
||||
}
|
||||
function resolveCurrentSidepanelCapabilities() {
|
||||
return ${capabilityStateSource};
|
||||
}
|
||||
let latestState = ${stateSource};
|
||||
let currentPlusPaymentMethod = 'paypal';
|
||||
let currentPlusAccountAccessStrategy = latestState.plusAccountAccessStrategy || 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const rowPlusMode = { style: { display: '' } };
|
||||
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||
const rowPlusAccountAccessStrategy = { style: { display: 'none' } };
|
||||
const rowPayPalAccount = { style: { display: 'none' } };
|
||||
const selectPlusPaymentMethod = { value: latestState.plusPaymentMethod || 'paypal', style: { display: 'none' } };
|
||||
const selectPlusAccountAccessStrategy = {
|
||||
value: latestState.plusAccountAccessStrategy || 'oauth',
|
||||
disabled: false,
|
||||
dataset: { requestedValue: latestState.plusAccountAccessStrategy || 'oauth' },
|
||||
setAttribute(name, value) {
|
||||
this[name] = value;
|
||||
},
|
||||
};
|
||||
const plusPaymentMethodCaption = { textContent: '' };
|
||||
const plusAccountAccessStrategyCaption = { textContent: '' };
|
||||
return {
|
||||
getRequestedPlusAccountAccessStrategy,
|
||||
plusAccountAccessStrategyCaption,
|
||||
rowPlusAccountAccessStrategy,
|
||||
selectPlusAccountAccessStrategy,
|
||||
updatePlusModeUI,
|
||||
};
|
||||
`)();
|
||||
}
|
||||
|
||||
test('sidepanel keeps requested Plus account strategy while OAuth-only targets force the effective value', () => {
|
||||
const api = buildHarness(
|
||||
`{
|
||||
canShowPlusSettings: true,
|
||||
runtimeLocks: { plusModeEnabled: true },
|
||||
canEditPlusAccountAccessStrategy: false,
|
||||
effectivePlusAccountAccessStrategy: 'oauth',
|
||||
}`,
|
||||
`{
|
||||
activeFlowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
}`
|
||||
);
|
||||
|
||||
api.updatePlusModeUI();
|
||||
|
||||
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.disabled, true);
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.dataset.requestedValue, 'sub2api_codex_session');
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.value, 'oauth');
|
||||
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'sub2api_codex_session');
|
||||
assert.match(api.plusAccountAccessStrategyCaption.textContent, /OAuth/);
|
||||
});
|
||||
|
||||
test('sidepanel enables SUB2API session strategy selection when the current Plus target supports it', () => {
|
||||
const api = buildHarness(
|
||||
`{
|
||||
canShowPlusSettings: true,
|
||||
runtimeLocks: { plusModeEnabled: true },
|
||||
canEditPlusAccountAccessStrategy: true,
|
||||
effectivePlusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
}`,
|
||||
`{
|
||||
activeFlowId: 'openai',
|
||||
panelMode: 'sub2api',
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
}`
|
||||
);
|
||||
|
||||
api.updatePlusModeUI();
|
||||
|
||||
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.disabled, false);
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.value, 'sub2api_codex_session');
|
||||
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'sub2api_codex_session');
|
||||
assert.match(api.plusAccountAccessStrategyCaption.textContent, /SUB2API/);
|
||||
});
|
||||
|
||||
test('sidepanel rebuilds step definitions and workflow nodes with the effective Plus account strategy', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizeSignupMethod'),
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getStepDefinitionsForMode'),
|
||||
extractFunction('getWorkflowNodesForMode'),
|
||||
extractFunction('rebuildStepDefinitionState'),
|
||||
extractFunction('syncStepDefinitionsForMode'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
const calls = [];
|
||||
const window = {
|
||||
MultiPageStepDefinitions: {
|
||||
getSteps(options) {
|
||||
calls.push({ type: 'getSteps', options });
|
||||
return [{ id: 10, order: 100, key: 'sub2api-session-import', title: 'session-import' }];
|
||||
},
|
||||
getNodes(options) {
|
||||
calls.push({ type: 'getNodes', options });
|
||||
return [{ nodeId: 'sub2api-session-import', displayOrder: 10, next: [] }];
|
||||
},
|
||||
},
|
||||
};
|
||||
let latestState = { activeFlowId: 'openai' };
|
||||
let currentPlusModeEnabled = false;
|
||||
let currentPlusPaymentMethod = 'paypal';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
let currentSignupMethod = 'email';
|
||||
let currentPhoneSignupReloginAfterBindEmailEnabled = false;
|
||||
let currentStepDefinitionFlowId = 'openai';
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const DEFAULT_PLUS_PAYMENT_METHOD = 'paypal';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = 'oauth';
|
||||
const DEFAULT_SIGNUP_METHOD = 'email';
|
||||
let stepDefinitions = [];
|
||||
let workflowNodes = [];
|
||||
let STEP_IDS = [];
|
||||
let STEP_DEFAULT_STATUSES = {};
|
||||
let SKIPPABLE_STEPS = new Set();
|
||||
let NODE_IDS = [];
|
||||
let NODE_DEFAULT_STATUSES = {};
|
||||
function getSelectedPlusPaymentMethod() {
|
||||
return currentPlusPaymentMethod;
|
||||
}
|
||||
function renderStepsList() {}
|
||||
${bundle}
|
||||
return {
|
||||
calls,
|
||||
syncStepDefinitionsForMode,
|
||||
snapshot() {
|
||||
return {
|
||||
currentPlusAccountAccessStrategy,
|
||||
workflowNodes,
|
||||
stepDefinitions,
|
||||
STEP_IDS,
|
||||
NODE_IDS,
|
||||
};
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
api.syncStepDefinitionsForMode(true, {
|
||||
activeFlowId: 'openai',
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
signupMethod: 'email',
|
||||
render: true,
|
||||
});
|
||||
|
||||
const snapshot = api.snapshot();
|
||||
assert.equal(snapshot.currentPlusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.deepStrictEqual(snapshot.STEP_IDS, [10]);
|
||||
assert.deepStrictEqual(snapshot.NODE_IDS, ['sub2api-session-import']);
|
||||
assert.equal(snapshot.stepDefinitions[0]?.key, 'sub2api-session-import');
|
||||
assert.equal(snapshot.workflowNodes[0]?.nodeId, 'sub2api-session-import');
|
||||
assert.deepStrictEqual(api.calls, [
|
||||
{
|
||||
type: 'getSteps',
|
||||
options: {
|
||||
activeFlowId: 'openai',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
signupMethod: 'email',
|
||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'getNodes',
|
||||
options: {
|
||||
activeFlowId: 'openai',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
signupMethod: 'email',
|
||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -65,6 +65,7 @@ test('sidepanel step definitions keep the selected Plus payment method', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizeSignupMethod'),
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getStepDefinitionsForMode'),
|
||||
extractFunction('rebuildStepDefinitionState'),
|
||||
extractFunction('syncStepDefinitionsForMode'),
|
||||
@@ -82,6 +83,10 @@ const window = {
|
||||
};
|
||||
let currentPlusModeEnabled = false;
|
||||
let currentPlusPaymentMethod = 'paypal';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
let currentPlusAccountAccessStrategy = DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY;
|
||||
let currentSignupMethod = 'email';
|
||||
let currentPhoneSignupReloginAfterBindEmailEnabled = false;
|
||||
const DEFAULT_SIGNUP_METHOD = 'email';
|
||||
@@ -107,7 +112,7 @@ return {
|
||||
assert.deepEqual(api.getStepIds(), [7]);
|
||||
assert.deepEqual(api.calls[0], {
|
||||
type: 'getSteps',
|
||||
options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gopay', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: false },
|
||||
options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gopay', plusAccountAccessStrategy: 'oauth', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: false },
|
||||
});
|
||||
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [7] });
|
||||
});
|
||||
@@ -141,7 +146,9 @@ test('sidepanel applies restored signup method when rebuilding shared step defin
|
||||
test('sidepanel Plus UI hides PayPal account selector while GoPay is selected', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
@@ -156,10 +163,14 @@ test('sidepanel Plus UI hides PayPal account selector while GoPay is selected',
|
||||
const api = new Function(`
|
||||
let latestState = { plusPaymentMethod: 'gopay' };
|
||||
let currentPlusPaymentMethod = 'paypal';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gopay', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
${bundle}
|
||||
return { updatePlusModeUI, selectPlusPaymentMethod, rowPayPalAccount };
|
||||
@@ -178,7 +189,9 @@ return { updatePlusModeUI, selectPlusPaymentMethod, rowPayPalAccount };
|
||||
test('sidepanel Plus UI can hide Plus controls when the shared flow capability registry disables them', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
@@ -206,6 +219,7 @@ const window = {
|
||||
},
|
||||
};
|
||||
let latestState = { plusPaymentMethod: 'paypal' };
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const rowPlusMode = { style: { display: '' } };
|
||||
const selectPlusPaymentMethod = { value: 'paypal', style: { display: '' } };
|
||||
@@ -213,6 +227,9 @@ const rowPlusPaymentMethod = { style: { display: '' } };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
${bundle}
|
||||
return {
|
||||
rowPlusMode,
|
||||
@@ -235,6 +252,7 @@ test('sidepanel step definitions keep GPC helper mode distinct', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizeSignupMethod'),
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getStepDefinitionsForMode'),
|
||||
extractFunction('rebuildStepDefinitionState'),
|
||||
extractFunction('syncStepDefinitionsForMode'),
|
||||
@@ -252,6 +270,10 @@ const window = {
|
||||
};
|
||||
let currentPlusModeEnabled = false;
|
||||
let currentPlusPaymentMethod = 'paypal';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
let currentPlusAccountAccessStrategy = DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY;
|
||||
let currentSignupMethod = 'email';
|
||||
let currentPhoneSignupReloginAfterBindEmailEnabled = false;
|
||||
const DEFAULT_SIGNUP_METHOD = 'email';
|
||||
@@ -277,14 +299,16 @@ return {
|
||||
assert.deepEqual(api.getStepIds(), [13]);
|
||||
assert.deepEqual(api.calls[0], {
|
||||
type: 'getSteps',
|
||||
options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gpc-helper', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: false },
|
||||
options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gpc-helper', plusAccountAccessStrategy: 'oauth', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: false },
|
||||
});
|
||||
});
|
||||
|
||||
test('sidepanel Plus UI shows GPC fields and purchase button only for GPC', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
@@ -299,10 +323,14 @@ test('sidepanel Plus UI shows GPC fields and purchase button only for GPC', () =
|
||||
const api = new Function(`
|
||||
let latestState = { plusPaymentMethod: 'gpc-helper', gopayHelperAutoModeEnabled: true };
|
||||
let currentPlusPaymentMethod = 'paypal';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const plusPaymentMethodCaption = { textContent: '' };
|
||||
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
@@ -380,7 +408,9 @@ return {
|
||||
test('sidepanel keeps selected GPC auto mode when API Key has no auto permission', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
@@ -395,10 +425,14 @@ test('sidepanel keeps selected GPC auto mode when API Key has no auto permission
|
||||
const api = new Function(`
|
||||
let latestState = { plusPaymentMethod: 'gpc-helper', gopayHelperPhoneMode: 'auto', gopayHelperAutoModeEnabled: false, gopayHelperBalancePayload: { auto_mode_enabled: false } };
|
||||
let currentPlusPaymentMethod = 'gpc-helper';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const plusPaymentMethodCaption = { textContent: '' };
|
||||
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
@@ -432,7 +466,9 @@ return { updatePlusModeUI, selectGpcHelperPhoneMode, plusPaymentMethodCaption, r
|
||||
test('sidepanel keeps selected GPC auto mode when persisted permission survives stop refresh', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
@@ -452,10 +488,14 @@ let latestState = {
|
||||
gopayHelperBalancePayload: { auto_mode_enabled: true },
|
||||
};
|
||||
let currentPlusPaymentMethod = 'gpc-helper';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const plusPaymentMethodCaption = { textContent: '' };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||
@@ -513,7 +553,9 @@ return {
|
||||
test('sidepanel keeps selected GPC auto mode before permission has been queried', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
@@ -528,10 +570,14 @@ test('sidepanel keeps selected GPC auto mode before permission has been queried'
|
||||
const api = new Function(`
|
||||
let latestState = { plusPaymentMethod: 'gpc-helper', gopayHelperPhoneMode: 'auto', gopayHelperAutoModeEnabled: false, gopayHelperBalancePayload: null };
|
||||
let currentPlusPaymentMethod = 'gpc-helper';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const plusPaymentMethodCaption = { textContent: '' };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||
|
||||
@@ -254,6 +254,87 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
assert.equal(gpcSteps[6].title, '等待 GPC 任务完成');
|
||||
});
|
||||
|
||||
test('Plus session strategy swaps the OAuth tail for a single SUB2API import node', () => {
|
||||
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const forbiddenTailKeys = [
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'post-login-phone-verification',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
];
|
||||
|
||||
[
|
||||
{
|
||||
label: 'paypal',
|
||||
options: {
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
},
|
||||
previousNodeId: 'plus-checkout-return',
|
||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
},
|
||||
{
|
||||
label: 'gopay',
|
||||
options: {
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'gopay',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
},
|
||||
previousNodeId: 'gopay-subscription-confirm',
|
||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 10],
|
||||
},
|
||||
{
|
||||
label: 'gpc-helper',
|
||||
options: {
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
},
|
||||
previousNodeId: 'plus-checkout-billing',
|
||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 10],
|
||||
},
|
||||
].forEach(({ label, options, previousNodeId, expectedStepIds }) => {
|
||||
const steps = api.getSteps(options);
|
||||
const nodes = api.getNodes(options);
|
||||
const stepKeys = steps.map((step) => step.key);
|
||||
const nodeIds = nodes.map((node) => node.nodeId);
|
||||
const previousNode = nodes.find((node) => node.nodeId === previousNodeId);
|
||||
const sessionImportNode = nodes.find((node) => node.nodeId === 'sub2api-session-import');
|
||||
|
||||
assert.equal(stepKeys.at(-1), 'sub2api-session-import', `${label} should end with session import`);
|
||||
assert.equal(nodeIds.at(-1), 'sub2api-session-import', `${label} node order should end with session import`);
|
||||
forbiddenTailKeys.forEach((key) => {
|
||||
assert.equal(stepKeys.includes(key), false, `${label} should not keep ${key} in session mode`);
|
||||
assert.equal(nodeIds.includes(key), false, `${label} nodes should not keep ${key} in session mode`);
|
||||
});
|
||||
assert.deepStrictEqual(api.getStepIds(options), expectedStepIds, `${label} step ids should follow the new tail`);
|
||||
assert.equal(api.getLastStepId(options), expectedStepIds.at(-1), `${label} last step id should match session import`);
|
||||
assert.deepStrictEqual(previousNode?.next, ['sub2api-session-import'], `${label} previous node should link to session import`);
|
||||
assert.deepStrictEqual(sessionImportNode?.next, [], `${label} session import should be terminal`);
|
||||
});
|
||||
});
|
||||
|
||||
test('Plus phone signup never switches to SUB2API session tail even if the requested strategy is session import', () => {
|
||||
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const steps = api.getSteps({
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
signupMethod: 'phone',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
});
|
||||
const stepKeys = steps.map((step) => step.key);
|
||||
|
||||
assert.equal(stepKeys.includes('sub2api-session-import'), false);
|
||||
assert.equal(stepKeys.includes('oauth-login'), true);
|
||||
assert.equal(stepKeys.includes('platform-verify'), true);
|
||||
});
|
||||
|
||||
test('sidepanel html loads shared step definitions before sidepanel bootstrap', () => {
|
||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||
const definitionsIndex = html.indexOf('<script src="../data/step-definitions.js"></script>');
|
||||
|
||||
Reference in New Issue
Block a user