feat: add CPA plus session import strategy
This commit is contained in:
+66
-6
@@ -21,6 +21,7 @@ importScripts(
|
|||||||
'background/ip-proxy-provider-711proxy.js',
|
'background/ip-proxy-provider-711proxy.js',
|
||||||
'background/ip-proxy-core.js',
|
'background/ip-proxy-core.js',
|
||||||
'background/sub2api-api.js',
|
'background/sub2api-api.js',
|
||||||
|
'background/cpa-api.js',
|
||||||
'background/panel-bridge.js',
|
'background/panel-bridge.js',
|
||||||
'background/registration-email-state.js',
|
'background/registration-email-state.js',
|
||||||
'background/workflow-engine.js',
|
'background/workflow-engine.js',
|
||||||
@@ -56,6 +57,7 @@ importScripts(
|
|||||||
'background/steps/gopay-approve.js',
|
'background/steps/gopay-approve.js',
|
||||||
'background/steps/plus-return-confirm.js',
|
'background/steps/plus-return-confirm.js',
|
||||||
'background/steps/sub2api-session-import.js',
|
'background/steps/sub2api-session-import.js',
|
||||||
|
'background/steps/cpa-session-import.js',
|
||||||
'background/steps/oauth-login.js',
|
'background/steps/oauth-login.js',
|
||||||
'background/steps/fetch-login-code.js',
|
'background/steps/fetch-login-code.js',
|
||||||
'background/steps/confirm-oauth.js',
|
'background/steps/confirm-oauth.js',
|
||||||
@@ -77,6 +79,7 @@ importScripts(
|
|||||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const NORMAL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
const NORMAL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
plusModeEnabled: false,
|
plusModeEnabled: false,
|
||||||
@@ -103,6 +106,12 @@ const PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitio
|
|||||||
plusPaymentMethod: 'paypal',
|
plusPaymentMethod: 'paypal',
|
||||||
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||||
}) || PLUS_PAYPAL_STEP_DEFINITIONS;
|
}) || PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||||
|
const PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION,
|
||||||
|
}) || PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||||
const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
plusModeEnabled: true,
|
plusModeEnabled: true,
|
||||||
@@ -127,6 +136,12 @@ const PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinition
|
|||||||
plusPaymentMethod: 'gopay',
|
plusPaymentMethod: 'gopay',
|
||||||
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||||
}) || PLUS_GOPAY_STEP_DEFINITIONS;
|
}) || PLUS_GOPAY_STEP_DEFINITIONS;
|
||||||
|
const PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'gopay',
|
||||||
|
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION,
|
||||||
|
}) || PLUS_GOPAY_STEP_DEFINITIONS;
|
||||||
const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
plusModeEnabled: true,
|
plusModeEnabled: true,
|
||||||
@@ -151,6 +166,12 @@ const PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitions?
|
|||||||
plusPaymentMethod: 'gpc-helper',
|
plusPaymentMethod: 'gpc-helper',
|
||||||
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||||
}) || PLUS_GPC_STEP_DEFINITIONS;
|
}) || PLUS_GPC_STEP_DEFINITIONS;
|
||||||
|
const PLUS_GPC_CPA_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'gpc-helper',
|
||||||
|
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION,
|
||||||
|
}) || PLUS_GPC_STEP_DEFINITIONS;
|
||||||
const PLUS_GPC_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
const PLUS_GPC_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
plusModeEnabled: true,
|
plusModeEnabled: true,
|
||||||
@@ -187,14 +208,17 @@ const ALL_STEP_DEFINITIONS = (() => {
|
|||||||
...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_STEP_DEFINITIONS,
|
...PLUS_GOPAY_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
...PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_STEP_DEFINITIONS,
|
...PLUS_GPC_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS,
|
...PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_GPC_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
...PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
];
|
];
|
||||||
@@ -830,6 +854,12 @@ function getStepDefinitionsForState(state = {}) {
|
|||||||
) {
|
) {
|
||||||
return PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS;
|
return PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
signupMethod === SIGNUP_METHOD_EMAIL
|
||||||
|
&& plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION
|
||||||
|
) {
|
||||||
|
return PLUS_GPC_CPA_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
return PLUS_GPC_STEP_DEFINITIONS;
|
return PLUS_GPC_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) {
|
if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) {
|
||||||
@@ -839,6 +869,12 @@ function getStepDefinitionsForState(state = {}) {
|
|||||||
) {
|
) {
|
||||||
return PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS;
|
return PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
signupMethod === SIGNUP_METHOD_EMAIL
|
||||||
|
&& plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION
|
||||||
|
) {
|
||||||
|
return PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
return PLUS_GOPAY_STEP_DEFINITIONS;
|
return PLUS_GOPAY_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -847,6 +883,12 @@ function getStepDefinitionsForState(state = {}) {
|
|||||||
) {
|
) {
|
||||||
return PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS;
|
return PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
signupMethod === SIGNUP_METHOD_EMAIL
|
||||||
|
&& plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION
|
||||||
|
) {
|
||||||
|
return PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
return PLUS_PAYPAL_STEP_DEFINITIONS;
|
return PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1798,9 +1840,13 @@ function normalizePlusPaymentMethod(value = '') {
|
|||||||
|
|
||||||
function normalizePlusAccountAccessStrategy(value = '') {
|
function normalizePlusAccountAccessStrategy(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
return normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
|
||||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
}
|
||||||
|
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION;
|
||||||
|
}
|
||||||
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeFiveSimCountryId(value, fallback = FIVE_SIM_COUNTRY_ID) {
|
function normalizeFiveSimCountryId(value, fallback = FIVE_SIM_COUNTRY_ID) {
|
||||||
@@ -2977,9 +3023,7 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
case 'plusPaymentMethod':
|
case 'plusPaymentMethod':
|
||||||
return normalizePlusPaymentMethod(value);
|
return normalizePlusPaymentMethod(value);
|
||||||
case 'plusAccountAccessStrategy':
|
case 'plusAccountAccessStrategy':
|
||||||
return String(value || '').trim().toLowerCase() === 'sub2api_codex_session'
|
return normalizePlusAccountAccessStrategy(value);
|
||||||
? 'sub2api_codex_session'
|
|
||||||
: 'oauth';
|
|
||||||
case 'paypalEmail':
|
case 'paypalEmail':
|
||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
case 'paypalPassword':
|
case 'paypalPassword':
|
||||||
@@ -10361,6 +10405,7 @@ const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([
|
|||||||
'paypal-approve',
|
'paypal-approve',
|
||||||
'plus-checkout-return',
|
'plus-checkout-return',
|
||||||
'sub2api-session-import',
|
'sub2api-session-import',
|
||||||
|
'cpa-session-import',
|
||||||
'oauth-login',
|
'oauth-login',
|
||||||
'fetch-login-code',
|
'fetch-login-code',
|
||||||
'post-login-phone-verification',
|
'post-login-phone-verification',
|
||||||
@@ -11460,6 +11505,7 @@ const AUTO_RUN_NODE_DELAYS = Object.freeze({
|
|||||||
'paypal-approve': 2000,
|
'paypal-approve': 2000,
|
||||||
'plus-checkout-return': 1000,
|
'plus-checkout-return': 1000,
|
||||||
'sub2api-session-import': 0,
|
'sub2api-session-import': 0,
|
||||||
|
'cpa-session-import': 0,
|
||||||
'oauth-login': 2000,
|
'oauth-login': 2000,
|
||||||
'fetch-login-code': 2000,
|
'fetch-login-code': 2000,
|
||||||
'confirm-oauth': 1000,
|
'confirm-oauth': 1000,
|
||||||
@@ -13280,6 +13326,19 @@ const sub2ApiSessionImportExecutor = self.MultiPageBackgroundSub2ApiSessionImpor
|
|||||||
waitForTabCompleteUntilStopped,
|
waitForTabCompleteUntilStopped,
|
||||||
DEFAULT_SUB2API_GROUP_NAME,
|
DEFAULT_SUB2API_GROUP_NAME,
|
||||||
});
|
});
|
||||||
|
const cpaSessionImportExecutor = self.MultiPageBackgroundCpaSessionImport?.createCpaSessionImportExecutor({
|
||||||
|
addLog,
|
||||||
|
chrome,
|
||||||
|
completeNodeFromBackground,
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped,
|
||||||
|
getTabId,
|
||||||
|
isTabAlive,
|
||||||
|
registerTab,
|
||||||
|
sendTabMessageUntilStopped,
|
||||||
|
sleepWithStop,
|
||||||
|
throwIfStopped,
|
||||||
|
waitForTabCompleteUntilStopped,
|
||||||
|
});
|
||||||
const kiroRegisterRunner = self.MultiPageBackgroundKiroRegisterRunner?.createKiroRegisterRunner({
|
const kiroRegisterRunner = self.MultiPageBackgroundKiroRegisterRunner?.createKiroRegisterRunner({
|
||||||
addLog,
|
addLog,
|
||||||
chrome,
|
chrome,
|
||||||
@@ -13429,6 +13488,7 @@ const stepExecutorsByKey = {
|
|||||||
: payPalApproveExecutor.executePayPalApprove(state),
|
: payPalApproveExecutor.executePayPalApprove(state),
|
||||||
'plus-checkout-return': (state) => plusReturnConfirmExecutor.executePlusReturnConfirm(state),
|
'plus-checkout-return': (state) => plusReturnConfirmExecutor.executePlusReturnConfirm(state),
|
||||||
'sub2api-session-import': (state) => sub2ApiSessionImportExecutor.executeSub2ApiSessionImport(state),
|
'sub2api-session-import': (state) => sub2ApiSessionImportExecutor.executeSub2ApiSessionImport(state),
|
||||||
|
'cpa-session-import': (state) => cpaSessionImportExecutor.executeCpaSessionImport(state),
|
||||||
'oauth-login': (state) => step7Executor.executeStep7(state),
|
'oauth-login': (state) => step7Executor.executeStep7(state),
|
||||||
'fetch-login-code': (state) => step8Executor.executeStep8(state),
|
'fetch-login-code': (state) => step8Executor.executeStep8(state),
|
||||||
'post-login-phone-verification': (state) => step8Executor.executePostLoginPhoneVerification(state),
|
'post-login-phone-verification': (state) => step8Executor.executePostLoginPhoneVerification(state),
|
||||||
|
|||||||
@@ -0,0 +1,507 @@
|
|||||||
|
(function attachBackgroundCpaApi(root, factory) {
|
||||||
|
root.MultiPageBackgroundCpaApi = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundCpaApiModule() {
|
||||||
|
function createCpaApi(deps = {}) {
|
||||||
|
const {
|
||||||
|
addLog = async () => {},
|
||||||
|
fetchImpl = (...args) => fetch(...args),
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
function normalizeString(value = '') {
|
||||||
|
return String(value || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlainObject(value) {
|
||||||
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstNonEmpty(...values) {
|
||||||
|
for (const value of values) {
|
||||||
|
const normalized = normalizeString(value);
|
||||||
|
if (normalized) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeEmailValue(value = '') {
|
||||||
|
const email = normalizeString(value);
|
||||||
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ? email : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractStateFromAuthUrl(authUrl = '') {
|
||||||
|
try {
|
||||||
|
return new URL(authUrl).searchParams.get('state') || '';
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveCpaManagementOrigin(vpsUrl) {
|
||||||
|
const normalizedUrl = normalizeString(vpsUrl);
|
||||||
|
if (!normalizedUrl) {
|
||||||
|
throw new Error('尚未配置 CPA 地址,请先在侧边栏填写。');
|
||||||
|
}
|
||||||
|
let parsed;
|
||||||
|
try {
|
||||||
|
parsed = new URL(normalizedUrl);
|
||||||
|
} catch {
|
||||||
|
throw new Error('CPA 地址格式无效,请先在侧边栏检查。');
|
||||||
|
}
|
||||||
|
return parsed.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCpaApiErrorMessage(payload, responseStatus = 500) {
|
||||||
|
const candidates = [
|
||||||
|
payload?.error,
|
||||||
|
payload?.message,
|
||||||
|
payload?.detail,
|
||||||
|
payload?.reason,
|
||||||
|
];
|
||||||
|
const message = candidates.map(normalizeString).find(Boolean);
|
||||||
|
return message || `CPA 管理接口请求失败(HTTP ${responseStatus})。`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCpaManagementJson(origin, path, options = {}) {
|
||||||
|
const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || 20000));
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const managementKey = normalizeString(options.managementKey);
|
||||||
|
const headers = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
if (managementKey) {
|
||||||
|
headers.Authorization = `Bearer ${managementKey}`;
|
||||||
|
headers['X-Management-Key'] = managementKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetchImpl(`${origin}${path}`, {
|
||||||
|
method: options.method || 'POST',
|
||||||
|
headers,
|
||||||
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
let payload = {};
|
||||||
|
try {
|
||||||
|
payload = await response.json();
|
||||||
|
} catch {
|
||||||
|
payload = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(getCpaApiErrorMessage(payload, response.status));
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.name === 'AbortError') {
|
||||||
|
throw new Error('CPA 管理接口请求超时,请稍后重试。');
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeBase64UrlSegment(segment = '') {
|
||||||
|
const normalized = normalizeString(segment)
|
||||||
|
.replace(/-/g, '+')
|
||||||
|
.replace(/_/g, '/');
|
||||||
|
if (!normalized) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
|
||||||
|
try {
|
||||||
|
if (typeof Buffer !== 'undefined') {
|
||||||
|
return Buffer.from(padded, 'base64').toString('utf8');
|
||||||
|
}
|
||||||
|
if (typeof atob === 'function') {
|
||||||
|
const binary = atob(padded);
|
||||||
|
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
||||||
|
if (typeof TextDecoder !== 'undefined') {
|
||||||
|
return new TextDecoder().decode(bytes);
|
||||||
|
}
|
||||||
|
return binary;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeBase64UrlJson(value) {
|
||||||
|
const json = JSON.stringify(value);
|
||||||
|
if (typeof Buffer !== 'undefined') {
|
||||||
|
return Buffer.from(json, 'utf8')
|
||||||
|
.toString('base64')
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=+$/g, '');
|
||||||
|
}
|
||||||
|
const bytes = new TextEncoder().encode(json);
|
||||||
|
let binary = '';
|
||||||
|
bytes.forEach((byte) => {
|
||||||
|
binary += String.fromCharCode(byte);
|
||||||
|
});
|
||||||
|
return btoa(binary)
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=+$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJwtPayload(token = '') {
|
||||||
|
const normalized = normalizeString(token);
|
||||||
|
if (!normalized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const parts = normalized.split('.');
|
||||||
|
if (parts.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(decodeBase64UrlSegment(parts[1]));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOpenAiAuthSection(payload) {
|
||||||
|
if (!isPlainObject(payload)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auth = payload['https://api.openai.com/auth'];
|
||||||
|
return isPlainObject(auth) ? auth : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOpenAiProfileSection(payload) {
|
||||||
|
if (!isPlainObject(payload)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const profile = payload['https://api.openai.com/profile'];
|
||||||
|
return isPlainObject(profile) ? profile : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTimestamp(value) {
|
||||||
|
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
||||||
|
return value.toISOString();
|
||||||
|
}
|
||||||
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||||
|
const milliseconds = value > 1e11 ? value : value * 1000;
|
||||||
|
const date = new Date(milliseconds);
|
||||||
|
return Number.isNaN(date.getTime()) ? '' : date.toISOString();
|
||||||
|
}
|
||||||
|
if (typeof value !== 'string' || !value.trim()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const date = new Date(value);
|
||||||
|
return Number.isNaN(date.getTime()) ? '' : date.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function timestampFromUnixSeconds(value) {
|
||||||
|
const numeric = Number(value);
|
||||||
|
if (!Number.isFinite(numeric)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const date = new Date(numeric * 1000);
|
||||||
|
return Number.isNaN(date.getTime()) ? '' : date.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function epochSecondsFromValue(value) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const numeric = Number(value);
|
||||||
|
if (Number.isFinite(numeric)) {
|
||||||
|
return Math.trunc(numeric > 1e11 ? numeric / 1000 : numeric);
|
||||||
|
}
|
||||||
|
const parsed = Date.parse(String(value));
|
||||||
|
return Number.isFinite(parsed) ? Math.trunc(parsed / 1000) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSyntheticCodexIdToken(email, accountId, planType, userId, expiresAt) {
|
||||||
|
const normalizedAccountId = normalizeString(accountId);
|
||||||
|
if (!normalizedAccountId) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const now = Math.trunc(Date.now() / 1000);
|
||||||
|
const expires = epochSecondsFromValue(expiresAt) || now + 90 * 24 * 60 * 60;
|
||||||
|
const authInfo = { chatgpt_account_id: normalizedAccountId };
|
||||||
|
|
||||||
|
if (planType) {
|
||||||
|
authInfo.chatgpt_plan_type = normalizeString(planType);
|
||||||
|
}
|
||||||
|
if (userId) {
|
||||||
|
authInfo.chatgpt_user_id = normalizeString(userId);
|
||||||
|
authInfo.user_id = normalizeString(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
iat: now,
|
||||||
|
exp: expires,
|
||||||
|
'https://api.openai.com/auth': authInfo,
|
||||||
|
};
|
||||||
|
if (email) {
|
||||||
|
payload.email = normalizeString(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${encodeBase64UrlJson({ alg: 'none', typ: 'JWT', cpa_synthetic: true })}.${encodeBase64UrlJson(payload)}.synthetic`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePlanTypeForFileName(planType = '') {
|
||||||
|
return normalizeString(planType)
|
||||||
|
.split(/[^a-zA-Z0-9]+/)
|
||||||
|
.map((part) => part.trim().toLowerCase())
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeFileSegment(value = '', fallback = 'chatgpt-session') {
|
||||||
|
const normalized = normalizeString(value)
|
||||||
|
.replace(/[\\/:*?"<>|]+/g, '-')
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(/-+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '');
|
||||||
|
return normalized || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCpaAuthFileName(metadata = {}) {
|
||||||
|
const email = sanitizeFileSegment(metadata.email || '');
|
||||||
|
const planType = normalizePlanTypeForFileName(metadata.planType || '');
|
||||||
|
const accountId = sanitizeFileSegment(metadata.accountId || '');
|
||||||
|
if (email && planType) {
|
||||||
|
return `codex-${email}-${planType}.json`;
|
||||||
|
}
|
||||||
|
if (email) {
|
||||||
|
return `codex-${email}.json`;
|
||||||
|
}
|
||||||
|
if (accountId && planType) {
|
||||||
|
return `codex-${accountId}-${planType}.json`;
|
||||||
|
}
|
||||||
|
if (accountId) {
|
||||||
|
return `codex-${accountId}.json`;
|
||||||
|
}
|
||||||
|
return `codex-${Date.now()}.json`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCpaSessionAuthJson(state = {}, options = {}) {
|
||||||
|
const session = isPlainObject(state?.session) ? state.session : {};
|
||||||
|
const accessToken = normalizeString(state?.accessToken || session?.accessToken);
|
||||||
|
if (!accessToken) {
|
||||||
|
throw new Error('未读取到可导入的 ChatGPT accessToken。');
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputIdToken = firstNonEmpty(
|
||||||
|
state?.idToken,
|
||||||
|
state?.id_token,
|
||||||
|
session?.idToken,
|
||||||
|
session?.id_token
|
||||||
|
);
|
||||||
|
const refreshToken = firstNonEmpty(
|
||||||
|
state?.refreshToken,
|
||||||
|
state?.refresh_token,
|
||||||
|
session?.refreshToken,
|
||||||
|
session?.refresh_token
|
||||||
|
);
|
||||||
|
const sessionToken = firstNonEmpty(
|
||||||
|
state?.sessionToken,
|
||||||
|
state?.session_token,
|
||||||
|
session?.sessionToken,
|
||||||
|
session?.session_token
|
||||||
|
);
|
||||||
|
const accessPayload = parseJwtPayload(accessToken);
|
||||||
|
const idPayload = parseJwtPayload(inputIdToken);
|
||||||
|
const accessAuth = getOpenAiAuthSection(accessPayload);
|
||||||
|
const idAuth = getOpenAiAuthSection(idPayload);
|
||||||
|
const profile = getOpenAiProfileSection(accessPayload);
|
||||||
|
const expiresAt = firstNonEmpty(
|
||||||
|
timestampFromUnixSeconds(accessPayload?.exp),
|
||||||
|
normalizeTimestamp(session?.expires),
|
||||||
|
normalizeTimestamp(session?.expiresAt),
|
||||||
|
normalizeTimestamp(session?.expired),
|
||||||
|
normalizeTimestamp(session?.expires_at)
|
||||||
|
);
|
||||||
|
const accountIdentifierEmail = normalizeString(state?.accountIdentifierType).toLowerCase() === 'email'
|
||||||
|
? normalizeEmailValue(state?.accountIdentifier)
|
||||||
|
: '';
|
||||||
|
const email = firstNonEmpty(
|
||||||
|
normalizeEmailValue(session?.user?.email),
|
||||||
|
normalizeEmailValue(session?.email),
|
||||||
|
normalizeEmailValue(state?.email),
|
||||||
|
accountIdentifierEmail,
|
||||||
|
normalizeEmailValue(profile?.email),
|
||||||
|
normalizeEmailValue(idPayload?.email),
|
||||||
|
normalizeEmailValue(accessPayload?.email)
|
||||||
|
);
|
||||||
|
const accountId = firstNonEmpty(
|
||||||
|
session?.account?.id,
|
||||||
|
session?.account_id,
|
||||||
|
accessAuth?.chatgpt_account_id,
|
||||||
|
idAuth?.chatgpt_account_id
|
||||||
|
);
|
||||||
|
const userId = firstNonEmpty(
|
||||||
|
session?.user?.id,
|
||||||
|
session?.user_id,
|
||||||
|
accessAuth?.chatgpt_user_id,
|
||||||
|
accessAuth?.user_id,
|
||||||
|
idAuth?.chatgpt_user_id,
|
||||||
|
idAuth?.user_id
|
||||||
|
);
|
||||||
|
const planType = firstNonEmpty(
|
||||||
|
session?.account?.planType,
|
||||||
|
session?.account?.plan_type,
|
||||||
|
session?.planType,
|
||||||
|
session?.plan_type,
|
||||||
|
accessAuth?.chatgpt_plan_type,
|
||||||
|
idAuth?.chatgpt_plan_type
|
||||||
|
);
|
||||||
|
const exportedAt = normalizeTimestamp(options.now || new Date()) || new Date().toISOString();
|
||||||
|
const syntheticIdToken = inputIdToken
|
||||||
|
? ''
|
||||||
|
: buildSyntheticCodexIdToken(email, accountId, planType, userId, expiresAt);
|
||||||
|
const idToken = inputIdToken || syntheticIdToken;
|
||||||
|
const authJson = Object.fromEntries(
|
||||||
|
Object.entries({
|
||||||
|
type: 'codex',
|
||||||
|
account_id: accountId,
|
||||||
|
chatgpt_account_id: accountId,
|
||||||
|
email,
|
||||||
|
name: firstNonEmpty(email, state?.email, 'ChatGPT Account'),
|
||||||
|
plan_type: planType,
|
||||||
|
chatgpt_plan_type: planType,
|
||||||
|
id_token: idToken,
|
||||||
|
id_token_synthetic: syntheticIdToken ? true : undefined,
|
||||||
|
access_token: accessToken,
|
||||||
|
refresh_token: refreshToken || '',
|
||||||
|
session_token: sessionToken,
|
||||||
|
last_refresh: exportedAt,
|
||||||
|
expired: expiresAt,
|
||||||
|
disabled: session?.disabled === true ? true : undefined,
|
||||||
|
}).filter(([, value]) => value !== undefined && value !== null && value !== '')
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
authJson,
|
||||||
|
accountId,
|
||||||
|
email,
|
||||||
|
expiresAt,
|
||||||
|
fileName: buildCpaAuthFileName({ email, planType, accountId }),
|
||||||
|
hasRefreshToken: Boolean(refreshToken),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logWithOptions(message, level = 'info', options = {}) {
|
||||||
|
await addLog(message, level, options.logOptions || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestOAuthUrl(state, options = {}) {
|
||||||
|
const managementKey = normalizeString(state?.vpsPassword);
|
||||||
|
if (!managementKey) {
|
||||||
|
throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。');
|
||||||
|
}
|
||||||
|
const origin = deriveCpaManagementOrigin(state?.vpsUrl);
|
||||||
|
const result = await fetchCpaManagementJson(origin, '/v0/management/codex-auth-url', {
|
||||||
|
method: 'GET',
|
||||||
|
managementKey,
|
||||||
|
timeoutMs: options.timeoutMs,
|
||||||
|
});
|
||||||
|
const oauthUrl = firstNonEmpty(
|
||||||
|
result?.url,
|
||||||
|
result?.auth_url,
|
||||||
|
result?.authUrl,
|
||||||
|
result?.data?.url,
|
||||||
|
result?.data?.auth_url,
|
||||||
|
result?.data?.authUrl
|
||||||
|
);
|
||||||
|
const oauthState = firstNonEmpty(
|
||||||
|
result?.state,
|
||||||
|
result?.auth_state,
|
||||||
|
result?.authState,
|
||||||
|
result?.data?.state,
|
||||||
|
result?.data?.auth_state,
|
||||||
|
result?.data?.authState,
|
||||||
|
extractStateFromAuthUrl(oauthUrl)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!oauthUrl || !oauthUrl.startsWith('http')) {
|
||||||
|
throw new Error('CPA 管理接口未返回有效的 auth_url。');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
oauthUrl,
|
||||||
|
cpaOAuthState: oauthState || null,
|
||||||
|
cpaManagementOrigin: origin,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitOAuthCallback(state, callbackUrl, options = {}) {
|
||||||
|
const managementKey = normalizeString(state?.vpsPassword);
|
||||||
|
if (!managementKey) {
|
||||||
|
throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。');
|
||||||
|
}
|
||||||
|
const origin = normalizeString(state?.cpaManagementOrigin) || deriveCpaManagementOrigin(state?.vpsUrl);
|
||||||
|
const result = await fetchCpaManagementJson(origin, '/v0/management/oauth-callback', {
|
||||||
|
method: 'POST',
|
||||||
|
managementKey,
|
||||||
|
timeoutMs: options.timeoutMs,
|
||||||
|
body: {
|
||||||
|
provider: 'codex',
|
||||||
|
redirect_url: normalizeString(callbackUrl),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
localhostUrl: normalizeString(callbackUrl),
|
||||||
|
verifiedStatus: firstNonEmpty(result?.message, result?.status_message, 'CPA 已通过接口提交回调'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importCurrentChatGptSession(state = {}, options = {}) {
|
||||||
|
const logLabel = normalizeString(options.logLabel) || 'CPA 会话导入';
|
||||||
|
const managementKey = normalizeString(state?.vpsPassword);
|
||||||
|
if (!managementKey) {
|
||||||
|
throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。');
|
||||||
|
}
|
||||||
|
const origin = deriveCpaManagementOrigin(state?.vpsUrl);
|
||||||
|
const sessionAuth = buildCpaSessionAuthJson(state, options);
|
||||||
|
|
||||||
|
await logWithOptions(`${logLabel}:正在通过 CPA 管理接口导入当前 ChatGPT 会话...`, 'info', options);
|
||||||
|
if (!sessionAuth.hasRefreshToken) {
|
||||||
|
await logWithOptions(`${logLabel}:未包含 refresh_token,access_token 过期后无法自动续期。`, 'warn', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetchCpaManagementJson(origin, `/v0/management/auth-files?name=${encodeURIComponent(sessionAuth.fileName)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
managementKey,
|
||||||
|
timeoutMs: options.importTimeoutMs || options.timeoutMs,
|
||||||
|
body: sessionAuth.authJson,
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifiedStatus = sessionAuth.email
|
||||||
|
? `CPA 会话导入完成:${sessionAuth.email}`
|
||||||
|
: `CPA 会话导入完成:${sessionAuth.fileName}`;
|
||||||
|
await logWithOptions(verifiedStatus, 'ok', options);
|
||||||
|
return {
|
||||||
|
verifiedStatus,
|
||||||
|
cpaImportedFileName: sessionAuth.fileName,
|
||||||
|
cpaImportedEmail: sessionAuth.email || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildCpaSessionAuthJson,
|
||||||
|
deriveCpaManagementOrigin,
|
||||||
|
fetchCpaManagementJson,
|
||||||
|
importCurrentChatGptSession,
|
||||||
|
requestOAuthUrl,
|
||||||
|
submitOAuthCallback,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createCpaApi,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -593,9 +593,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizePlusAccountAccessStrategyForDisplay(value = '') {
|
function normalizePlusAccountAccessStrategyForDisplay(value = '') {
|
||||||
return String(value || '').trim().toLowerCase() === 'sub2api_codex_session'
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
? 'sub2api_codex_session'
|
if (normalized === 'sub2api_codex_session') {
|
||||||
: 'oauth';
|
return 'sub2api_codex_session';
|
||||||
|
}
|
||||||
|
if (normalized === 'cpa_codex_session') {
|
||||||
|
return 'cpa_codex_session';
|
||||||
|
}
|
||||||
|
return 'oauth';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlusAccountAccessStrategyLabel(value = '') {
|
function getPlusAccountAccessStrategyLabel(value = '') {
|
||||||
@@ -604,6 +609,27 @@
|
|||||||
: 'OAuth';
|
: 'OAuth';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPlusAccountAccessStrategyLabel(value = '', targetId = '') {
|
||||||
|
const strategy = normalizePlusAccountAccessStrategyForDisplay(value);
|
||||||
|
const normalizedTargetId = String(targetId || '').trim().toLowerCase();
|
||||||
|
if (strategy === 'sub2api_codex_session') {
|
||||||
|
return '导入当前 ChatGPT 会话到 SUB2API';
|
||||||
|
}
|
||||||
|
if (strategy === 'cpa_codex_session') {
|
||||||
|
return '导入当前 ChatGPT 会话到 CPA';
|
||||||
|
}
|
||||||
|
if (normalizedTargetId === 'cpa') {
|
||||||
|
return '通过 OAuth 回调创建 CPA 账号';
|
||||||
|
}
|
||||||
|
if (normalizedTargetId === 'sub2api') {
|
||||||
|
return '通过 OAuth 回调创建 SUB2API 账号';
|
||||||
|
}
|
||||||
|
if (normalizedTargetId === 'codex2api') {
|
||||||
|
return '通过 OAuth 回调创建 Codex2API 账号';
|
||||||
|
}
|
||||||
|
return 'OAuth';
|
||||||
|
}
|
||||||
|
|
||||||
async function handlePlatformVerifyStepData(payload) {
|
async function handlePlatformVerifyStepData(payload) {
|
||||||
if (payload.localhostUrl) {
|
if (payload.localhostUrl) {
|
||||||
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
||||||
@@ -1561,7 +1587,12 @@
|
|||||||
stateUpdates.plusPaymentMethod ?? currentState?.plusPaymentMethod ?? 'paypal'
|
stateUpdates.plusPaymentMethod ?? currentState?.plusPaymentMethod ?? 'paypal'
|
||||||
);
|
);
|
||||||
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
|
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
|
||||||
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth'
|
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth',
|
||||||
|
stateUpdates.panelMode
|
||||||
|
?? currentState?.panelMode
|
||||||
|
?? stateUpdates.openaiIntegrationTargetId
|
||||||
|
?? currentState?.openaiIntegrationTargetId
|
||||||
|
?? 'cpa'
|
||||||
);
|
);
|
||||||
await addLog(
|
await addLog(
|
||||||
Boolean(updates.plusModeEnabled)
|
Boolean(updates.plusModeEnabled)
|
||||||
@@ -1576,7 +1607,12 @@
|
|||||||
await addLog(`Plus 支付方式已切换为 ${selectedPlusPaymentMethod},已更新对应的 Plus 步骤。`, 'info');
|
await addLog(`Plus 支付方式已切换为 ${selectedPlusPaymentMethod},已更新对应的 Plus 步骤。`, 'info');
|
||||||
} else if (plusAccountAccessStrategyChanged && nextPlusModeEnabled) {
|
} else if (plusAccountAccessStrategyChanged && nextPlusModeEnabled) {
|
||||||
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
|
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
|
||||||
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth'
|
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth',
|
||||||
|
stateUpdates.panelMode
|
||||||
|
?? currentState?.panelMode
|
||||||
|
?? stateUpdates.openaiIntegrationTargetId
|
||||||
|
?? currentState?.openaiIntegrationTargetId
|
||||||
|
?? 'cpa'
|
||||||
);
|
);
|
||||||
await addLog(`Plus 账号接入策略已切换为 ${selectedPlusAccountAccessStrategy},已更新对应的 Plus 尾链。`, 'info');
|
await addLog(`Plus 账号接入策略已切换为 ${selectedPlusAccountAccessStrategy},已更新对应的 Plus 尾链。`, 'info');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
(function attachBackgroundCpaSessionImport(root, factory) {
|
||||||
|
root.MultiPageBackgroundCpaSessionImport = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundCpaSessionImportModule() {
|
||||||
|
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
|
||||||
|
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js'];
|
||||||
|
|
||||||
|
function createCpaSessionImportExecutor(deps = {}) {
|
||||||
|
const {
|
||||||
|
addLog: rawAddLog = async () => {},
|
||||||
|
chrome,
|
||||||
|
completeNodeFromBackground,
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped,
|
||||||
|
getTabId,
|
||||||
|
isTabAlive,
|
||||||
|
registerTab,
|
||||||
|
sendTabMessageUntilStopped,
|
||||||
|
sleepWithStop = async () => {},
|
||||||
|
throwIfStopped = () => {},
|
||||||
|
waitForTabCompleteUntilStopped = async () => {},
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
let cpaApi = null;
|
||||||
|
|
||||||
|
function addStepLog(step, message, level = 'info') {
|
||||||
|
return rawAddLog(message, level, {
|
||||||
|
step,
|
||||||
|
stepKey: 'cpa-session-import',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCpaApi() {
|
||||||
|
if (cpaApi) {
|
||||||
|
return cpaApi;
|
||||||
|
}
|
||||||
|
const factory = deps.createCpaApi
|
||||||
|
|| self.MultiPageBackgroundCpaApi?.createCpaApi;
|
||||||
|
if (typeof factory !== 'function') {
|
||||||
|
throw new Error('CPA 接口模块未加载,无法导入当前 ChatGPT 会话。');
|
||||||
|
}
|
||||||
|
cpaApi = factory({
|
||||||
|
addLog: rawAddLog,
|
||||||
|
});
|
||||||
|
return cpaApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSessionTabHostPriority(url = '') {
|
||||||
|
try {
|
||||||
|
const hostname = String(new URL(String(url || '')).hostname || '').trim().toLowerCase();
|
||||||
|
if (/(^|\.)chatgpt\.com$/.test(hostname)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (hostname === 'chat.openai.com') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (/(^|\.)openai\.com$/.test(hostname)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return Number.POSITIVE_INFINITY;
|
||||||
|
}
|
||||||
|
return Number.POSITIVE_INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSessionTabActivityPriority(tab = {}) {
|
||||||
|
if (tab?.active && tab?.currentWindow) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (tab?.active) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickPreferredSessionTab(tabs = []) {
|
||||||
|
const candidates = (Array.isArray(tabs) ? tabs : [])
|
||||||
|
.filter((tab) => Number.isInteger(tab?.id) && isSupportedChatGptSessionUrl(tab.url));
|
||||||
|
if (!candidates.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates.reduce((best, candidate) => {
|
||||||
|
if (!best) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateHostPriority = getSessionTabHostPriority(candidate.url);
|
||||||
|
const bestHostPriority = getSessionTabHostPriority(best.url);
|
||||||
|
if (candidateHostPriority !== bestHostPriority) {
|
||||||
|
return candidateHostPriority < bestHostPriority ? candidate : best;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateActivityPriority = getSessionTabActivityPriority(candidate);
|
||||||
|
const bestActivityPriority = getSessionTabActivityPriority(best);
|
||||||
|
if (candidateActivityPriority !== bestActivityPriority) {
|
||||||
|
return candidateActivityPriority < bestActivityPriority ? candidate : best;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateLastAccessed = Number(candidate?.lastAccessed) || 0;
|
||||||
|
const bestLastAccessed = Number(best?.lastAccessed) || 0;
|
||||||
|
if (candidateLastAccessed !== bestLastAccessed) {
|
||||||
|
return candidateLastAccessed > bestLastAccessed ? candidate : best;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(candidate.id) < Number(best.id) ? candidate : best;
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readSupportedSessionTab(tabId) {
|
||||||
|
const numericTabId = Number(tabId) || 0;
|
||||||
|
if (!numericTabId || !chrome?.tabs?.get) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tab = await chrome.tabs.get(numericTabId).catch(() => null);
|
||||||
|
return tab?.id && isSupportedChatGptSessionUrl(tab.url)
|
||||||
|
? tab
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findFallbackSessionTab() {
|
||||||
|
if (!chrome?.tabs?.query) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTabs = await chrome.tabs.query({ active: true, currentWindow: true }).catch(() => []);
|
||||||
|
const activeMatch = pickPreferredSessionTab(activeTabs);
|
||||||
|
const allTabs = await chrome.tabs.query({}).catch(() => []);
|
||||||
|
const globalMatch = pickPreferredSessionTab(allTabs);
|
||||||
|
return pickPreferredSessionTab([activeMatch, globalMatch]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
const registeredTab = await readSupportedSessionTab(registeredTabId);
|
||||||
|
if (registeredTab?.id) {
|
||||||
|
return registeredTab.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedTabId = Number(state?.plusCheckoutTabId) || 0;
|
||||||
|
const storedTab = await readSupportedSessionTab(storedTabId);
|
||||||
|
if (storedTab?.id) {
|
||||||
|
if (typeof registerTab === 'function') {
|
||||||
|
await registerTab(PLUS_CHECKOUT_SOURCE, storedTab.id);
|
||||||
|
}
|
||||||
|
return storedTab.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackTab = await findFallbackSessionTab();
|
||||||
|
if (fallbackTab?.id) {
|
||||||
|
if (typeof registerTab === 'function') {
|
||||||
|
await registerTab(PLUS_CHECKOUT_SOURCE, fallbackTab.id);
|
||||||
|
}
|
||||||
|
return fallbackTab.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('未找到可读取 ChatGPT 会话的标签页,请先打开一个已登录的 ChatGPT / OpenAI 页面,或完成当前 Plus 支付链路。');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getResolvedSessionTab(tabId, visibleStep) {
|
||||||
|
const tab = await chrome?.tabs?.get?.(tabId).catch(() => null);
|
||||||
|
if (!tab?.id) {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:ChatGPT 会话标签页不存在或已关闭,无法继续导入 CPA。`);
|
||||||
|
}
|
||||||
|
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 executeCpaSessionImport(state = {}) {
|
||||||
|
throwIfStopped();
|
||||||
|
const visibleStep = resolveVisibleStep(state);
|
||||||
|
const api = getCpaApi();
|
||||||
|
|
||||||
|
await addStepLog(visibleStep, '正在定位当前 ChatGPT 会话页并准备导入 CPA...', '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: 'cpa-session-import' },
|
||||||
|
timeoutMs: 120000,
|
||||||
|
importTimeoutMs: 120000,
|
||||||
|
});
|
||||||
|
|
||||||
|
await completeNodeFromBackground(state?.nodeId || 'cpa-session-import', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
executeCpaSessionImport,
|
||||||
|
isSupportedChatGptSessionUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createCpaSessionImportExecutor,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -4,14 +4,28 @@
|
|||||||
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
|
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
|
||||||
const GOPAY_CONFIRM_NODE_ID = 'gopay-subscription-confirm';
|
const GOPAY_CONFIRM_NODE_ID = 'gopay-subscription-confirm';
|
||||||
const SUB2API_SESSION_IMPORT_NODE_ID = 'sub2api-session-import';
|
const SUB2API_SESSION_IMPORT_NODE_ID = 'sub2api-session-import';
|
||||||
|
const CPA_SESSION_IMPORT_NODE_ID = 'cpa-session-import';
|
||||||
const DEFAULT_CONFIRM_TITLE = 'GoPay 订阅确认';
|
const DEFAULT_CONFIRM_TITLE = 'GoPay 订阅确认';
|
||||||
const OAUTH_CONTINUATION_LABEL = 'OAuth 登录';
|
const OAUTH_CONTINUATION_LABEL = 'OAuth 登录';
|
||||||
const SUB2API_SESSION_CONTINUATION_LABEL = '导入当前 ChatGPT 会话到 SUB2API';
|
const SUB2API_SESSION_CONTINUATION_LABEL = '导入当前 ChatGPT 会话到 SUB2API';
|
||||||
|
|
||||||
|
const CPA_SESSION_CONTINUATION_LABEL = '导入当前 ChatGPT 会话到 CPA';
|
||||||
|
|
||||||
function normalizeString(value = '') {
|
function normalizeString(value = '') {
|
||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getContinuationActionLabelForNodeId(nodeId = '') {
|
||||||
|
const normalizedNodeId = normalizeString(nodeId);
|
||||||
|
if (normalizedNodeId === SUB2API_SESSION_IMPORT_NODE_ID) {
|
||||||
|
return SUB2API_SESSION_CONTINUATION_LABEL;
|
||||||
|
}
|
||||||
|
if (normalizedNodeId === CPA_SESSION_IMPORT_NODE_ID) {
|
||||||
|
return CPA_SESSION_CONTINUATION_LABEL;
|
||||||
|
}
|
||||||
|
return OAUTH_CONTINUATION_LABEL;
|
||||||
|
}
|
||||||
|
|
||||||
function getContinuationActionLabel(state = {}, options = {}) {
|
function getContinuationActionLabel(state = {}, options = {}) {
|
||||||
const { getNodeIdsForState = null } = options;
|
const { getNodeIdsForState = null } = options;
|
||||||
if (typeof getNodeIdsForState === 'function') {
|
if (typeof getNodeIdsForState === 'function') {
|
||||||
@@ -24,11 +38,16 @@
|
|||||||
? normalizeString(nodeIds[currentNodeIndex + 1])
|
? normalizeString(nodeIds[currentNodeIndex + 1])
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
if (
|
if (nextNodeId) {
|
||||||
nextNodeId === SUB2API_SESSION_IMPORT_NODE_ID
|
return getContinuationActionLabelForNodeId(nextNodeId);
|
||||||
|| (currentNodeIndex < 0 && nodeIds.includes(SUB2API_SESSION_IMPORT_NODE_ID))
|
}
|
||||||
) {
|
if (currentNodeIndex < 0) {
|
||||||
return SUB2API_SESSION_CONTINUATION_LABEL;
|
if (nodeIds.includes(SUB2API_SESSION_IMPORT_NODE_ID)) {
|
||||||
|
return SUB2API_SESSION_CONTINUATION_LABEL;
|
||||||
|
}
|
||||||
|
if (nodeIds.includes(CPA_SESSION_IMPORT_NODE_ID)) {
|
||||||
|
return CPA_SESSION_CONTINUATION_LABEL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return OAUTH_CONTINUATION_LABEL;
|
return OAUTH_CONTINUATION_LABEL;
|
||||||
|
|||||||
+74
-10
@@ -7,6 +7,7 @@
|
|||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const PLUS_PAYMENT_STEP_KEY = 'paypal-approve';
|
const PLUS_PAYMENT_STEP_KEY = 'paypal-approve';
|
||||||
const SIGNUP_METHOD_EMAIL = 'email';
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
const SIGNUP_METHOD_PHONE = 'phone';
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
@@ -110,21 +111,51 @@
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePlusAccountAccessStrategy(value = '') {
|
function createCpaSessionImportTail(startId, startOrder) {
|
||||||
return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
const id = Number(startId) || 10;
|
||||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
const order = Number(startOrder) || id * 10;
|
||||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
return [
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
order,
|
||||||
|
key: 'cpa-session-import',
|
||||||
|
title: '导入当前 ChatGPT 会话到 CPA',
|
||||||
|
sourceId: 'vps-panel',
|
||||||
|
driverId: 'background/cpa-session-import',
|
||||||
|
command: 'cpa-session-import',
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldUseSub2ApiSessionImportTail(options = {}, signupMethod = SIGNUP_METHOD_EMAIL) {
|
function normalizePlusAccountAccessStrategy(value = '') {
|
||||||
return signupMethod === SIGNUP_METHOD_EMAIL
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
&& normalizePlusAccountAccessStrategy(options?.plusAccountAccessStrategy)
|
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
=== PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
|
||||||
|
}
|
||||||
|
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION;
|
||||||
|
}
|
||||||
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePlusSessionImportTail(options = {}, signupMethod = SIGNUP_METHOD_EMAIL) {
|
||||||
|
if (signupMethod !== SIGNUP_METHOD_EMAIL) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const strategy = normalizePlusAccountAccessStrategy(options?.plusAccountAccessStrategy);
|
||||||
|
if (strategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
|
return createSub2ApiSessionImportTail;
|
||||||
|
}
|
||||||
|
if (strategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return createCpaSessionImportTail;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOpenAiSteps(prefixSteps, startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) {
|
function createOpenAiSteps(prefixSteps, startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) {
|
||||||
const tailSteps = shouldUseSub2ApiSessionImportTail(options, signupMethod)
|
const sessionTailFactory = resolvePlusSessionImportTail(options, signupMethod);
|
||||||
? createSub2ApiSessionImportTail(startId, startOrder)
|
const tailSteps = sessionTailFactory
|
||||||
|
? sessionTailFactory(startId, startOrder)
|
||||||
: createOpenAiAuthTail(startId, startOrder, signupMethod, options);
|
: createOpenAiAuthTail(startId, startOrder, signupMethod, options);
|
||||||
return [
|
return [
|
||||||
...prefixSteps,
|
...prefixSteps,
|
||||||
@@ -143,6 +174,13 @@
|
|||||||
SIGNUP_METHOD_EMAIL,
|
SIGNUP_METHOD_EMAIL,
|
||||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
||||||
);
|
);
|
||||||
|
const PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS = createOpenAiSteps(
|
||||||
|
PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS,
|
||||||
|
10,
|
||||||
|
100,
|
||||||
|
SIGNUP_METHOD_EMAIL,
|
||||||
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION }
|
||||||
|
);
|
||||||
const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
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_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_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||||
@@ -153,6 +191,13 @@
|
|||||||
SIGNUP_METHOD_EMAIL,
|
SIGNUP_METHOD_EMAIL,
|
||||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
||||||
);
|
);
|
||||||
|
const PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS = createOpenAiSteps(
|
||||||
|
PLUS_GOPAY_PREFIX_STEP_DEFINITIONS,
|
||||||
|
10,
|
||||||
|
100,
|
||||||
|
SIGNUP_METHOD_EMAIL,
|
||||||
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION }
|
||||||
|
);
|
||||||
const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
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_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_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||||
@@ -163,6 +208,13 @@
|
|||||||
SIGNUP_METHOD_EMAIL,
|
SIGNUP_METHOD_EMAIL,
|
||||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
||||||
);
|
);
|
||||||
|
const PLUS_GPC_CPA_SESSION_STEP_DEFINITIONS = createOpenAiSteps(
|
||||||
|
PLUS_GPC_PREFIX_STEP_DEFINITIONS,
|
||||||
|
10,
|
||||||
|
100,
|
||||||
|
SIGNUP_METHOD_EMAIL,
|
||||||
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION }
|
||||||
|
);
|
||||||
const PLUS_GPC_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
const PLUS_GPC_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
||||||
const PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
const PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
||||||
const KIRO_STEP_DEFINITIONS = [
|
const KIRO_STEP_DEFINITIONS = [
|
||||||
@@ -308,6 +360,9 @@
|
|||||||
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
return PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS;
|
return PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return PLUS_GPC_CPA_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
return PLUS_GPC_STEP_DEFINITIONS;
|
return PLUS_GPC_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) {
|
if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) {
|
||||||
@@ -319,6 +374,9 @@
|
|||||||
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
return PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS;
|
return PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
return PLUS_GOPAY_STEP_DEFINITIONS;
|
return PLUS_GOPAY_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
||||||
@@ -329,6 +387,9 @@
|
|||||||
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
return PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS;
|
return PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
return PLUS_PAYPAL_STEP_DEFINITIONS;
|
return PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,14 +425,17 @@
|
|||||||
...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_STEP_DEFINITIONS,
|
...PLUS_GOPAY_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
...PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_STEP_DEFINITIONS,
|
...PLUS_GPC_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS,
|
...PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_GPC_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
...PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
]) {
|
]) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
const SIGNUP_METHOD_PHONE = 'phone';
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const VALID_OPENAI_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_TARGET_IDS)
|
const VALID_OPENAI_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_TARGET_IDS)
|
||||||
? flowRegistryApi.OPENAI_TARGET_IDS.slice()
|
? flowRegistryApi.OPENAI_TARGET_IDS.slice()
|
||||||
: ['cpa', 'sub2api', 'codex2api'];
|
: ['cpa', 'sub2api', 'codex2api'];
|
||||||
@@ -71,7 +72,10 @@
|
|||||||
cpa: Object.freeze({
|
cpa: Object.freeze({
|
||||||
supportsPhoneSignup: true,
|
supportsPhoneSignup: true,
|
||||||
requiresPhoneSignupWarning: true,
|
requiresPhoneSignupWarning: true,
|
||||||
supportedPlusAccountAccessStrategies: Object.freeze([PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]),
|
supportedPlusAccountAccessStrategies: Object.freeze([
|
||||||
|
PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH,
|
||||||
|
PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION,
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
sub2api: Object.freeze({
|
sub2api: Object.freeze({
|
||||||
supportsPhoneSignup: true,
|
supportsPhoneSignup: true,
|
||||||
@@ -127,9 +131,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizePlusAccountAccessStrategy(value = '') {
|
function normalizePlusAccountAccessStrategy(value = '') {
|
||||||
return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
|
||||||
|
}
|
||||||
|
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION;
|
||||||
|
}
|
||||||
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeOpenAiTargetList(values = []) {
|
function normalizeOpenAiTargetList(values = []) {
|
||||||
@@ -618,6 +627,7 @@
|
|||||||
OPENAI_TARGET_CAPABILITIES,
|
OPENAI_TARGET_CAPABILITIES,
|
||||||
PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH,
|
PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH,
|
||||||
PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||||
|
PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION,
|
||||||
SIGNUP_METHOD_EMAIL,
|
SIGNUP_METHOD_EMAIL,
|
||||||
SIGNUP_METHOD_PHONE,
|
SIGNUP_METHOD_PHONE,
|
||||||
VALID_OPENAI_TARGET_IDS,
|
VALID_OPENAI_TARGET_IDS,
|
||||||
|
|||||||
@@ -47,11 +47,16 @@
|
|||||||
const normalizeTargetId = typeof flowRegistry.normalizeTargetId === 'function'
|
const normalizeTargetId = typeof flowRegistry.normalizeTargetId === 'function'
|
||||||
? flowRegistry.normalizeTargetId
|
? flowRegistry.normalizeTargetId
|
||||||
: ((_flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase());
|
: ((_flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase());
|
||||||
const normalizePlusAccountAccessStrategy = (value = '') => (
|
const normalizePlusAccountAccessStrategy = (value = '') => {
|
||||||
String(value || '').trim().toLowerCase() === 'sub2api_codex_session'
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
? 'sub2api_codex_session'
|
if (normalized === 'sub2api_codex_session') {
|
||||||
: 'oauth'
|
return 'sub2api_codex_session';
|
||||||
);
|
}
|
||||||
|
if (normalized === 'cpa_codex_session') {
|
||||||
|
return 'cpa_codex_session';
|
||||||
|
}
|
||||||
|
return 'oauth';
|
||||||
|
};
|
||||||
|
|
||||||
function buildDefaultSettingsState() {
|
function buildDefaultSettingsState() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -306,6 +306,7 @@
|
|||||||
<div class="data-inline">
|
<div class="data-inline">
|
||||||
<select id="select-plus-account-access-strategy" class="data-select">
|
<select id="select-plus-account-access-strategy" class="data-select">
|
||||||
<option value="oauth">OAuth</option>
|
<option value="oauth">OAuth</option>
|
||||||
|
<option value="cpa_codex_session">导入当前 ChatGPT 会话到 CPA</option>
|
||||||
<option value="sub2api_codex_session">导入当前 ChatGPT 会话到 SUB2API</option>
|
<option value="sub2api_codex_session">导入当前 ChatGPT 会话到 SUB2API</option>
|
||||||
</select>
|
</select>
|
||||||
<span class="setting-caption" id="plus-account-access-strategy-caption">当前来源仅支持 OAuth</span>
|
<span class="setting-caption" id="plus-account-access-strategy-caption">当前来源仅支持 OAuth</span>
|
||||||
|
|||||||
+184
-3
@@ -547,6 +547,7 @@ const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
|||||||
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
const SIGNUP_METHOD_EMAIL = 'email';
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
const SIGNUP_METHOD_PHONE = 'phone';
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
@@ -2853,9 +2854,13 @@ function normalizePlusPaymentMethod(value = '') {
|
|||||||
|
|
||||||
function normalizePlusAccountAccessStrategy(value = '') {
|
function normalizePlusAccountAccessStrategy(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
return normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
|
||||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
}
|
||||||
|
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION;
|
||||||
|
}
|
||||||
|
return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedPlusPaymentMethod(state = latestState) {
|
function getSelectedPlusPaymentMethod(state = latestState) {
|
||||||
@@ -2886,6 +2891,58 @@ function getRequestedPlusAccountAccessStrategy(state = latestState) {
|
|||||||
return fallbackStrategy;
|
return fallbackStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizePlusStrategyTargetId(value = '') {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === 'sub2api') {
|
||||||
|
return 'sub2api';
|
||||||
|
}
|
||||||
|
if (normalized === 'codex2api') {
|
||||||
|
return 'codex2api';
|
||||||
|
}
|
||||||
|
return 'cpa';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlusAccountAccessStrategyContinuationLabel(strategy = '', targetId = '') {
|
||||||
|
const normalizedStrategy = normalizePlusAccountAccessStrategy(strategy);
|
||||||
|
if (normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
|
return '导入当前 ChatGPT 会话到 SUB2API';
|
||||||
|
}
|
||||||
|
if (normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return '导入当前 ChatGPT 会话到 CPA';
|
||||||
|
}
|
||||||
|
return 'OAuth 登录';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlusAccountAccessStrategyDescription(strategy = '', targetId = '') {
|
||||||
|
const normalizedStrategy = normalizePlusAccountAccessStrategy(strategy);
|
||||||
|
const normalizedTargetId = normalizePlusStrategyTargetId(targetId);
|
||||||
|
if (normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
|
return '复用当前 Plus 已登录会话,直接导入到 SUB2API';
|
||||||
|
}
|
||||||
|
if (normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return '复用当前 Plus 已登录会话,直接导入到 CPA';
|
||||||
|
}
|
||||||
|
if (normalizedTargetId === 'sub2api') {
|
||||||
|
return '通过 OAuth 回调创建 SUB2API 账号';
|
||||||
|
}
|
||||||
|
if (normalizedTargetId === 'codex2api') {
|
||||||
|
return '通过 OAuth 回调创建 Codex2API 账号';
|
||||||
|
}
|
||||||
|
return '通过 OAuth 回调创建 CPA 账号';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePlusManualContinuationActionLabelFromState(state = latestState) {
|
||||||
|
const activeFlowId = String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase();
|
||||||
|
const signupMethod = normalizeSignupMethod(state?.resolvedSignupMethod || state?.signupMethod || DEFAULT_SIGNUP_METHOD);
|
||||||
|
const plusModeEnabled = state?.plusModeEnabled === undefined ? true : Boolean(state?.plusModeEnabled);
|
||||||
|
const targetId = normalizePlusStrategyTargetId(state?.panelMode || state?.openaiIntegrationTargetId || 'cpa');
|
||||||
|
const strategy = normalizePlusAccountAccessStrategy(state?.plusAccountAccessStrategy || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY);
|
||||||
|
const effectiveStrategy = plusModeEnabled && activeFlowId === DEFAULT_ACTIVE_FLOW_ID && signupMethod === SIGNUP_METHOD_EMAIL
|
||||||
|
? strategy
|
||||||
|
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
|
return getPlusAccountAccessStrategyContinuationLabel(effectiveStrategy, targetId);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeGpcHelperPhoneModeValue(value = '') {
|
function normalizeGpcHelperPhoneModeValue(value = '') {
|
||||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
if (rootScope.GoPayUtils?.normalizeGpcHelperPhoneMode) {
|
if (rootScope.GoPayUtils?.normalizeGpcHelperPhoneMode) {
|
||||||
@@ -9000,7 +9057,41 @@ function updatePlusModeUI() {
|
|||||||
const sub2apiSessionStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION !== 'undefined'
|
const sub2apiSessionStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION !== 'undefined'
|
||||||
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||||
: 'sub2api_codex_session';
|
: 'sub2api_codex_session';
|
||||||
|
const cpaSessionStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION !== 'undefined'
|
||||||
|
? PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION
|
||||||
|
: 'cpa_codex_session';
|
||||||
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : paypalValue;
|
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : paypalValue;
|
||||||
|
const resolveStrategyTargetId = typeof normalizePlusStrategyTargetId === 'function'
|
||||||
|
? normalizePlusStrategyTargetId
|
||||||
|
: ((value = '') => {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === 'sub2api') {
|
||||||
|
return 'sub2api';
|
||||||
|
}
|
||||||
|
if (normalized === 'codex2api') {
|
||||||
|
return 'codex2api';
|
||||||
|
}
|
||||||
|
return 'cpa';
|
||||||
|
});
|
||||||
|
const describePlusAccountAccessStrategy = typeof getPlusAccountAccessStrategyDescription === 'function'
|
||||||
|
? getPlusAccountAccessStrategyDescription
|
||||||
|
: ((strategy = '', targetId = '') => {
|
||||||
|
const normalizedStrategy = normalizePlusAccountAccessStrategy(strategy);
|
||||||
|
const normalizedTargetId = resolveStrategyTargetId(targetId);
|
||||||
|
if (normalizedStrategy === sub2apiSessionStrategyValue) {
|
||||||
|
return '复用当前 Plus 已登录会话,直接导入到 SUB2API';
|
||||||
|
}
|
||||||
|
if (normalizedStrategy === cpaSessionStrategyValue) {
|
||||||
|
return '复用当前 Plus 已登录会话,直接导入到 CPA';
|
||||||
|
}
|
||||||
|
if (normalizedTargetId === 'sub2api') {
|
||||||
|
return '通过 OAuth 回调创建 SUB2API 账号';
|
||||||
|
}
|
||||||
|
if (normalizedTargetId === 'codex2api') {
|
||||||
|
return '通过 OAuth 回调创建 Codex2API 账号';
|
||||||
|
}
|
||||||
|
return '通过 OAuth 回调创建 CPA 账号';
|
||||||
|
});
|
||||||
const requestedPlusAccountAccessStrategy = typeof getRequestedPlusAccountAccessStrategy === 'function'
|
const requestedPlusAccountAccessStrategy = typeof getRequestedPlusAccountAccessStrategy === 'function'
|
||||||
? getRequestedPlusAccountAccessStrategy(latestState)
|
? getRequestedPlusAccountAccessStrategy(latestState)
|
||||||
: normalizePlusAccountAccessStrategy(latestState?.plusAccountAccessStrategy || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY);
|
: normalizePlusAccountAccessStrategy(latestState?.plusAccountAccessStrategy || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY);
|
||||||
@@ -9038,9 +9129,20 @@ function updatePlusModeUI() {
|
|||||||
: true;
|
: true;
|
||||||
const enabled = supportsPlusMode && rawEnabled;
|
const enabled = supportsPlusMode && rawEnabled;
|
||||||
const canEditPlusAccountAccessStrategy = Boolean(capabilityState?.canEditPlusAccountAccessStrategy);
|
const canEditPlusAccountAccessStrategy = Boolean(capabilityState?.canEditPlusAccountAccessStrategy);
|
||||||
|
const availablePlusAccountAccessStrategies = Array.isArray(capabilityState?.availablePlusAccountAccessStrategies)
|
||||||
|
&& capabilityState.availablePlusAccountAccessStrategies.length > 0
|
||||||
|
? capabilityState.availablePlusAccountAccessStrategies
|
||||||
|
: [oauthStrategyValue];
|
||||||
const effectivePlusAccountAccessStrategy = capabilityState?.effectivePlusAccountAccessStrategy
|
const effectivePlusAccountAccessStrategy = capabilityState?.effectivePlusAccountAccessStrategy
|
||||||
|| requestedPlusAccountAccessStrategy
|
|| requestedPlusAccountAccessStrategy
|
||||||
|| oauthStrategyValue;
|
|| oauthStrategyValue;
|
||||||
|
const effectiveTargetId = resolveStrategyTargetId(
|
||||||
|
capabilityState?.effectivePanelMode
|
||||||
|
|| capabilityState?.effectiveTargetId
|
||||||
|
|| capabilityState?.panelMode
|
||||||
|
|| (typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode)
|
||||||
|
|| 'cpa'
|
||||||
|
);
|
||||||
const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod;
|
const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod;
|
||||||
const gpcPhoneMode = normalizeGpcHelperPhoneModeValue(
|
const gpcPhoneMode = normalizeGpcHelperPhoneModeValue(
|
||||||
typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode
|
typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode
|
||||||
@@ -9103,6 +9205,13 @@ function updatePlusModeUI() {
|
|||||||
row.style.display = enabled ? '' : 'none';
|
row.style.display = enabled ? '' : 'none';
|
||||||
});
|
});
|
||||||
if (typeof selectPlusAccountAccessStrategy !== 'undefined' && selectPlusAccountAccessStrategy) {
|
if (typeof selectPlusAccountAccessStrategy !== 'undefined' && selectPlusAccountAccessStrategy) {
|
||||||
|
const availableStrategySet = new Set(availablePlusAccountAccessStrategies);
|
||||||
|
Array.from(selectPlusAccountAccessStrategy.options || []).forEach((option) => {
|
||||||
|
const optionValue = normalizePlusAccountAccessStrategy(option?.value || '');
|
||||||
|
const optionSupported = availableStrategySet.has(optionValue);
|
||||||
|
option.hidden = enabled ? !optionSupported : false;
|
||||||
|
option.disabled = enabled ? !optionSupported : false;
|
||||||
|
});
|
||||||
selectPlusAccountAccessStrategy.dataset.requestedValue = requestedPlusAccountAccessStrategy;
|
selectPlusAccountAccessStrategy.dataset.requestedValue = requestedPlusAccountAccessStrategy;
|
||||||
selectPlusAccountAccessStrategy.value = effectivePlusAccountAccessStrategy;
|
selectPlusAccountAccessStrategy.value = effectivePlusAccountAccessStrategy;
|
||||||
selectPlusAccountAccessStrategy.disabled = !enabled || !canEditPlusAccountAccessStrategy;
|
selectPlusAccountAccessStrategy.disabled = !enabled || !canEditPlusAccountAccessStrategy;
|
||||||
@@ -9121,6 +9230,24 @@ function updatePlusModeUI() {
|
|||||||
plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
|
plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) {
|
||||||
|
if (!enabled || !canEditPlusAccountAccessStrategy) {
|
||||||
|
plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
|
||||||
|
} else {
|
||||||
|
plusAccountAccessStrategyCaption.textContent = describePlusAccountAccessStrategy(
|
||||||
|
effectivePlusAccountAccessStrategy,
|
||||||
|
effectiveTargetId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) {
|
||||||
|
plusAccountAccessStrategyCaption.textContent = !enabled || !canEditPlusAccountAccessStrategy
|
||||||
|
? '当前来源仅支持 OAuth'
|
||||||
|
: describePlusAccountAccessStrategy(
|
||||||
|
effectivePlusAccountAccessStrategy,
|
||||||
|
effectiveTargetId
|
||||||
|
);
|
||||||
|
}
|
||||||
[
|
[
|
||||||
typeof rowPayPalAccount !== 'undefined' ? rowPayPalAccount : null,
|
typeof rowPayPalAccount !== 'undefined' ? rowPayPalAccount : null,
|
||||||
].forEach((row) => {
|
].forEach((row) => {
|
||||||
@@ -9649,6 +9776,60 @@ async function syncPlusManualConfirmationDialog() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 continuationActionLabel = resolvePlusManualContinuationActionLabelFromState(latestState);
|
||||||
|
if (method === 'gopay-otp') {
|
||||||
|
if (!sharedFormDialog?.open) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const result = await sharedFormDialog.open({
|
||||||
|
title: String(options.title || '').trim() || 'GPC OTP 验证',
|
||||||
|
message: String(options.message || '').trim() || '请在WhatsApp里面获取验证码(耐心等待三十秒左右)',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'otp',
|
||||||
|
label: 'OTP',
|
||||||
|
type: 'text',
|
||||||
|
placeholder: '请输入 OTP 验证码',
|
||||||
|
inputMode: 'numeric',
|
||||||
|
autocomplete: 'one-time-code',
|
||||||
|
required: true,
|
||||||
|
requiredMessage: '请输入 OTP 验证码。',
|
||||||
|
normalize: (value) => String(value || '').trim().replace(/[^\d]/g, ''),
|
||||||
|
validate: (value) => {
|
||||||
|
const normalized = String(value || '').trim().replace(/[^\d]/g, '');
|
||||||
|
if (!normalized) return '请输入 OTP 验证码。';
|
||||||
|
if (!/^\d{6}$/.test(normalized)) return 'OTP 必须是 6 位数字,请检查。';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
confirmLabel: '提交 OTP',
|
||||||
|
});
|
||||||
|
return result
|
||||||
|
? { action: 'confirm', otp: String(result.otp || '').trim().replace(/[^\d]/g, '') }
|
||||||
|
: { action: 'cancel' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = String(options.title || '').trim() || (method === gopayValue ? 'GoPay subscription confirmation' : 'Manual confirmation');
|
||||||
|
const message = String(options.message || '').trim()
|
||||||
|
|| (method === gopayValue
|
||||||
|
? 'Complete the GoPay subscription on the current page, then continue.'
|
||||||
|
: 'Finish the current manual action on the page, then continue.');
|
||||||
|
return openActionModal({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
actions: [
|
||||||
|
{ id: 'cancel', label: 'Cancel', variant: 'btn-ghost' },
|
||||||
|
{ id: 'confirm', label: 'Continue', variant: 'btn-primary' },
|
||||||
|
],
|
||||||
|
alert: method === gopayValue
|
||||||
|
? { text: `After confirmation, the Plus flow will continue with ${continuationActionLabel}.`, tone: 'info' }
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function clearRegistrationEmail(options = {}) {
|
async function clearRegistrationEmail(options = {}) {
|
||||||
const { silent = false } = options;
|
const { silent = false } = options;
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ test('background account history settings are normalized independently from hotm
|
|||||||
extractFunction('normalizeAccountRunHistoryHelperBaseUrl'),
|
extractFunction('normalizeAccountRunHistoryHelperBaseUrl'),
|
||||||
extractFunction('normalizeVerificationResendCount'),
|
extractFunction('normalizeVerificationResendCount'),
|
||||||
extractFunction('normalizePlusPaymentMethod'),
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
|
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||||
extractFunction('normalizeGpcHelperPhoneMode'),
|
extractFunction('normalizeGpcHelperPhoneMode'),
|
||||||
extractFunction('normalizePhoneSmsProvider'),
|
extractFunction('normalizePhoneSmsProvider'),
|
||||||
extractFunction('normalizePhoneSmsProviderOrder'),
|
extractFunction('normalizePhoneSmsProviderOrder'),
|
||||||
@@ -125,6 +126,10 @@ const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
|||||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
const DEFAULT_FIVE_SIM_PRODUCT = 'openai';
|
const DEFAULT_FIVE_SIM_PRODUCT = 'openai';
|
||||||
const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot';
|
const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot';
|
||||||
const FIVE_SIM_COUNTRY_ID = 'vietnam';
|
const FIVE_SIM_COUNTRY_ID = 'vietnam';
|
||||||
@@ -228,6 +233,7 @@ return {
|
|||||||
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'paypal'), 'paypal');
|
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'paypal'), 'paypal');
|
||||||
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'unknown'), 'paypal');
|
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'unknown'), 'paypal');
|
||||||
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'sub2api_codex_session'), 'sub2api_codex_session');
|
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'sub2api_codex_session'), 'sub2api_codex_session');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'cpa_codex_session'), 'cpa_codex_session');
|
||||||
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'unknown'), 'oauth');
|
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'unknown'), 'oauth');
|
||||||
assert.equal(
|
assert.equal(
|
||||||
api.normalizePersistentSettingValue('gopayHelperApiUrl', ' https://gpc.qlhazycoder.top/api/checkout/start '),
|
api.normalizePersistentSettingValue('gopayHelperApiUrl', ' https://gpc.qlhazycoder.top/api/checkout/start '),
|
||||||
|
|||||||
@@ -48,13 +48,14 @@ function extractFunction(name) {
|
|||||||
return source.slice(start, end);
|
return source.slice(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
test('background auth chain set does not include SUB2API session import node', () => {
|
test('background auth chain set does not include Plus session import nodes', () => {
|
||||||
const authChainStart = source.indexOf('const AUTH_CHAIN_NODE_IDS = new Set([');
|
const authChainStart = source.indexOf('const AUTH_CHAIN_NODE_IDS = new Set([');
|
||||||
const authChainEnd = source.indexOf(']);', authChainStart);
|
const authChainEnd = source.indexOf(']);', authChainStart);
|
||||||
const authChainBlock = source.slice(authChainStart, authChainEnd);
|
const authChainBlock = source.slice(authChainStart, authChainEnd);
|
||||||
|
|
||||||
assert.ok(authChainStart >= 0, 'expected AUTH_CHAIN_NODE_IDS block to exist');
|
assert.ok(authChainStart >= 0, 'expected AUTH_CHAIN_NODE_IDS block to exist');
|
||||||
assert.doesNotMatch(authChainBlock, /sub2api-session-import/);
|
assert.doesNotMatch(authChainBlock, /sub2api-session-import/);
|
||||||
|
assert.doesNotMatch(authChainBlock, /cpa-session-import/);
|
||||||
});
|
});
|
||||||
|
|
||||||
const NODE_EXECUTE_COMPAT_HELPERS = `
|
const NODE_EXECUTE_COMPAT_HELPERS = `
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
function createJsonResponse(payload, status = 200) {
|
||||||
|
return {
|
||||||
|
ok: status >= 200 && status < 300,
|
||||||
|
status,
|
||||||
|
json: async () => payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCpaApiModule() {
|
||||||
|
const source = fs.readFileSync('background/cpa-api.js', 'utf8');
|
||||||
|
return new Function('self', `${source}; return self.MultiPageBackgroundCpaApi;`)({});
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeBase64UrlJson(value) {
|
||||||
|
return Buffer.from(JSON.stringify(value)).toString('base64')
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=+$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createJwtToken(payload = {}) {
|
||||||
|
return [
|
||||||
|
encodeBase64UrlJson({ alg: 'HS256', typ: 'JWT' }),
|
||||||
|
encodeBase64UrlJson(payload),
|
||||||
|
'signature',
|
||||||
|
].join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
test('cpa api imports current ChatGPT session through management auth-files endpoint', async () => {
|
||||||
|
const apiModule = loadCpaApiModule();
|
||||||
|
const logs = [];
|
||||||
|
const fetchCalls = [];
|
||||||
|
const expiresAt = '2026-05-20T12:34:56.000Z';
|
||||||
|
const accessToken = createJwtToken({
|
||||||
|
exp: Math.floor(Date.parse(expiresAt) / 1000),
|
||||||
|
email: 'jwt@example.com',
|
||||||
|
'https://api.openai.com/auth': {
|
||||||
|
chatgpt_account_id: 'acct_123',
|
||||||
|
chatgpt_plan_type: 'plus',
|
||||||
|
chatgpt_user_id: 'user_123',
|
||||||
|
},
|
||||||
|
'https://api.openai.com/profile': {
|
||||||
|
email: 'profile@example.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = apiModule.createCpaApi({
|
||||||
|
addLog: async (message, level = 'info', options = {}) => {
|
||||||
|
logs.push({ message, level, step: options.step, stepKey: options.stepKey });
|
||||||
|
},
|
||||||
|
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 || 'POST',
|
||||||
|
headers: options.headers || {},
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (parsed.pathname === '/v0/management/auth-files') {
|
||||||
|
return createJsonResponse({});
|
||||||
|
}
|
||||||
|
|
||||||
|
return createJsonResponse({ message: `unexpected path ${parsed.pathname}` }, 404);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await api.importCurrentChatGptSession({
|
||||||
|
vpsUrl: 'https://cpa.example.com/management.html#/oauth',
|
||||||
|
vpsPassword: 'management-key',
|
||||||
|
session: {
|
||||||
|
accessToken,
|
||||||
|
expires: expiresAt,
|
||||||
|
user: {
|
||||||
|
email: 'flow@example.com',
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
id: 'acct_123',
|
||||||
|
planType: 'plus',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accessToken,
|
||||||
|
email: 'registration@example.com',
|
||||||
|
accountIdentifierType: 'email',
|
||||||
|
accountIdentifier: 'identifier@example.com',
|
||||||
|
}, {
|
||||||
|
logLabel: '步骤 10',
|
||||||
|
logOptions: { step: 10, stepKey: 'cpa-session-import' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const importCall = fetchCalls.find((call) => call.path === '/v0/management/auth-files');
|
||||||
|
assert.ok(importCall, 'expected CPA auth-files import call');
|
||||||
|
assert.equal(importCall.method, 'POST');
|
||||||
|
assert.equal(importCall.headers.Authorization, 'Bearer management-key');
|
||||||
|
assert.equal(importCall.headers['X-Management-Key'], 'management-key');
|
||||||
|
assert.equal(
|
||||||
|
decodeURIComponent(new URLSearchParams(importCall.search).get('name')),
|
||||||
|
'codex-flow@example.com-plus.json'
|
||||||
|
);
|
||||||
|
assert.equal(importCall.body.type, 'codex');
|
||||||
|
assert.equal(importCall.body.account_id, 'acct_123');
|
||||||
|
assert.equal(importCall.body.chatgpt_account_id, 'acct_123');
|
||||||
|
assert.equal(importCall.body.email, 'flow@example.com');
|
||||||
|
assert.equal(importCall.body.plan_type, 'plus');
|
||||||
|
assert.equal(importCall.body.chatgpt_plan_type, 'plus');
|
||||||
|
assert.equal(importCall.body.access_token, accessToken);
|
||||||
|
assert.equal(importCall.body.id_token_synthetic, true);
|
||||||
|
assert.match(String(importCall.body.id_token || ''), /\.synthetic$/);
|
||||||
|
assert.equal(importCall.body.expired, expiresAt);
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(importCall.body, 'refresh_token'), false);
|
||||||
|
assert.equal(result.verifiedStatus, 'CPA 会话导入完成:flow@example.com');
|
||||||
|
assert.equal(
|
||||||
|
logs.some((entry) => entry.level === 'warn' && /refresh_token/.test(entry.message)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cpa api falls back to registration email when session has no readable email', () => {
|
||||||
|
const apiModule = loadCpaApiModule();
|
||||||
|
const accessToken = createJwtToken({
|
||||||
|
exp: Math.floor(Date.parse('2026-05-20T12:34:56.000Z') / 1000),
|
||||||
|
'https://api.openai.com/auth': {
|
||||||
|
chatgpt_account_id: 'acct_456',
|
||||||
|
chatgpt_plan_type: 'plus',
|
||||||
|
chatgpt_user_id: 'user_456',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = apiModule.createCpaApi();
|
||||||
|
const result = api.buildCpaSessionAuthJson({
|
||||||
|
session: {
|
||||||
|
accessToken,
|
||||||
|
user: {
|
||||||
|
name: 'Flow User',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accessToken,
|
||||||
|
email: 'registration@example.com',
|
||||||
|
accountIdentifierType: 'email',
|
||||||
|
accountIdentifier: 'identifier@example.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.email, 'registration@example.com');
|
||||||
|
assert.equal(result.authJson.email, 'registration@example.com');
|
||||||
|
assert.equal(result.fileName, 'codex-registration@example.com-plus.json');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cpa api preserves provided id_token and refresh_token when available', () => {
|
||||||
|
const apiModule = loadCpaApiModule();
|
||||||
|
const accessToken = createJwtToken({
|
||||||
|
exp: Math.floor(Date.parse('2026-05-20T12:34:56.000Z') / 1000),
|
||||||
|
'https://api.openai.com/auth': {
|
||||||
|
chatgpt_account_id: 'acct_789',
|
||||||
|
chatgpt_plan_type: 'plus',
|
||||||
|
chatgpt_user_id: 'user_789',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = apiModule.createCpaApi();
|
||||||
|
const result = api.buildCpaSessionAuthJson({
|
||||||
|
session: {
|
||||||
|
accessToken,
|
||||||
|
user: {
|
||||||
|
email: 'session@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accessToken,
|
||||||
|
id_token: 'provided-id-token',
|
||||||
|
refresh_token: 'refresh-token-1',
|
||||||
|
session_token: 'session-token-1',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.authJson.id_token, 'provided-id-token');
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(result.authJson, 'id_token_synthetic'), false);
|
||||||
|
assert.equal(result.authJson.refresh_token, 'refresh-token-1');
|
||||||
|
assert.equal(result.authJson.session_token, 'session-token-1');
|
||||||
|
assert.equal(result.hasRefreshToken, true);
|
||||||
|
});
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const test = require('node:test');
|
||||||
|
|
||||||
|
function loadCpaSessionImportModule() {
|
||||||
|
const source = fs.readFileSync('background/steps/cpa-session-import.js', 'utf8');
|
||||||
|
return new Function('self', `${source}; return self.MultiPageBackgroundCpaSessionImport;`)({});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('CPA session import step reads current ChatGPT session and completes node', async () => {
|
||||||
|
const moduleApi = loadCpaSessionImportModule();
|
||||||
|
const completed = [];
|
||||||
|
const logs = [];
|
||||||
|
const ensureCalls = [];
|
||||||
|
const sentMessages = [];
|
||||||
|
const importedPayloads = [];
|
||||||
|
|
||||||
|
const executor = moduleApi.createCpaSessionImportExecutor({
|
||||||
|
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 });
|
||||||
|
},
|
||||||
|
createCpaApi: () => ({
|
||||||
|
importCurrentChatGptSession: async (state, options) => {
|
||||||
|
importedPayloads.push({ state, options });
|
||||||
|
return {
|
||||||
|
verifiedStatus: 'CPA 会话导入完成:flow@example.com',
|
||||||
|
cpaImportedFileName: 'codex-flow@example.com-plus.json',
|
||||||
|
cpaImportedEmail: 'flow@example.com',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped: async (source, tabId, options = {}) => {
|
||||||
|
ensureCalls.push({ source, tabId, options });
|
||||||
|
},
|
||||||
|
getTabId: async () => null,
|
||||||
|
isTabAlive: async () => false,
|
||||||
|
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 () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeCpaSessionImport({
|
||||||
|
nodeId: 'cpa-session-import',
|
||||||
|
visibleStep: 10,
|
||||||
|
plusCheckoutTabId: 91,
|
||||||
|
vpsUrl: 'https://cpa.example.com/management.html#/oauth',
|
||||||
|
vpsPassword: 'management-key',
|
||||||
|
});
|
||||||
|
|
||||||
|
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: 'cpa-session-import',
|
||||||
|
payload: {
|
||||||
|
verifiedStatus: 'CPA 会话导入完成:flow@example.com',
|
||||||
|
cpaImportedFileName: 'codex-flow@example.com-plus.json',
|
||||||
|
cpaImportedEmail: 'flow@example.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
logs.some((entry) => entry.stepKey === 'cpa-session-import' && /ChatGPT/.test(entry.message)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CPA session import step falls back to an active ChatGPT tab when no checkout tab is tracked', async () => {
|
||||||
|
const moduleApi = loadCpaSessionImportModule();
|
||||||
|
const completed = [];
|
||||||
|
const importedPayloads = [];
|
||||||
|
const queryCalls = [];
|
||||||
|
const registerCalls = [];
|
||||||
|
|
||||||
|
const sessionTab = {
|
||||||
|
id: 77,
|
||||||
|
url: 'https://chatgpt.com/?model=gpt-4o',
|
||||||
|
active: true,
|
||||||
|
currentWindow: true,
|
||||||
|
lastAccessed: 1234,
|
||||||
|
};
|
||||||
|
|
||||||
|
const executor = moduleApi.createCpaSessionImportExecutor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
get: async (tabId) => ({
|
||||||
|
...sessionTab,
|
||||||
|
id: tabId,
|
||||||
|
}),
|
||||||
|
query: async (queryInfo = {}) => {
|
||||||
|
queryCalls.push(queryInfo);
|
||||||
|
if (queryInfo.active && queryInfo.currentWindow) {
|
||||||
|
return [sessionTab];
|
||||||
|
}
|
||||||
|
return [sessionTab];
|
||||||
|
},
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async (nodeId, payload) => {
|
||||||
|
completed.push({ nodeId, payload });
|
||||||
|
},
|
||||||
|
createCpaApi: () => ({
|
||||||
|
importCurrentChatGptSession: async (state) => {
|
||||||
|
importedPayloads.push(state);
|
||||||
|
return {
|
||||||
|
verifiedStatus: 'CPA 会话导入完成:fallback@example.com',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||||
|
getTabId: async () => null,
|
||||||
|
isTabAlive: async () => false,
|
||||||
|
registerTab: async (source, tabId) => {
|
||||||
|
registerCalls.push({ source, tabId });
|
||||||
|
},
|
||||||
|
sendTabMessageUntilStopped: async () => ({
|
||||||
|
session: {
|
||||||
|
accessToken: 'session-access-token',
|
||||||
|
user: {
|
||||||
|
email: 'fallback@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accessToken: 'session-access-token',
|
||||||
|
}),
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
waitForTabCompleteUntilStopped: async () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeCpaSessionImport({
|
||||||
|
nodeId: 'cpa-session-import',
|
||||||
|
visibleStep: 10,
|
||||||
|
vpsUrl: 'https://cpa.example.com/management.html#/oauth',
|
||||||
|
vpsPassword: 'management-key',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(queryCalls, [
|
||||||
|
{ active: true, currentWindow: true },
|
||||||
|
{},
|
||||||
|
]);
|
||||||
|
assert.deepStrictEqual(registerCalls, [{
|
||||||
|
source: 'plus-checkout',
|
||||||
|
tabId: 77,
|
||||||
|
}]);
|
||||||
|
assert.equal(importedPayloads.length, 1);
|
||||||
|
assert.equal(importedPayloads[0].session.user.email, 'fallback@example.com');
|
||||||
|
assert.equal(completed.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CPA session import step reports missing readable session tab when tracked tabs are unusable', async () => {
|
||||||
|
const moduleApi = loadCpaSessionImportModule();
|
||||||
|
let sendCalled = false;
|
||||||
|
|
||||||
|
const executor = moduleApi.createCpaSessionImportExecutor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
get: async (tabId) => ({
|
||||||
|
id: tabId,
|
||||||
|
url: 'https://example.com/not-chatgpt',
|
||||||
|
}),
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async () => {},
|
||||||
|
createCpaApi: () => ({
|
||||||
|
importCurrentChatGptSession: async () => ({}),
|
||||||
|
}),
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||||
|
getTabId: async () => 91,
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
sendTabMessageUntilStopped: async () => {
|
||||||
|
sendCalled = true;
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
waitForTabCompleteUntilStopped: async () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => executor.executeCpaSessionImport({
|
||||||
|
nodeId: 'cpa-session-import',
|
||||||
|
visibleStep: 10,
|
||||||
|
vpsUrl: 'https://cpa.example.com/management.html#/oauth',
|
||||||
|
vpsPassword: 'management-key',
|
||||||
|
}),
|
||||||
|
/未找到可读取 ChatGPT 会话的标签页/
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(sendCalled, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('background wires CPA session import executor into the workflow runtime', () => {
|
||||||
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
|
assert.match(source, /background\/cpa-api\.js/);
|
||||||
|
assert.match(source, /background\/steps\/cpa-session-import\.js/);
|
||||||
|
assert.match(source, /'cpa-session-import': \(state\) => cpaSessionImportExecutor\.executeCpaSessionImport\(state\)/);
|
||||||
|
assert.match(source, /'cpa-session-import',[\s\S]*'oauth-login'/);
|
||||||
|
});
|
||||||
@@ -59,14 +59,25 @@ const workflowEngine = null;
|
|||||||
const self = {
|
const self = {
|
||||||
MultiPageStepDefinitions: {
|
MultiPageStepDefinitions: {
|
||||||
getSteps(options = {}) {
|
getSteps(options = {}) {
|
||||||
return String(options.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
|
const strategy = String(options.plusAccountAccessStrategy || '').trim();
|
||||||
? [
|
if (strategy === 'sub2api_codex_session') {
|
||||||
|
return [
|
||||||
{ id: 1, key: 'open-chatgpt' },
|
{ id: 1, key: 'open-chatgpt' },
|
||||||
{ id: 2, key: 'plus-checkout-create' },
|
{ id: 2, key: 'plus-checkout-create' },
|
||||||
{ id: 3, key: 'plus-checkout-billing' },
|
{ id: 3, key: 'plus-checkout-billing' },
|
||||||
{ id: 4, key: 'paypal-approve' },
|
{ id: 4, key: 'paypal-approve' },
|
||||||
{ id: 5, key: 'plus-checkout-return' },
|
{ id: 5, key: 'plus-checkout-return' },
|
||||||
{ id: 6, key: 'sub2api-session-import' },
|
{ id: 6, key: 'sub2api-session-import' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return strategy === 'cpa_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: 'cpa-session-import' },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ id: 1, key: 'open-chatgpt' },
|
{ id: 1, key: 'open-chatgpt' },
|
||||||
@@ -96,17 +107,26 @@ function normalizePlusPaymentMethod(value = '') {
|
|||||||
return normalized === 'gopay' || normalized === 'gpc-helper' ? normalized : 'paypal';
|
return normalized === 'gopay' || normalized === 'gpc-helper' ? normalized : 'paypal';
|
||||||
}
|
}
|
||||||
function normalizePlusAccountAccessStrategy(value = '') {
|
function normalizePlusAccountAccessStrategy(value = '') {
|
||||||
return String(value || '').trim().toLowerCase() === 'sub2api_codex_session'
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
? 'sub2api_codex_session'
|
if (normalized === 'sub2api_codex_session') {
|
||||||
: 'oauth';
|
return 'sub2api_codex_session';
|
||||||
|
}
|
||||||
|
if (normalized === 'cpa_codex_session') {
|
||||||
|
return 'cpa_codex_session';
|
||||||
|
}
|
||||||
|
return 'oauth';
|
||||||
}
|
}
|
||||||
function isPlusModeState(state = {}) {
|
function isPlusModeState(state = {}) {
|
||||||
return Boolean(state?.plusModeEnabled);
|
return Boolean(state?.plusModeEnabled);
|
||||||
}
|
}
|
||||||
function resolveCurrentFlowCapabilities(state = {}, options = {}) {
|
function resolveCurrentFlowCapabilities(state = {}, options = {}) {
|
||||||
const effectiveStrategy = String(options.panelMode || '').trim().toLowerCase() === 'sub2api'
|
const normalizedPanelMode = String(options.panelMode || '').trim().toLowerCase();
|
||||||
? normalizePlusAccountAccessStrategy(state.plusAccountAccessStrategy)
|
const requestedStrategy = normalizePlusAccountAccessStrategy(state.plusAccountAccessStrategy);
|
||||||
: 'oauth';
|
const effectiveStrategy = normalizedPanelMode === 'sub2api'
|
||||||
|
? (requestedStrategy === 'sub2api_codex_session' ? 'sub2api_codex_session' : 'oauth')
|
||||||
|
: (normalizedPanelMode === 'cpa'
|
||||||
|
? (requestedStrategy === 'cpa_codex_session' ? 'cpa_codex_session' : 'oauth')
|
||||||
|
: 'oauth');
|
||||||
return {
|
return {
|
||||||
effectivePanelMode: options.panelMode,
|
effectivePanelMode: options.panelMode,
|
||||||
effectivePlusAccountAccessStrategy: effectiveStrategy,
|
effectivePlusAccountAccessStrategy: effectiveStrategy,
|
||||||
@@ -159,6 +179,32 @@ test('background step resolution keeps SUB2API session tail only when the effect
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('background step resolution keeps CPA session tail when the effective Plus target supports it', () => {
|
||||||
|
const api = createHarness();
|
||||||
|
const state = {
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
flowId: 'openai',
|
||||||
|
panelMode: 'cpa',
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||||
|
signupMethod: 'email',
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolvedState = api.buildResolvedStepDefinitionState(state);
|
||||||
|
const nodeIds = api.getNodeIdsForState(state);
|
||||||
|
|
||||||
|
assert.equal(resolvedState.plusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
|
assert.deepStrictEqual(nodeIds, [
|
||||||
|
'open-chatgpt',
|
||||||
|
'plus-checkout-create',
|
||||||
|
'plus-checkout-billing',
|
||||||
|
'paypal-approve',
|
||||||
|
'plus-checkout-return',
|
||||||
|
'cpa-session-import',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('background step resolution falls back to OAuth tail when the requested session strategy is not effective for the current panel mode', () => {
|
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 api = createHarness();
|
||||||
const state = {
|
const state = {
|
||||||
|
|||||||
@@ -173,3 +173,33 @@ test('message router appends success record when SUB2API session import is the f
|
|||||||
assert.equal(appendCalls.length, 1);
|
assert.equal(appendCalls.length, 1);
|
||||||
assert.equal(appendCalls[0][0], 'success');
|
assert.equal(appendCalls[0][0], 'success');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('message router appends success record when CPA session import is the final Plus node', async () => {
|
||||||
|
const { appendCalls, router } = createRouterWithFinalNode({
|
||||||
|
finalNodeId: 'cpa-session-import',
|
||||||
|
nodeIds: [
|
||||||
|
'open-chatgpt',
|
||||||
|
'plus-checkout-create',
|
||||||
|
'plus-checkout-billing',
|
||||||
|
'paypal-approve',
|
||||||
|
'plus-checkout-return',
|
||||||
|
'cpa-session-import',
|
||||||
|
],
|
||||||
|
nodeStepMap: {
|
||||||
|
'plus-checkout-create': 6,
|
||||||
|
'plus-checkout-billing': 7,
|
||||||
|
'paypal-approve': 8,
|
||||||
|
'plus-checkout-return': 9,
|
||||||
|
'cpa-session-import': 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await router.handleMessage({
|
||||||
|
type: 'NODE_COMPLETE',
|
||||||
|
nodeId: 'cpa-session-import',
|
||||||
|
payload: { nodeId: 'cpa-session-import' },
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
assert.equal(appendCalls.length, 1);
|
||||||
|
assert.equal(appendCalls[0][0], 'success');
|
||||||
|
});
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
|||||||
const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState'];
|
const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState'];
|
||||||
const LEGACY_AUTO_STEP_DELAY_KEYS = [];
|
const LEGACY_AUTO_STEP_DELAY_KEYS = [];
|
||||||
const LEGACY_VERIFICATION_RESEND_COUNT_KEYS = [];
|
const LEGACY_VERIFICATION_RESEND_COUNT_KEYS = [];
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
function isPlainObjectValue(value) {
|
function isPlainObjectValue(value) {
|
||||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||||
}
|
}
|
||||||
@@ -125,6 +129,7 @@ function normalizePlusPaymentMethod(value = '') {
|
|||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
return normalized === 'gopay' || normalized === 'gpc-helper' ? normalized : 'paypal';
|
return normalized === 'gopay' || normalized === 'gpc-helper' ? normalized : 'paypal';
|
||||||
}
|
}
|
||||||
|
${extractFunction('normalizePlusAccountAccessStrategy')}
|
||||||
function normalizeSub2ApiGroupNames(value) {
|
function normalizeSub2ApiGroupNames(value) {
|
||||||
return Array.isArray(value) ? value.map((entry) => String(entry || '').trim()).filter(Boolean) : [];
|
return Array.isArray(value) ? value.map((entry) => String(entry || '').trim()).filter(Boolean) : [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -601,5 +601,5 @@ test('background wires sub2api session import executor into the workflow runtime
|
|||||||
const source = fs.readFileSync('background.js', 'utf8');
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
assert.match(source, /background\/steps\/sub2api-session-import\.js/);
|
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': \(state\) => sub2ApiSessionImportExecutor\.executeSub2ApiSessionImport\(state\)/);
|
||||||
assert.match(source, /'sub2api-session-import',\s*\n\s*'oauth-login'/);
|
assert.match(source, /'sub2api-session-import',[\s\S]*'oauth-login'/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ test('flow capability registry exposes editable Plus account access strategies f
|
|||||||
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'sub2api_codex_session');
|
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('flow capability registry preserves requested Plus account strategy while forcing OAuth on unsupported targets', () => {
|
test('flow capability registry exposes editable Plus account access strategies for CPA', () => {
|
||||||
const api = loadApi();
|
const api = loadApi();
|
||||||
const registry = api.createFlowCapabilityRegistry();
|
const registry = api.createFlowCapabilityRegistry();
|
||||||
|
|
||||||
@@ -226,12 +226,36 @@ test('flow capability registry preserves requested Plus account strategy while f
|
|||||||
openaiIntegrationTargetId: 'cpa',
|
openaiIntegrationTargetId: 'cpa',
|
||||||
signupMethod: 'email',
|
signupMethod: 'email',
|
||||||
plusModeEnabled: true,
|
plusModeEnabled: true,
|
||||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
capabilityState.availablePlusAccountAccessStrategies,
|
||||||
|
['oauth', 'cpa_codex_session']
|
||||||
|
);
|
||||||
|
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
|
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
|
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, true);
|
||||||
|
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flow capability registry forces OAuth when the current target only supports OAuth', () => {
|
||||||
|
const api = loadApi();
|
||||||
|
const registry = api.createFlowCapabilityRegistry();
|
||||||
|
|
||||||
|
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||||
|
state: {
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
openaiIntegrationTargetId: 'codex2api',
|
||||||
|
signupMethod: 'email',
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(capabilityState.availablePlusAccountAccessStrategies, ['oauth']);
|
assert.deepEqual(capabilityState.availablePlusAccountAccessStrategies, ['oauth']);
|
||||||
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'sub2api_codex_session');
|
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'oauth');
|
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'oauth');
|
||||||
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, false);
|
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, false);
|
||||||
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'oauth');
|
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'oauth');
|
||||||
|
|||||||
@@ -99,3 +99,15 @@ test('settings schema can project canonical state into a read view without legac
|
|||||||
assert.equal(view.settingsSchemaVersion, 4);
|
assert.equal(view.settingsSchemaVersion, 4);
|
||||||
assert.equal(view.settingsState.activeFlowId, 'kiro');
|
assert.equal(view.settingsState.activeFlowId, 'kiro');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('settings schema preserves CPA session strategy in canonical state and read view', () => {
|
||||||
|
const { settingsSchema } = loadApis();
|
||||||
|
const schema = settingsSchema.createSettingsSchema();
|
||||||
|
const normalized = schema.normalizeSettingsState({
|
||||||
|
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||||
|
});
|
||||||
|
const view = schema.buildSettingsView(normalized);
|
||||||
|
|
||||||
|
assert.equal(normalized.flows.openai.plus.plusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
|
assert.equal(view.plusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
|
});
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ test('GoPay manual confirm executor publishes pending manual confirmation state
|
|||||||
assert.equal(stateUpdates[0].plusManualConfirmationMethod, 'gopay');
|
assert.equal(stateUpdates[0].plusManualConfirmationMethod, 'gopay');
|
||||||
assert.equal(stateUpdates[0].plusManualConfirmationStep, 7);
|
assert.equal(stateUpdates[0].plusManualConfirmationStep, 7);
|
||||||
assert.match(String(stateUpdates[0].plusManualConfirmationRequestId || ''), /^gopay-/);
|
assert.match(String(stateUpdates[0].plusManualConfirmationRequestId || ''), /^gopay-/);
|
||||||
assert.match(stateUpdates[0].plusManualConfirmationMessage, /OAuth 登录/);
|
assert.match(stateUpdates[0].plusManualConfirmationMessage, /OAuth/);
|
||||||
assert.equal(broadcasts.length, 1);
|
assert.equal(broadcasts.length, 1);
|
||||||
assert.equal(broadcasts[0].plusManualConfirmationPending, true);
|
assert.equal(broadcasts[0].plusManualConfirmationPending, true);
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
@@ -59,7 +59,7 @@ test('GoPay manual confirm executor publishes pending manual confirmation state
|
|||||||
);
|
);
|
||||||
assert.match(
|
assert.match(
|
||||||
events.find((event) => event.type === 'log')?.message || '',
|
events.find((event) => event.type === 'log')?.message || '',
|
||||||
/确认后继续OAuth 登录/
|
/OAuth/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,9 +100,53 @@ test('GoPay manual confirm executor switches continuation copy to SUB2API sessio
|
|||||||
|
|
||||||
assert.equal(stateUpdates.length, 1);
|
assert.equal(stateUpdates.length, 1);
|
||||||
assert.equal(stateUpdates[0].plusManualConfirmationStep, 9);
|
assert.equal(stateUpdates[0].plusManualConfirmationStep, 9);
|
||||||
assert.match(stateUpdates[0].plusManualConfirmationMessage, /导入当前 ChatGPT 会话到 SUB2API/);
|
assert.match(stateUpdates[0].plusManualConfirmationMessage, /SUB2API/);
|
||||||
assert.match(
|
assert.match(
|
||||||
events.find((event) => event.type === 'log')?.message || '',
|
events.find((event) => event.type === 'log')?.message || '',
|
||||||
/确认后继续导入当前 ChatGPT 会话到 SUB2API/
|
/SUB2API/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GoPay manual confirm executor switches continuation copy to CPA session import when the effective tail is CPA 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',
|
||||||
|
'cpa-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, /CPA/);
|
||||||
|
assert.match(
|
||||||
|
events.find((event) => event.type === 'log')?.message || '',
|
||||||
|
/CPA/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
|||||||
const DEFAULT_PLUS_PAYMENT_METHOD = 'paypal';
|
const DEFAULT_PLUS_PAYMENT_METHOD = 'paypal';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = 'oauth';
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = 'oauth';
|
||||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
@@ -165,6 +166,33 @@ test('sidepanel enables SUB2API session strategy selection when the current Plus
|
|||||||
assert.match(api.plusAccountAccessStrategyCaption.textContent, /SUB2API/);
|
assert.match(api.plusAccountAccessStrategyCaption.textContent, /SUB2API/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sidepanel enables CPA session strategy selection when the current Plus target supports it', () => {
|
||||||
|
const api = buildHarness(
|
||||||
|
`{
|
||||||
|
canShowPlusSettings: true,
|
||||||
|
runtimeLocks: { plusModeEnabled: true },
|
||||||
|
canEditPlusAccountAccessStrategy: true,
|
||||||
|
availablePlusAccountAccessStrategies: ['oauth', 'cpa_codex_session'],
|
||||||
|
effectivePanelMode: 'cpa',
|
||||||
|
effectivePlusAccountAccessStrategy: 'cpa_codex_session',
|
||||||
|
}`,
|
||||||
|
`{
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
panelMode: 'cpa',
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
|
||||||
|
api.updatePlusModeUI();
|
||||||
|
|
||||||
|
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
|
||||||
|
assert.equal(api.selectPlusAccountAccessStrategy.disabled, false);
|
||||||
|
assert.equal(api.selectPlusAccountAccessStrategy.value, 'cpa_codex_session');
|
||||||
|
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'cpa_codex_session');
|
||||||
|
assert.match(api.plusAccountAccessStrategyCaption.textContent, /CPA/);
|
||||||
|
});
|
||||||
|
|
||||||
test('sidepanel rebuilds step definitions and workflow nodes with the effective Plus account strategy', () => {
|
test('sidepanel rebuilds step definitions and workflow nodes with the effective Plus account strategy', () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('normalizeSignupMethod'),
|
extractFunction('normalizeSignupMethod'),
|
||||||
@@ -195,6 +223,7 @@ let currentPlusModeEnabled = false;
|
|||||||
let currentPlusPaymentMethod = 'paypal';
|
let currentPlusPaymentMethod = 'paypal';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
let currentPlusAccountAccessStrategy = 'oauth';
|
let currentPlusAccountAccessStrategy = 'oauth';
|
||||||
let currentSignupMethod = 'email';
|
let currentSignupMethod = 'email';
|
||||||
let currentPhoneSignupReloginAfterBindEmailEnabled = false;
|
let currentPhoneSignupReloginAfterBindEmailEnabled = false;
|
||||||
@@ -272,12 +301,20 @@ return {
|
|||||||
|
|
||||||
test('background declares Plus account access strategy constants before precomputing session-tail step definitions', () => {
|
test('background declares Plus account access strategy constants before precomputing session-tail step definitions', () => {
|
||||||
const strategyConstantIndex = backgroundSource.indexOf("const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';");
|
const strategyConstantIndex = backgroundSource.indexOf("const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';");
|
||||||
|
const cpaStrategyConstantIndex = backgroundSource.indexOf("const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';");
|
||||||
const precomputedSessionStepsIndex = backgroundSource.indexOf('const PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS =');
|
const precomputedSessionStepsIndex = backgroundSource.indexOf('const PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS =');
|
||||||
|
const precomputedCpaSessionStepsIndex = backgroundSource.indexOf('const PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS =');
|
||||||
|
|
||||||
assert.ok(strategyConstantIndex >= 0, 'expected Plus account access strategy constant declaration');
|
assert.ok(strategyConstantIndex >= 0, 'expected Plus account access strategy constant declaration');
|
||||||
|
assert.ok(cpaStrategyConstantIndex >= 0, 'expected CPA Plus account access strategy constant declaration');
|
||||||
assert.ok(precomputedSessionStepsIndex >= 0, 'expected precomputed SUB2API session step definitions');
|
assert.ok(precomputedSessionStepsIndex >= 0, 'expected precomputed SUB2API session step definitions');
|
||||||
|
assert.ok(precomputedCpaSessionStepsIndex >= 0, 'expected precomputed CPA session step definitions');
|
||||||
assert.ok(
|
assert.ok(
|
||||||
strategyConstantIndex < precomputedSessionStepsIndex,
|
strategyConstantIndex < precomputedSessionStepsIndex,
|
||||||
'strategy constant must be declared before background precomputes session-tail step definitions'
|
'strategy constant must be declared before background precomputes session-tail step definitions'
|
||||||
);
|
);
|
||||||
|
assert.ok(
|
||||||
|
cpaStrategyConstantIndex < precomputedCpaSessionStepsIndex,
|
||||||
|
'CPA strategy constant must be declared before background precomputes CPA session-tail step definitions'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ let currentPlusModeEnabled = false;
|
|||||||
let currentPlusPaymentMethod = 'paypal';
|
let currentPlusPaymentMethod = 'paypal';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
let currentPlusAccountAccessStrategy = DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY;
|
let currentPlusAccountAccessStrategy = DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY;
|
||||||
let currentSignupMethod = 'email';
|
let currentSignupMethod = 'email';
|
||||||
@@ -170,6 +171,7 @@ const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
|||||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
const rowPayPalAccount = { style: { display: '' } };
|
const rowPayPalAccount = { style: { display: '' } };
|
||||||
${bundle}
|
${bundle}
|
||||||
@@ -229,6 +231,7 @@ const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
|||||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
${bundle}
|
${bundle}
|
||||||
return {
|
return {
|
||||||
@@ -272,6 +275,7 @@ let currentPlusModeEnabled = false;
|
|||||||
let currentPlusPaymentMethod = 'paypal';
|
let currentPlusPaymentMethod = 'paypal';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
let currentPlusAccountAccessStrategy = DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY;
|
let currentPlusAccountAccessStrategy = DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY;
|
||||||
let currentSignupMethod = 'email';
|
let currentSignupMethod = 'email';
|
||||||
@@ -330,6 +334,7 @@ const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
|||||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
const plusPaymentMethodCaption = { textContent: '' };
|
const plusPaymentMethodCaption = { textContent: '' };
|
||||||
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
||||||
@@ -432,6 +437,7 @@ const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
|||||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
const plusPaymentMethodCaption = { textContent: '' };
|
const plusPaymentMethodCaption = { textContent: '' };
|
||||||
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
||||||
@@ -495,6 +501,7 @@ const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
|||||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
const plusPaymentMethodCaption = { textContent: '' };
|
const plusPaymentMethodCaption = { textContent: '' };
|
||||||
const rowPayPalAccount = { style: { display: '' } };
|
const rowPayPalAccount = { style: { display: '' } };
|
||||||
@@ -577,6 +584,7 @@ const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
|||||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
const plusPaymentMethodCaption = { textContent: '' };
|
const plusPaymentMethodCaption = { textContent: '' };
|
||||||
const rowPayPalAccount = { style: { display: '' } };
|
const rowPayPalAccount = { style: { display: '' } };
|
||||||
@@ -745,6 +753,7 @@ test('sidepanel resolves pending GoPay manual confirmation from DATA_UPDATED sta
|
|||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
const events = [];
|
const events = [];
|
||||||
let latestState = {
|
let latestState = {
|
||||||
|
activeFlowId: 'openai',
|
||||||
plusManualConfirmationPending: true,
|
plusManualConfirmationPending: true,
|
||||||
plusManualConfirmationRequestId: 'gopay-request-1',
|
plusManualConfirmationRequestId: 'gopay-request-1',
|
||||||
plusManualConfirmationStep: 7,
|
plusManualConfirmationStep: 7,
|
||||||
@@ -799,6 +808,11 @@ return { events, syncPlusManualConfirmationDialog };
|
|||||||
|
|
||||||
test('sidepanel resolves pending GPC OTP with typed code', async () => {
|
test('sidepanel resolves pending GPC OTP with typed code', async () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
|
extractFunction('normalizeSignupMethod'),
|
||||||
|
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||||
|
extractFunction('normalizePlusStrategyTargetId'),
|
||||||
|
extractFunction('getPlusAccountAccessStrategyContinuationLabel'),
|
||||||
|
extractFunction('resolvePlusManualContinuationActionLabelFromState'),
|
||||||
extractLastFunction('openPlusManualConfirmationDialog'),
|
extractLastFunction('openPlusManualConfirmationDialog'),
|
||||||
extractLastFunction('syncPlusManualConfirmationDialog'),
|
extractLastFunction('syncPlusManualConfirmationDialog'),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
@@ -815,6 +829,14 @@ let latestState = {
|
|||||||
};
|
};
|
||||||
let activePlusManualConfirmationRequestId = '';
|
let activePlusManualConfirmationRequestId = '';
|
||||||
let plusManualConfirmationDialogInFlight = false;
|
let plusManualConfirmationDialogInFlight = false;
|
||||||
|
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||||
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
|
const DEFAULT_SIGNUP_METHOD = 'email';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
|
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = 'oauth';
|
||||||
const sharedFormDialog = {
|
const sharedFormDialog = {
|
||||||
async open(options) {
|
async open(options) {
|
||||||
events.push({ type: 'form', options });
|
events.push({ type: 'form', options });
|
||||||
|
|||||||
@@ -335,6 +335,70 @@ test('Plus phone signup never switches to SUB2API session tail even if the reque
|
|||||||
assert.equal(stepKeys.includes('platform-verify'), true);
|
assert.equal(stepKeys.includes('platform-verify'), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Plus session strategy swaps the OAuth tail for a single CPA 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: 'cpa_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: 'cpa_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: 'cpa_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 === 'cpa-session-import');
|
||||||
|
|
||||||
|
assert.equal(stepKeys.at(-1), 'cpa-session-import', `${label} should end with CPA session import`);
|
||||||
|
assert.equal(nodeIds.at(-1), 'cpa-session-import', `${label} node order should end with CPA session import`);
|
||||||
|
forbiddenTailKeys.forEach((key) => {
|
||||||
|
assert.equal(stepKeys.includes(key), false, `${label} should not keep ${key} in CPA session mode`);
|
||||||
|
assert.equal(nodeIds.includes(key), false, `${label} nodes should not keep ${key} in CPA session mode`);
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(api.getStepIds(options), expectedStepIds, `${label} step ids should follow the CPA tail`);
|
||||||
|
assert.equal(api.getLastStepId(options), expectedStepIds.at(-1), `${label} last step id should match CPA session import`);
|
||||||
|
assert.deepStrictEqual(previousNode?.next, ['cpa-session-import'], `${label} previous node should link to CPA session import`);
|
||||||
|
assert.deepStrictEqual(sessionImportNode?.next, [], `${label} CPA session import should be terminal`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('sidepanel html loads shared step definitions before sidepanel bootstrap', () => {
|
test('sidepanel html loads shared step definitions before sidepanel bootstrap', () => {
|
||||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||||
const definitionsIndex = html.indexOf('<script src="../data/step-definitions.js"></script>');
|
const definitionsIndex = html.indexOf('<script src="../data/step-definitions.js"></script>');
|
||||||
|
|||||||
Reference in New Issue
Block a user