fix: restore dev flow compatibility in CPA auth hardening
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
const RUNTIME_DEFAULTS = {
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionNickname: '',
|
||||
contributionQq: '',
|
||||
contributionSessionId: '',
|
||||
@@ -38,6 +40,8 @@
|
||||
} = deps;
|
||||
|
||||
let listenersBound = false;
|
||||
const pendingCallbackSubmissions = new Map();
|
||||
const pendingCapturedCallbacks = new Map();
|
||||
|
||||
function normalizeString(value = '') {
|
||||
return String(value || '').trim();
|
||||
@@ -256,6 +260,41 @@
|
||||
return qq;
|
||||
}
|
||||
|
||||
function isPlusModeState(state = {}) {
|
||||
return Boolean(state?.plusModeEnabled);
|
||||
}
|
||||
|
||||
function normalizeContributionModeSource(value = '') {
|
||||
const normalized = normalizeString(value).toLowerCase();
|
||||
return normalized === 'sub2api' ? 'sub2api' : 'cpa';
|
||||
}
|
||||
|
||||
function resolveContributionModeRoutingState(state = {}) {
|
||||
const currentStatus = normalizeString(state?.contributionStatus).toLowerCase();
|
||||
const currentSource = normalizeContributionModeSource(state?.contributionSource);
|
||||
const hasActiveSession = Boolean(
|
||||
normalizeString(state?.contributionSessionId)
|
||||
&& currentStatus
|
||||
&& !FINAL_STATUSES.has(currentStatus)
|
||||
);
|
||||
|
||||
if (hasActiveSession) {
|
||||
return {
|
||||
source: currentSource,
|
||||
targetGroupName: currentSource === 'sub2api'
|
||||
? (normalizeString(state?.contributionTargetGroupName) || 'codex号池')
|
||||
: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
source: 'sub2api',
|
||||
targetGroupName: isPlusModeState(state)
|
||||
? 'openai-plus'
|
||||
: (normalizeString(state?.contributionTargetGroupName) || 'codex号池'),
|
||||
};
|
||||
}
|
||||
|
||||
function buildStatusMessage(status, payload = {}) {
|
||||
const label = getStatusLabel(status);
|
||||
const details = [
|
||||
@@ -435,46 +474,58 @@
|
||||
return currentState;
|
||||
}
|
||||
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'submitting',
|
||||
contributionCallbackMessage: buildCallbackMessage('submitting'),
|
||||
});
|
||||
|
||||
try {
|
||||
const payload = await fetchContributionJson('/submit-callback', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
session_id: sessionId,
|
||||
callback_url: normalizedUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const nextStatus = 'submitted';
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: nextStatus,
|
||||
contributionCallbackMessage: buildCallbackMessage(nextStatus, payload),
|
||||
});
|
||||
|
||||
if (typeof closeLocalhostCallbackTabs === 'function') {
|
||||
await closeLocalhostCallbackTabs(normalizedUrl).catch(() => {});
|
||||
}
|
||||
|
||||
return await pollContributionStatus({ reason: options.reason || 'submit_callback' });
|
||||
} catch (error) {
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'failed',
|
||||
contributionCallbackMessage: `回调提交失败:${error.message}`,
|
||||
});
|
||||
|
||||
if (typeof addLog === 'function') {
|
||||
await addLog(`贡献模式:回调提交失败:${error.message}`, 'warn');
|
||||
}
|
||||
|
||||
throw error;
|
||||
const dedupeKey = `${sessionId}::${normalizedUrl}`;
|
||||
if (pendingCallbackSubmissions.has(dedupeKey)) {
|
||||
return pendingCallbackSubmissions.get(dedupeKey);
|
||||
}
|
||||
|
||||
const task = (async () => {
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'submitting',
|
||||
contributionCallbackMessage: buildCallbackMessage('submitting'),
|
||||
});
|
||||
|
||||
try {
|
||||
const payload = await fetchContributionJson('/submit-callback', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
session_id: sessionId,
|
||||
callback_url: normalizedUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const nextStatus = 'submitted';
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: nextStatus,
|
||||
contributionCallbackMessage: buildCallbackMessage(nextStatus, payload),
|
||||
});
|
||||
|
||||
if (typeof closeLocalhostCallbackTabs === 'function') {
|
||||
await closeLocalhostCallbackTabs(normalizedUrl).catch(() => {});
|
||||
}
|
||||
|
||||
return await pollContributionStatus({ reason: options.reason || 'submit_callback' });
|
||||
} catch (error) {
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'failed',
|
||||
contributionCallbackMessage: `回调提交失败:${error.message}`,
|
||||
});
|
||||
|
||||
if (typeof addLog === 'function') {
|
||||
await addLog(`贡献模式:回调提交失败:${error.message}`, 'warn');
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
pendingCallbackSubmissions.delete(dedupeKey);
|
||||
}
|
||||
})();
|
||||
|
||||
pendingCallbackSubmissions.set(dedupeKey, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
async function handleCapturedCallback(rawUrl, metadata = {}) {
|
||||
@@ -487,6 +538,13 @@
|
||||
}
|
||||
|
||||
const normalizedUrl = normalizeString(rawUrl);
|
||||
const callbackDedupeKey = `${normalizeString(currentState.contributionSessionId)}::${normalizedUrl}`;
|
||||
if (pendingCapturedCallbacks.has(callbackDedupeKey)) {
|
||||
return pendingCapturedCallbacks.get(callbackDedupeKey);
|
||||
}
|
||||
if (pendingCallbackSubmissions.has(callbackDedupeKey)) {
|
||||
return pendingCallbackSubmissions.get(callbackDedupeKey);
|
||||
}
|
||||
const currentCallbackStatus = normalizeContributionCallbackStatus(currentState.contributionCallbackStatus);
|
||||
if (
|
||||
normalizedUrl
|
||||
@@ -496,24 +554,31 @@
|
||||
return currentState;
|
||||
}
|
||||
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'captured',
|
||||
contributionCallbackMessage: buildCallbackMessage('captured'),
|
||||
});
|
||||
|
||||
if (typeof addLog === 'function') {
|
||||
await addLog(`贡献模式:已捕获回调地址(${metadata.source || 'unknown'})。`, 'info');
|
||||
}
|
||||
|
||||
try {
|
||||
return await submitContributionCallback(normalizedUrl, {
|
||||
reason: metadata.source || 'navigation',
|
||||
stateOverride: await getState(),
|
||||
const task = (async () => {
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'captured',
|
||||
contributionCallbackMessage: buildCallbackMessage('captured'),
|
||||
});
|
||||
} catch {
|
||||
return getState();
|
||||
}
|
||||
|
||||
if (typeof addLog === 'function') {
|
||||
await addLog(`贡献模式:已捕获回调地址(${metadata.source || 'unknown'})。`, 'info');
|
||||
}
|
||||
|
||||
try {
|
||||
return await submitContributionCallback(normalizedUrl, {
|
||||
reason: metadata.source || 'navigation',
|
||||
stateOverride: await getState(),
|
||||
});
|
||||
} catch {
|
||||
return getState();
|
||||
} finally {
|
||||
pendingCapturedCallbacks.delete(callbackDedupeKey);
|
||||
}
|
||||
})();
|
||||
|
||||
pendingCapturedCallbacks.set(callbackDedupeKey, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
async function pollContributionStatus(options = {}) {
|
||||
@@ -594,13 +659,15 @@
|
||||
return pollContributionStatus({ reason: 'resume_existing' });
|
||||
}
|
||||
|
||||
const routingState = resolveContributionModeRoutingState(currentState);
|
||||
const payload = await fetchContributionJson('/start', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
nickname: buildNickname(currentState, options.nickname),
|
||||
qq: buildContributionQq(currentState, options.qq),
|
||||
email: normalizeString(currentState.email),
|
||||
source: 'cpa',
|
||||
source: routingState.source,
|
||||
target_group_name: routingState.targetGroupName,
|
||||
channel: 'codex-extension',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
getPendingAutoRunTimerPlan,
|
||||
getSourceLabel,
|
||||
getState,
|
||||
getStepDefinitionForState,
|
||||
getStepIdsForState,
|
||||
getLastStepIdForState,
|
||||
getTabId,
|
||||
getStopRequested,
|
||||
handleAutoRunLoopUnhandledError,
|
||||
@@ -127,7 +130,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
function resolveLastStepIdForState(state = {}) {
|
||||
if (typeof getLastStepIdForState === 'function') {
|
||||
const fromResolver = Number(getLastStepIdForState(state));
|
||||
if (Number.isFinite(fromResolver) && fromResolver > 0) {
|
||||
return fromResolver;
|
||||
}
|
||||
}
|
||||
const stepIds = typeof getStepIdsForState === 'function'
|
||||
? (getStepIdsForState(state) || [])
|
||||
: Object.keys(state?.stepStatuses || {}).map((step) => Number(step)).filter(Number.isFinite);
|
||||
const sorted = stepIds.slice().sort((left, right) => left - right);
|
||||
return Math.max(10, sorted[sorted.length - 1] || 0);
|
||||
}
|
||||
|
||||
async function handleStepData(step, payload) {
|
||||
if (payload.skipLoginVerificationStep || payload.directOAuthConsentPage) {
|
||||
const latestState = await getState();
|
||||
const currentDefinition = typeof getStepDefinitionForState === 'function'
|
||||
? getStepDefinitionForState(step, latestState)
|
||||
: null;
|
||||
const currentKey = String(currentDefinition?.key || '').trim();
|
||||
if (currentKey === 'oauth-login') {
|
||||
const stepIds = typeof getStepIdsForState === 'function'
|
||||
? (getStepIdsForState(latestState) || [])
|
||||
: Object.keys(latestState?.stepStatuses || {}).map((item) => Number(item)).filter(Number.isFinite);
|
||||
const ordered = stepIds.slice().sort((left, right) => left - right);
|
||||
const currentIndex = ordered.indexOf(Number(step));
|
||||
const nextStep = currentIndex >= 0 ? ordered[currentIndex + 1] : null;
|
||||
const nextDefinition = Number.isFinite(nextStep) && typeof getStepDefinitionForState === 'function'
|
||||
? getStepDefinitionForState(nextStep, latestState)
|
||||
: null;
|
||||
if (Number.isFinite(nextStep) && String(nextDefinition?.key || '').trim() === 'fetch-login-code') {
|
||||
const nextStatus = latestState.stepStatuses?.[nextStep];
|
||||
if (nextStatus !== 'running' && nextStatus !== 'completed' && nextStatus !== 'manual_completed') {
|
||||
await setStepStatus(nextStep, 'skipped');
|
||||
await addLog(`步骤 ${step}:检测到已直达 OAuth 授权页,已自动跳过步骤 ${nextStep} 登录验证码。`, 'warn');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 1: {
|
||||
const updates = {};
|
||||
@@ -217,7 +260,8 @@
|
||||
broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
|
||||
}
|
||||
break;
|
||||
case 10: {
|
||||
case 10:
|
||||
case 13: {
|
||||
if (payload.localhostUrl) {
|
||||
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
||||
}
|
||||
@@ -305,11 +349,13 @@
|
||||
return { ok: true, error: errorMessage };
|
||||
}
|
||||
|
||||
const completionState = message.step === 10 ? await getState() : null;
|
||||
const latestState = await getState();
|
||||
const lastStepId = resolveLastStepIdForState(latestState);
|
||||
const completionState = message.step === lastStepId ? latestState : null;
|
||||
await setStepStatus(message.step, 'completed');
|
||||
await addLog(`步骤 ${message.step} 已完成`, 'ok');
|
||||
await handleStepData(message.step, message.payload);
|
||||
if (message.step === 10 && typeof appendAccountRunRecord === 'function') {
|
||||
if (message.step === lastStepId && typeof appendAccountRunRecord === 'function') {
|
||||
await appendAccountRunRecord('success', completionState);
|
||||
}
|
||||
notifyStepComplete(message.step, message.payload);
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function resolvePlatformVerifyStep(state = {}) {
|
||||
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||||
return visibleStep >= 10 ? visibleStep : 10;
|
||||
}
|
||||
|
||||
function parseLocalhostCallback(rawUrl) {
|
||||
let parsed;
|
||||
try {
|
||||
@@ -180,6 +185,7 @@
|
||||
}
|
||||
|
||||
async function executeCpaStep10(state) {
|
||||
const platformVerifyStep = resolvePlatformVerifyStep(state);
|
||||
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
|
||||
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
|
||||
}
|
||||
@@ -192,7 +198,7 @@
|
||||
|
||||
if (shouldBypassStep9ForLocalCpa(state)) {
|
||||
await addLog('步骤 10:检测到本地 CPA,且当前策略为“跳过第10步”,本轮不再重复提交回调地址。', 'info');
|
||||
await completeStepFromBackground(10, {
|
||||
await completeStepFromBackground(platformVerifyStep, {
|
||||
localhostUrl: state.localhostUrl,
|
||||
verifiedStatus: 'local-auto',
|
||||
});
|
||||
@@ -225,7 +231,7 @@
|
||||
|| normalizeString(result?.status_message)
|
||||
|| 'CPA 已通过接口提交回调';
|
||||
await addLog(`步骤 10:${verifiedStatus}`, 'ok');
|
||||
await completeStepFromBackground(10, {
|
||||
await completeStepFromBackground(platformVerifyStep, {
|
||||
localhostUrl: callback.url,
|
||||
verifiedStatus,
|
||||
});
|
||||
@@ -237,6 +243,7 @@
|
||||
}
|
||||
|
||||
async function executeCodex2ApiStep10(state) {
|
||||
const platformVerifyStep = resolvePlatformVerifyStep(state);
|
||||
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
|
||||
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
|
||||
}
|
||||
@@ -272,7 +279,7 @@
|
||||
|
||||
const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功';
|
||||
await addLog(`步骤 10:${verifiedStatus}`, 'ok');
|
||||
await completeStepFromBackground(10, {
|
||||
await completeStepFromBackground(platformVerifyStep, {
|
||||
localhostUrl: callback.url,
|
||||
verifiedStatus,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user