feat: upgrade multi-flow account contributions

This commit is contained in:
QLHazycoder
2026-05-19 13:20:06 +00:00
parent 68c1f387e1
commit 4687d07e2d
36 changed files with 2960 additions and 442 deletions
+200 -33
View File
@@ -2,6 +2,7 @@
importScripts( importScripts(
'shared/flow-registry.js', 'shared/flow-registry.js',
'shared/contribution-registry.js',
'shared/settings-schema.js', 'shared/settings-schema.js',
'shared/source-registry.js', 'shared/source-registry.js',
'shared/flow-capabilities.js', 'shared/flow-capabilities.js',
@@ -27,6 +28,8 @@ importScripts(
'background/workflow-engine.js', 'background/workflow-engine.js',
'background/runtime-state.js', 'background/runtime-state.js',
'background/kiro/state.js', 'background/kiro/state.js',
'background/kiro/credential-artifact.js',
'background/contribution/adapters/kiro-builder-id.js',
'background/kiro/register-runner.js', 'background/kiro/register-runner.js',
'background/kiro/desktop-client.js', 'background/kiro/desktop-client.js',
'background/kiro/desktop-authorize-runner.js', 'background/kiro/desktop-authorize-runner.js',
@@ -694,8 +697,10 @@ const SIGNUP_METHOD_EMAIL = 'email';
const SIGNUP_METHOD_PHONE = 'phone'; const SIGNUP_METHOD_PHONE = 'phone';
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL; const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
const CONTRIBUTION_RUNTIME_DEFAULTS = self.MultiPageBackgroundContributionOAuth?.RUNTIME_DEFAULTS || { const CONTRIBUTION_RUNTIME_DEFAULTS = self.MultiPageBackgroundContributionOAuth?.RUNTIME_DEFAULTS || {
contributionMode: false, accountContributionEnabled: false,
contributionModeExpected: false, accountContributionExpected: false,
contributionAdapterId: '',
flowContributionRuntime: {},
contributionSource: CONTRIBUTION_SOURCE_SUB2API, contributionSource: CONTRIBUTION_SOURCE_SUB2API,
contributionTargetGroupName: CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME, contributionTargetGroupName: CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME,
contributionNickname: '', contributionNickname: '',
@@ -715,6 +720,61 @@ const CONTRIBUTION_RUNTIME_DEFAULTS = self.MultiPageBackgroundContributionOAuth?
const CONTRIBUTION_RUNTIME_KEYS = self.MultiPageBackgroundContributionOAuth?.RUNTIME_KEYS const CONTRIBUTION_RUNTIME_KEYS = self.MultiPageBackgroundContributionOAuth?.RUNTIME_KEYS
|| Object.keys(CONTRIBUTION_RUNTIME_DEFAULTS); || Object.keys(CONTRIBUTION_RUNTIME_DEFAULTS);
function normalizeAccountContributionFlowId(value = '', fallback = DEFAULT_ACTIVE_FLOW_ID) {
return self.MultiPageFlowRegistry?.normalizeFlowId
? self.MultiPageFlowRegistry.normalizeFlowId(value, fallback)
: (String(value || fallback || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID);
}
function normalizeAccountContributionAdapterId(flowId = DEFAULT_ACTIVE_FLOW_ID, adapterId = '') {
const normalizedFlowId = normalizeAccountContributionFlowId(flowId);
const contributionRegistry = self.MultiPageContributionRegistry || {};
if (typeof contributionRegistry.normalizeAdapterId === 'function') {
const normalizedAdapterId = contributionRegistry.normalizeAdapterId(adapterId);
if (normalizedAdapterId && contributionRegistry.hasContributionAdapter?.(normalizedFlowId, normalizedAdapterId)) {
return normalizedAdapterId;
}
}
if (typeof contributionRegistry.getDefaultContributionAdapterId === 'function') {
return contributionRegistry.getDefaultContributionAdapterId(normalizedFlowId) || '';
}
return normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID ? 'openai-oauth' : '';
}
function assertAccountContributionAdapterAvailable(flowId = DEFAULT_ACTIVE_FLOW_ID, adapterId = '') {
const normalizedFlowId = normalizeAccountContributionFlowId(flowId);
const normalizedAdapterId = normalizeAccountContributionAdapterId(normalizedFlowId, adapterId);
const contributionRegistry = self.MultiPageContributionRegistry || {};
const hasAdapter = typeof contributionRegistry.hasContributionAdapter === 'function'
? contributionRegistry.hasContributionAdapter(normalizedFlowId, normalizedAdapterId)
: (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID && normalizedAdapterId === 'openai-oauth');
if (!normalizedAdapterId || !hasAdapter) {
throw new Error('当前 flow 尚未接入账号贡献适配器。');
}
return normalizedAdapterId;
}
function buildFlowContributionRuntimePatch(currentRuntime = {}, flowId = DEFAULT_ACTIVE_FLOW_ID, adapterId = '', enabled = false) {
const normalizedFlowId = normalizeAccountContributionFlowId(flowId);
const normalizedAdapterId = normalizeAccountContributionAdapterId(normalizedFlowId, adapterId);
const current = currentRuntime && typeof currentRuntime === 'object' && !Array.isArray(currentRuntime)
? currentRuntime
: {};
if (!enabled) {
return {};
}
return {
...current,
[normalizedFlowId]: {
...(current[normalizedFlowId] && typeof current[normalizedFlowId] === 'object' && !Array.isArray(current[normalizedFlowId])
? current[normalizedFlowId]
: {}),
enabled: true,
adapterId: normalizedAdapterId,
},
};
}
function isPlusModeState(state = {}) { function isPlusModeState(state = {}) {
return Boolean(state?.plusModeEnabled); return Boolean(state?.plusModeEnabled);
} }
@@ -732,16 +792,16 @@ function normalizeGpcHelperPhoneMode(value = '') {
return normalized === 'auto' || normalized === 'builtin' ? 'auto' : 'manual'; return normalized === 'auto' || normalized === 'builtin' ? 'auto' : 'manual';
} }
function normalizeContributionModeSource(value = '') { function normalizeOpenAiContributionSource(value = '') {
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
return normalized === CONTRIBUTION_SOURCE_SUB2API return normalized === CONTRIBUTION_SOURCE_SUB2API
? CONTRIBUTION_SOURCE_SUB2API ? CONTRIBUTION_SOURCE_SUB2API
: CONTRIBUTION_SOURCE_CPA; : CONTRIBUTION_SOURCE_CPA;
} }
function resolveContributionModeRoutingState(state = {}) { function resolveOpenAiContributionRoutingState(state = {}) {
const currentStatus = String(state?.contributionStatus || '').trim().toLowerCase(); const currentStatus = String(state?.contributionStatus || '').trim().toLowerCase();
const currentSource = normalizeContributionModeSource(state?.contributionSource); const currentSource = normalizeOpenAiContributionSource(state?.contributionSource);
const hasActiveSession = Boolean( const hasActiveSession = Boolean(
String(state?.contributionSessionId || '').trim() String(state?.contributionSessionId || '').trim()
&& currentStatus && currentStatus
@@ -1748,7 +1808,7 @@ function canUsePhoneSignup(state = {}) {
} }
return Boolean(state?.phoneVerificationEnabled) return Boolean(state?.phoneVerificationEnabled)
&& !Boolean(state?.plusModeEnabled) && !Boolean(state?.plusModeEnabled)
&& !Boolean(state?.contributionMode); && !Boolean(state?.accountContributionEnabled);
} }
function resolveSignupMethod(state = {}) { function resolveSignupMethod(state = {}) {
@@ -3753,6 +3813,56 @@ async function initializeSessionStorageAccess() {
} }
} }
async function migrateLegacyAccountContributionState() {
const legacyKeys = ['contributionMode', 'contributionModeExpected'];
const sessionKeys = [
...legacyKeys,
'accountContributionEnabled',
'accountContributionExpected',
'contributionAdapterId',
'flowContributionRuntime',
'activeFlowId',
'flowId',
];
const legacySessionState = await chrome.storage.session.get(sessionKeys).catch(() => ({}));
const updates = {};
const shouldEnable = legacySessionState.accountContributionEnabled === undefined
&& legacySessionState.contributionMode === true;
if (shouldEnable) {
const flowId = normalizeAccountContributionFlowId(legacySessionState.activeFlowId || legacySessionState.flowId);
const adapterId = normalizeAccountContributionAdapterId(flowId, legacySessionState.contributionAdapterId);
updates.accountContributionEnabled = true;
updates.accountContributionExpected = legacySessionState.accountContributionExpected !== undefined
? Boolean(legacySessionState.accountContributionExpected)
: Boolean(legacySessionState.contributionModeExpected);
updates.contributionAdapterId = adapterId;
updates.flowContributionRuntime = buildFlowContributionRuntimePatch(
legacySessionState.flowContributionRuntime,
flowId,
adapterId,
true
);
} else if (
legacySessionState.contributionMode !== undefined
&& legacySessionState.accountContributionEnabled === undefined
) {
updates.accountContributionEnabled = false;
updates.accountContributionExpected = false;
} else if (
legacySessionState.contributionModeExpected !== undefined
&& legacySessionState.accountContributionExpected === undefined
) {
updates.accountContributionExpected = Boolean(legacySessionState.contributionModeExpected);
}
if (Object.keys(updates).length > 0) {
await chrome.storage.session.set(updates);
}
await Promise.all([
chrome.storage.session.remove?.(legacyKeys),
chrome.storage.local.remove?.(legacyKeys),
].filter(Boolean)).catch(() => {});
}
async function setState(updates) { async function setState(updates) {
console.log(LOG_PREFIX, 'storage.set:', JSON.stringify(updates).slice(0, 200)); console.log(LOG_PREFIX, 'storage.set:', JSON.stringify(updates).slice(0, 200));
if (Object.keys(updates || {}).length > 0) { if (Object.keys(updates || {}).length > 0) {
@@ -3902,7 +4012,7 @@ async function importSettingsBundle(configBundle) {
|| Object.prototype.hasOwnProperty.call(importedSettings, 'signupMethod') || Object.prototype.hasOwnProperty.call(importedSettings, 'signupMethod')
|| Object.prototype.hasOwnProperty.call(importedSettings, 'panelMode') || Object.prototype.hasOwnProperty.call(importedSettings, 'panelMode')
|| Object.prototype.hasOwnProperty.call(importedSettings, 'activeFlowId') || Object.prototype.hasOwnProperty.call(importedSettings, 'activeFlowId')
|| Object.prototype.hasOwnProperty.call(importedSettings, 'contributionMode') || Object.prototype.hasOwnProperty.call(importedSettings, 'accountContributionEnabled')
) { ) {
importedSettings.signupMethod = resolveSignupMethod({ importedSettings.signupMethod = resolveSignupMethod({
...state, ...state,
@@ -4112,27 +4222,42 @@ async function setPasswordState(password) {
broadcastDataUpdate({ password }); broadcastDataUpdate({ password });
} }
function buildContributionModeState(enabled, persistedSettings = {}, currentState = {}) { function buildAccountContributionState(enabled, persistedSettings = {}, currentState = {}, options = {}) {
const currentContributionState = {}; const currentContributionState = {};
for (const key of CONTRIBUTION_RUNTIME_KEYS) { for (const key of CONTRIBUTION_RUNTIME_KEYS) {
currentContributionState[key] = currentState[key] !== undefined currentContributionState[key] = currentState[key] !== undefined
? currentState[key] ? currentState[key]
: CONTRIBUTION_RUNTIME_DEFAULTS[key]; : CONTRIBUTION_RUNTIME_DEFAULTS[key];
} }
if (enabled) { if (enabled) {
const routing = resolveContributionModeRoutingState({ const activeFlowId = normalizeAccountContributionFlowId(
options.flowId
|| currentState.activeFlowId
|| currentState.flowId
|| persistedSettings.activeFlowId
|| persistedSettings.flowId
|| DEFAULT_ACTIVE_FLOW_ID
);
const adapterId = assertAccountContributionAdapterAvailable(
activeFlowId,
options.adapterId || currentState.contributionAdapterId
);
const routing = activeFlowId === DEFAULT_ACTIVE_FLOW_ID ? resolveOpenAiContributionRoutingState({
...persistedSettings, ...persistedSettings,
...currentState, ...currentState,
...currentContributionState, ...currentContributionState,
}); }) : null;
return { return {
...currentContributionState, ...currentContributionState,
contributionMode: true, accountContributionEnabled: true,
contributionModeExpected: true, accountContributionExpected: true,
contributionSource: routing.source, contributionAdapterId: adapterId,
contributionTargetGroupName: routing.targetGroupName, flowContributionRuntime: buildFlowContributionRuntimePatch(currentContributionState.flowContributionRuntime, activeFlowId, adapterId, true),
panelMode: routing.source, ...(routing ? {
contributionSource: routing.source,
contributionTargetGroupName: routing.targetGroupName,
panelMode: routing.source,
} : {}),
customPassword: '', customPassword: '',
accountRunHistoryTextEnabled: false, accountRunHistoryTextEnabled: false,
}; };
@@ -4140,22 +4265,24 @@ function buildContributionModeState(enabled, persistedSettings = {}, currentStat
return { return {
...CONTRIBUTION_RUNTIME_DEFAULTS, ...CONTRIBUTION_RUNTIME_DEFAULTS,
contributionMode: false, accountContributionEnabled: false,
contributionModeExpected: false, accountContributionExpected: false,
contributionAdapterId: '',
flowContributionRuntime: {},
panelMode: persistedSettings.panelMode || DEFAULT_STATE.panelMode, panelMode: persistedSettings.panelMode || DEFAULT_STATE.panelMode,
customPassword: persistedSettings.customPassword || '', customPassword: persistedSettings.customPassword || '',
accountRunHistoryTextEnabled: Boolean(persistedSettings.accountRunHistoryTextEnabled), accountRunHistoryTextEnabled: Boolean(persistedSettings.accountRunHistoryTextEnabled),
}; };
} }
async function setContributionMode(enabled) { async function setAccountContributionMode(enabled, options = {}) {
const normalizedEnabled = Boolean(enabled); const normalizedEnabled = Boolean(enabled);
const [persistedSettings, currentState] = await Promise.all([ const [persistedSettings, currentState] = await Promise.all([
getPersistedSettings(), getPersistedSettings(),
getState(), getState(),
]); ]);
const updates = buildContributionModeState(normalizedEnabled, persistedSettings, currentState); const updates = buildAccountContributionState(normalizedEnabled, persistedSettings, currentState, options);
await setState(updates); await setState(updates);
const nextState = await getState(); const nextState = await getState();
@@ -4410,7 +4537,10 @@ async function resetState() {
getPersistedSettings(), getPersistedSettings(),
getPersistedAliasState(), getPersistedAliasState(),
]); ]);
const contributionModeState = buildContributionModeState(Boolean(prev.contributionMode), persistedSettings, prev); const accountContributionState = buildAccountContributionState(Boolean(prev.accountContributionEnabled), persistedSettings, prev, {
adapterId: prev.contributionAdapterId,
flowId: prev.activeFlowId || prev.flowId,
});
const reusablePhoneActivation = ( const reusablePhoneActivation = (
prev.reusablePhoneActivation prev.reusablePhoneActivation
&& typeof prev.reusablePhoneActivation === 'object' && typeof prev.reusablePhoneActivation === 'object'
@@ -4453,7 +4583,7 @@ async function resetState() {
...DEFAULT_STATE, ...DEFAULT_STATE,
...persistedSettings, ...persistedSettings,
...persistedAliasState, ...persistedAliasState,
...contributionModeState, ...accountContributionState,
seenCodes: prev.seenCodes || [], seenCodes: prev.seenCodes || [],
seenInbucketMailIds: prev.seenInbucketMailIds || [], seenInbucketMailIds: prev.seenInbucketMailIds || [],
accounts: prev.accounts || [], accounts: prev.accounts || [],
@@ -13378,6 +13508,32 @@ const kiroRegisterRunner = self.MultiPageBackgroundKiroRegisterRunner?.createKir
waitForTabStableComplete, waitForTabStableComplete,
KIRO_REGISTER_INJECT_FILES, KIRO_REGISTER_INJECT_FILES,
}); });
const kiroBuilderIdContributionAdapter = self.MultiPageBackgroundKiroBuilderIdContributionAdapter?.createKiroBuilderIdContributionAdapter?.({
addLog,
fetchImpl: typeof fetch === 'function' ? fetch.bind(globalThis) : null,
getState,
setState,
});
async function maybeSubmitFlowContribution(state = {}, options = {}) {
const currentState = state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length
? state
: await getState();
const activeFlowId = normalizeAccountContributionFlowId(currentState.activeFlowId || currentState.flowId);
const adapterId = normalizeAccountContributionAdapterId(activeFlowId, currentState.contributionAdapterId);
if (!currentState.accountContributionEnabled) {
return { ok: true, skipped: true, reason: 'account_contribution_disabled' };
}
if (activeFlowId === 'kiro' && adapterId === 'kiro-builder-id') {
if (!kiroBuilderIdContributionAdapter?.maybeSubmitFlowContribution) {
return { ok: false, skipped: true, reason: 'kiro_builder_id_adapter_missing' };
}
return kiroBuilderIdContributionAdapter.maybeSubmitFlowContribution({
...currentState,
contributionAdapterId: adapterId,
}, options);
}
return { ok: true, skipped: true, reason: 'adapter_not_handled_by_flow_submission' };
}
const kiroDesktopAuthorizeRunner = self.MultiPageBackgroundKiroDesktopAuthorizeRunner?.createKiroDesktopAuthorizeRunner({ const kiroDesktopAuthorizeRunner = self.MultiPageBackgroundKiroDesktopAuthorizeRunner?.createKiroDesktopAuthorizeRunner({
addLog, addLog,
chrome, chrome,
@@ -13398,6 +13554,7 @@ const kiroDesktopAuthorizeRunner = self.MultiPageBackgroundKiroDesktopAuthorizeR
YYDS_MAIL_PROVIDER, YYDS_MAIL_PROVIDER,
MAIL_2925_VERIFICATION_INTERVAL_MS, MAIL_2925_VERIFICATION_INTERVAL_MS,
MAIL_2925_VERIFICATION_MAX_ATTEMPTS, MAIL_2925_VERIFICATION_MAX_ATTEMPTS,
maybeSubmitFlowContribution,
pollCloudflareTempEmailVerificationCode, pollCloudflareTempEmailVerificationCode,
pollCloudMailVerificationCode, pollCloudMailVerificationCode,
pollHotmailVerificationCode, pollHotmailVerificationCode,
@@ -13626,7 +13783,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
setCurrentPayPalAccount, setCurrentPayPalAccount,
setCurrentHotmailAccount, setCurrentHotmailAccount,
setCurrentMail2925Account, setCurrentMail2925Account,
setContributionMode, setAccountContributionMode,
setEmailState, setEmailState,
setEmailStateSilently, setEmailStateSilently,
persistRegistrationEmailState, persistRegistrationEmailState,
@@ -13643,9 +13800,10 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
setNodeStatus, setNodeStatus,
skipAutoRunCountdown, skipAutoRunCountdown,
skipNode, skipNode,
startContributionFlow: (...args) => contributionOAuthManager?.startContributionFlow?.(...args), startFlowContribution: (...args) => contributionOAuthManager?.startFlowContribution?.(...args),
startAutoRunLoop, startAutoRunLoop,
pollContributionStatus: (...args) => contributionOAuthManager?.pollContributionStatus?.(...args), pollContributionStatus: (...args) => contributionOAuthManager?.pollContributionStatus?.(...args),
submitFlowContribution: (...args) => contributionOAuthManager?.submitContributionCallback?.(...args),
syncHotmailAccounts, syncHotmailAccounts,
syncPayPalAccounts, syncPayPalAccounts,
deleteMail2925Account, deleteMail2925Account,
@@ -13958,15 +14116,15 @@ async function executeStep5(state) {
async function refreshOAuthUrlBeforeStep6(state, options = {}) { async function refreshOAuthUrlBeforeStep6(state, options = {}) {
const visibleStep = Number(options.visibleStep) || Number(state?.visibleStep) || 7; const visibleStep = Number(options.visibleStep) || Number(state?.visibleStep) || 7;
if (state?.contributionModeExpected && !state?.contributionMode) { if (state?.accountContributionExpected && !state?.accountContributionEnabled) {
throw new Error(`步骤 ${visibleStep}:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 链路。请重新进入贡献模式后再点击自动。`); throw new Error(`步骤 ${visibleStep}:当前自动流程预期使用账号贡献,但运行态 accountContributionEnabled 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 链路。请重新进入账号贡献后再点击自动。`);
} }
if (state?.contributionMode && contributionOAuthManager?.startContributionFlow) { if (state?.accountContributionEnabled && contributionOAuthManager?.startFlowContribution) {
await addLog('contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...', 'info', { await addLog('账号贡献已开启,走公开贡献接口,正在申请 OAuth 登录地址...', 'info', {
step: visibleStep, step: visibleStep,
stepKey: 'oauth-login', stepKey: 'oauth-login',
}); });
const contributionState = await contributionOAuthManager.startContributionFlow({ const contributionState = await contributionOAuthManager.startFlowContribution({
nickname: state.contributionNickname || '', nickname: state.contributionNickname || '',
openAuthTab: false, openAuthTab: false,
stateOverride: state, stateOverride: state,
@@ -13978,7 +14136,7 @@ async function refreshOAuthUrlBeforeStep6(state, options = {}) {
await handleStepData(1, { oauthUrl }); await handleStepData(1, { oauthUrl });
return oauthUrl; return oauthUrl;
} }
await addLog(`contributionMode=false,走普通 CPA / SUB2API / Codex2API 链路(当前面板:${getPanelModeLabel(state)}),正在刷新 OAuth 登录地址...`, 'info', { await addLog(`账号贡献未开启,走普通 CPA / SUB2API / Codex2API 链路(当前面板:${getPanelModeLabel(state)}),正在刷新 OAuth 登录地址...`, 'info', {
step: visibleStep, step: visibleStep,
stepKey: 'oauth-login', stepKey: 'oauth-login',
}); });
@@ -15337,10 +15495,10 @@ async function executeStep10(state) {
const platformVerifyStep = typeof getStepIdByKeyForState === 'function' const platformVerifyStep = typeof getStepIdByKeyForState === 'function'
? (getStepIdByKeyForState('platform-verify', state || {}) || 10) ? (getStepIdByKeyForState('platform-verify', state || {}) || 10)
: 10; : 10;
if (state?.contributionModeExpected && !state?.contributionMode) { if (state?.accountContributionExpected && !state?.accountContributionEnabled) {
throw new Error(`步骤 ${platformVerifyStep}:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 提交。请重新进入贡献模式后再点击自动。`); throw new Error(`步骤 ${platformVerifyStep}:当前自动流程预期使用账号贡献,但运行态 accountContributionEnabled 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 提交。请重新进入账号贡献后再点击自动。`);
} }
if (state?.contributionMode) { if (state?.accountContributionEnabled) {
return executeContributionStep10(state); return executeContributionStep10(state);
} }
return step10Executor.executeStep10(state); return step10Executor.executeStep10(state);
@@ -15367,6 +15525,9 @@ chrome.alarms.onAlarm.addListener((alarm) => {
}); });
chrome.runtime.onStartup.addListener(() => { chrome.runtime.onStartup.addListener(() => {
migrateLegacyAccountContributionState().catch((err) => {
console.error(LOG_PREFIX, 'Failed to migrate legacy account contribution state on startup:', err);
});
restoreAutoRunTimerIfNeeded().catch((err) => { restoreAutoRunTimerIfNeeded().catch((err) => {
console.error(LOG_PREFIX, 'Failed to restore auto run timer on startup:', err); console.error(LOG_PREFIX, 'Failed to restore auto run timer on startup:', err);
}); });
@@ -15384,6 +15545,9 @@ chrome.runtime.onStartup.addListener(() => {
}); });
chrome.runtime.onInstalled.addListener(() => { chrome.runtime.onInstalled.addListener(() => {
migrateLegacyAccountContributionState().catch((err) => {
console.error(LOG_PREFIX, 'Failed to migrate legacy account contribution state on install/update:', err);
});
restoreAutoRunTimerIfNeeded().catch((err) => { restoreAutoRunTimerIfNeeded().catch((err) => {
console.error(LOG_PREFIX, 'Failed to restore auto run timer on install/update:', err); console.error(LOG_PREFIX, 'Failed to restore auto run timer on install/update:', err);
}); });
@@ -15400,6 +15564,9 @@ chrome.runtime.onInstalled.addListener(() => {
}); });
}); });
migrateLegacyAccountContributionState().catch((err) => {
console.error(LOG_PREFIX, 'Failed to migrate legacy account contribution state:', err);
});
restoreAutoRunTimerIfNeeded().catch((err) => { restoreAutoRunTimerIfNeeded().catch((err) => {
console.error(LOG_PREFIX, 'Failed to restore auto run timer:', err); console.error(LOG_PREFIX, 'Failed to restore auto run timer:', err);
}); });
+3 -3
View File
@@ -431,7 +431,7 @@
source, source,
autoRunContext: source === 'auto' ? autoRunContext : null, autoRunContext: source === 'auto' ? autoRunContext : null,
plusModeEnabled: Boolean(record.plusModeEnabled), plusModeEnabled: Boolean(record.plusModeEnabled),
contributionMode: Boolean(record.contributionMode), accountContributionEnabled: Boolean(record.accountContributionEnabled),
}; };
} }
@@ -526,7 +526,7 @@
source, source,
autoRunContext, autoRunContext,
plusModeEnabled: Boolean(state.plusModeEnabled), plusModeEnabled: Boolean(state.plusModeEnabled),
contributionMode: Boolean(state.contributionMode), accountContributionEnabled: Boolean(state.accountContributionEnabled),
}; };
} }
@@ -637,7 +637,7 @@
} }
function shouldSyncAccountRunHistorySnapshot(state = {}) { function shouldSyncAccountRunHistorySnapshot(state = {}) {
if (Boolean(state.contributionMode)) { if (Boolean(state.accountContributionEnabled)) {
return false; return false;
} }
+12 -10
View File
@@ -8,8 +8,10 @@
const CALLBACK_WAITING_STATUSES = new Set(['idle', 'waiting', 'captured', 'failed', 'submitting']); const CALLBACK_WAITING_STATUSES = new Set(['idle', 'waiting', 'captured', 'failed', 'submitting']);
const RUNTIME_DEFAULTS = { const RUNTIME_DEFAULTS = {
contributionMode: false, accountContributionEnabled: false,
contributionModeExpected: false, accountContributionExpected: false,
contributionAdapterId: '',
flowContributionRuntime: {},
contributionSource: 'sub2api', contributionSource: 'sub2api',
contributionTargetGroupName: 'codex号池', contributionTargetGroupName: 'codex号池',
contributionNickname: '', contributionNickname: '',
@@ -265,14 +267,14 @@
return Boolean(state?.plusModeEnabled); return Boolean(state?.plusModeEnabled);
} }
function normalizeContributionModeSource(value = '') { function normalizeOpenAiContributionSource(value = '') {
const normalized = normalizeString(value).toLowerCase(); const normalized = normalizeString(value).toLowerCase();
return normalized === 'sub2api' ? 'sub2api' : 'cpa'; return normalized === 'sub2api' ? 'sub2api' : 'cpa';
} }
function resolveContributionModeRoutingState(state = {}) { function resolveOpenAiContributionRoutingState(state = {}) {
const currentStatus = normalizeString(state?.contributionStatus).toLowerCase(); const currentStatus = normalizeString(state?.contributionStatus).toLowerCase();
const currentSource = normalizeContributionModeSource(state?.contributionSource); const currentSource = normalizeOpenAiContributionSource(state?.contributionSource);
const hasActiveSession = Boolean( const hasActiveSession = Boolean(
normalizeString(state?.contributionSessionId) normalizeString(state?.contributionSessionId)
&& currentStatus && currentStatus
@@ -533,7 +535,7 @@
async function handleCapturedCallback(rawUrl, metadata = {}) { async function handleCapturedCallback(rawUrl, metadata = {}) {
const currentState = await getState(); const currentState = await getState();
if (!normalizeString(currentState.contributionSessionId) || !currentState.contributionMode) { if (!normalizeString(currentState.contributionSessionId) || !currentState.accountContributionEnabled) {
return currentState; return currentState;
} }
if (!isContributionCallbackUrl(rawUrl, currentState)) { if (!isContributionCallbackUrl(rawUrl, currentState)) {
@@ -642,10 +644,10 @@
return nextState; return nextState;
} }
async function startContributionFlow(options = {}) { async function startFlowContribution(options = {}) {
const currentState = options.stateOverride || await getState(); const currentState = options.stateOverride || await getState();
const shouldOpenAuthTab = options.openAuthTab !== false; const shouldOpenAuthTab = options.openAuthTab !== false;
if (!currentState.contributionMode) { if (!currentState.accountContributionEnabled) {
throw new Error('请先进入贡献模式。'); throw new Error('请先进入贡献模式。');
} }
@@ -662,7 +664,7 @@
return pollContributionStatus({ reason: 'resume_existing' }); return pollContributionStatus({ reason: 'resume_existing' });
} }
const routingState = resolveContributionModeRoutingState(currentState); const routingState = resolveOpenAiContributionRoutingState(currentState);
const payload = await fetchContributionJson('/start', { const payload = await fetchContributionJson('/start', {
method: 'POST', method: 'POST',
body: { body: {
@@ -744,7 +746,7 @@
isContributionCallbackUrl, isContributionCallbackUrl,
isContributionFinalStatus, isContributionFinalStatus,
pollContributionStatus, pollContributionStatus,
startContributionFlow, startFlowContribution,
submitContributionCallback, submitContributionCallback,
}; };
} }
@@ -0,0 +1,245 @@
(function attachBackgroundKiroBuilderIdContributionAdapter(root, factory) {
root.MultiPageBackgroundKiroBuilderIdContributionAdapter = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroBuilderIdContributionAdapterModule(root) {
const artifactApi = root?.MultiPageBackgroundKiroCredentialArtifact || {};
const FLOW_ID = artifactApi.FLOW_ID || 'kiro';
const ADAPTER_ID = artifactApi.ADAPTER_ID || 'kiro-builder-id';
const ARTIFACT_KIND = artifactApi.ARTIFACT_KIND || 'kiro-builder-id';
const DEFAULT_CONTRIBUTION_API_URL = 'https://flowpilot.qlhazycoder.top/api/contributions';
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cleanString(value = '') {
return String(value ?? '').trim();
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : String(error ?? '未知错误');
}
function normalizeFlowId(state = {}) {
return cleanString(state.activeFlowId || state.flowId).toLowerCase() || 'openai';
}
function normalizeAdapterId(state = {}) {
return cleanString(state.contributionAdapterId || ADAPTER_ID).toLowerCase() || ADAPTER_ID;
}
function shouldSubmitKiroBuilderIdContribution(state = {}) {
return Boolean(state?.accountContributionEnabled)
&& normalizeFlowId(state) === FLOW_ID
&& normalizeAdapterId(state) === ADAPTER_ID;
}
function normalizeContributionApiUrl(value = '') {
const normalized = cleanString(value).replace(/\/+$/, '');
if (!normalized) {
return DEFAULT_CONTRIBUTION_API_URL;
}
if (/\/api\/contributions$/i.test(normalized)) {
return normalized;
}
return `${normalized}/api/contributions`;
}
function mergeFlowContributionRuntime(currentRuntime = {}, flowId = FLOW_ID, patch = {}) {
const runtime = isPlainObject(currentRuntime) ? currentRuntime : {};
const currentFlowRuntime = isPlainObject(runtime[flowId]) ? runtime[flowId] : {};
return {
...runtime,
[flowId]: {
...currentFlowRuntime,
...patch,
},
};
}
function redactKnownArtifactSecrets(message = '', artifact = {}) {
let result = cleanString(message);
const secrets = [
artifact?.credentials?.refreshToken,
artifact?.credentials?.clientSecret,
]
.map((value) => cleanString(value))
.filter(Boolean);
secrets.forEach((secret) => {
result = result.split(secret).join(artifactApi.redactSecret?.(secret) || '[redacted]');
});
return result;
}
async function readJsonResponse(response) {
const text = await response.text();
try {
return text ? JSON.parse(text) : {};
} catch (_error) {
return { message: text };
}
}
async function submitContributionArtifact(artifact = {}, options = {}) {
const fetchImpl = options.fetchImpl;
if (typeof fetchImpl !== 'function') {
throw new Error('账号贡献提交能力缺少 fetch。');
}
const endpoint = normalizeContributionApiUrl(options.apiUrl);
const payload = {
flow: FLOW_ID,
adapter_id: ADAPTER_ID,
artifact_kind: ARTIFACT_KIND,
source: 'flowpilot-extension',
contributor: {
nickname: cleanString(options.nickname),
qq: cleanString(options.qq),
},
artifact,
};
const response = await fetchImpl(endpoint, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
const body = await readJsonResponse(response);
if (!response.ok || body?.ok === false) {
throw new Error(cleanString(body?.message || body?.detail || body?.error) || `贡献服务请求失败(HTTP ${response.status})。`);
}
return {
ok: true,
status: response.status,
contributionId: cleanString(body?.contribution_id || body?.contributionId || body?.id),
message: cleanString(body?.message) || '贡献提交成功',
raw: body,
};
}
function createKiroBuilderIdContributionAdapter(deps = {}) {
const {
addLog = async () => {},
contributionApiUrl = DEFAULT_CONTRIBUTION_API_URL,
fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null,
getState = async () => ({}),
setState = async () => {},
} = deps;
async function updateFlowContributionRuntime(currentState = {}, patch = {}) {
const runtime = mergeFlowContributionRuntime(currentState.flowContributionRuntime, FLOW_ID, {
adapterId: ADAPTER_ID,
...patch,
});
await setState({
flowContributionRuntime: runtime,
});
return runtime;
}
async function log(message, level = 'info', nodeId = '') {
await addLog(message, level, nodeId ? { nodeId } : {});
}
async function maybeSubmitFlowContribution(state = {}, options = {}) {
const currentState = Object.keys(state || {}).length ? state : await getState();
const nodeId = cleanString(options.nodeId || 'kiro-complete-desktop-authorize');
if (!shouldSubmitKiroBuilderIdContribution(currentState)) {
return {
ok: true,
skipped: true,
reason: 'not_enabled_for_kiro_builder_id',
};
}
let artifact = null;
try {
artifact = artifactApi.buildKiroBuilderIdArtifact(currentState);
} catch (error) {
const message = redactKnownArtifactSecrets(getErrorMessage(error), artifact);
await updateFlowContributionRuntime(currentState, {
enabled: true,
status: 'skipped',
error: message,
lastMessage: message,
updatedAt: Date.now(),
});
await log(`Kiro 账号贡献已跳过:${message}`, 'warn', nodeId);
return {
ok: false,
skipped: true,
reason: error?.code || 'artifact_invalid',
message,
};
}
await updateFlowContributionRuntime(currentState, {
enabled: true,
status: 'submitting',
error: '',
lastMessage: '正在提交 Kiro Builder ID 贡献',
updatedAt: Date.now(),
});
const safeSummary = artifactApi.buildSafeArtifactSummary?.(artifact) || {};
await log(`Kiro 账号贡献:正在提交 Builder ID,邮箱 ${safeSummary.email || '未知'}`, 'info', nodeId);
try {
const result = await submitContributionArtifact(artifact, {
apiUrl: options.contributionApiUrl || contributionApiUrl,
fetchImpl,
nickname: currentState.contributionNickname,
qq: currentState.contributionQq,
});
await updateFlowContributionRuntime(currentState, {
enabled: true,
status: 'submitted',
error: '',
contributionId: result.contributionId,
lastMessage: result.message,
submittedAt: Date.now(),
updatedAt: Date.now(),
});
await log(`Kiro 账号贡献:提交完成,${result.message}`, 'ok', nodeId);
return result;
} catch (error) {
const message = redactKnownArtifactSecrets(getErrorMessage(error), artifact);
await updateFlowContributionRuntime(currentState, {
enabled: true,
status: 'error',
error: message,
lastMessage: message,
updatedAt: Date.now(),
});
await log(`Kiro 账号贡献提交失败:${message}`, 'warn', nodeId);
return {
ok: false,
skipped: false,
reason: 'submit_failed',
message,
};
}
}
return {
maybeSubmitFlowContribution,
shouldSubmitKiroBuilderIdContribution,
submitContributionArtifact: (artifact, options = {}) => submitContributionArtifact(artifact, {
...options,
apiUrl: options.apiUrl || contributionApiUrl,
fetchImpl: options.fetchImpl || fetchImpl,
}),
};
}
return {
ADAPTER_ID,
ARTIFACT_KIND,
DEFAULT_CONTRIBUTION_API_URL,
FLOW_ID,
createKiroBuilderIdContributionAdapter,
normalizeContributionApiUrl,
shouldSubmitKiroBuilderIdContribution,
submitContributionArtifact,
};
});
+138
View File
@@ -0,0 +1,138 @@
(function attachBackgroundKiroCredentialArtifact(root, factory) {
root.MultiPageBackgroundKiroCredentialArtifact = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroCredentialArtifactModule(root) {
const kiroStateApi = root?.MultiPageBackgroundKiroState || null;
const FLOW_ID = 'kiro';
const ADAPTER_ID = 'kiro-builder-id';
const ARTIFACT_KIND = 'kiro-builder-id';
const DEFAULT_REGION = kiroStateApi?.DEFAULT_REGION || 'us-east-1';
const DEFAULT_TARGET_ID = kiroStateApi?.DEFAULT_TARGET_ID || 'kiro-rs';
const BUILDER_ID_PROFILE_ARN = 'arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX';
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cleanString(value = '') {
return String(value ?? '').trim();
}
function readKiroRuntime(state = {}) {
return kiroStateApi?.ensureRuntimeState
? kiroStateApi.ensureRuntimeState(state)
: (isPlainObject(state?.kiroRuntime) ? state.kiroRuntime : {});
}
function resolveKiroTargetId(state = {}, runtimeState = readKiroRuntime(state)) {
return cleanString(
state?.settingsState?.flows?.kiro?.targetId
|| state?.flows?.kiro?.targetId
|| state?.kiroTargetId
|| runtimeState?.upload?.targetId
|| DEFAULT_TARGET_ID
) || DEFAULT_TARGET_ID;
}
function resolveEmail(state = {}, runtimeState = readKiroRuntime(state)) {
return cleanString(
runtimeState?.register?.email
|| state?.email
|| state?.registrationEmailState?.current
|| state?.accountIdentifier
);
}
function resolveRegion(state = {}, runtimeState = readKiroRuntime(state), targetId = DEFAULT_TARGET_ID) {
return cleanString(
runtimeState?.desktopAuth?.region
|| state?.settingsState?.flows?.kiro?.targets?.[targetId]?.region
|| state?.flows?.kiro?.targets?.[targetId]?.region
|| DEFAULT_REGION
) || DEFAULT_REGION;
}
function assertRequiredField(name, value, message) {
if (!cleanString(value)) {
const error = new Error(message);
error.code = `missing_${name}`;
throw error;
}
}
function redactSecret(value = '') {
const normalized = cleanString(value);
if (!normalized) {
return '';
}
if (normalized.length <= 10) {
return `${normalized.slice(0, 2)}***`;
}
return `${normalized.slice(0, 6)}...${normalized.slice(-4)}`;
}
function buildKiroBuilderIdArtifact(state = {}, options = {}) {
const runtimeState = readKiroRuntime(state);
const desktopAuth = runtimeState?.desktopAuth || {};
const targetId = resolveKiroTargetId(state, runtimeState);
const region = resolveRegion(state, runtimeState, targetId);
const email = resolveEmail(state, runtimeState);
const refreshToken = String(desktopAuth.refreshToken || '');
const clientId = cleanString(desktopAuth.clientId);
const clientSecret = String(desktopAuth.clientSecret || '');
assertRequiredField('refreshToken', refreshToken, '缺少桌面授权 refreshToken,无法提交 Kiro Builder ID 贡献。');
assertRequiredField('clientId', clientId, '缺少桌面授权 clientId,无法提交 Kiro Builder ID 贡献。');
assertRequiredField('clientSecret', clientSecret, '缺少桌面授权 clientSecret,无法提交 Kiro Builder ID 贡献。');
assertRequiredField('email', email, '缺少注册邮箱,无法提交 Kiro Builder ID 贡献。');
return {
schemaVersion: 1,
flow: FLOW_ID,
adapter: ADAPTER_ID,
artifact: ARTIFACT_KIND,
account: {
email,
},
credentials: {
refreshToken,
clientId,
clientSecret,
profileArn: cleanString(options.profileArn) || BUILDER_ID_PROFILE_ARN,
authMethod: 'idc',
region,
authRegion: region,
apiRegion: region,
tokenSource: cleanString(desktopAuth.tokenSource) || 'desktop_authorization_code_pkce',
},
metadata: {
targetId,
authorizedAt: Math.max(0, Number(desktopAuth.authorizedAt) || 0),
generatedAt: new Date().toISOString(),
source: 'flowpilot-extension',
},
};
}
function buildSafeArtifactSummary(artifact = {}) {
return {
flow: cleanString(artifact.flow),
adapter: cleanString(artifact.adapter),
artifact: cleanString(artifact.artifact),
email: cleanString(artifact.account?.email),
clientId: cleanString(artifact.credentials?.clientId),
refreshToken: redactSecret(artifact.credentials?.refreshToken),
clientSecret: redactSecret(artifact.credentials?.clientSecret),
region: cleanString(artifact.credentials?.region),
};
}
return {
ADAPTER_ID,
ARTIFACT_KIND,
BUILDER_ID_PROFILE_ARN,
FLOW_ID,
buildKiroBuilderIdArtifact,
buildSafeArtifactSummary,
redactSecret,
};
});
@@ -365,6 +365,7 @@
MAIL_2925_VERIFICATION_INTERVAL_MS = 15000, MAIL_2925_VERIFICATION_INTERVAL_MS = 15000,
MAIL_2925_VERIFICATION_MAX_ATTEMPTS = 15, MAIL_2925_VERIFICATION_MAX_ATTEMPTS = 15,
isTabAlive = async () => false, isTabAlive = async () => false,
maybeSubmitFlowContribution = async () => null,
KIRO_REGISTER_INJECT_FILES = null, KIRO_REGISTER_INJECT_FILES = null,
KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = null, KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = null,
pollCloudflareTempEmailVerificationCode = null, pollCloudflareTempEmailVerificationCode = null,
@@ -477,6 +478,15 @@
}, },
}); });
await log('步骤 8:桌面授权回调已捕获,Token 换取成功。', 'ok', nodeId); await log('步骤 8:桌面授权回调已捕获,Token 换取成功。', 'ok', nodeId);
await maybeSubmitFlowContribution({
...currentState,
...payload,
}, {
nodeId,
trigger: 'kiro-step-8',
}).catch(async (error) => {
await log(`步骤 8:Kiro 公共贡献提交异常,已保留桌面授权结果:${getErrorMessage(error)}`, 'warn', nodeId);
});
await completeNodeFromBackground(nodeId, payload); await completeNodeFromBackground(nodeId, payload);
return payload; return payload;
} }
+44 -32
View File
@@ -67,7 +67,7 @@
} }
return Boolean(state?.phoneVerificationEnabled) return Boolean(state?.phoneVerificationEnabled)
&& !Boolean(state?.plusModeEnabled) && !Boolean(state?.plusModeEnabled)
&& !Boolean(state?.contributionMode); && !Boolean(state?.accountContributionEnabled);
}, },
resolveSignupMethod = (state = {}) => { resolveSignupMethod = (state = {}) => {
const method = normalizeSignupMethod(state?.signupMethod); const method = normalizeSignupMethod(state?.signupMethod);
@@ -149,6 +149,7 @@
patchMail2925Account, patchMail2925Account,
patchHotmailAccount, patchHotmailAccount,
pollContributionStatus, pollContributionStatus,
submitFlowContribution,
registerTab, registerTab,
requestStop, requestStop,
probeIpProxyExit, probeIpProxyExit,
@@ -162,7 +163,7 @@
setCurrentPayPalAccount, setCurrentPayPalAccount,
setCurrentMail2925Account, setCurrentMail2925Account,
setCurrentHotmailAccount, setCurrentHotmailAccount,
setContributionMode, setAccountContributionMode,
setEmailState, setEmailState,
setEmailStateSilently, setEmailStateSilently,
persistRegistrationEmailState, persistRegistrationEmailState,
@@ -179,7 +180,7 @@
setNodeStatus, setNodeStatus,
skipAutoRunCountdown, skipAutoRunCountdown,
skipNode, skipNode,
startContributionFlow, startFlowContribution,
startAutoRunLoop, startAutoRunLoop,
deleteMail2925Account, deleteMail2925Account,
deleteMail2925Accounts, deleteMail2925Accounts,
@@ -1154,59 +1155,61 @@
return await setFreeReusablePhoneActivation(message.payload || {}); return await setFreeReusablePhoneActivation(message.payload || {});
} }
case 'SET_CONTRIBUTION_MODE': { case 'SET_ACCOUNT_CONTRIBUTION_MODE': {
const enabled = Boolean(message.payload?.enabled); const enabled = Boolean(message.payload?.enabled);
const state = await ensureManualInteractionAllowed(enabled ? '进入贡献模式' : '退出贡献模式'); const state = await ensureManualInteractionAllowed(enabled ? '进入账号贡献' : '退出账号贡献');
if (Object.values(state.nodeStatuses || {}).some((status) => status === 'running')) { if (Object.values(state.nodeStatuses || {}).some((status) => status === 'running')) {
throw new Error(enabled ? '当前有步骤正在执行,无法进入贡献模式。' : '当前有步骤正在执行,无法退出贡献模式。'); throw new Error(enabled ? '当前有步骤正在执行,无法进入账号贡献。' : '当前有步骤正在执行,无法退出账号贡献。');
} }
if (typeof setContributionMode !== 'function') { if (typeof setAccountContributionMode !== 'function') {
throw new Error('贡献模式切换能力未接入。'); throw new Error('账号贡献切换能力未接入。');
} }
return { return {
ok: true, ok: true,
state: await setContributionMode(enabled), state: await setAccountContributionMode(enabled, {
adapterId: message.payload?.adapterId,
flowId: message.payload?.flowId || state?.activeFlowId || state?.flowId,
}),
}; };
} }
case 'START_CONTRIBUTION_FLOW': { case 'START_FLOW_CONTRIBUTION': {
const state = await ensureManualInteractionAllowed('开始贡献'); const state = await ensureManualInteractionAllowed('开始贡献');
if (Object.values(state.nodeStatuses || {}).some((status) => status === 'running')) { if (Object.values(state.nodeStatuses || {}).some((status) => status === 'running')) {
throw new Error('当前有步骤正在执行,无法开始贡献流程。'); throw new Error('当前有步骤正在执行,无法开始贡献流程。');
} }
if (typeof startContributionFlow !== 'function') { if (!state?.accountContributionEnabled) {
throw new Error('请先进入账号贡献。');
}
if (typeof startFlowContribution !== 'function') {
throw new Error('贡献 OAuth 流程尚未接入。'); throw new Error('贡献 OAuth 流程尚未接入。');
} }
return { return {
ok: true, ok: true,
state: await startContributionFlow({ state: await startFlowContribution({
nickname: message.payload?.nickname, nickname: message.payload?.nickname,
qq: message.payload?.qq, qq: message.payload?.qq,
}), }),
}; };
} }
case 'SET_CONTRIBUTION_PROFILE': { case 'SUBMIT_FLOW_CONTRIBUTION': {
const state = await getState(); const state = await getState();
if (!state?.contributionMode) { if (!state?.accountContributionEnabled) {
throw new Error('请先进入贡献模式。'); throw new Error('请先进入账号贡献。');
} }
const nickname = String(message.payload?.nickname || '').trim(); if (typeof submitFlowContribution !== 'function') {
const qq = String(message.payload?.qq || '').trim(); throw new Error('贡献提交能力尚未接入。');
if (qq && !/^\d{1,20}$/.test(qq)) {
throw new Error('QQ 只能填写数字,且长度不能超过 20 位。');
} }
await setState({
contributionNickname: nickname,
contributionQq: qq,
});
return { return {
ok: true, ok: true,
state: await getState(), state: await submitFlowContribution(message.payload?.callbackUrl, {
reason: message.payload?.reason || 'sidepanel_submit',
}),
}; };
} }
case 'POLL_CONTRIBUTION_STATUS': { case 'POLL_FLOW_CONTRIBUTION_STATUS': {
if (typeof pollContributionStatus !== 'function') { if (typeof pollContributionStatus !== 'function') {
throw new Error('贡献状态轮询能力尚未接入。'); throw new Error('贡献状态轮询能力尚未接入。');
} }
@@ -1285,8 +1288,11 @@
if (message.source === 'sidepanel') { if (message.source === 'sidepanel') {
await lockAutomationWindowFromMessage(message, sender); await lockAutomationWindowFromMessage(message, sender);
} }
if (Boolean(message.payload?.contributionMode) && typeof setContributionMode === 'function') { if (Boolean(message.payload?.accountContributionEnabled) && typeof setAccountContributionMode === 'function') {
await setContributionMode(true); await setAccountContributionMode(true, {
adapterId: message.payload?.contributionAdapterId,
flowId: message.payload?.activeFlowId || message.payload?.flowId,
});
if (typeof setState === 'function') { if (typeof setState === 'function') {
const contributionNickname = String(message.payload?.contributionNickname || '').trim(); const contributionNickname = String(message.payload?.contributionNickname || '').trim();
const contributionQq = String(message.payload?.contributionQq || '').trim(); const contributionQq = String(message.payload?.contributionQq || '').trim();
@@ -1325,8 +1331,11 @@
if (message.source === 'sidepanel') { if (message.source === 'sidepanel') {
await lockAutomationWindowFromMessage(message, sender); await lockAutomationWindowFromMessage(message, sender);
} }
if (Boolean(message.payload?.contributionMode) && typeof setContributionMode === 'function') { if (Boolean(message.payload?.accountContributionEnabled) && typeof setAccountContributionMode === 'function') {
await setContributionMode(true); await setAccountContributionMode(true, {
adapterId: message.payload?.contributionAdapterId,
flowId: message.payload?.activeFlowId || message.payload?.flowId,
});
if (typeof setState === 'function') { if (typeof setState === 'function') {
const contributionNickname = String(message.payload?.contributionNickname || '').trim(); const contributionNickname = String(message.payload?.contributionNickname || '').trim();
const contributionQq = String(message.payload?.contributionQq || '').trim(); const contributionQq = String(message.payload?.contributionQq || '').trim();
@@ -1444,7 +1453,7 @@
|| Object.prototype.hasOwnProperty.call(updates, 'signupMethod') || Object.prototype.hasOwnProperty.call(updates, 'signupMethod')
|| Object.prototype.hasOwnProperty.call(updates, 'panelMode') || Object.prototype.hasOwnProperty.call(updates, 'panelMode')
|| Object.prototype.hasOwnProperty.call(updates, 'activeFlowId') || Object.prototype.hasOwnProperty.call(updates, 'activeFlowId')
|| Object.prototype.hasOwnProperty.call(updates, 'contributionMode') || Object.prototype.hasOwnProperty.call(updates, 'accountContributionEnabled')
) { ) {
updates.signupMethod = resolveSignupMethod(nextSignupState); updates.signupMethod = resolveSignupMethod(nextSignupState);
} }
@@ -1576,8 +1585,11 @@
error: error?.message || String(error || '代理应用失败'), error: error?.message || String(error || '代理应用失败'),
})); }));
} }
if (Boolean(currentState?.contributionMode) && typeof setContributionMode === 'function') { if (Boolean(currentState?.accountContributionEnabled) && typeof setAccountContributionMode === 'function') {
await setContributionMode(true); await setAccountContributionMode(true, {
adapterId: currentState?.contributionAdapterId,
flowId: currentState?.activeFlowId || currentState?.flowId,
});
} }
if (Object.keys(stateUpdates).length > 0 && typeof broadcastDataUpdate === 'function') { if (Object.keys(stateUpdates).length > 0 && typeof broadcastDataUpdate === 'function') {
broadcastDataUpdate(stateUpdates); broadcastDataUpdate(stateUpdates);
+1 -1
View File
@@ -59,7 +59,7 @@
return normalizeStep7SignupMethod(state?.signupMethod) === 'phone' return normalizeStep7SignupMethod(state?.signupMethod) === 'phone'
&& Boolean(state?.phoneVerificationEnabled) && Boolean(state?.phoneVerificationEnabled)
&& !Boolean(state?.plusModeEnabled) && !Boolean(state?.plusModeEnabled)
&& !Boolean(state?.contributionMode); && !Boolean(state?.accountContributionEnabled);
} }
function hasStep7PhoneSignupIdentity(state = {}) { function hasStep7PhoneSignupIdentity(state = {}) {
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1,5 +1,7 @@
# 多注册流程侧边栏能力矩阵 # 多注册流程侧边栏能力矩阵
> 历史说明:本文记录的是多 flow 侧边栏能力矩阵的早期设计背景。2026-05-19 起,账号贡献运行态已从历史 `contributionMode` 升级为 `accountContributionEnabled` + `contributionAdapterId`;后续开发请以 `多Flow资源入口与贡献体系升级开发方案.md` 和当前代码为准,不要继续新增或恢复 `contributionMode` 运行时逻辑。
本文解决的是一个很容易被低估的问题:sidepanel 现在不只是“渲染 UI”,它还承担了大量业务约束。如果未来要支持多个 flow,不能只做动态渲染,还必须把“哪些能力允许显示、允许切换、允许启动”做成统一能力矩阵。 本文解决的是一个很容易被低估的问题:sidepanel 现在不只是“渲染 UI”,它还承担了大量业务约束。如果未来要支持多个 flow,不能只做动态渲染,还必须把“哪些能力允许显示、允许切换、允许启动”做成统一能力矩阵。
## 1. 当前源码里的真实问题 ## 1. 当前源码里的真实问题
@@ -255,4 +257,3 @@ getStepDefinitions({
- 手机号注册能力由多个开关共同决定,当前逻辑分散且重复 - 手机号注册能力由多个开关共同决定,当前逻辑分散且重复
- `contributionMode``panelMode` 容易混淆 - `contributionMode``panelMode` 容易混淆
- 多 flow 之后,步骤列表也必须纳入能力矩阵,而不是继续全局写死 - 多 flow 之后,步骤列表也必须纳入能力矩阵,而不是继续全局写死
+224
View File
@@ -0,0 +1,224 @@
(function attachMultiPageContributionRegistry(root, factory) {
root.MultiPageContributionRegistry = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createContributionRegistryModule(root) {
const flowRegistryApi = root?.MultiPageFlowRegistry || {};
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
const DEFAULT_KIRO_TARGET_ID = flowRegistryApi.DEFAULT_KIRO_TARGET_ID || 'kiro-rs';
const DEFAULT_OPENAI_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_TARGET_ID || 'cpa';
const ADAPTER_DEFINITIONS = Object.freeze({
'openai-oauth': Object.freeze({
id: 'openai-oauth',
flowId: 'openai',
artifactKind: 'openai-oauth',
trigger: 'interactive-oauth',
label: 'OpenAI OAuth 贡献',
defaultTargetId: DEFAULT_OPENAI_TARGET_ID,
sensitiveFieldPaths: Object.freeze([
'credentials.access_token',
'credentials.refresh_token',
'credentials.id_token',
]),
}),
'openai-codex-file': Object.freeze({
id: 'openai-codex-file',
flowId: 'openai',
artifactKind: 'codex',
trigger: 'manual-upload',
label: 'OpenAI Codex 文件贡献',
defaultTargetId: 'codex2api',
sensitiveFieldPaths: Object.freeze([
'credentials.access_token',
'credentials.refresh_token',
'credentials.id_token',
]),
}),
'openai-sub2api-file': Object.freeze({
id: 'openai-sub2api-file',
flowId: 'openai',
artifactKind: 'sub2api',
trigger: 'manual-upload',
label: 'OpenAI Sub2API 文件贡献',
defaultTargetId: 'sub2api',
sensitiveFieldPaths: Object.freeze([
'credentials.accounts[].credentials.access_token',
'credentials.accounts[].credentials.refresh_token',
'credentials.accounts[].credentials.id_token',
]),
}),
'kiro-builder-id': Object.freeze({
id: 'kiro-builder-id',
flowId: 'kiro',
artifactKind: 'kiro-builder-id',
trigger: 'after-desktop-authorize',
label: 'Kiro Builder ID 贡献',
defaultTargetId: DEFAULT_KIRO_TARGET_ID,
sensitiveFieldPaths: Object.freeze([
'credentials.refreshToken',
'credentials.clientSecret',
'metadata.proxyPassword',
]),
}),
});
const FLOW_ADAPTER_IDS = Object.freeze({
openai: Object.freeze(['openai-oauth', 'openai-codex-file', 'openai-sub2api-file']),
kiro: Object.freeze(['kiro-builder-id']),
});
const CONTRIBUTION_TUTORIAL_ENTRIES = Object.freeze({
openai: Object.freeze({
id: 'openai-contribution-tutorial',
flowId: 'openai',
label: '贡献/使用教程',
portalPath: '/tutorial',
defaultTargetId: DEFAULT_OPENAI_TARGET_ID,
contributionAdapterId: 'openai-oauth',
action: 'open-portal-and-enable-contribution',
}),
kiro: Object.freeze({
id: 'kiro-contribution-tutorial',
flowId: 'kiro',
label: '贡献/使用教程',
portalPath: '/tutorial',
defaultTargetId: DEFAULT_KIRO_TARGET_ID,
contributionAdapterId: 'kiro-builder-id',
action: 'open-portal-and-enable-contribution',
}),
});
function normalizeString(value = '') {
return String(value || '').trim();
}
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
const normalized = normalizeString(value).toLowerCase();
if (normalized && Object.prototype.hasOwnProperty.call(FLOW_ADAPTER_IDS, normalized)) {
return normalized;
}
if (!normalized && typeof flowRegistryApi.normalizeFlowId === 'function') {
return flowRegistryApi.normalizeFlowId(value, fallback);
}
return normalized || normalizeString(fallback).toLowerCase() || DEFAULT_FLOW_ID;
}
function normalizeAdapterId(value = '') {
return normalizeString(value).toLowerCase();
}
function normalizeTargetId(flowId = DEFAULT_FLOW_ID, targetId = '') {
if (typeof flowRegistryApi.normalizeTargetId === 'function') {
return flowRegistryApi.normalizeTargetId(flowId, targetId);
}
const normalizedFlowId = normalizeFlowId(flowId, DEFAULT_FLOW_ID);
const normalizedTargetId = normalizeString(targetId).toLowerCase();
if (normalizedTargetId) {
return normalizedTargetId;
}
return normalizedFlowId === 'kiro' ? DEFAULT_KIRO_TARGET_ID : DEFAULT_OPENAI_TARGET_ID;
}
function cloneAdapter(adapter) {
if (!adapter) {
return null;
}
return {
...adapter,
sensitiveFieldPaths: Array.isArray(adapter.sensitiveFieldPaths)
? adapter.sensitiveFieldPaths.slice()
: [],
};
}
function getAdapterDefinition(adapterId = '') {
return cloneAdapter(ADAPTER_DEFINITIONS[normalizeAdapterId(adapterId)] || null);
}
function getContributionAdapterIds(flowId = DEFAULT_FLOW_ID) {
const normalizedFlowId = normalizeFlowId(flowId, DEFAULT_FLOW_ID);
return (FLOW_ADAPTER_IDS[normalizedFlowId] || []).slice();
}
function getContributionAdapters(flowId = DEFAULT_FLOW_ID) {
return getContributionAdapterIds(flowId)
.map((adapterId) => getAdapterDefinition(adapterId))
.filter(Boolean);
}
function getDefaultContributionAdapterId(flowId = DEFAULT_FLOW_ID) {
return getContributionAdapterIds(flowId)[0] || '';
}
function buildPortalPageUrl(portalBaseUrl = '', portalPath = '/tutorial', params = {}) {
const baseUrl = normalizeString(portalBaseUrl).replace(/\/+$/, '') || 'https://flowpilot.qlhazycoder.top';
const path = normalizeString(portalPath) || '/tutorial';
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
const query = Object.entries(params)
.map(([key, value]) => [normalizeString(key), normalizeString(value)])
.filter(([key, value]) => key && value)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
return `${baseUrl}${normalizedPath}${query ? `?${query}` : ''}`;
}
function getContributionTutorialEntry(flowId = DEFAULT_FLOW_ID, options = {}) {
const normalizedFlowId = normalizeFlowId(flowId, DEFAULT_FLOW_ID);
const definition = CONTRIBUTION_TUTORIAL_ENTRIES[normalizedFlowId] || null;
if (!definition) {
return null;
}
const requestedAdapterId = normalizeAdapterId(options.adapterId);
const adapterId = requestedAdapterId && hasContributionAdapter(normalizedFlowId, requestedAdapterId)
? requestedAdapterId
: definition.contributionAdapterId;
const targetId = normalizeTargetId(
normalizedFlowId,
options.targetId || definition.defaultTargetId
);
return {
...definition,
targetId,
contributionAdapterId: adapterId,
portalUrl: buildPortalPageUrl(options.portalBaseUrl, definition.portalPath, {
flow: normalizedFlowId,
target: targetId,
}),
};
}
function hasContributionAdapter(flowId = DEFAULT_FLOW_ID, adapterId = '') {
const normalizedAdapterId = normalizeAdapterId(adapterId);
return Boolean(normalizedAdapterId) && getContributionAdapterIds(flowId).includes(normalizedAdapterId);
}
function assertPublishedFlowsHaveContributionAdapters(flowIds = undefined) {
const ids = Array.isArray(flowIds)
? flowIds
: (typeof flowRegistryApi.getRegisteredFlowIds === 'function'
? flowRegistryApi.getRegisteredFlowIds()
: Object.keys(FLOW_ADAPTER_IDS));
const missing = ids
.map((flowId) => normalizeString(flowId).toLowerCase())
.filter(Boolean)
.filter((flowId) => getContributionAdapterIds(flowId).length === 0);
if (missing.length) {
throw new Error(`缺少账号贡献适配器:${missing.join(', ')}`);
}
return true;
}
return {
ADAPTER_DEFINITIONS,
CONTRIBUTION_TUTORIAL_ENTRIES,
FLOW_ADAPTER_IDS,
assertPublishedFlowsHaveContributionAdapters,
getContributionTutorialEntry,
getAdapterDefinition,
getContributionAdapterIds,
getContributionAdapters,
getDefaultContributionAdapterId,
hasContributionAdapter,
normalizeAdapterId,
normalizeFlowId,
};
});
+26 -8
View File
@@ -3,6 +3,7 @@
})(typeof self !== 'undefined' ? self : globalThis, function createFlowCapabilitiesModule() { })(typeof self !== 'undefined' ? self : globalThis, function createFlowCapabilitiesModule() {
const rootScope = typeof self !== 'undefined' ? self : globalThis; const rootScope = typeof self !== 'undefined' ? self : globalThis;
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {}; const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
const contributionRegistryApi = rootScope.MultiPageContributionRegistry || {};
const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {}; const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {};
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai'; const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
const DEFAULT_OPENAI_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_TARGET_ID || 'cpa'; const DEFAULT_OPENAI_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_TARGET_ID || 'cpa';
@@ -25,6 +26,9 @@
supportsPhoneVerificationSettings: false, supportsPhoneVerificationSettings: false,
supportsPlusMode: false, supportsPlusMode: false,
supportsContributionMode: false, supportsContributionMode: false,
supportsAccountContribution: false,
supportsOpenAiOAuthContribution: false,
contributionAdapterIds: [],
supportedTargetIds: [], supportedTargetIds: [],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false, supportsOauthTimeoutBudget: false,
@@ -58,7 +62,7 @@
const MODE_SWITCH_RELEVANT_KEYS = Object.freeze([ const MODE_SWITCH_RELEVANT_KEYS = Object.freeze([
'activeFlowId', 'activeFlowId',
'contributionMode', 'accountContributionEnabled',
'panelMode', 'panelMode',
'phoneVerificationEnabled', 'phoneVerificationEnabled',
'plusModeEnabled', 'plusModeEnabled',
@@ -195,6 +199,14 @@
function getFlowCapabilities(flowId) { function getFlowCapabilities(flowId) {
const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId); const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId);
const entry = flowCapabilities[normalizedFlowId] || null; const entry = flowCapabilities[normalizedFlowId] || null;
const registryAdapterIds = typeof contributionRegistryApi.getContributionAdapterIds === 'function'
? contributionRegistryApi.getContributionAdapterIds(normalizedFlowId)
: [];
const contributionAdapterIds = registryAdapterIds.length
? registryAdapterIds
: (Array.isArray(entry?.contributionAdapterIds)
? entry.contributionAdapterIds.map((value) => String(value || '').trim()).filter(Boolean)
: []);
const supportedTargetIds = normalizedFlowId === 'openai' const supportedTargetIds = normalizedFlowId === 'openai'
? normalizeOpenAiTargetList( ? normalizeOpenAiTargetList(
entry?.supportedTargetIds || defaultFlowCapabilities.supportedTargetIds entry?.supportedTargetIds || defaultFlowCapabilities.supportedTargetIds
@@ -206,6 +218,8 @@
...defaultFlowCapabilities, ...defaultFlowCapabilities,
...(entry || {}), ...(entry || {}),
supportedTargetIds, supportedTargetIds,
contributionAdapterIds,
supportsAccountContribution: Boolean(entry?.supportsAccountContribution || contributionAdapterIds.length > 0),
}; };
} }
@@ -332,7 +346,7 @@
: defaultTargetCapabilities; : defaultTargetCapabilities;
const runtimeLocks = { const runtimeLocks = {
autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked), autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked),
contributionMode: activeFlowId === 'openai' && flowState.supportsContributionMode && Boolean(state?.contributionMode), accountContribution: Boolean(flowState.supportsAccountContribution) && Boolean(state?.accountContributionEnabled),
phoneVerificationEnabled: activeFlowId === 'openai' && flowState.supportsPhoneVerificationSettings && Boolean(state?.phoneVerificationEnabled), phoneVerificationEnabled: activeFlowId === 'openai' && flowState.supportsPhoneVerificationSettings && Boolean(state?.phoneVerificationEnabled),
plusModeEnabled: activeFlowId === 'openai' && flowState.supportsPlusMode && Boolean(state?.plusModeEnabled), plusModeEnabled: activeFlowId === 'openai' && flowState.supportsPlusMode && Boolean(state?.plusModeEnabled),
settingsMenuLocked: Boolean(options?.settingsMenuLocked ?? state?.settingsMenuLocked), settingsMenuLocked: Boolean(options?.settingsMenuLocked ?? state?.settingsMenuLocked),
@@ -346,7 +360,7 @@
&& Boolean(targetState.supportsPhoneSignup) && Boolean(targetState.supportsPhoneSignup)
&& runtimeLocks.phoneVerificationEnabled && runtimeLocks.phoneVerificationEnabled
&& !runtimeLocks.plusModeEnabled && !runtimeLocks.plusModeEnabled
&& !runtimeLocks.contributionMode; && !runtimeLocks.accountContribution;
if (canSelectPhoneSignup) { if (canSelectPhoneSignup) {
effectiveSignupMethods.push(SIGNUP_METHOD_PHONE); effectiveSignupMethods.push(SIGNUP_METHOD_PHONE);
} }
@@ -390,7 +404,7 @@
return { return {
activeFlowId, activeFlowId,
canShowContributionMode: activeFlowId === 'openai' && Boolean(flowState.supportsContributionMode), canShowContributionMode: Boolean(flowState.supportsAccountContribution),
canShowLuckmail: activeFlowId === 'openai' && Boolean(flowState.supportsLuckmail), canShowLuckmail: activeFlowId === 'openai' && Boolean(flowState.supportsLuckmail),
canShowPhoneSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPhoneVerificationSettings), canShowPhoneSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPhoneVerificationSettings),
canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode), canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode),
@@ -459,7 +473,7 @@
message: 'Plus 模式开启时不能使用手机号注册。', message: 'Plus 模式开启时不能使用手机号注册。',
}; };
} }
if (runtimeLocks.contributionMode) { if (runtimeLocks.accountContribution) {
return { return {
code: 'phone_signup_contribution_mode_locked', code: 'phone_signup_contribution_mode_locked',
message: '贡献模式开启时不能使用手机号注册。', message: '贡献模式开启时不能使用手机号注册。',
@@ -494,7 +508,7 @@
}); });
} }
if (Boolean(state?.contributionMode) && !capabilityState.flowCapabilities?.supportsContributionMode) { if (Boolean(state?.accountContributionEnabled) && !capabilityState.flowCapabilities?.supportsAccountContribution) {
errors.push({ errors.push({
code: 'contribution_mode_unsupported', code: 'contribution_mode_unsupported',
message: '当前 flow 不支持贡献模式。', message: '当前 flow 不支持贡献模式。',
@@ -553,8 +567,12 @@
}); });
} }
if (changedKeySet.has('contributionMode') && Boolean(state?.contributionMode) && !flowState.supportsContributionMode) { if (
normalizedUpdates.contributionMode = false; changedKeySet.has('accountContributionEnabled')
&& Boolean(state?.accountContributionEnabled)
&& !flowState.supportsAccountContribution
) {
normalizedUpdates.accountContributionEnabled = false;
errors.push({ errors.push({
code: 'contribution_mode_unsupported', code: 'contribution_mode_unsupported',
message: '当前 flow 不支持贡献模式。', message: '当前 flow 不支持贡献模式。',
+8
View File
@@ -15,6 +15,9 @@
supportsPhoneVerificationSettings: false, supportsPhoneVerificationSettings: false,
supportsPlusMode: false, supportsPlusMode: false,
supportsContributionMode: false, supportsContributionMode: false,
supportsAccountContribution: false,
supportsOpenAiOAuthContribution: false,
contributionAdapterIds: [],
supportedTargetIds: [], supportedTargetIds: [],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false, supportsOauthTimeoutBudget: false,
@@ -44,6 +47,9 @@
supportsPhoneVerificationSettings: true, supportsPhoneVerificationSettings: true,
supportsPlusMode: true, supportsPlusMode: true,
supportsContributionMode: true, supportsContributionMode: true,
supportsAccountContribution: true,
supportsOpenAiOAuthContribution: true,
contributionAdapterIds: ['openai-oauth', 'openai-codex-file', 'openai-sub2api-file'],
supportedTargetIds: [...OPENAI_TARGET_IDS], supportedTargetIds: [...OPENAI_TARGET_IDS],
supportsLuckmail: true, supportsLuckmail: true,
supportsOauthTimeoutBudget: true, supportsOauthTimeoutBudget: true,
@@ -203,6 +209,8 @@
services: ['account', 'email', 'proxy'], services: ['account', 'email', 'proxy'],
capabilities: { capabilities: {
...DEFAULT_FLOW_CAPABILITIES, ...DEFAULT_FLOW_CAPABILITIES,
supportsAccountContribution: true,
contributionAdapterIds: ['kiro-builder-id'],
supportedTargetIds: [DEFAULT_KIRO_TARGET_ID], supportedTargetIds: [DEFAULT_KIRO_TARGET_ID],
stepDefinitionMode: 'kiro', stepDefinitionMode: 'kiro',
}, },
@@ -1,9 +1,31 @@
(() => { (() => {
const PORTAL_BASE_URL = 'https://flowpilot.qlhazycoder.top'; const PORTAL_BASE_URL = 'https://flowpilot.qlhazycoder.top';
const CONTENT_SUMMARY_API_URL = `${PORTAL_BASE_URL}/api/content-summary`; const CONTENT_SUMMARY_API_URL = `${PORTAL_BASE_URL}/api/content-summary`;
const CACHE_KEY = 'multipage-contribution-content-summary-v1'; const CACHE_KEY_PREFIX = 'multipage-contribution-content-summary-v2';
const FETCH_TIMEOUT_MS = 6000; const FETCH_TIMEOUT_MS = 6000;
function normalizeScopeId(value = '', fallback = '') {
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '-') || fallback;
}
function normalizeScope(options = {}) {
const flowId = normalizeScopeId(options.flowId || options.flow || options.activeFlowId, 'openai');
const targetFallback = flowId === 'kiro' ? 'kiro-rs' : 'cpa';
const targetId = normalizeScopeId(options.targetId || options.target || options.activeTargetId, targetFallback);
return { flowId, targetId };
}
function getCacheKey(scope = normalizeScope()) {
return `${CACHE_KEY_PREFIX}:${scope.flowId}:${scope.targetId}`;
}
function buildSummaryApiUrl(scope = normalizeScope()) {
const url = new URL(CONTENT_SUMMARY_API_URL);
url.searchParams.set('flow', scope.flowId);
url.searchParams.set('target', scope.targetId);
return url.toString();
}
function sanitizeItem(item = {}) { function sanitizeItem(item = {}) {
return { return {
slug: String(item?.slug || '').trim(), slug: String(item?.slug || '').trim(),
@@ -14,13 +36,20 @@
isVisible: Boolean(item?.is_visible ?? item?.isVisible), isVisible: Boolean(item?.is_visible ?? item?.isVisible),
updatedAt: String(item?.updated_at ?? item?.updatedAt ?? '').trim(), updatedAt: String(item?.updated_at ?? item?.updatedAt ?? '').trim(),
updatedAtDisplay: String(item?.updated_at_display ?? item?.updatedAtDisplay ?? '').trim(), updatedAtDisplay: String(item?.updated_at_display ?? item?.updatedAtDisplay ?? '').trim(),
flowId: normalizeScopeId(item?.flow_id ?? item?.flowId, ''),
targetId: normalizeScopeId(item?.target_id ?? item?.targetId, ''),
scope: String(item?.scope || '').trim(),
}; };
} }
function buildSnapshot(payload = {}) { function buildSnapshot(payload = {}, scope = normalizeScope()) {
const items = Array.isArray(payload?.items) const items = Array.isArray(payload?.items)
? payload.items.map(sanitizeItem).filter((item) => item.slug) ? payload.items.map(sanitizeItem).filter((item) => item.slug)
: []; : [];
const responseScope = normalizeScope({
flowId: payload?.flow_id || payload?.flowId || scope.flowId,
targetId: payload?.target_id || payload?.targetId || scope.targetId,
});
const promptVersion = String(payload?.prompt_version || '').trim(); const promptVersion = String(payload?.prompt_version || '').trim();
const latestUpdatedAt = String(payload?.latest_updated_at || '').trim(); const latestUpdatedAt = String(payload?.latest_updated_at || '').trim();
const latestUpdatedAtDisplay = String(payload?.latest_updated_at_display || '').trim(); const latestUpdatedAtDisplay = String(payload?.latest_updated_at_display || '').trim();
@@ -33,15 +62,17 @@
latestUpdatedAt, latestUpdatedAt,
latestUpdatedAtDisplay, latestUpdatedAtDisplay,
items, items,
flowId: responseScope.flowId,
targetId: responseScope.targetId,
portalUrl: PORTAL_BASE_URL, portalUrl: PORTAL_BASE_URL,
apiUrl: CONTENT_SUMMARY_API_URL, apiUrl: buildSummaryApiUrl(responseScope),
checkedAt: Date.now(), checkedAt: Date.now(),
}; };
} }
function readCache() { function readCache(scope = normalizeScope()) {
try { try {
const raw = localStorage.getItem(CACHE_KEY); const raw = localStorage.getItem(getCacheKey(scope));
if (!raw) { if (!raw) {
return null; return null;
} }
@@ -57,7 +88,9 @@
has_visible_updates: parsed.hasVisibleUpdates, has_visible_updates: parsed.hasVisibleUpdates,
latest_updated_at: parsed.latestUpdatedAt, latest_updated_at: parsed.latestUpdatedAt,
latest_updated_at_display: parsed.latestUpdatedAtDisplay, latest_updated_at_display: parsed.latestUpdatedAtDisplay,
}); flow_id: parsed.flowId,
target_id: parsed.targetId,
}, scope);
if (!Number.isFinite(parsed.checkedAt)) { if (!Number.isFinite(parsed.checkedAt)) {
return snapshot; return snapshot;
} }
@@ -68,20 +101,21 @@
} }
} }
function writeCache(snapshot) { function writeCache(snapshot, scope = normalizeScope(snapshot)) {
try { try {
localStorage.setItem(CACHE_KEY, JSON.stringify(snapshot)); localStorage.setItem(getCacheKey(scope), JSON.stringify(snapshot));
} catch (error) { } catch (error) {
// Ignore cache write failures. // Ignore cache write failures.
} }
} }
async function fetchContentSummary() { async function fetchContentSummary(options = {}) {
const scope = normalizeScope(options);
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
try { try {
const response = await fetch(CONTENT_SUMMARY_API_URL, { const response = await fetch(buildSummaryApiUrl(scope), {
method: 'GET', method: 'GET',
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
@@ -99,8 +133,8 @@
throw new Error('内容摘要返回格式异常'); throw new Error('内容摘要返回格式异常');
} }
const snapshot = buildSnapshot(payload); const snapshot = buildSnapshot(payload, scope);
writeCache(snapshot); writeCache(snapshot, scope);
return snapshot; return snapshot;
} catch (error) { } catch (error) {
if (error?.name === 'AbortError') { if (error?.name === 'AbortError') {
@@ -112,11 +146,12 @@
} }
} }
async function getContentUpdateSnapshot() { async function getContentUpdateSnapshot(options = {}) {
const scope = normalizeScope(options);
try { try {
return await fetchContentSummary(); return await fetchContentSummary(scope);
} catch (error) { } catch (error) {
const cached = readCache(); const cached = readCache(scope);
if (cached) { if (cached) {
return { return {
...cached, ...cached,
@@ -132,8 +167,10 @@
latestUpdatedAt: '', latestUpdatedAt: '',
latestUpdatedAtDisplay: '', latestUpdatedAtDisplay: '',
items: [], items: [],
flowId: scope.flowId,
targetId: scope.targetId,
portalUrl: PORTAL_BASE_URL, portalUrl: PORTAL_BASE_URL,
apiUrl: CONTENT_SUMMARY_API_URL, apiUrl: buildSummaryApiUrl(scope),
checkedAt: Date.now(), checkedAt: Date.now(),
errorMessage: error?.message || '内容摘要获取失败', errorMessage: error?.message || '内容摘要获取失败',
}; };
@@ -141,6 +178,7 @@
} }
window.SidepanelContributionContentService = { window.SidepanelContributionContentService = {
buildSummaryApiUrl,
getContentUpdateSnapshot, getContentUpdateSnapshot,
portalUrl: PORTAL_BASE_URL, portalUrl: PORTAL_BASE_URL,
apiUrl: CONTENT_SUMMARY_API_URL, apiUrl: CONTENT_SUMMARY_API_URL,
+138 -42
View File
@@ -81,6 +81,14 @@
} }
function getContributionSourceLabel(currentState = getLatestState()) { function getContributionSourceLabel(currentState = getLatestState()) {
if (getActiveFlowId(currentState) !== 'openai') {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const registry = rootScope.MultiPageContributionRegistry || {};
const adapter = typeof registry.getAdapterDefinition === 'function'
? registry.getAdapterDefinition(currentState.contributionAdapterId || '', { flowId: getActiveFlowId(currentState) })
: null;
return normalizeString(adapter?.label) || '账号贡献';
}
return getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API ? 'SUB2API' : 'CPA'; return getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API ? 'SUB2API' : 'CPA';
} }
@@ -88,12 +96,54 @@
return normalizeString(currentState.activeFlowId || currentState.flowId).toLowerCase() || 'openai'; return normalizeString(currentState.activeFlowId || currentState.flowId).toLowerCase() || 'openai';
} }
function getActiveTargetId(currentState = getLatestState()) {
const activeFlowId = getActiveFlowId(currentState);
if (activeFlowId === 'kiro') {
return normalizeString(currentState.kiroTargetId || currentState.targetId || 'kiro-rs').toLowerCase() || 'kiro-rs';
}
return normalizeString(currentState.openaiIntegrationTargetId || currentState.panelMode || currentState.targetId || 'cpa').toLowerCase() || 'cpa';
}
function getContributionTutorialEntry(currentState = getLatestState()) {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const registry = rootScope.MultiPageContributionRegistry || {};
const activeFlowId = getActiveFlowId(currentState);
if (typeof registry.getContributionTutorialEntry === 'function') {
return registry.getContributionTutorialEntry(activeFlowId, {
adapterId: currentState.contributionAdapterId,
portalBaseUrl: contributionPortalUrl,
targetId: getActiveTargetId(currentState),
});
}
return {
flowId: activeFlowId,
targetId: getActiveTargetId(currentState),
contributionAdapterId: normalizeString(currentState.contributionAdapterId),
portalUrl: normalizeString(contributionPortalUrl),
};
}
function getContributionEntryAdapterId(currentState = getLatestState()) {
return normalizeString(getContributionTutorialEntry(currentState)?.contributionAdapterId);
}
function isContributionModeAvailable(currentState = getLatestState()) { function isContributionModeAvailable(currentState = getLatestState()) {
return getActiveFlowId(currentState) === 'openai'; const rootScope = typeof window !== 'undefined' ? window : globalThis;
const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
defaultFlowId: 'openai',
}) || null;
if (registry?.resolveSidepanelCapabilities) {
return Boolean(registry.resolveSidepanelCapabilities({
activeFlowId: getActiveFlowId(currentState),
panelMode: currentState?.panelMode,
state: currentState,
})?.canShowContributionMode);
}
return Boolean(currentState?.supportsAccountContribution || getActiveFlowId(currentState) === 'openai');
} }
function isContributionModeEnabled(currentState = getLatestState()) { function isContributionModeEnabled(currentState = getLatestState()) {
return isContributionModeAvailable(currentState) && Boolean(currentState.contributionMode); return isContributionModeAvailable(currentState) && Boolean(currentState.accountContributionEnabled);
} }
function hasActiveContributionSession(currentState = getLatestState()) { function hasActiveContributionSession(currentState = getLatestState()) {
@@ -127,10 +177,10 @@
dom.btnContributionMode.title = '当前 flow 不支持贡献模式'; dom.btnContributionMode.title = '当前 flow 不支持贡献模式';
return; return;
} }
dom.btnContributionMode.disabled = enabled || blocked; dom.btnContributionMode.disabled = actionInFlight;
dom.btnContributionMode.title = blocked dom.btnContributionMode.title = enabled
? '当前流程运行中,暂时不能切换贡献模式' ? '打开当前 flow 教程;当前已在贡献模式'
: (enabled ? '当前已在贡献模式' : '进入贡献模式'); : (blocked ? '打开当前 flow 教程;当前流程运行中暂时不能进入贡献模式' : '打开当前 flow 教程并进入贡献模式');
} }
function stopPolling() { function stopPolling() {
@@ -163,6 +213,23 @@
} }
function getOauthStatusText(currentState = getLatestState()) { function getOauthStatusText(currentState = getLatestState()) {
if (getActiveFlowId(currentState) !== 'openai') {
const flowRuntime = getCurrentFlowContributionRuntime(currentState);
const status = normalizeString(flowRuntime.status).toLowerCase();
if (status === 'submitting') {
return '正在提交账号产物';
}
if (status === 'submitted') {
return '账号产物已提交';
}
if (status === 'skipped') {
return '账号产物未就绪';
}
if (status === 'error') {
return '账号产物提交失败';
}
return isContributionModeEnabled(currentState) ? '等待账号产物' : '未开启贡献模式';
}
const status = normalizeStatus(currentState.contributionStatus); const status = normalizeStatus(currentState.contributionStatus);
const hasAuthUrl = Boolean(normalizeString(currentState.contributionAuthUrl)); const hasAuthUrl = Boolean(normalizeString(currentState.contributionAuthUrl));
if (!normalizeString(currentState.contributionSessionId) || !hasAuthUrl) { if (!normalizeString(currentState.contributionSessionId) || !hasAuthUrl) {
@@ -184,6 +251,10 @@
} }
function getCallbackStatusText(currentState = getLatestState()) { function getCallbackStatusText(currentState = getLatestState()) {
if (getActiveFlowId(currentState) !== 'openai') {
const flowRuntime = getCurrentFlowContributionRuntime(currentState);
return normalizeString(flowRuntime.lastMessage || flowRuntime.error) || '账号产物就绪后会自动提交';
}
const status = normalizeCallbackStatus(currentState.contributionCallbackStatus); const status = normalizeCallbackStatus(currentState.contributionCallbackStatus);
switch (status) { switch (status) {
case 'captured': case 'captured':
@@ -208,6 +279,9 @@
if (statusMessage) { if (statusMessage) {
return statusMessage; return statusMessage;
} }
if (getActiveFlowId(currentState) !== 'openai') {
return '当前账号将用于支持项目维护。扩展会按当前 flow 的贡献适配器收集并提交账号产物,提交过程不会依赖 OpenAI OAuth 配置。';
}
if (getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API) { if (getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API) {
const groupName = normalizeString(currentState.contributionTargetGroupName) || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME; const groupName = normalizeString(currentState.contributionTargetGroupName) || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME;
return `当前账号将用于支持项目维护。贡献会通过 SUB2API 完成,并固定写入 ${groupName} 分组;如检测到回调地址,扩展会自动提交并等待服务端确认。`; return `当前账号将用于支持项目维护。贡献会通过 SUB2API 完成,并固定写入 ${groupName} 分组;如检测到回调地址,扩展会自动提交并等待服务端确认。`;
@@ -215,11 +289,24 @@
return DEFAULT_COPY; return DEFAULT_COPY;
} }
function getCurrentFlowContributionRuntime(currentState = getLatestState()) {
const runtime = currentState?.flowContributionRuntime;
if (!runtime || typeof runtime !== 'object') {
return {};
}
const flowRuntime = runtime[getActiveFlowId(currentState)];
return flowRuntime && typeof flowRuntime === 'object' ? flowRuntime : {};
}
function getContributionPortalPageUrl() { function getContributionPortalPageUrl() {
return normalizeString(contributionPortalUrl); return normalizeString(getContributionTutorialEntry()?.portalUrl || contributionPortalUrl);
} }
function getContributionUploadPageUrl() { function getContributionUploadPageUrl() {
const currentState = getLatestState();
if (getActiveFlowId(currentState) !== 'openai') {
return normalizeString(getContributionTutorialEntry(currentState)?.portalUrl || contributionPortalUrl);
}
return normalizeString(contributionUploadUrl); return normalizeString(contributionUploadUrl);
} }
@@ -240,28 +327,27 @@
} }
async function syncContributionProfile(partial = {}) { async function syncContributionProfile(partial = {}) {
const payload = { const nickname = normalizeString(partial.nickname);
nickname: normalizeString(partial.nickname), const qq = normalizeString(partial.qq);
qq: normalizeString(partial.qq), if (qq && !/^\d{1,20}$/.test(qq)) {
}; throw new Error('QQ 只能填写数字,且长度不能超过 20 位。');
const response = await runtime.sendMessage({ }
type: 'SET_CONTRIBUTION_PROFILE', helpers.applySettingsState?.({
source: 'sidepanel', ...getLatestState(),
payload, contributionNickname: nickname,
contributionQq: qq,
}); });
if (response?.error) {
throw new Error(response.error);
}
if (response?.state) {
helpers.applySettingsState?.(response.state);
}
} }
async function requestContributionMode(enabled) { async function requestContributionMode(enabled) {
const response = await runtime.sendMessage({ const response = await runtime.sendMessage({
type: 'SET_CONTRIBUTION_MODE', type: 'SET_ACCOUNT_CONTRIBUTION_MODE',
source: 'sidepanel', source: 'sidepanel',
payload: { enabled: Boolean(enabled) }, payload: {
enabled: Boolean(enabled),
flowId: getActiveFlowId(),
adapterId: getContributionEntryAdapterId(),
},
}); });
if (response?.error) { if (response?.error) {
@@ -287,7 +373,7 @@
pollInFlight = true; pollInFlight = true;
try { try {
const response = await runtime.sendMessage({ const response = await runtime.sendMessage({
type: 'POLL_CONTRIBUTION_STATUS', type: 'POLL_FLOW_CONTRIBUTION_STATUS',
source: 'sidepanel', source: 'sidepanel',
payload: { payload: {
reason: options.reason || 'sidepanel_poll', reason: options.reason || 'sidepanel_poll',
@@ -312,7 +398,7 @@
} }
} }
async function startContributionFlow() { async function startAccountContributionFlow() {
if (typeof helpers.startContributionAutoRun !== 'function') { if (typeof helpers.startContributionAutoRun !== 'function') {
throw new Error('贡献模式尚未接入主自动流程启动能力。'); throw new Error('贡献模式尚未接入主自动流程启动能力。');
} }
@@ -323,7 +409,6 @@
throw new Error('QQ 只能填写数字,且长度不能超过 20 位。'); throw new Error('QQ 只能填写数字,且长度不能超过 20 位。');
} }
await syncContributionProfile(profile); await syncContributionProfile(profile);
const started = await helpers.startContributionAutoRun(); const started = await helpers.startContributionAutoRun();
if (!started) { if (!started) {
return; return;
@@ -348,28 +433,26 @@
const currentState = getLatestState(); const currentState = getLatestState();
const available = isContributionModeAvailable(currentState); const available = isContributionModeAvailable(currentState);
const enabled = isContributionModeEnabled(currentState); const enabled = isContributionModeEnabled(currentState);
const activeFlowId = getActiveFlowId(currentState);
const blocked = available ? isModeSwitchBlocked() : false; const blocked = available ? isModeSwitchBlocked() : false;
const activeElement = typeof document !== 'undefined' ? document.activeElement : null; const activeElement = typeof document !== 'undefined' ? document.activeElement : null;
const sourceLabel = available ? getContributionSourceLabel(currentState) : ''; const sourceLabel = available ? getContributionSourceLabel(currentState) : '';
if (enabled && dom.selectPanelMode) { if (enabled && activeFlowId === 'openai' && dom.selectPanelMode) {
dom.selectPanelMode.value = getContributionSource(currentState); dom.selectPanelMode.value = getContributionSource(currentState);
} }
helpers.updatePanelModeUI?.(); helpers.updatePanelModeUI?.();
helpers.updateAccountRunHistorySettingsUI?.(); helpers.updateAccountRunHistorySettingsUI?.();
if (dom.contributionModePanel) { if (dom.accountContributionPanel) {
dom.contributionModePanel.hidden = !available || !enabled; dom.accountContributionPanel.hidden = !available || !enabled;
} }
if (dom.contributionModeText) { if (dom.accountContributionText) {
dom.contributionModeText.textContent = getSummaryText({ dom.accountContributionText.textContent = getSummaryText(currentState);
contributionSource: currentState.contributionSource,
contributionTargetGroupName: currentState.contributionTargetGroupName,
});
} }
if (dom.contributionModeBadge) { if (dom.accountContributionBadge) {
dom.contributionModeBadge.textContent = enabled ? sourceLabel : ''; dom.accountContributionBadge.textContent = enabled ? sourceLabel : '';
} }
if (dom.inputContributionNickname && activeElement !== dom.inputContributionNickname) { if (dom.inputContributionNickname && activeElement !== dom.inputContributionNickname) {
const nextNickname = normalizeString(currentState.contributionNickname); const nextNickname = normalizeString(currentState.contributionNickname);
@@ -386,18 +469,24 @@
if (dom.contributionOauthStatus) { if (dom.contributionOauthStatus) {
dom.contributionOauthStatus.textContent = getOauthStatusText(currentState); dom.contributionOauthStatus.textContent = getOauthStatusText(currentState);
} }
if (dom.contributionPrimaryStatusLabel) {
dom.contributionPrimaryStatusLabel.textContent = activeFlowId === 'openai' ? 'OAUTH' : '账号产物';
}
if (dom.contributionCallbackStatus) { if (dom.contributionCallbackStatus) {
dom.contributionCallbackStatus.textContent = getCallbackStatusText(currentState); dom.contributionCallbackStatus.textContent = getCallbackStatusText(currentState);
} }
if (dom.contributionModeSummary) { if (dom.contributionSecondaryStatusLabel) {
dom.contributionModeSummary.textContent = getSummaryText(currentState); dom.contributionSecondaryStatusLabel.textContent = activeFlowId === 'openai' ? '回调' : '提交';
}
if (dom.accountContributionSummary) {
dom.accountContributionSummary.textContent = getSummaryText(currentState);
} }
syncContributionRows(enabled); syncContributionRows(enabled && activeFlowId === 'openai');
syncContributionButton(enabled, blocked, available); syncContributionButton(enabled, blocked, available);
if (dom.selectPanelMode) { if (dom.selectPanelMode) {
dom.selectPanelMode.disabled = available && enabled; dom.selectPanelMode.disabled = activeFlowId === 'openai' && available && enabled;
} }
if (dom.btnStartContribution) { if (dom.btnStartContribution) {
@@ -406,6 +495,7 @@
if (dom.btnOpenContributionUpload) { if (dom.btnOpenContributionUpload) {
dom.btnOpenContributionUpload.disabled = !available; dom.btnOpenContributionUpload.disabled = !available;
dom.btnOpenContributionUpload.textContent = activeFlowId === 'openai' ? '已有认证文件?前往上传' : '查看当前 flow 贡献说明';
} }
if (dom.btnExitContributionMode) { if (dom.btnExitContributionMode) {
@@ -441,7 +531,13 @@
} }
render(); render();
try { try {
await enterContributionMode(); if (isContributionModeEnabled()) {
helpers.showToast?.('已打开当前 flow 教程。', 'info', 1800);
} else if (isModeSwitchBlocked()) {
helpers.showToast?.('已打开当前 flow 教程;当前流程运行中,暂时不能进入贡献模式。', 'warning', 2200);
} else {
await enterContributionMode();
}
} catch (error) { } catch (error) {
helpers.showToast?.(error.message, 'error'); helpers.showToast?.(error.message, 'error');
} finally { } finally {
@@ -457,7 +553,7 @@
actionInFlight = true; actionInFlight = true;
render(); render();
try { try {
await startContributionFlow(); await startAccountContributionFlow();
} catch (error) { } catch (error) {
helpers.showToast?.(error.message, 'error'); helpers.showToast?.(error.message, 'error');
} finally { } finally {
+3 -2
View File
@@ -152,11 +152,11 @@
</div> </div>
<div class="contribution-mode-status-grid"> <div class="contribution-mode-status-grid">
<div class="contribution-mode-status-card"> <div class="contribution-mode-status-card">
<span class="contribution-mode-status-label">OAUTH</span> <span id="contribution-primary-status-label" class="contribution-mode-status-label">OAUTH</span>
<span id="contribution-oauth-status" class="contribution-mode-status-value">未生成登录地址</span> <span id="contribution-oauth-status" class="contribution-mode-status-value">未生成登录地址</span>
</div> </div>
<div class="contribution-mode-status-card"> <div class="contribution-mode-status-card">
<span class="contribution-mode-status-label">回调</span> <span id="contribution-secondary-status-label" class="contribution-mode-status-label">回调</span>
<span id="contribution-callback-status" class="contribution-mode-status-value">等待回调</span> <span id="contribution-callback-status" class="contribution-mode-status-value">等待回调</span>
</div> </div>
</div> </div>
@@ -1857,6 +1857,7 @@
<script src="../luckmail-utils.js"></script> <script src="../luckmail-utils.js"></script>
<script src="../yyds-mail-utils.js"></script> <script src="../yyds-mail-utils.js"></script>
<script src="../shared/flow-registry.js"></script> <script src="../shared/flow-registry.js"></script>
<script src="../shared/contribution-registry.js"></script>
<script src="../shared/settings-schema.js"></script> <script src="../shared/settings-schema.js"></script>
<script src="../shared/flow-capabilities.js"></script> <script src="../shared/flow-capabilities.js"></script>
<script src="../data/step-definitions.js"></script> <script src="../data/step-definitions.js"></script>
+87 -51
View File
@@ -36,14 +36,16 @@ const btnIgnoreRelease = document.getElementById('btn-ignore-release');
const btnOpenRelease = document.getElementById('btn-open-release'); const btnOpenRelease = document.getElementById('btn-open-release');
const settingsCard = document.getElementById('settings-card'); const settingsCard = document.getElementById('settings-card');
const selectFlow = document.getElementById('select-flow'); const selectFlow = document.getElementById('select-flow');
const contributionModePanel = document.getElementById('contribution-mode-panel'); const accountContributionPanel = document.getElementById('contribution-mode-panel');
const contributionModeBadge = document.getElementById('contribution-mode-badge'); const accountContributionBadge = document.getElementById('contribution-mode-badge');
const contributionModeText = document.getElementById('contribution-mode-text'); const accountContributionText = document.getElementById('contribution-mode-text');
const inputContributionNickname = document.getElementById('input-contribution-nickname'); const inputContributionNickname = document.getElementById('input-contribution-nickname');
const inputContributionQq = document.getElementById('input-contribution-qq'); const inputContributionQq = document.getElementById('input-contribution-qq');
const contributionPrimaryStatusLabel = document.getElementById('contribution-primary-status-label');
const contributionSecondaryStatusLabel = document.getElementById('contribution-secondary-status-label');
const contributionOauthStatus = document.getElementById('contribution-oauth-status'); const contributionOauthStatus = document.getElementById('contribution-oauth-status');
const contributionCallbackStatus = document.getElementById('contribution-callback-status'); const contributionCallbackStatus = document.getElementById('contribution-callback-status');
const contributionModeSummary = document.getElementById('contribution-mode-summary'); const accountContributionSummary = document.getElementById('contribution-mode-summary');
const btnStartContribution = document.getElementById('btn-start-contribution'); const btnStartContribution = document.getElementById('btn-start-contribution');
const btnOpenContributionUpload = document.getElementById('btn-open-contribution-upload'); const btnOpenContributionUpload = document.getElementById('btn-open-contribution-upload');
const btnExitContributionMode = document.getElementById('btn-exit-contribution-mode'); const btnExitContributionMode = document.getElementById('btn-exit-contribution-mode');
@@ -2074,7 +2076,7 @@ function shouldPromptNewUserGuide() {
} }
if (typeof isContributionModeActiveForFlow === 'function' if (typeof isContributionModeActiveForFlow === 'function'
? isContributionModeActiveForFlow(latestState) ? isContributionModeActiveForFlow(latestState)
: Boolean(latestState?.contributionMode)) { : Boolean(latestState?.accountContributionEnabled)) {
return false; return false;
} }
return true; return true;
@@ -2084,6 +2086,18 @@ function getContributionPortalUrl() {
return String(contributionContentService?.portalUrl || 'https://flowpilot.qlhazycoder.top').trim(); return String(contributionContentService?.portalUrl || 'https://flowpilot.qlhazycoder.top').trim();
} }
function getContributionContentFlowId(state = latestState) {
return String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
}
function getContributionContentTargetId(state = latestState) {
const flowId = getContributionContentFlowId(state);
if (flowId === 'kiro') {
return String(state?.kiroTargetId || state?.targetId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs';
}
return String(state?.openaiIntegrationTargetId || state?.panelMode || state?.targetId || 'cpa').trim().toLowerCase() || 'cpa';
}
function openNewUserGuidePrompt() { function openNewUserGuidePrompt() {
return openActionModal({ return openActionModal({
title: '新手引导', title: '新手引导',
@@ -2112,16 +2126,29 @@ async function maybeShowNewUserGuidePrompt() {
return false; return false;
} }
function getDismissedContributionContentPromptVersion() { function getContributionContentPromptScope(snapshot = currentContributionContentSnapshot) {
return String(localStorage.getItem(CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY) || '').trim(); return {
flowId: String(snapshot?.flowId || getContributionContentFlowId()).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID,
targetId: String(snapshot?.targetId || getContributionContentTargetId()).trim().toLowerCase() || 'cpa',
};
} }
function setDismissedContributionContentPromptVersion(version) { function getContributionContentPromptDismissedStorageKey(snapshot = currentContributionContentSnapshot) {
const scope = getContributionContentPromptScope(snapshot);
return `${CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY}:${scope.flowId}:${scope.targetId}`;
}
function getDismissedContributionContentPromptVersion(snapshot = currentContributionContentSnapshot) {
return String(localStorage.getItem(getContributionContentPromptDismissedStorageKey(snapshot)) || '').trim();
}
function setDismissedContributionContentPromptVersion(version, snapshot = currentContributionContentSnapshot) {
const normalized = String(version || '').trim(); const normalized = String(version || '').trim();
const storageKey = getContributionContentPromptDismissedStorageKey(snapshot);
if (normalized) { if (normalized) {
localStorage.setItem(CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY, normalized); localStorage.setItem(storageKey, normalized);
} else { } else {
localStorage.removeItem(CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY); localStorage.removeItem(storageKey);
} }
} }
@@ -2301,25 +2328,25 @@ async function openAutoRunFallbackRiskConfirmModal(totalRuns) {
function updateConfigMenuControls() { function updateConfigMenuControls() {
const disabled = configActionInFlight || settingsSaveInFlight; const disabled = configActionInFlight || settingsSaveInFlight;
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function' const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
? isContributionModeActiveForFlow(latestState) ? isContributionModeActiveForFlow(latestState)
: Boolean(latestState?.contributionMode); : Boolean(latestState?.accountContributionEnabled);
if (contributionModeEnabled && configMenuOpen) { if (accountContributionEnabled && configMenuOpen) {
configMenuOpen = false; configMenuOpen = false;
} }
const importLocked = disabled const importLocked = disabled
|| contributionModeEnabled || accountContributionEnabled
|| currentAutoRun.autoRunning || currentAutoRun.autoRunning
|| Object.values(getStepStatuses()).some((status) => status === 'running'); || Object.values(getStepStatuses()).some((status) => status === 'running');
if (btnConfigMenu) { if (btnConfigMenu) {
btnConfigMenu.disabled = disabled || contributionModeEnabled; btnConfigMenu.disabled = disabled || accountContributionEnabled;
btnConfigMenu.setAttribute('aria-expanded', String(configMenuOpen)); btnConfigMenu.setAttribute('aria-expanded', String(configMenuOpen));
} }
if (configMenu) { if (configMenu) {
configMenu.hidden = contributionModeEnabled || !configMenuOpen; configMenu.hidden = accountContributionEnabled || !configMenuOpen;
} }
if (btnExportSettings) { if (btnExportSettings) {
btnExportSettings.disabled = disabled || contributionModeEnabled; btnExportSettings.disabled = disabled || accountContributionEnabled;
} }
if (btnImportSettings) { if (btnImportSettings) {
btnImportSettings.disabled = importLocked; btnImportSettings.disabled = importLocked;
@@ -2776,7 +2803,10 @@ function isContributionModeActiveForFlow(state = latestState, flowId = undefined
const normalizedFlowId = typeof normalizeFlowId === 'function' const normalizedFlowId = typeof normalizeFlowId === 'function'
? normalizeFlowId(rawFlowId, DEFAULT_ACTIVE_FLOW_ID) ? normalizeFlowId(rawFlowId, DEFAULT_ACTIVE_FLOW_ID)
: (String(rawFlowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID); : (String(rawFlowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID);
return normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID && Boolean(state?.contributionMode); const stateFlowId = typeof normalizeFlowId === 'function'
? normalizeFlowId(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID, DEFAULT_ACTIVE_FLOW_ID)
: (String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID);
return normalizedFlowId === stateFlowId && Boolean(state?.accountContributionEnabled);
} }
let accountRunHistoryRefreshTimer = null; let accountRunHistoryRefreshTimer = null;
@@ -3775,9 +3805,9 @@ function collectSettingsPayload() {
} }
return normalized || defaultFlowId; return normalized || defaultFlowId;
})(); })();
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function' const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
? isContributionModeActiveForFlow(latestState, activeFlowId) ? isContributionModeActiveForFlow(latestState, activeFlowId)
: (activeFlowId === defaultFlowId && Boolean(latestState?.contributionMode)); : (activeFlowId === defaultFlowId && Boolean(latestState?.accountContributionEnabled));
const icloudFetchModeRawValue = typeof selectIcloudFetchMode !== 'undefined' const icloudFetchModeRawValue = typeof selectIcloudFetchMode !== 'undefined'
? String(selectIcloudFetchMode?.value || '') ? String(selectIcloudFetchMode?.value || '')
: ''; : '';
@@ -4419,7 +4449,7 @@ function collectSettingsPayload() {
: null; : null;
return { return {
activeFlowId, activeFlowId,
...(contributionModeEnabled ? {} : { ...(accountContributionEnabled ? {} : {
...(activeFlowId === defaultFlowId ? { panelMode: effectivePanelMode } : {}), ...(activeFlowId === defaultFlowId ? { panelMode: effectivePanelMode } : {}),
}), }),
kiroTargetId: normalizeKiroTargetIdSafe( kiroTargetId: normalizeKiroTargetIdSafe(
@@ -4528,7 +4558,7 @@ function collectSettingsPayload() {
? inputGpcHelperLocalSmsUrl.value ? inputGpcHelperLocalSmsUrl.value
: (latestState?.gopayHelperLocalSmsHelperUrl || '') : (latestState?.gopayHelperLocalSmsHelperUrl || '')
), ),
...(contributionModeEnabled ? {} : { ...(accountContributionEnabled ? {} : {
customPassword: inputPassword.value, customPassword: inputPassword.value,
}), }),
mailProvider: selectMailProvider.value, mailProvider: selectMailProvider.value,
@@ -4548,7 +4578,7 @@ function collectSettingsPayload() {
icloudFetchMode: (icloudFetchModeRawValue.trim().toLowerCase() === 'always_new' icloudFetchMode: (icloudFetchModeRawValue.trim().toLowerCase() === 'always_new'
? 'always_new' ? 'always_new'
: 'reuse_existing'), : 'reuse_existing'),
...(contributionModeEnabled ? {} : { ...(accountContributionEnabled ? {} : {
accountRunHistoryTextEnabled: true, accountRunHistoryTextEnabled: true,
accountRunHistoryHelperBaseUrl: normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl?.value), accountRunHistoryHelperBaseUrl: normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl?.value),
}), }),
@@ -8695,9 +8725,9 @@ function canSelectPhoneSignupMethod() {
const plusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled const plusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
? Boolean(inputPlusModeEnabled.checked) ? Boolean(inputPlusModeEnabled.checked)
: Boolean(latestState?.plusModeEnabled); : Boolean(latestState?.plusModeEnabled);
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function' const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
? isContributionModeActiveForFlow(latestState) ? isContributionModeActiveForFlow(latestState)
: Boolean(latestState?.contributionMode); : Boolean(latestState?.accountContributionEnabled);
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
? resolveCurrentSidepanelCapabilities({ ? resolveCurrentSidepanelCapabilities({
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode, panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode,
@@ -8705,7 +8735,7 @@ function canSelectPhoneSignupMethod() {
...(typeof latestState !== 'undefined' ? latestState : {}), ...(typeof latestState !== 'undefined' ? latestState : {}),
phoneVerificationEnabled: phoneEnabled, phoneVerificationEnabled: phoneEnabled,
plusModeEnabled, plusModeEnabled,
contributionMode: contributionModeEnabled, accountContributionEnabled,
}, },
}) })
: (() => { : (() => {
@@ -8721,7 +8751,7 @@ function canSelectPhoneSignupMethod() {
...(typeof latestState !== 'undefined' ? latestState : {}), ...(typeof latestState !== 'undefined' ? latestState : {}),
phoneVerificationEnabled: phoneEnabled, phoneVerificationEnabled: phoneEnabled,
plusModeEnabled, plusModeEnabled,
contributionMode: contributionModeEnabled, accountContributionEnabled,
}, },
}) })
: null; : null;
@@ -8729,7 +8759,7 @@ function canSelectPhoneSignupMethod() {
if (capabilityState && typeof capabilityState.canSelectPhoneSignup === 'boolean') { if (capabilityState && typeof capabilityState.canSelectPhoneSignup === 'boolean') {
return capabilityState.canSelectPhoneSignup; return capabilityState.canSelectPhoneSignup;
} }
return phoneEnabled && !plusModeEnabled && !contributionModeEnabled; return phoneEnabled && !plusModeEnabled && !accountContributionEnabled;
} }
function isSignupMethodSwitchLocked() { function isSignupMethodSwitchLocked() {
@@ -8751,9 +8781,9 @@ function updateSignupMethodUI(options = {}) {
let selectedMethod = normalizeSignupMethod(getSelectedSignupMethod()); let selectedMethod = normalizeSignupMethod(getSelectedSignupMethod());
const phoneSelectable = canSelectPhoneSignupMethod(); const phoneSelectable = canSelectPhoneSignupMethod();
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function' const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
? isContributionModeActiveForFlow(latestState) ? isContributionModeActiveForFlow(latestState)
: Boolean(latestState?.contributionMode); : Boolean(latestState?.accountContributionEnabled);
if (!phoneSelectable && selectedMethod === SIGNUP_METHOD_PHONE) { if (!phoneSelectable && selectedMethod === SIGNUP_METHOD_PHONE) {
selectedMethod = setSignupMethod(SIGNUP_METHOD_EMAIL); selectedMethod = setSignupMethod(SIGNUP_METHOD_EMAIL);
if (options.notify && typeof showToast === 'function') { if (options.notify && typeof showToast === 'function') {
@@ -8773,9 +8803,9 @@ function updateSignupMethodUI(options = {}) {
if (!Boolean(inputPhoneVerificationEnabled?.checked)) { if (!Boolean(inputPhoneVerificationEnabled?.checked)) {
button.title = '开启接码后可选择手机号注册'; button.title = '开启接码后可选择手机号注册';
} else if (typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled?.checked) { } else if (typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled?.checked) {
button.title = 'Plus 模式第一版暂不支持手机号注册'; button.title = 'Plus 模式暂不支持手机号注册';
} else if (contributionModeEnabled) { } else if (accountContributionEnabled) {
button.title = '贡献模式第一版暂不支持手机号注册'; button.title = '账号贡献开启时不能使用手机号注册';
} else if (locked) { } else if (locked) {
button.title = '自动流程运行中不能切换注册方式'; button.title = '自动流程运行中不能切换注册方式';
} else { } else {
@@ -11392,12 +11422,12 @@ function shouldShowContributionUpdateHint(snapshot = currentContributionContentS
if (!getContributionUpdatePromptLines(snapshot).length) { if (!getContributionUpdatePromptLines(snapshot).length) {
return false; return false;
} }
if (promptVersion === getDismissedContributionContentPromptVersion()) { if (promptVersion === getDismissedContributionContentPromptVersion(snapshot)) {
return false; return false;
} }
if (typeof isContributionModeActiveForFlow === 'function' if (typeof isContributionModeActiveForFlow === 'function'
? isContributionModeActiveForFlow(latestState) ? isContributionModeActiveForFlow(latestState)
: Boolean(latestState?.contributionMode)) { : Boolean(latestState?.accountContributionEnabled)) {
return false; return false;
} }
return !btnContributionMode.disabled; return !btnContributionMode.disabled;
@@ -11522,7 +11552,10 @@ async function refreshContributionContentHint() {
return contributionContentSnapshotRequestInFlight; return contributionContentSnapshotRequestInFlight;
} }
contributionContentSnapshotRequestInFlight = contributionContentService.getContentUpdateSnapshot() contributionContentSnapshotRequestInFlight = contributionContentService.getContentUpdateSnapshot({
flowId: getContributionContentFlowId(),
targetId: getContributionContentTargetId(),
})
.then((snapshot) => { .then((snapshot) => {
currentContributionContentSnapshot = snapshot; currentContributionContentSnapshot = snapshot;
renderContributionUpdateHint(snapshot); renderContributionUpdateHint(snapshot);
@@ -11543,10 +11576,10 @@ async function refreshContributionContentHint() {
} }
function syncPasswordField(state) { function syncPasswordField(state) {
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function' const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
? isContributionModeActiveForFlow(state) ? isContributionModeActiveForFlow(state)
: Boolean(state?.contributionMode); : Boolean(state?.accountContributionEnabled);
inputPassword.value = contributionModeEnabled ? '' : (state.customPassword || state.password || ''); inputPassword.value = accountContributionEnabled ? '' : (state.customPassword || state.password || '');
} }
function isCustomMailProvider(provider = selectMailProvider.value) { function isCustomMailProvider(provider = selectMailProvider.value) {
@@ -13518,7 +13551,7 @@ const bindAccountRecordEvents = accountRecordsManager?.bindEvents
const closeAccountRecordsPanel = accountRecordsManager?.closePanel const closeAccountRecordsPanel = accountRecordsManager?.closePanel
|| (() => { }); || (() => { });
bindAccountRecordEvents(); bindAccountRecordEvents();
const contributionModeManager = window.SidepanelContributionMode?.createContributionModeManager({ const accountContributionManager = window.SidepanelContributionMode?.createContributionModeManager({
state: { state: {
getLatestState: () => latestState, getLatestState: () => latestState,
}, },
@@ -13527,15 +13560,17 @@ const contributionModeManager = window.SidepanelContributionMode?.createContribu
btnContributionMode, btnContributionMode,
inputContributionNickname, inputContributionNickname,
inputContributionQq, inputContributionQq,
contributionPrimaryStatusLabel,
contributionSecondaryStatusLabel,
contributionCallbackStatus, contributionCallbackStatus,
btnExitContributionMode, btnExitContributionMode,
btnOpenAccountRecords, btnOpenAccountRecords,
btnOpenContributionUpload, btnOpenContributionUpload,
btnStartContribution, btnStartContribution,
contributionModeBadge, accountContributionBadge,
contributionModePanel, accountContributionPanel,
contributionModeSummary, accountContributionSummary,
contributionModeText, accountContributionText,
contributionOauthStatus, contributionOauthStatus,
rowAccountRunHistoryHelperBaseUrl, rowAccountRunHistoryHelperBaseUrl,
rowPhoneVerificationEnabled, rowPhoneVerificationEnabled,
@@ -13579,16 +13614,16 @@ const contributionModeManager = window.SidepanelContributionMode?.createContribu
contributionUploadUrl: `${String(contributionContentService?.portalUrl || 'https://flowpilot.qlhazycoder.top').replace(/\/+$/, '')}/upload`, contributionUploadUrl: `${String(contributionContentService?.portalUrl || 'https://flowpilot.qlhazycoder.top').replace(/\/+$/, '')}/upload`,
}, },
}); });
const baseRenderContributionMode = contributionModeManager?.render const baseRenderAccountContribution = accountContributionManager?.render
|| (() => { }); || (() => { });
const renderContributionMode = () => { const renderContributionMode = () => {
baseRenderContributionMode(); baseRenderAccountContribution();
renderContributionUpdateHint(); renderContributionUpdateHint();
updateSignupMethodUI({ notify: true }); updateSignupMethodUI({ notify: true });
}; };
const bindContributionModeEvents = contributionModeManager?.bindEvents const bindAccountContributionEvents = accountContributionManager?.bindEvents
|| (() => { }); || (() => { });
bindContributionModeEvents(); bindAccountContributionEvents();
renderStepsList(); renderStepsList();
async function exportSettingsFile() { async function exportSettingsFile() {
@@ -14094,7 +14129,7 @@ async function startAutoRunFromCurrentSettings() {
plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
? Boolean(inputPlusModeEnabled.checked) ? Boolean(inputPlusModeEnabled.checked)
: Boolean(latestState?.plusModeEnabled), : Boolean(latestState?.plusModeEnabled),
contributionMode: Boolean(latestState?.contributionMode), accountContributionEnabled: Boolean(latestState?.accountContributionEnabled),
}; };
return registry.validateAutoRunStart({ return registry.validateAutoRunStart({
activeFlowId: validationState.activeFlowId, activeFlowId: validationState.activeFlowId,
@@ -14184,7 +14219,8 @@ async function startAutoRunFromCurrentSettings() {
activeFlowId, activeFlowId,
targetId, targetId,
autoRunSkipFailures, autoRunSkipFailures,
contributionMode: Boolean(latestState?.contributionMode), accountContributionEnabled: Boolean(latestState?.accountContributionEnabled),
contributionAdapterId: latestState?.contributionAdapterId || '',
contributionNickname, contributionNickname,
contributionQq, contributionQq,
mode, mode,
@@ -16533,7 +16569,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if ( if (
message.payload.password !== undefined message.payload.password !== undefined
|| message.payload.customPassword !== undefined || message.payload.customPassword !== undefined
|| message.payload.contributionMode !== undefined || message.payload.accountContributionEnabled !== undefined
) { ) {
syncPasswordField(latestState || {}); syncPasswordField(latestState || {});
} }
+1 -1
View File
@@ -559,7 +559,7 @@ test('auto-run restarts from confirm-oauth step after transient step10 token_exc
stepStatuses: { 3: 'completed' }, stepStatuses: { 3: 'completed' },
stepsVersion: 'ultra2.0', stepsVersion: 'ultra2.0',
visibleStep: 10, visibleStep: 10,
contributionMode: false, accountContributionEnabled: false,
}, },
}); });
@@ -102,7 +102,7 @@ test('account run history helper upgrades old records, keeps stopped items and s
attemptRun: 3, attemptRun: 3,
}, },
plusModeEnabled: false, plusModeEnabled: false,
contributionMode: false, accountContributionEnabled: false,
}); });
const appended = await helpers.appendAccountRunRecord('node:fetch-login-code:failed', null, '步骤 8:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。'); const appended = await helpers.appendAccountRunRecord('node:fetch-login-code:failed', null, '步骤 8:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。');
@@ -213,7 +213,7 @@ test('account run history helper accepts phone-only records without forcing emai
source: 'manual', source: 'manual',
autoRunContext: null, autoRunContext: null,
plusModeEnabled: false, plusModeEnabled: false,
contributionMode: false, accountContributionEnabled: false,
}); });
const normalized = helpers.normalizeAccountRunHistoryRecord({ const normalized = helpers.normalizeAccountRunHistoryRecord({
@@ -426,22 +426,22 @@ test('account run history records preserve Plus and contribution mode flags', ()
email: 'plus@example.com', email: 'plus@example.com',
password: 'secret', password: 'secret',
plusModeEnabled: true, plusModeEnabled: true,
contributionMode: true, accountContributionEnabled: true,
}, 'success'); }, 'success');
assert.equal(record.plusModeEnabled, true); assert.equal(record.plusModeEnabled, true);
assert.equal(record.contributionMode, true); assert.equal(record.accountContributionEnabled, true);
const normalized = helpers.normalizeAccountRunHistoryRecord({ const normalized = helpers.normalizeAccountRunHistoryRecord({
email: 'plus@example.com', email: 'plus@example.com',
password: 'secret', password: 'secret',
finalStatus: 'success', finalStatus: 'success',
plusModeEnabled: true, plusModeEnabled: true,
contributionMode: true, accountContributionEnabled: true,
}); });
assert.equal(normalized.plusModeEnabled, true); assert.equal(normalized.plusModeEnabled, true);
assert.equal(normalized.contributionMode, true); assert.equal(normalized.accountContributionEnabled, true);
}); });
test('account run history helper clears persisted records and syncs full snapshot payload to local helper', async () => { test('account run history helper clears persisted records and syncs full snapshot payload to local helper', async () => {
+187 -166
View File
@@ -69,7 +69,7 @@ test('background imports contribution oauth module and keeps contribution runtim
assert.match(backgroundSource, /background\/contribution-oauth\.js/); assert.match(backgroundSource, /background\/contribution-oauth\.js/);
assert.doesNotMatch(persistedBlock, /contributionSessionId|contributionAuthUrl|contributionCallbackUrl|contributionStatus/); assert.doesNotMatch(persistedBlock, /contributionSessionId|contributionAuthUrl|contributionCallbackUrl|contributionStatus/);
assert.match(defaultStateBlock, /contributionMode:\s*false|CONTRIBUTION_RUNTIME_DEFAULTS/); assert.match(defaultStateBlock, /accountContributionEnabled:\s*false|CONTRIBUTION_RUNTIME_DEFAULTS/);
}); });
test('contribution oauth module exposes a factory', () => { test('contribution oauth module exposes a factory', () => {
@@ -84,14 +84,48 @@ test('contribution oauth module exposes a factory', () => {
assert.equal(Array.isArray(api?.RUNTIME_KEYS), true); assert.equal(Array.isArray(api?.RUNTIME_KEYS), true);
}); });
test('buildContributionModeState preserves active contribution runtime while keeping contribution on sub2api', () => { test('buildAccountContributionState preserves active contribution runtime while keeping contribution on sub2api', () => {
const bundle = extractFunction(backgroundSource, 'buildContributionModeState'); const bundle = extractFunction(backgroundSource, 'buildAccountContributionState');
const helperBundle = [
'normalizeAccountContributionFlowId',
'normalizeAccountContributionAdapterId',
'assertAccountContributionAdapterAvailable',
'buildFlowContributionRuntimePatch',
'normalizeOpenAiContributionSource',
'resolveOpenAiContributionRoutingState',
].map((name) => extractFunction(backgroundSource, name)).join('\n');
const api = new Function(` const api = new Function(`
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const CONTRIBUTION_SOURCE_CPA = 'cpa';
const CONTRIBUTION_SOURCE_SUB2API = 'sub2api';
const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex号池';
const CONTRIBUTION_SUB2API_PLUS_GROUP_NAME = 'openai-plus';
const self = {
MultiPageFlowRegistry: {
normalizeFlowId(value = '', fallback = 'openai') {
const normalized = String(value || '').trim().toLowerCase();
return normalized || fallback || 'openai';
},
},
MultiPageContributionRegistry: {
normalizeAdapterId(value = '') {
return String(value || '').trim().toLowerCase();
},
hasContributionAdapter(flowId, adapterId) {
return flowId === 'openai' && adapterId === 'openai-oauth';
},
getDefaultContributionAdapterId(flowId) {
return flowId === 'openai' ? 'openai-oauth' : '';
},
},
};
const DEFAULT_STATE = { panelMode: 'cpa' }; const DEFAULT_STATE = { panelMode: 'cpa' };
const CONTRIBUTION_RUNTIME_DEFAULTS = { const CONTRIBUTION_RUNTIME_DEFAULTS = {
contributionMode: false, accountContributionEnabled: false,
contributionModeExpected: false, accountContributionExpected: false,
contributionAdapterId: '',
flowContributionRuntime: {},
contributionSource: 'sub2api', contributionSource: 'sub2api',
contributionTargetGroupName: 'codex号池', contributionTargetGroupName: 'codex号池',
contributionNickname: '', contributionNickname: '',
@@ -110,143 +144,130 @@ const CONTRIBUTION_RUNTIME_DEFAULTS = {
}; };
const CONTRIBUTION_RUNTIME_KEYS = Object.keys(CONTRIBUTION_RUNTIME_DEFAULTS); const CONTRIBUTION_RUNTIME_KEYS = Object.keys(CONTRIBUTION_RUNTIME_DEFAULTS);
function isPlusModeState(state = {}) { return Boolean(state?.plusModeEnabled); } function isPlusModeState(state = {}) { return Boolean(state?.plusModeEnabled); }
function normalizeContributionModeSource(value = '') { ${helperBundle}
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'sub2api' ? 'sub2api' : 'cpa';
}
function resolveContributionModeRoutingState(state = {}) {
const currentStatus = String(state?.contributionStatus || '').trim().toLowerCase();
const currentSource = normalizeContributionModeSource(state?.contributionSource);
const hasActiveSession = Boolean(
String(state?.contributionSessionId || '').trim()
&& currentStatus
&& !['auto_approved', 'auto_rejected', 'expired', 'error'].includes(currentStatus)
);
if (hasActiveSession) {
return {
source: currentSource,
targetGroupName: currentSource === 'sub2api'
? (String(state?.contributionTargetGroupName || '').trim() || 'codex号池')
: '',
};
}
const source = 'sub2api';
return {
source,
targetGroupName: isPlusModeState(state)
? 'openai-plus'
: (String(state?.contributionTargetGroupName || '').trim() || 'codex号池'),
};
}
${bundle} ${bundle}
return { buildContributionModeState }; return { buildAccountContributionState };
`)(); `)();
assert.deepStrictEqual( const enabledState = api.buildAccountContributionState(true, {
api.buildContributionModeState(true, { panelMode: 'sub2api',
panelMode: 'sub2api', customPassword: 'Secret123!',
customPassword: 'Secret123!', accountRunHistoryTextEnabled: true,
accountRunHistoryTextEnabled: true, }, {
}, { contributionSessionId: 'session-001',
contributionSessionId: 'session-001', contributionAuthUrl: 'https://auth.example.com',
contributionAuthUrl: 'https://auth.example.com', contributionStatus: 'waiting',
contributionStatus: 'waiting', contributionCallbackStatus: 'waiting',
contributionCallbackStatus: 'waiting', });
}), assert.equal(enabledState.accountContributionEnabled, true);
{ assert.equal(enabledState.accountContributionExpected, true);
contributionMode: true, assert.equal(enabledState.contributionAdapterId, 'openai-oauth');
contributionModeExpected: true, assert.deepStrictEqual(enabledState.flowContributionRuntime, {
contributionSource: 'sub2api', openai: { enabled: true, adapterId: 'openai-oauth' },
contributionTargetGroupName: 'codex号池', });
contributionNickname: '', assert.equal(enabledState.contributionSessionId, 'session-001');
contributionQq: '', assert.equal(enabledState.panelMode, 'sub2api');
contributionSessionId: 'session-001', assert.equal(enabledState.customPassword, '');
contributionAuthUrl: 'https://auth.example.com', assert.equal(enabledState.accountRunHistoryTextEnabled, false);
contributionAuthState: '',
contributionCallbackUrl: '',
contributionStatus: 'waiting',
contributionStatusMessage: '',
contributionLastPollAt: 0,
contributionCallbackStatus: 'waiting',
contributionCallbackMessage: '',
contributionAuthOpenedAt: 0,
contributionAuthTabId: 0,
panelMode: 'sub2api',
customPassword: '',
accountRunHistoryTextEnabled: false,
}
);
assert.deepStrictEqual( const disabledState = api.buildAccountContributionState(false, {
api.buildContributionModeState(false, { panelMode: 'sub2api',
panelMode: 'sub2api', customPassword: 'Secret123!',
customPassword: 'Secret123!', accountRunHistoryTextEnabled: true,
accountRunHistoryTextEnabled: true, }, {
}, { contributionSessionId: 'session-001',
contributionSessionId: 'session-001', contributionAuthUrl: 'https://auth.example.com',
contributionAuthUrl: 'https://auth.example.com', contributionStatus: 'waiting',
contributionStatus: 'waiting', });
}), assert.equal(disabledState.accountContributionEnabled, false);
{ assert.equal(disabledState.accountContributionExpected, false);
contributionMode: false, assert.equal(disabledState.contributionAdapterId, '');
contributionModeExpected: false, assert.deepStrictEqual(disabledState.flowContributionRuntime, {});
contributionSource: 'sub2api', assert.equal(disabledState.contributionSessionId, '');
contributionTargetGroupName: 'codex号池', assert.equal(disabledState.panelMode, 'sub2api');
contributionNickname: '', assert.equal(disabledState.customPassword, 'Secret123!');
contributionQq: '',
contributionSessionId: '',
contributionAuthUrl: '',
contributionAuthState: '',
contributionCallbackUrl: '',
contributionStatus: '',
contributionStatusMessage: '',
contributionLastPollAt: 0,
contributionCallbackStatus: 'idle',
contributionCallbackMessage: '',
contributionAuthOpenedAt: 0,
contributionAuthTabId: 0,
panelMode: 'sub2api',
customPassword: 'Secret123!',
accountRunHistoryTextEnabled: true,
}
);
assert.deepStrictEqual( const plusContributionState = api.buildAccountContributionState(true, {
api.buildContributionModeState(true, { panelMode: 'cpa',
panelMode: 'cpa', plusModeEnabled: true,
plusModeEnabled: true, customPassword: 'Secret123!',
customPassword: 'Secret123!', accountRunHistoryTextEnabled: true,
accountRunHistoryTextEnabled: true, }, {});
}, {}), assert.equal(plusContributionState.contributionTargetGroupName, 'openai-plus');
{ assert.equal(plusContributionState.panelMode, 'sub2api');
contributionMode: true,
contributionModeExpected: true,
contributionSource: 'sub2api',
contributionTargetGroupName: 'openai-plus',
contributionNickname: '',
contributionQq: '',
contributionSessionId: '',
contributionAuthUrl: '',
contributionAuthState: '',
contributionCallbackUrl: '',
contributionStatus: '',
contributionStatusMessage: '',
contributionLastPollAt: 0,
contributionCallbackStatus: 'idle',
contributionCallbackMessage: '',
contributionAuthOpenedAt: 0,
contributionAuthTabId: 0,
panelMode: 'sub2api',
customPassword: '',
accountRunHistoryTextEnabled: false,
}
);
}); });
test('resetState preserves contribution runtime across reset', () => { test('resetState preserves contribution runtime across reset', () => {
assert.match(backgroundSource, /CONTRIBUTION_RUNTIME_KEYS/); assert.match(backgroundSource, /CONTRIBUTION_RUNTIME_KEYS/);
assert.match(backgroundSource, /const contributionModeState = buildContributionModeState/); assert.match(backgroundSource, /const accountContributionState = buildAccountContributionState/);
assert.match(backgroundSource, /\.\.\.contributionModeState/); assert.match(backgroundSource, /\.\.\.accountContributionState/);
});
test('storage migration upgrades legacy contribution mode into unified account contribution state', async () => {
const helperBundle = [
'normalizeAccountContributionFlowId',
'normalizeAccountContributionAdapterId',
'buildFlowContributionRuntimePatch',
].map((name) => extractFunction(backgroundSource, name)).join('\n');
const migrationBundle = extractFunction(backgroundSource, 'migrateLegacyAccountContributionState');
const api = new Function(`
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const self = {
MultiPageFlowRegistry: {
normalizeFlowId(value = '', fallback = 'openai') {
const normalized = String(value || '').trim().toLowerCase();
return normalized || fallback || 'openai';
},
},
MultiPageContributionRegistry: {
normalizeAdapterId(value = '') {
return String(value || '').trim().toLowerCase();
},
hasContributionAdapter(flowId, adapterId) {
return (flowId === 'openai' && adapterId === 'openai-oauth')
|| (flowId === 'kiro' && adapterId === 'kiro-builder-id');
},
getDefaultContributionAdapterId(flowId) {
return flowId === 'kiro' ? 'kiro-builder-id' : 'openai-oauth';
},
},
};
const sessionStore = {
contributionMode: true,
contributionModeExpected: true,
activeFlowId: 'kiro',
};
const removed = [];
const chrome = {
storage: {
session: {
async get() { return { ...sessionStore }; },
async set(updates) { Object.assign(sessionStore, updates); },
async remove(keys) { removed.push(['session', ...keys]); keys.forEach((key) => { delete sessionStore[key]; }); },
},
local: {
async remove(keys) { removed.push(['local', ...keys]); },
},
},
};
${helperBundle}
${migrationBundle}
return { migrateLegacyAccountContributionState, sessionStore, removed };
`)();
await api.migrateLegacyAccountContributionState();
assert.equal(api.sessionStore.accountContributionEnabled, true);
assert.equal(api.sessionStore.accountContributionExpected, true);
assert.equal(api.sessionStore.contributionAdapterId, 'kiro-builder-id');
assert.deepStrictEqual(api.sessionStore.flowContributionRuntime, {
kiro: { enabled: true, adapterId: 'kiro-builder-id' },
});
assert.equal(Object.prototype.hasOwnProperty.call(api.sessionStore, 'contributionMode'), false);
assert.deepStrictEqual(api.removed, [
['session', 'contributionMode', 'contributionModeExpected'],
['local', 'contributionMode', 'contributionModeExpected'],
]);
}); });
test('message router handles contribution mode, start flow, and status polling messages', async () => { test('message router handles contribution mode, start flow, and status polling messages', async () => {
@@ -258,23 +279,23 @@ test('message router handles contribution mode, start flow, and status polling m
const router = api.createMessageRouter({ const router = api.createMessageRouter({
ensureManualInteractionAllowed: async () => ({ ensureManualInteractionAllowed: async () => ({
stepStatuses: { 1: 'pending', 2: 'completed' }, stepStatuses: { 1: 'pending', 2: 'completed' },
contributionMode: true, accountContributionEnabled: true,
}), }),
pollContributionStatus: async (options) => { pollContributionStatus: async (options) => {
calls.push({ type: 'poll', options }); calls.push({ type: 'poll', options });
return { contributionStatus: 'waiting' }; return { contributionStatus: 'waiting' };
}, },
setContributionMode: async (enabled) => { setAccountContributionMode: async (enabled) => {
calls.push({ type: 'toggle', enabled }); calls.push({ type: 'toggle', enabled });
return { return {
contributionMode: Boolean(enabled), accountContributionEnabled: Boolean(enabled),
panelMode: 'cpa', panelMode: 'cpa',
}; };
}, },
startContributionFlow: async (options) => { startFlowContribution: async (options) => {
calls.push({ type: 'start', options }); calls.push({ type: 'start', options });
return { return {
contributionMode: true, accountContributionEnabled: true,
contributionSessionId: 'session-001', contributionSessionId: 'session-001',
contributionStatus: 'started', contributionStatus: 'started',
}; };
@@ -282,15 +303,15 @@ test('message router handles contribution mode, start flow, and status polling m
}); });
const enableResponse = await router.handleMessage({ const enableResponse = await router.handleMessage({
type: 'SET_CONTRIBUTION_MODE', type: 'SET_ACCOUNT_CONTRIBUTION_MODE',
payload: { enabled: true }, payload: { enabled: true },
}); });
const startResponse = await router.handleMessage({ const startResponse = await router.handleMessage({
type: 'START_CONTRIBUTION_FLOW', type: 'START_FLOW_CONTRIBUTION',
payload: { nickname: '阿青', qq: '123456' }, payload: { nickname: '阿青', qq: '123456' },
}); });
const pollResponse = await router.handleMessage({ const pollResponse = await router.handleMessage({
type: 'POLL_CONTRIBUTION_STATUS', type: 'POLL_FLOW_CONTRIBUTION_STATUS',
payload: { reason: 'test_poll' }, payload: { reason: 'test_poll' },
}); });
@@ -304,7 +325,7 @@ test('message router handles contribution mode, start flow, and status polling m
]); ]);
}); });
test('message router re-syncs contribution mode before AUTO_RUN when sidepanel payload marks contributionMode=true', async () => { test('message router re-syncs contribution mode before AUTO_RUN when sidepanel payload marks accountContributionEnabled=true', async () => {
const source = fs.readFileSync('background/message-router.js', 'utf8'); const source = fs.readFileSync('background/message-router.js', 'utf8');
const globalScope = {}; const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope); const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
@@ -314,13 +335,13 @@ test('message router re-syncs contribution mode before AUTO_RUN when sidepanel p
clearStopRequest: () => {}, clearStopRequest: () => {},
getPendingAutoRunTimerPlan: () => null, getPendingAutoRunTimerPlan: () => null,
getState: async () => ({ getState: async () => ({
contributionMode: false, accountContributionEnabled: false,
stepStatuses: {}, stepStatuses: {},
}), }),
normalizeRunCount: (value) => Number(value) || 1, normalizeRunCount: (value) => Number(value) || 1,
setContributionMode: async (enabled) => { setAccountContributionMode: async (enabled) => {
calls.push({ type: 'toggle', enabled }); calls.push({ type: 'toggle', enabled });
return { contributionMode: true }; return { accountContributionEnabled: true };
}, },
setState: async (updates) => { setState: async (updates) => {
calls.push({ type: 'setState', updates }); calls.push({ type: 'setState', updates });
@@ -336,7 +357,7 @@ test('message router re-syncs contribution mode before AUTO_RUN when sidepanel p
totalRuns: 2, totalRuns: 2,
autoRunSkipFailures: true, autoRunSkipFailures: true,
mode: 'restart', mode: 'restart',
contributionMode: true, accountContributionEnabled: true,
contributionNickname: '阿青', contributionNickname: '阿青',
contributionQq: '123456', contributionQq: '123456',
}, },
@@ -433,7 +454,7 @@ test('account run history snapshot sync is disabled in contribution mode', () =>
assert.equal( assert.equal(
helpers.shouldSyncAccountRunHistorySnapshot({ helpers.shouldSyncAccountRunHistorySnapshot({
contributionMode: true, accountContributionEnabled: true,
accountRunHistoryTextEnabled: true, accountRunHistoryTextEnabled: true,
accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373', accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373',
}), }),
@@ -442,7 +463,7 @@ test('account run history snapshot sync is disabled in contribution mode', () =>
assert.equal( assert.equal(
helpers.shouldSyncAccountRunHistorySnapshot({ helpers.shouldSyncAccountRunHistorySnapshot({
contributionMode: false, accountContributionEnabled: false,
accountRunHistoryTextEnabled: true, accountRunHistoryTextEnabled: true,
accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373', accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373',
}), }),
@@ -458,7 +479,7 @@ test('contribution oauth manager starts session, opens auth url, submits callbac
const closeCallbackCalls = []; const closeCallbackCalls = [];
let statusPollCount = 0; let statusPollCount = 0;
let currentState = { let currentState = {
contributionMode: true, accountContributionEnabled: true,
contributionSource: 'sub2api', contributionSource: 'sub2api',
contributionTargetGroupName: 'codex号池', contributionTargetGroupName: 'codex号池',
email: 'user@example.com', email: 'user@example.com',
@@ -541,7 +562,7 @@ test('contribution oauth manager starts session, opens auth url, submits callbac
}, },
}); });
const startedState = await manager.startContributionFlow(); const startedState = await manager.startFlowContribution();
assert.equal(startedState.contributionSessionId, 'session-001'); assert.equal(startedState.contributionSessionId, 'session-001');
assert.equal(startedState.contributionAuthState, 'oauth-state-001'); assert.equal(startedState.contributionAuthState, 'oauth-state-001');
assert.equal(startedState.contributionStatus, 'waiting'); assert.equal(startedState.contributionStatus, 'waiting');
@@ -577,7 +598,7 @@ test('contribution oauth manager deduplicates concurrent callback captures for t
let submitCallCount = 0; let submitCallCount = 0;
let resolveSubmitRequest = null; let resolveSubmitRequest = null;
let currentState = { let currentState = {
contributionMode: true, accountContributionEnabled: true,
contributionSource: 'sub2api', contributionSource: 'sub2api',
contributionTargetGroupName: 'codex号池', contributionTargetGroupName: 'codex号池',
contributionSessionId: 'session-001', contributionSessionId: 'session-001',
@@ -664,7 +685,7 @@ test('contribution oauth manager ignores tabs.onUpdated events without a real ur
const fetchCalls = []; const fetchCalls = [];
const addedListeners = {}; const addedListeners = {};
let currentState = { let currentState = {
contributionMode: true, accountContributionEnabled: true,
contributionSource: 'sub2api', contributionSource: 'sub2api',
contributionTargetGroupName: 'codex号池', contributionTargetGroupName: 'codex号池',
contributionSessionId: 'session-001', contributionSessionId: 'session-001',
@@ -756,7 +777,7 @@ test('contribution oauth manager switches Plus contribution traffic to sub2api o
const globalScope = {}; const globalScope = {};
const fetchCalls = []; const fetchCalls = [];
let currentState = { let currentState = {
contributionMode: true, accountContributionEnabled: true,
plusModeEnabled: true, plusModeEnabled: true,
contributionSource: 'sub2api', contributionSource: 'sub2api',
contributionTargetGroupName: 'openai-plus', contributionTargetGroupName: 'openai-plus',
@@ -817,7 +838,7 @@ test('contribution oauth manager switches Plus contribution traffic to sub2api o
}, },
}); });
await manager.startContributionFlow(); await manager.startFlowContribution();
assert.match(String(fetchCalls[0].options.body || ''), /"source":"sub2api"/); assert.match(String(fetchCalls[0].options.body || ''), /"source":"sub2api"/);
assert.match(String(fetchCalls[0].options.body || ''), /"target_group_name":"openai-plus"/); assert.match(String(fetchCalls[0].options.body || ''), /"target_group_name":"openai-plus"/);
@@ -836,7 +857,7 @@ return { refreshOAuthUrlBeforeStep6 };
calls.push({ type: 'log', message, level, options }); calls.push({ type: 'log', message, level, options });
}; };
globalThis.contributionOAuthManager = { globalThis.contributionOAuthManager = {
async startContributionFlow(options) { async startFlowContribution(options) {
calls.push({ type: 'contribution', options }); calls.push({ type: 'contribution', options });
return { return {
contributionAuthUrl: 'https://auth.example.com/oauth?state=oauth-state-001', contributionAuthUrl: 'https://auth.example.com/oauth?state=oauth-state-001',
@@ -854,7 +875,7 @@ return { refreshOAuthUrlBeforeStep6 };
globalThis.LOG_PREFIX = '[test]'; globalThis.LOG_PREFIX = '[test]';
const oauthUrl = await api.refreshOAuthUrlBeforeStep6({ const oauthUrl = await api.refreshOAuthUrlBeforeStep6({
contributionMode: true, accountContributionEnabled: true,
email: 'user@example.com', email: 'user@example.com',
}); });
@@ -862,7 +883,7 @@ return { refreshOAuthUrlBeforeStep6 };
assert.deepStrictEqual(calls, [ assert.deepStrictEqual(calls, [
{ {
type: 'log', type: 'log',
message: 'contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...', message: '账号贡献已开启,走公开贡献接口,正在申请 OAuth 登录地址...',
level: 'info', level: 'info',
options: { step: 7, stepKey: 'oauth-login' }, options: { step: 7, stepKey: 'oauth-login' },
}, },
@@ -872,7 +893,7 @@ return { refreshOAuthUrlBeforeStep6 };
nickname: '', nickname: '',
openAuthTab: false, openAuthTab: false,
stateOverride: { stateOverride: {
contributionMode: true, accountContributionEnabled: true,
email: 'user@example.com', email: 'user@example.com',
}, },
}, },
@@ -894,7 +915,7 @@ return { refreshOAuthUrlBeforeStep6 };
delete globalThis.LOG_PREFIX; delete globalThis.LOG_PREFIX;
}); });
test('refreshOAuthUrlBeforeStep6 logs the normal CPA/SUB2API/Codex2API path explicitly when contributionMode=false', async () => { test('refreshOAuthUrlBeforeStep6 logs the normal CPA/SUB2API/Codex2API path explicitly when accountContributionEnabled=false', async () => {
const bundle = extractFunction(backgroundSource, 'refreshOAuthUrlBeforeStep6'); const bundle = extractFunction(backgroundSource, 'refreshOAuthUrlBeforeStep6');
const calls = []; const calls = [];
@@ -907,7 +928,7 @@ return { refreshOAuthUrlBeforeStep6 };
calls.push({ type: 'log', message, level, options }); calls.push({ type: 'log', message, level, options });
}; };
globalThis.contributionOAuthManager = { globalThis.contributionOAuthManager = {
async startContributionFlow() { async startFlowContribution() {
calls.push({ type: 'contribution' }); calls.push({ type: 'contribution' });
return { return {
contributionAuthUrl: 'https://auth.example.com/oauth?state=unexpected', contributionAuthUrl: 'https://auth.example.com/oauth?state=unexpected',
@@ -925,7 +946,7 @@ return { refreshOAuthUrlBeforeStep6 };
globalThis.LOG_PREFIX = '[test]'; globalThis.LOG_PREFIX = '[test]';
const oauthUrl = await api.refreshOAuthUrlBeforeStep6({ const oauthUrl = await api.refreshOAuthUrlBeforeStep6({
contributionMode: false, accountContributionEnabled: false,
panelMode: 'sub2api', panelMode: 'sub2api',
email: 'user@example.com', email: 'user@example.com',
}); });
@@ -934,7 +955,7 @@ return { refreshOAuthUrlBeforeStep6 };
assert.deepStrictEqual(calls, [ assert.deepStrictEqual(calls, [
{ {
type: 'log', type: 'log',
message: 'contributionMode=false,走普通 CPA / SUB2API / Codex2API 链路(当前面板:SUB2API),正在刷新 OAuth 登录地址...', message: '账号贡献未开启,走普通 CPA / SUB2API / Codex2API 链路(当前面板:SUB2API),正在刷新 OAuth 登录地址...',
level: 'info', level: 'info',
options: { step: 7, stepKey: 'oauth-login' }, options: { step: 7, stepKey: 'oauth-login' },
}, },
@@ -956,7 +977,7 @@ return { refreshOAuthUrlBeforeStep6 };
delete globalThis.LOG_PREFIX; delete globalThis.LOG_PREFIX;
}); });
test('executeStep10 blocks silent fallback when contributionModeExpected=true but contributionMode=false', async () => { test('executeStep10 blocks silent fallback when accountContributionExpected=true but accountContributionEnabled=false', async () => {
const bundle = extractFunction(backgroundSource, 'executeStep10'); const bundle = extractFunction(backgroundSource, 'executeStep10');
const api = new Function(` const api = new Function(`
@@ -973,10 +994,10 @@ return { executeStep10 };
await assert.rejects( await assert.rejects(
() => api.executeStep10({ () => api.executeStep10({
contributionModeExpected: true, accountContributionExpected: true,
contributionMode: false, accountContributionEnabled: false,
}), }),
/步骤 10:当前自动流程预期使用贡献模式/ /步骤 10:当前自动流程预期使用账号贡献/
); );
delete globalThis.executeContributionStep10; delete globalThis.executeContributionStep10;
+2 -2
View File
@@ -674,7 +674,7 @@ return {
test('resetState preserves LuckMail session config, used map, and preserve tag cache while clearing runtime purchase state', async () => { test('resetState preserves LuckMail session config, used map, and preserve tag cache while clearing runtime purchase state', async () => {
const bundle = [ const bundle = [
extractFunction('buildContributionModeState'), extractFunction('buildAccountContributionState'),
extractFunction('resetState'), extractFunction('resetState'),
].join('\n'); ].join('\n');
@@ -702,7 +702,7 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
' email: null,', ' email: null,',
'};', '};',
'const CONTRIBUTION_RUNTIME_DEFAULTS = {', 'const CONTRIBUTION_RUNTIME_DEFAULTS = {',
' contributionMode: false,', ' accountContributionEnabled: false,',
" contributionSessionId: '',", " contributionSessionId: '',",
" contributionAuthUrl: '',", " contributionAuthUrl: '',",
" contributionAuthState: '',", " contributionAuthState: '',",
@@ -100,7 +100,7 @@ function createRouterWithFinalNode(options = {}) {
selectLuckmailPurchase: async () => {}, selectLuckmailPurchase: async () => {},
setCurrentHotmailAccount: async () => {}, setCurrentHotmailAccount: async () => {},
setCurrentMail2925Account: async () => {}, setCurrentMail2925Account: async () => {},
setContributionMode: async () => {}, setAccountContributionMode: async () => {},
setEmailState: async () => {}, setEmailState: async () => {},
setEmailStateSilently: async () => {}, setEmailStateSilently: async () => {},
setIcloudAliasPreservedState: async () => {}, setIcloudAliasPreservedState: async () => {},
@@ -58,7 +58,7 @@ let state = {
signupMethod: 'phone', signupMethod: 'phone',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: false, plusModeEnabled: false,
contributionMode: false, accountContributionEnabled: false,
resolvedSignupMethod: null, resolvedSignupMethod: null,
}; };
async function getState() { return { ...state }; } async function getState() { return { ...state }; }
+1 -1
View File
@@ -61,7 +61,7 @@ function getErrorMessage(error) {
} }
async function getState() { async function getState() {
events.push({ type: 'getState' }); events.push({ type: 'getState' });
return { nodeStatuses: {}, contributionMode: true }; return { nodeStatuses: {}, accountContributionEnabled: true };
} }
function getLastNodeIdForState() { function getLastNodeIdForState() {
return lastNodeId; return lastNodeId;
@@ -8,6 +8,7 @@ function createContributionContentService(options = {}) {
const cache = new Map(); const cache = new Map();
const windowObject = {}; const windowObject = {};
let fetchCalls = 0; let fetchCalls = 0;
const fetchUrls = [];
const localStorage = { const localStorage = {
getItem(key) { getItem(key) {
@@ -23,7 +24,7 @@ function createContributionContentService(options = {}) {
if (options.cachedSnapshot) { if (options.cachedSnapshot) {
cache.set( cache.set(
'multipage-contribution-content-summary-v1', options.cacheKey || 'multipage-contribution-content-summary-v2:openai:cpa',
JSON.stringify(options.cachedSnapshot) JSON.stringify(options.cachedSnapshot)
); );
} }
@@ -44,6 +45,7 @@ function createContributionContentService(options = {}) {
const wrappedFetch = async (...args) => { const wrappedFetch = async (...args) => {
fetchCalls += 1; fetchCalls += 1;
fetchUrls.push(String(args[0] || ''));
return fetchImpl(...args); return fetchImpl(...args);
}; };
@@ -69,11 +71,14 @@ function createContributionContentService(options = {}) {
getFetchCalls() { getFetchCalls() {
return fetchCalls; return fetchCalls;
}, },
getFetchUrls() {
return fetchUrls.slice();
},
}; };
} }
test('getContentUpdateSnapshot returns a prompt version for visible contribution content updates', async () => { test('getContentUpdateSnapshot returns a prompt version for visible contribution content updates', async () => {
const { api } = createContributionContentService({ const { api, getFetchUrls } = createContributionContentService({
fetchImpl: async () => ({ fetchImpl: async () => ({
ok: true, ok: true,
async json() { async json() {
@@ -100,10 +105,13 @@ test('getContentUpdateSnapshot returns a prompt version for visible contribution
}), }),
}); });
const snapshot = await api.getContentUpdateSnapshot(); const snapshot = await api.getContentUpdateSnapshot({ flowId: 'kiro', targetId: 'kiro-rs' });
assert.equal(snapshot.status, 'update-available'); assert.equal(snapshot.status, 'update-available');
assert.equal(snapshot.promptVersion, 'auto_run_notice:2026-04-21T12:05:00Z'); assert.equal(snapshot.promptVersion, 'auto_run_notice:2026-04-21T12:05:00Z');
assert.equal(snapshot.flowId, 'kiro');
assert.equal(snapshot.targetId, 'kiro-rs');
assert.equal(getFetchUrls()[0], 'https://flowpilot.qlhazycoder.top/api/content-summary?flow=kiro&target=kiro-rs');
assert.equal(snapshot.hasVisibleUpdates, true); assert.equal(snapshot.hasVisibleUpdates, true);
assert.equal(snapshot.latestUpdatedAt, '2026-04-21T12:05:00Z'); assert.equal(snapshot.latestUpdatedAt, '2026-04-21T12:05:00Z');
assert.equal(snapshot.items.length, 1); assert.equal(snapshot.items.length, 1);
@@ -132,7 +140,7 @@ test('getContentUpdateSnapshot falls back to cached snapshot when the live reque
}, },
], ],
portalUrl: 'https://flowpilot.qlhazycoder.top', portalUrl: 'https://flowpilot.qlhazycoder.top',
apiUrl: 'https://flowpilot.qlhazycoder.top/api/content-summary', apiUrl: 'https://flowpilot.qlhazycoder.top/api/content-summary?flow=openai&target=cpa',
checkedAt: Date.now() - 1000, checkedAt: Date.now() - 1000,
}; };
@@ -151,3 +159,35 @@ test('getContentUpdateSnapshot falls back to cached snapshot when the live reque
assert.equal(snapshot.errorMessage, 'offline'); assert.equal(snapshot.errorMessage, 'offline');
assert.equal(snapshot.items[0].slug, 'announcement'); assert.equal(snapshot.items[0].slug, 'announcement');
}); });
test('getContentUpdateSnapshot keeps flow caches isolated', async () => {
const cachedSnapshot = {
status: 'update-available',
promptVersion: 'flow:kiro|target:kiro-rs|auto_run_notice:2026-04-22T00:00:00Z',
hasVisibleUpdates: true,
latestUpdatedAt: '2026-04-22T00:00:00Z',
latestUpdatedAtDisplay: '2026-04-22 08:00',
flowId: 'kiro',
targetId: 'kiro-rs',
items: [{ slug: 'auto_run_notice', isVisible: true, text: 'Kiro 提示' }],
checkedAt: Date.now() - 1000,
};
const { api } = createContributionContentService({
cachedSnapshot,
cacheKey: 'multipage-contribution-content-summary-v2:kiro:kiro-rs',
fetchImpl: async () => {
throw new Error('offline');
},
});
const openAiSnapshot = await api.getContentUpdateSnapshot({ flowId: 'openai', targetId: 'cpa' });
const kiroSnapshot = await api.getContentUpdateSnapshot({ flowId: 'kiro', targetId: 'kiro-rs' });
assert.equal(openAiSnapshot.status, 'error');
assert.equal(openAiSnapshot.fromCache, undefined);
assert.equal(openAiSnapshot.flowId, 'openai');
assert.equal(kiroSnapshot.fromCache, true);
assert.equal(kiroSnapshot.flowId, 'kiro');
assert.equal(kiroSnapshot.promptVersion, cachedSnapshot.promptVersion);
});
+79
View File
@@ -0,0 +1,79 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
function loadApi() {
const scope = {};
return new Function(
'self',
`${flowRegistrySource}; ${contributionRegistrySource}; return self.MultiPageContributionRegistry;`
)(scope);
}
test('contribution registry exposes OpenAI and Kiro adapters through one contract', () => {
const api = loadApi();
assert.deepEqual(
api.getContributionAdapterIds('openai'),
['openai-oauth', 'openai-codex-file', 'openai-sub2api-file']
);
assert.deepEqual(api.getContributionAdapterIds('kiro'), ['kiro-builder-id']);
assert.equal(api.getDefaultContributionAdapterId('openai'), 'openai-oauth');
assert.equal(api.getDefaultContributionAdapterId('kiro'), 'kiro-builder-id');
assert.equal(api.getAdapterDefinition('kiro-builder-id')?.artifactKind, 'kiro-builder-id');
assert.equal(api.getAdapterDefinition('openai-oauth')?.flowId, 'openai');
assert.equal(api.hasContributionAdapter('kiro', 'kiro-builder-id'), true);
assert.equal(api.hasContributionAdapter('kiro', 'openai-oauth'), false);
});
test('contribution registry resolves the combined tutorial entry per flow', () => {
const api = loadApi();
assert.deepEqual(
api.getContributionTutorialEntry('openai', {
portalBaseUrl: 'https://flowpilot.example/root/',
targetId: 'sub2api',
}),
{
id: 'openai-contribution-tutorial',
flowId: 'openai',
label: '贡献/使用教程',
portalPath: '/tutorial',
defaultTargetId: 'cpa',
contributionAdapterId: 'openai-oauth',
action: 'open-portal-and-enable-contribution',
targetId: 'sub2api',
portalUrl: 'https://flowpilot.example/root/tutorial?flow=openai&target=sub2api',
}
);
assert.deepEqual(
api.getContributionTutorialEntry('kiro', {
portalBaseUrl: 'https://flowpilot.example',
}),
{
id: 'kiro-contribution-tutorial',
flowId: 'kiro',
label: '贡献/使用教程',
portalPath: '/tutorial',
defaultTargetId: 'kiro-rs',
contributionAdapterId: 'kiro-builder-id',
action: 'open-portal-and-enable-contribution',
targetId: 'kiro-rs',
portalUrl: 'https://flowpilot.example/tutorial?flow=kiro&target=kiro-rs',
}
);
});
test('contribution registry fails fast when a published flow has no adapter', () => {
const api = loadApi();
assert.equal(api.assertPublishedFlowsHaveContributionAdapters(['openai', 'kiro']), true);
assert.throws(
() => api.assertPublishedFlowsHaveContributionAdapters(['openai', 'missing-flow']),
/缺少账号贡献适配器:missing-flow/
);
});
+12 -8
View File
@@ -3,6 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs'); const fs = require('node:fs');
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8'); const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8'); const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8'); const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8');
@@ -10,7 +11,7 @@ function loadApi() {
const scope = {}; const scope = {};
return new Function( return new Function(
'self', 'self',
`${flowRegistrySource}; ${settingsSchemaSource}; ${source}; return self.MultiPageFlowCapabilities;` `${flowRegistrySource}; ${contributionRegistrySource}; ${settingsSchemaSource}; ${source}; return self.MultiPageFlowCapabilities;`
)(scope); )(scope);
} }
@@ -24,7 +25,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
openaiIntegrationTargetId: 'cpa', openaiIntegrationTargetId: 'cpa',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: false, plusModeEnabled: false,
contributionMode: false, accountContributionEnabled: false,
signupMethod: 'phone', signupMethod: 'phone',
}, },
}); });
@@ -40,7 +41,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
openaiIntegrationTargetId: 'sub2api', openaiIntegrationTargetId: 'sub2api',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: true, plusModeEnabled: true,
contributionMode: false, accountContributionEnabled: false,
signupMethod: 'phone', signupMethod: 'phone',
}, },
}); });
@@ -61,7 +62,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
openaiIntegrationTargetId: 'codex2api', openaiIntegrationTargetId: 'codex2api',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: true, plusModeEnabled: true,
contributionMode: true, accountContributionEnabled: true,
signupMethod: 'phone', signupMethod: 'phone',
}, },
}); });
@@ -94,8 +95,10 @@ test('flow capability registry exposes Kiro as an independent flow with its own
assert.equal(capabilityState.activeFlowId, 'kiro'); assert.equal(capabilityState.activeFlowId, 'kiro');
assert.equal(capabilityState.canShowPhoneSettings, false); assert.equal(capabilityState.canShowPhoneSettings, false);
assert.equal(capabilityState.canShowPlusSettings, false); assert.equal(capabilityState.canShowPlusSettings, false);
assert.equal(capabilityState.canShowContributionMode, true);
assert.equal(capabilityState.effectiveSignupMethod, 'email'); assert.equal(capabilityState.effectiveSignupMethod, 'email');
assert.equal(capabilityState.effectiveTargetId, 'kiro-rs'); assert.equal(capabilityState.effectiveTargetId, 'kiro-rs');
assert.deepEqual(capabilityState.flowCapabilities.contributionAdapterIds, ['kiro-builder-id']);
assert.deepEqual( assert.deepEqual(
capabilityState.visibleGroupIds, capabilityState.visibleGroupIds,
['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy'] ['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
@@ -121,7 +124,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
signupMethod: 'phone', signupMethod: 'phone',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: true, plusModeEnabled: true,
contributionMode: false, accountContributionEnabled: false,
}, },
}); });
@@ -159,14 +162,14 @@ test('flow capability registry normalizes unsupported mode switches back to the
signupMethod: 'phone', signupMethod: 'phone',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: true, plusModeEnabled: true,
contributionMode: true, accountContributionEnabled: true,
}, },
changedKeys: [ changedKeys: [
'openaiIntegrationTargetId', 'openaiIntegrationTargetId',
'signupMethod', 'signupMethod',
'phoneVerificationEnabled', 'phoneVerificationEnabled',
'plusModeEnabled', 'plusModeEnabled',
'contributionMode', 'accountContributionEnabled',
], ],
}); });
@@ -178,7 +181,8 @@ test('flow capability registry normalizes unsupported mode switches back to the
signupMethod: 'email', signupMethod: 'email',
phoneVerificationEnabled: false, phoneVerificationEnabled: false,
plusModeEnabled: false, plusModeEnabled: false,
contributionMode: false, accountContributionEnabled: false,
accountContributionEnabled: false,
}); });
assert.deepEqual( assert.deepEqual(
validation.errors.map((entry) => entry.code), validation.errors.map((entry) => entry.code),
@@ -39,6 +39,16 @@ test('flow registry exposes canonical flow and target metadata', () => {
['row-plus-mode', 'row-plus-account-access-strategy', 'row-plus-payment-method'] ['row-plus-mode', 'row-plus-account-access-strategy', 'row-plus-payment-method']
); );
assert.equal(flowRegistry.getPublicationTargetDefinition('kiro', 'kiro-rs')?.label, 'kiro.rs'); assert.equal(flowRegistry.getPublicationTargetDefinition('kiro', 'kiro-rs')?.label, 'kiro.rs');
assert.equal(flowRegistry.getFlowCapabilities('openai').supportsAccountContribution, true);
assert.equal(flowRegistry.getFlowCapabilities('kiro').supportsAccountContribution, true);
assert.deepEqual(
flowRegistry.getFlowCapabilities('openai').contributionAdapterIds,
['openai-oauth', 'openai-codex-file', 'openai-sub2api-file']
);
assert.deepEqual(
flowRegistry.getFlowCapabilities('kiro').contributionAdapterIds,
['kiro-builder-id']
);
}); });
test('settings schema normalizes view input into canonical nested namespaces', () => { test('settings schema normalizes view input into canonical nested namespaces', () => {
+217
View File
@@ -0,0 +1,217 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadKiroContributionModules() {
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
const artifactSource = fs.readFileSync('background/kiro/credential-artifact.js', 'utf8');
const adapterSource = fs.readFileSync('background/contribution/adapters/kiro-builder-id.js', 'utf8');
const globalScope = {};
new Function('self', `${stateSource}; ${artifactSource}; ${adapterSource}; return self;`)(globalScope);
return globalScope;
}
function buildAuthorizedKiroState(overrides = {}) {
return {
activeFlowId: 'kiro',
flowId: 'kiro',
accountContributionEnabled: true,
contributionAdapterId: 'kiro-builder-id',
contributionNickname: '贡献者',
contributionQq: '123456',
kiroRuntime: {
register: {
email: 'kiro-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-id-001',
clientSecret: 'client-secret-super-long',
refreshToken: 'refresh-token-super-secret',
tokenSource: 'desktop_authorization_code_pkce',
authorizedAt: 1760000000000,
},
upload: {
targetId: 'kiro-rs',
},
},
...overrides,
};
}
test('Kiro Builder ID artifact builder validates and builds unified contribution artifact', () => {
const scope = loadKiroContributionModules();
const api = scope.MultiPageBackgroundKiroCredentialArtifact;
const artifact = api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState());
assert.equal(artifact.flow, 'kiro');
assert.equal(artifact.adapter, 'kiro-builder-id');
assert.equal(artifact.artifact, 'kiro-builder-id');
assert.equal(artifact.account.email, 'kiro-user@example.com');
assert.equal(artifact.credentials.refreshToken, 'refresh-token-super-secret');
assert.equal(artifact.credentials.clientId, 'client-id-001');
assert.equal(artifact.credentials.clientSecret, 'client-secret-super-long');
assert.equal(artifact.credentials.region, 'us-east-1');
assert.equal(artifact.metadata.targetId, 'kiro-rs');
const safeSummary = api.buildSafeArtifactSummary(artifact);
assert.equal(safeSummary.refreshToken.includes('refresh-token-super-secret'), false);
assert.equal(safeSummary.clientSecret.includes('client-secret-super-long'), false);
});
test('Kiro Builder ID artifact builder rejects missing required fields', () => {
const scope = loadKiroContributionModules();
const api = scope.MultiPageBackgroundKiroCredentialArtifact;
assert.throws(
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
desktopAuth: {
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
refreshToken: '',
},
},
})),
/refreshToken/
);
assert.throws(
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
desktopAuth: {
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
clientId: '',
},
},
})),
/clientId/
);
assert.throws(
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
desktopAuth: {
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
clientSecret: '',
},
},
})),
/clientSecret/
);
assert.throws(
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
register: { email: '' },
},
email: '',
accountIdentifier: '',
})),
/注册邮箱/
);
});
test('Kiro contribution adapter submits public contribution without requiring kiro.rs config and redacts logs', async () => {
const scope = loadKiroContributionModules();
const adapterApi = scope.MultiPageBackgroundKiroBuilderIdContributionAdapter;
const fetchCalls = [];
const logs = [];
const statePatches = [];
const adapter = adapterApi.createKiroBuilderIdContributionAdapter({
addLog: async (message, level, options) => logs.push({ message, level, options }),
fetchImpl: async (url, options) => {
fetchCalls.push({ url, options });
return {
ok: true,
status: 200,
async text() {
return JSON.stringify({ ok: true, contribution_id: 'kiro-contribution-001', message: '已接收' });
},
};
},
setState: async (patch) => statePatches.push(patch),
});
const result = await adapter.maybeSubmitFlowContribution(buildAuthorizedKiroState({
kiroRsUrl: '',
kiroRsKey: '',
}), { nodeId: 'kiro-complete-desktop-authorize' });
assert.equal(result.ok, true);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].url, 'https://flowpilot.qlhazycoder.top/api/contributions');
const requestBody = JSON.parse(fetchCalls[0].options.body);
assert.equal(requestBody.flow, 'kiro');
assert.equal(requestBody.adapter_id, 'kiro-builder-id');
assert.equal(requestBody.artifact_kind, 'kiro-builder-id');
assert.equal(requestBody.artifact.credentials.refreshToken, 'refresh-token-super-secret');
assert.equal(statePatches.at(-1).flowContributionRuntime.kiro.status, 'submitted');
const logText = logs.map((entry) => entry.message).join('\n');
assert.equal(logText.includes('refresh-token-super-secret'), false);
assert.equal(logText.includes('client-secret-super-long'), false);
});
test('Kiro contribution adapter skips invalid artifacts without sending secrets', async () => {
const scope = loadKiroContributionModules();
const adapterApi = scope.MultiPageBackgroundKiroBuilderIdContributionAdapter;
const fetchCalls = [];
const logs = [];
const adapter = adapterApi.createKiroBuilderIdContributionAdapter({
addLog: async (message) => logs.push(message),
fetchImpl: async () => {
fetchCalls.push(true);
throw new Error('should not be called');
},
setState: async () => {},
});
const result = await adapter.maybeSubmitFlowContribution(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
desktopAuth: {
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
refreshToken: '',
},
},
}));
assert.equal(result.skipped, true);
assert.equal(result.reason, 'missing_refreshToken');
assert.equal(fetchCalls.length, 0);
assert.equal(logs.join('\n').includes('refresh-token-super-secret'), false);
});
test('Kiro contribution adapter redacts server errors that echo submitted secrets', async () => {
const scope = loadKiroContributionModules();
const adapterApi = scope.MultiPageBackgroundKiroBuilderIdContributionAdapter;
const logs = [];
const statePatches = [];
const adapter = adapterApi.createKiroBuilderIdContributionAdapter({
addLog: async (message) => logs.push(message),
fetchImpl: async () => ({
ok: false,
status: 400,
async text() {
return JSON.stringify({
ok: false,
message: 'bad refresh-token-super-secret and client-secret-super-long',
});
},
}),
setState: async (patch) => statePatches.push(patch),
});
const result = await adapter.maybeSubmitFlowContribution(buildAuthorizedKiroState());
assert.equal(result.ok, false);
const combined = `${logs.join('\n')}\n${JSON.stringify(statePatches)}`;
assert.equal(combined.includes('refresh-token-super-secret'), false);
assert.equal(combined.includes('client-secret-super-long'), false);
});
test('Kiro desktop authorization runner is wired to submit public contribution after step 8', () => {
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
assert.match(source, /maybeSubmitFlowContribution/);
assert.match(source, /trigger:\s*'kiro-step-8'/);
});
@@ -64,7 +64,7 @@ return new Function(`
const events = []; const events = [];
const DEFAULT_ACTIVE_FLOW_ID = 'openai'; const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const latestState = { const latestState = {
contributionMode: false, accountContributionEnabled: false,
activeFlowId: 'openai', activeFlowId: 'openai',
flowId: 'openai', flowId: 'openai',
panelMode: 'cpa', panelMode: 'cpa',
@@ -266,7 +266,7 @@ const latestState = {
activeFlowId: 'site-a', activeFlowId: 'site-a',
panelMode: 'cpa', panelMode: 'cpa',
signupMethod: 'phone', signupMethod: 'phone',
contributionMode: false, accountContributionEnabled: false,
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
}; };
const inputAutoSkipFailures = { checked: false }; const inputAutoSkipFailures = { checked: false };
@@ -4,6 +4,8 @@ const fs = require('node:fs');
const vm = require('node:vm'); const vm = require('node:vm');
const source = fs.readFileSync('sidepanel/contribution-mode.js', 'utf8'); const source = fs.readFileSync('sidepanel/contribution-mode.js', 'utf8');
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
function createElement() { function createElement() {
return { return {
@@ -19,7 +21,10 @@ function createElement() {
}, },
}, },
setAttribute() {}, setAttribute() {},
addEventListener() {}, listeners: {},
addEventListener(type, handler) {
this.listeners[type] = handler;
},
}; };
} }
@@ -37,12 +42,14 @@ test('contribution mode manager does not project openai-only ui state into kiro
const rowVpsUrl = createElement(); const rowVpsUrl = createElement();
const dom = { const dom = {
btnContributionMode: createElement(), btnContributionMode: createElement(),
contributionModePanel: createElement(), accountContributionPanel: createElement(),
contributionModeText: createElement(), accountContributionText: createElement(),
contributionModeBadge: createElement(), accountContributionBadge: createElement(),
contributionPrimaryStatusLabel: createElement(),
contributionSecondaryStatusLabel: createElement(),
contributionOauthStatus: createElement(), contributionOauthStatus: createElement(),
contributionCallbackStatus: createElement(), contributionCallbackStatus: createElement(),
contributionModeSummary: createElement(), accountContributionSummary: createElement(),
inputContributionNickname: createElement(), inputContributionNickname: createElement(),
inputContributionQq: createElement(), inputContributionQq: createElement(),
btnStartContribution: createElement(), btnStartContribution: createElement(),
@@ -57,7 +64,9 @@ test('contribution mode manager does not project openai-only ui state into kiro
getLatestState: () => ({ getLatestState: () => ({
activeFlowId: 'kiro', activeFlowId: 'kiro',
flowId: 'kiro', flowId: 'kiro',
contributionMode: true, accountContributionEnabled: true,
supportsAccountContribution: true,
contributionAdapterId: 'kiro-builder-id',
contributionSource: 'cpa', contributionSource: 'cpa',
}), }),
}, },
@@ -80,11 +89,107 @@ test('contribution mode manager does not project openai-only ui state into kiro
manager.render(); manager.render();
assert.equal(dom.contributionModePanel.hidden, true); assert.equal(dom.accountContributionPanel.hidden, false);
assert.equal(dom.contributionPrimaryStatusLabel.textContent, '账号产物');
assert.equal(dom.contributionSecondaryStatusLabel.textContent, '提交');
assert.equal(dom.contributionOauthStatus.textContent, '等待账号产物');
assert.equal(dom.selectPanelMode.disabled, false); assert.equal(dom.selectPanelMode.disabled, false);
assert.equal(dom.btnContributionMode.disabled, true); assert.equal(dom.selectPanelMode.value, '');
assert.equal(dom.btnContributionMode.title, '当前 flow 不支持贡献模式'); assert.equal(dom.btnContributionMode.disabled, false);
assert.equal(dom.btnStartContribution.disabled, true); assert.equal(dom.btnContributionMode.title, '打开当前 flow 教程;当前已在贡献模式');
assert.equal(dom.btnOpenContributionUpload.disabled, true); assert.equal(dom.btnStartContribution.disabled, false);
assert.equal(dom.btnOpenContributionUpload.disabled, false);
assert.equal(dom.btnOpenContributionUpload.textContent, '查看当前 flow 贡献说明');
assert.equal(rowVpsUrl.classList.hiddenState, false); assert.equal(rowVpsUrl.classList.hiddenState, false);
}); });
test('combined contribution tutorial button opens current flow page and enables current flow adapter', async () => {
const windowObject = {};
const api = new Function(
'self',
'window',
'setTimeout',
'clearTimeout',
`${flowRegistrySource}; ${contributionRegistrySource}; ${source}; return window.SidepanelContributionMode;`
)(windowObject, windowObject, setTimeout, clearTimeout);
let latestState = {
activeFlowId: 'kiro',
flowId: 'kiro',
accountContributionEnabled: false,
supportsAccountContribution: true,
contributionAdapterId: '',
kiroTargetId: 'kiro-rs',
};
const openedUrls = [];
const sentMessages = [];
const dom = {
btnContributionMode: createElement(),
accountContributionPanel: createElement(),
accountContributionText: createElement(),
accountContributionBadge: createElement(),
contributionPrimaryStatusLabel: createElement(),
contributionSecondaryStatusLabel: createElement(),
contributionOauthStatus: createElement(),
contributionCallbackStatus: createElement(),
accountContributionSummary: createElement(),
inputContributionNickname: createElement(),
inputContributionQq: createElement(),
btnStartContribution: createElement(),
btnOpenContributionUpload: createElement(),
btnExitContributionMode: createElement(),
btnOpenAccountRecords: createElement(),
selectPanelMode: createElement(),
};
const manager = api.createContributionModeManager({
state: {
getLatestState: () => latestState,
},
dom,
helpers: {
applySettingsState(nextState) {
latestState = nextState;
},
updatePanelModeUI() {},
updateAccountRunHistorySettingsUI() {},
updateConfigMenuControls() {},
closeConfigMenu() {},
closeAccountRecordsPanel() {},
isModeSwitchBlocked() {
return false;
},
openExternalUrl(url) {
openedUrls.push(url);
},
showToast() {},
},
runtime: {
sendMessage: async (message) => {
sentMessages.push(message);
return {
state: {
...latestState,
accountContributionEnabled: true,
contributionAdapterId: message.payload.adapterId,
},
};
},
},
constants: {
contributionPortalUrl: 'https://flowpilot.qlhazycoder.top',
},
});
manager.bindEvents();
await dom.btnContributionMode.listeners.click();
assert.deepEqual(openedUrls, ['https://flowpilot.qlhazycoder.top/tutorial?flow=kiro&target=kiro-rs']);
assert.equal(sentMessages[0].type, 'SET_ACCOUNT_CONTRIBUTION_MODE');
assert.deepEqual(sentMessages[0].payload, {
enabled: true,
flowId: 'kiro',
adapterId: 'kiro-builder-id',
});
assert.equal(latestState.accountContributionEnabled, true);
assert.equal(latestState.contributionAdapterId, 'kiro-builder-id');
});
+23 -31
View File
@@ -193,7 +193,7 @@ test('collectSettingsPayload omits custom password and local sync settings in co
const bundle = extractFunction('collectSettingsPayload'); const bundle = extractFunction('collectSettingsPayload');
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', ` const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
let latestState = { contributionMode: true }; let latestState = { accountContributionEnabled: true };
const window = {}; const window = {};
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';
@@ -337,7 +337,7 @@ return {
assert.equal(contributionPayload.phoneVerificationEnabled, true); assert.equal(contributionPayload.phoneVerificationEnabled, true);
assert.equal(contributionPayload.cloudflareTempEmailUseRandomSubdomain, true); assert.equal(contributionPayload.cloudflareTempEmailUseRandomSubdomain, true);
api.setLatestState({ contributionMode: false }); api.setLatestState({ accountContributionEnabled: false });
const normalPayload = api.collectSettingsPayload(); const normalPayload = api.collectSettingsPayload();
assert.equal(normalPayload.panelMode, 'cpa'); assert.equal(normalPayload.panelMode, 'cpa');
assert.equal(normalPayload.customPassword, 'Secret123!'); assert.equal(normalPayload.customPassword, 'Secret123!');
@@ -370,7 +370,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
assert.equal(typeof api?.createContributionModeManager, 'function'); assert.equal(typeof api?.createContributionModeManager, 'function');
let latestState = { let latestState = {
contributionMode: false, accountContributionEnabled: false,
panelMode: 'sub2api', panelMode: 'sub2api',
contributionSource: 'sub2api', contributionSource: 'sub2api',
contributionTargetGroupName: 'codex号池', contributionTargetGroupName: 'codex号池',
@@ -401,13 +401,13 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
btnOpenAccountRecords: createElement(), btnOpenAccountRecords: createElement(),
btnOpenContributionUpload: createElement(), btnOpenContributionUpload: createElement(),
btnStartContribution: createElement(), btnStartContribution: createElement(),
contributionModeBadge: createElement(), accountContributionBadge: createElement(),
inputContributionNickname: createElement({ value: '贡献者昵称' }), inputContributionNickname: createElement({ value: '贡献者昵称' }),
inputContributionQq: createElement({ value: '123456' }), inputContributionQq: createElement({ value: '123456' }),
contributionCallbackStatus: createElement(), contributionCallbackStatus: createElement(),
contributionModePanel: createElement({ hidden: true }), accountContributionPanel: createElement({ hidden: true }),
contributionModeSummary: createElement(), accountContributionSummary: createElement(),
contributionModeText: createElement(), accountContributionText: createElement(),
contributionOauthStatus: createElement(), contributionOauthStatus: createElement(),
rowAccountRunHistoryHelperBaseUrl: createElement(), rowAccountRunHistoryHelperBaseUrl: createElement(),
rowAccountRunHistoryTextEnabled: createElement(), rowAccountRunHistoryTextEnabled: createElement(),
@@ -492,11 +492,11 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
runtime: { runtime: {
sendMessage: async (message) => { sendMessage: async (message) => {
sentMessages.push(message); sentMessages.push(message);
if (message.type === 'SET_CONTRIBUTION_MODE') { if (message.type === 'SET_ACCOUNT_CONTRIBUTION_MODE') {
return { return {
state: message.payload.enabled state: message.payload.enabled
? { ? {
contributionMode: true, accountContributionEnabled: true,
panelMode: 'sub2api', panelMode: 'sub2api',
contributionSource: 'sub2api', contributionSource: 'sub2api',
contributionTargetGroupName: 'codex号池', contributionTargetGroupName: 'codex号池',
@@ -512,7 +512,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
email: latestState.email, email: latestState.email,
} }
: { : {
contributionMode: false, accountContributionEnabled: false,
panelMode: 'cpa', panelMode: 'cpa',
contributionSource: 'cpa', contributionSource: 'cpa',
contributionTargetGroupName: '', contributionTargetGroupName: '',
@@ -530,7 +530,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
}, },
}; };
} }
if (message.type === 'POLL_CONTRIBUTION_STATUS') { if (message.type === 'POLL_FLOW_CONTRIBUTION_STATUS') {
return { return {
state: { state: {
...latestState, ...latestState,
@@ -541,14 +541,6 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
}, },
}; };
} }
if (message.type === 'SET_CONTRIBUTION_PROFILE') {
latestState = {
...latestState,
contributionNickname: message.payload.nickname,
contributionQq: message.payload.qq,
};
return { state: latestState };
}
return {}; return {};
}, },
}, },
@@ -560,21 +552,21 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
}); });
manager.render(); manager.render();
assert.equal(dom.contributionModePanel.hidden, true); assert.equal(dom.accountContributionPanel.hidden, true);
assert.equal(dom.btnContributionMode.disabled, false); assert.equal(dom.btnContributionMode.disabled, false);
assert.equal(dom.contributionModeBadge.textContent, ''); assert.equal(dom.accountContributionBadge.textContent, '');
manager.bindEvents(); manager.bindEvents();
await dom.btnContributionMode.listeners.click(); await dom.btnContributionMode.listeners.click();
assert.equal(dom.contributionModePanel.hidden, false); assert.equal(dom.accountContributionPanel.hidden, false);
assert.equal(dom.selectPanelMode.value, 'sub2api'); assert.equal(dom.selectPanelMode.value, 'sub2api');
assert.equal(dom.selectPanelMode.disabled, true); assert.equal(dom.selectPanelMode.disabled, true);
assert.equal(dom.contributionModeBadge.textContent, 'SUB2API'); assert.equal(dom.accountContributionBadge.textContent, 'SUB2API');
assert.equal(dom.btnOpenAccountRecords.disabled, true); assert.equal(dom.btnOpenAccountRecords.disabled, true);
assert.equal(dom.contributionOauthStatus.textContent, '\u672a\u751f\u6210\u767b\u5f55\u5730\u5740'); assert.equal(dom.contributionOauthStatus.textContent, '\u672a\u751f\u6210\u767b\u5f55\u5730\u5740');
assert.equal(dom.contributionCallbackStatus.textContent, '\u7b49\u5f85\u56de\u8c03'); assert.equal(dom.contributionCallbackStatus.textContent, '\u7b49\u5f85\u56de\u8c03');
assert.equal(dom.contributionModeSummary.textContent.length > 0, true); assert.equal(dom.accountContributionSummary.textContent.length > 0, true);
assert.equal(dom.btnContributionMode.classList.contains('is-active'), true); assert.equal(dom.btnContributionMode.classList.contains('is-active'), true);
assert.equal(dom.rowVpsUrl.classList.contains('is-contribution-hidden'), true); assert.equal(dom.rowVpsUrl.classList.contains('is-contribution-hidden'), true);
assert.equal(dom.rowCodex2ApiUrl.classList.contains('is-contribution-hidden'), true); assert.equal(dom.rowCodex2ApiUrl.classList.contains('is-contribution-hidden'), true);
@@ -595,28 +587,28 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
assert.equal(appliedState.contributionSessionId, ''); assert.equal(appliedState.contributionSessionId, '');
assert.equal(latestState.contributionSessionId, 'session-002'); assert.equal(latestState.contributionSessionId, 'session-002');
assert.equal(latestState.contributionStatus, 'started'); assert.equal(latestState.contributionStatus, 'started');
const contributionProfileMessage = sentMessages.find((message) => message.type === 'SET_CONTRIBUTION_PROFILE'); assert.equal(latestState.contributionNickname, '贡献者昵称');
assert.deepStrictEqual(contributionProfileMessage?.payload, { nickname: '贡献者昵称', qq: '123456' }); assert.equal(latestState.contributionQq, '123456');
assert.equal(timers.length > 0, true); assert.equal(timers.length > 0, true);
await manager.pollOnce({ reason: 'test_poll' }); await manager.pollOnce({ reason: 'test_poll' });
assert.equal(statusState.contributionStatus, 'processing'); assert.equal(statusState.contributionStatus, 'processing');
assert.equal(dom.contributionOauthStatus.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03'); assert.equal(dom.contributionOauthStatus.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03');
assert.equal(dom.contributionCallbackStatus.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03'); assert.equal(dom.contributionCallbackStatus.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03');
assert.equal(dom.contributionModeSummary.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03\uff0c\u7b49\u5f85\u670d\u52a1\u7aef\u786e\u8ba4'); assert.equal(dom.accountContributionSummary.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03\uff0c\u7b49\u5f85\u670d\u52a1\u7aef\u786e\u8ba4');
dom.btnOpenContributionUpload.listeners.click(); dom.btnOpenContributionUpload.listeners.click();
assert.deepStrictEqual(openedUrls, ['https://flowpilot.qlhazycoder.top', 'https://flowpilot.qlhazycoder.top/upload']); assert.deepStrictEqual(openedUrls, ['https://flowpilot.qlhazycoder.top', 'https://flowpilot.qlhazycoder.top/upload']);
await dom.btnExitContributionMode.listeners.click(); await dom.btnExitContributionMode.listeners.click();
manager.render(); manager.render();
assert.equal(dom.contributionModePanel.hidden, true); assert.equal(dom.accountContributionPanel.hidden, true);
assert.equal(dom.btnContributionMode.classList.contains('is-active'), false); assert.equal(dom.btnContributionMode.classList.contains('is-active'), false);
assert.equal(dom.selectPanelMode.disabled, false); assert.equal(dom.selectPanelMode.disabled, false);
assert.equal(dom.rowVpsUrl.classList.contains('is-contribution-hidden'), false); assert.equal(dom.rowVpsUrl.classList.contains('is-contribution-hidden'), false);
assert.deepStrictEqual( assert.deepStrictEqual(
sentMessages.map((message) => message.type), sentMessages.map((message) => message.type),
['SET_CONTRIBUTION_MODE', 'SET_CONTRIBUTION_PROFILE', 'POLL_CONTRIBUTION_STATUS', 'SET_CONTRIBUTION_MODE'] ['SET_ACCOUNT_CONTRIBUTION_MODE', 'POLL_FLOW_CONTRIBUTION_STATUS', 'SET_ACCOUNT_CONTRIBUTION_MODE']
); );
assert.deepStrictEqual( assert.deepStrictEqual(
toasts.map((item) => item.message), toasts.map((item) => item.message),
@@ -625,7 +617,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
blocked = true; blocked = true;
latestState = { latestState = {
contributionMode: true, accountContributionEnabled: true,
panelMode: 'sub2api', panelMode: 'sub2api',
contributionSource: 'sub2api', contributionSource: 'sub2api',
contributionTargetGroupName: 'codex号池', contributionTargetGroupName: 'codex号池',
@@ -640,7 +632,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
}; };
manager.render(); manager.render();
assert.equal(dom.selectPanelMode.value, 'sub2api'); assert.equal(dom.selectPanelMode.value, 'sub2api');
assert.equal(dom.contributionModeBadge.textContent, 'SUB2API'); assert.equal(dom.accountContributionBadge.textContent, 'SUB2API');
assert.equal(dom.btnExitContributionMode.disabled, true); assert.equal(dom.btnExitContributionMode.disabled, true);
manager.stopPolling(); manager.stopPolling();
}); });
+1 -1
View File
@@ -84,7 +84,7 @@ test('collectSettingsPayload persists icloud target mailbox settings', () => {
const bundle = extractFunction('collectSettingsPayload'); const bundle = extractFunction('collectSettingsPayload');
const api = new Function(` const api = new Function(`
let latestState = { contributionMode: false }; let latestState = { accountContributionEnabled: false };
const window = {}; const window = {};
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';
+1 -1
View File
@@ -154,7 +154,7 @@ test('collectSettingsPayload persists currentMail2925AccountId for 2925 account
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', ` const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
let latestState = { let latestState = {
contributionMode: false, accountContributionEnabled: false,
mail2925UseAccountPool: true, mail2925UseAccountPool: true,
currentMail2925AccountId: 'acc-2', currentMail2925AccountId: 'acc-2',
}; };
+5 -5
View File
@@ -72,7 +72,7 @@ const localStorage = {
}, },
}; };
const btnContributionMode = { disabled: false }; const btnContributionMode = { disabled: false };
let latestState = { contributionMode: false }; let latestState = { accountContributionEnabled: false };
${bundle} ${bundle}
return { return {
shouldPromptNewUserGuide, shouldPromptNewUserGuide,
@@ -82,8 +82,8 @@ return {
setButtonDisabled(value) { setButtonDisabled(value) {
btnContributionMode.disabled = Boolean(value); btnContributionMode.disabled = Boolean(value);
}, },
setContributionMode(value) { setAccountContributionMode(value) {
latestState = { contributionMode: Boolean(value) }; latestState = { accountContributionEnabled: Boolean(value) };
}, },
}; };
`)(); `)();
@@ -98,7 +98,7 @@ return {
assert.equal(api.shouldPromptNewUserGuide(), false); assert.equal(api.shouldPromptNewUserGuide(), false);
api.setButtonDisabled(false); api.setButtonDisabled(false);
api.setContributionMode(true); api.setAccountContributionMode(true);
assert.equal(api.shouldPromptNewUserGuide(), false); assert.equal(api.shouldPromptNewUserGuide(), false);
}); });
@@ -129,7 +129,7 @@ const localStorage = {
}, },
}; };
const btnContributionMode = { disabled: false }; const btnContributionMode = { disabled: false };
const latestState = { contributionMode: false }; const latestState = { accountContributionEnabled: false };
const contributionContentService = { portalUrl: 'https://flowpilot.qlhazycoder.top' }; const contributionContentService = { portalUrl: 'https://flowpilot.qlhazycoder.top' };
const openedUrls = []; const openedUrls = [];
let modalOptions = null; let modalOptions = null;
@@ -337,7 +337,7 @@ const window = {
}; };
let latestState = { let latestState = {
activeFlowId: 'site-a', activeFlowId: 'site-a',
contributionMode: false, accountContributionEnabled: false,
panelMode: 'cpa', panelMode: 'cpa',
}; };
const inputPhoneVerificationEnabled = { checked: true }; const inputPhoneVerificationEnabled = { checked: true };
@@ -877,7 +877,7 @@ 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 DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
let latestState = { let latestState = {
contributionMode: false, accountContributionEnabled: false,
mail2925UseAccountPool: false, mail2925UseAccountPool: false,
currentMail2925AccountId: '', currentMail2925AccountId: '',
fiveSimCountryOrder: ['thailand', 'england'], fiveSimCountryOrder: ['thailand', 'england'],