Add Plus SUB2API session access strategy

This commit is contained in:
QLHazyCoder
2026-05-19 06:14:35 +08:00
parent a3c36e6f15
commit de17884a30
25 changed files with 2380 additions and 49 deletions
+177 -18
View File
@@ -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,
+64 -6
View File
@@ -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,
+45 -5
View File
@@ -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 {
+185
View File
@@ -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,
};
});
+156
View File
@@ -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,
+71 -1
View File
@@ -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,
+47
View File
@@ -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,
};
});
+1 -1
View File
@@ -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',
+12
View File
@@ -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;
+10
View File
@@ -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
View File
@@ -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
+42 -3
View File
@@ -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_tokenaccessToken 过期后无法自动续期',
}],
},
});
}
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'/);
});
+45
View File
@@ -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');
});
+62 -2
View File
@@ -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/
);
});
+270
View File
@@ -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,
},
},
]);
});
+48 -2
View File
@@ -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' } };
+81
View File
@@ -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>');