feat: upgrade multi-flow account contributions
This commit is contained in:
+200
-33
@@ -2,6 +2,7 @@
|
||||
|
||||
importScripts(
|
||||
'shared/flow-registry.js',
|
||||
'shared/contribution-registry.js',
|
||||
'shared/settings-schema.js',
|
||||
'shared/source-registry.js',
|
||||
'shared/flow-capabilities.js',
|
||||
@@ -27,6 +28,8 @@ importScripts(
|
||||
'background/workflow-engine.js',
|
||||
'background/runtime-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/desktop-client.js',
|
||||
'background/kiro/desktop-authorize-runner.js',
|
||||
@@ -694,8 +697,10 @@ const SIGNUP_METHOD_EMAIL = 'email';
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
||||
const CONTRIBUTION_RUNTIME_DEFAULTS = self.MultiPageBackgroundContributionOAuth?.RUNTIME_DEFAULTS || {
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
accountContributionEnabled: false,
|
||||
accountContributionExpected: false,
|
||||
contributionAdapterId: '',
|
||||
flowContributionRuntime: {},
|
||||
contributionSource: CONTRIBUTION_SOURCE_SUB2API,
|
||||
contributionTargetGroupName: CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME,
|
||||
contributionNickname: '',
|
||||
@@ -715,6 +720,61 @@ const CONTRIBUTION_RUNTIME_DEFAULTS = self.MultiPageBackgroundContributionOAuth?
|
||||
const CONTRIBUTION_RUNTIME_KEYS = self.MultiPageBackgroundContributionOAuth?.RUNTIME_KEYS
|
||||
|| 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 = {}) {
|
||||
return Boolean(state?.plusModeEnabled);
|
||||
}
|
||||
@@ -732,16 +792,16 @@ function normalizeGpcHelperPhoneMode(value = '') {
|
||||
return normalized === 'auto' || normalized === 'builtin' ? 'auto' : 'manual';
|
||||
}
|
||||
|
||||
function normalizeContributionModeSource(value = '') {
|
||||
function normalizeOpenAiContributionSource(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized === CONTRIBUTION_SOURCE_SUB2API
|
||||
? CONTRIBUTION_SOURCE_SUB2API
|
||||
: CONTRIBUTION_SOURCE_CPA;
|
||||
}
|
||||
|
||||
function resolveContributionModeRoutingState(state = {}) {
|
||||
function resolveOpenAiContributionRoutingState(state = {}) {
|
||||
const currentStatus = String(state?.contributionStatus || '').trim().toLowerCase();
|
||||
const currentSource = normalizeContributionModeSource(state?.contributionSource);
|
||||
const currentSource = normalizeOpenAiContributionSource(state?.contributionSource);
|
||||
const hasActiveSession = Boolean(
|
||||
String(state?.contributionSessionId || '').trim()
|
||||
&& currentStatus
|
||||
@@ -1748,7 +1808,7 @@ function canUsePhoneSignup(state = {}) {
|
||||
}
|
||||
return Boolean(state?.phoneVerificationEnabled)
|
||||
&& !Boolean(state?.plusModeEnabled)
|
||||
&& !Boolean(state?.contributionMode);
|
||||
&& !Boolean(state?.accountContributionEnabled);
|
||||
}
|
||||
|
||||
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) {
|
||||
console.log(LOG_PREFIX, 'storage.set:', JSON.stringify(updates).slice(0, 200));
|
||||
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, 'panelMode')
|
||||
|| Object.prototype.hasOwnProperty.call(importedSettings, 'activeFlowId')
|
||||
|| Object.prototype.hasOwnProperty.call(importedSettings, 'contributionMode')
|
||||
|| Object.prototype.hasOwnProperty.call(importedSettings, 'accountContributionEnabled')
|
||||
) {
|
||||
importedSettings.signupMethod = resolveSignupMethod({
|
||||
...state,
|
||||
@@ -4112,27 +4222,42 @@ async function setPasswordState(password) {
|
||||
broadcastDataUpdate({ password });
|
||||
}
|
||||
|
||||
function buildContributionModeState(enabled, persistedSettings = {}, currentState = {}) {
|
||||
function buildAccountContributionState(enabled, persistedSettings = {}, currentState = {}, options = {}) {
|
||||
const currentContributionState = {};
|
||||
for (const key of CONTRIBUTION_RUNTIME_KEYS) {
|
||||
currentContributionState[key] = currentState[key] !== undefined
|
||||
? currentState[key]
|
||||
: CONTRIBUTION_RUNTIME_DEFAULTS[key];
|
||||
}
|
||||
|
||||
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,
|
||||
...currentState,
|
||||
...currentContributionState,
|
||||
});
|
||||
}) : null;
|
||||
return {
|
||||
...currentContributionState,
|
||||
contributionMode: true,
|
||||
contributionModeExpected: true,
|
||||
contributionSource: routing.source,
|
||||
contributionTargetGroupName: routing.targetGroupName,
|
||||
panelMode: routing.source,
|
||||
accountContributionEnabled: true,
|
||||
accountContributionExpected: true,
|
||||
contributionAdapterId: adapterId,
|
||||
flowContributionRuntime: buildFlowContributionRuntimePatch(currentContributionState.flowContributionRuntime, activeFlowId, adapterId, true),
|
||||
...(routing ? {
|
||||
contributionSource: routing.source,
|
||||
contributionTargetGroupName: routing.targetGroupName,
|
||||
panelMode: routing.source,
|
||||
} : {}),
|
||||
customPassword: '',
|
||||
accountRunHistoryTextEnabled: false,
|
||||
};
|
||||
@@ -4140,22 +4265,24 @@ function buildContributionModeState(enabled, persistedSettings = {}, currentStat
|
||||
|
||||
return {
|
||||
...CONTRIBUTION_RUNTIME_DEFAULTS,
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
accountContributionEnabled: false,
|
||||
accountContributionExpected: false,
|
||||
contributionAdapterId: '',
|
||||
flowContributionRuntime: {},
|
||||
panelMode: persistedSettings.panelMode || DEFAULT_STATE.panelMode,
|
||||
customPassword: persistedSettings.customPassword || '',
|
||||
accountRunHistoryTextEnabled: Boolean(persistedSettings.accountRunHistoryTextEnabled),
|
||||
};
|
||||
}
|
||||
|
||||
async function setContributionMode(enabled) {
|
||||
async function setAccountContributionMode(enabled, options = {}) {
|
||||
const normalizedEnabled = Boolean(enabled);
|
||||
const [persistedSettings, currentState] = await Promise.all([
|
||||
getPersistedSettings(),
|
||||
getState(),
|
||||
]);
|
||||
|
||||
const updates = buildContributionModeState(normalizedEnabled, persistedSettings, currentState);
|
||||
const updates = buildAccountContributionState(normalizedEnabled, persistedSettings, currentState, options);
|
||||
|
||||
await setState(updates);
|
||||
const nextState = await getState();
|
||||
@@ -4410,7 +4537,10 @@ async function resetState() {
|
||||
getPersistedSettings(),
|
||||
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 = (
|
||||
prev.reusablePhoneActivation
|
||||
&& typeof prev.reusablePhoneActivation === 'object'
|
||||
@@ -4453,7 +4583,7 @@ async function resetState() {
|
||||
...DEFAULT_STATE,
|
||||
...persistedSettings,
|
||||
...persistedAliasState,
|
||||
...contributionModeState,
|
||||
...accountContributionState,
|
||||
seenCodes: prev.seenCodes || [],
|
||||
seenInbucketMailIds: prev.seenInbucketMailIds || [],
|
||||
accounts: prev.accounts || [],
|
||||
@@ -13378,6 +13508,32 @@ const kiroRegisterRunner = self.MultiPageBackgroundKiroRegisterRunner?.createKir
|
||||
waitForTabStableComplete,
|
||||
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({
|
||||
addLog,
|
||||
chrome,
|
||||
@@ -13398,6 +13554,7 @@ const kiroDesktopAuthorizeRunner = self.MultiPageBackgroundKiroDesktopAuthorizeR
|
||||
YYDS_MAIL_PROVIDER,
|
||||
MAIL_2925_VERIFICATION_INTERVAL_MS,
|
||||
MAIL_2925_VERIFICATION_MAX_ATTEMPTS,
|
||||
maybeSubmitFlowContribution,
|
||||
pollCloudflareTempEmailVerificationCode,
|
||||
pollCloudMailVerificationCode,
|
||||
pollHotmailVerificationCode,
|
||||
@@ -13626,7 +13783,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
||||
setCurrentPayPalAccount,
|
||||
setCurrentHotmailAccount,
|
||||
setCurrentMail2925Account,
|
||||
setContributionMode,
|
||||
setAccountContributionMode,
|
||||
setEmailState,
|
||||
setEmailStateSilently,
|
||||
persistRegistrationEmailState,
|
||||
@@ -13643,9 +13800,10 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
||||
setNodeStatus,
|
||||
skipAutoRunCountdown,
|
||||
skipNode,
|
||||
startContributionFlow: (...args) => contributionOAuthManager?.startContributionFlow?.(...args),
|
||||
startFlowContribution: (...args) => contributionOAuthManager?.startFlowContribution?.(...args),
|
||||
startAutoRunLoop,
|
||||
pollContributionStatus: (...args) => contributionOAuthManager?.pollContributionStatus?.(...args),
|
||||
submitFlowContribution: (...args) => contributionOAuthManager?.submitContributionCallback?.(...args),
|
||||
syncHotmailAccounts,
|
||||
syncPayPalAccounts,
|
||||
deleteMail2925Account,
|
||||
@@ -13958,15 +14116,15 @@ async function executeStep5(state) {
|
||||
|
||||
async function refreshOAuthUrlBeforeStep6(state, options = {}) {
|
||||
const visibleStep = Number(options.visibleStep) || Number(state?.visibleStep) || 7;
|
||||
if (state?.contributionModeExpected && !state?.contributionMode) {
|
||||
throw new Error(`步骤 ${visibleStep}:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 链路。请重新进入贡献模式后再点击自动。`);
|
||||
if (state?.accountContributionExpected && !state?.accountContributionEnabled) {
|
||||
throw new Error(`步骤 ${visibleStep}:当前自动流程预期使用账号贡献,但运行态 accountContributionEnabled 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 链路。请重新进入账号贡献后再点击自动。`);
|
||||
}
|
||||
if (state?.contributionMode && contributionOAuthManager?.startContributionFlow) {
|
||||
await addLog('contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...', 'info', {
|
||||
if (state?.accountContributionEnabled && contributionOAuthManager?.startFlowContribution) {
|
||||
await addLog('账号贡献已开启,走公开贡献接口,正在申请 OAuth 登录地址...', 'info', {
|
||||
step: visibleStep,
|
||||
stepKey: 'oauth-login',
|
||||
});
|
||||
const contributionState = await contributionOAuthManager.startContributionFlow({
|
||||
const contributionState = await contributionOAuthManager.startFlowContribution({
|
||||
nickname: state.contributionNickname || '',
|
||||
openAuthTab: false,
|
||||
stateOverride: state,
|
||||
@@ -13978,7 +14136,7 @@ async function refreshOAuthUrlBeforeStep6(state, options = {}) {
|
||||
await handleStepData(1, { 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,
|
||||
stepKey: 'oauth-login',
|
||||
});
|
||||
@@ -15337,10 +15495,10 @@ async function executeStep10(state) {
|
||||
const platformVerifyStep = typeof getStepIdByKeyForState === 'function'
|
||||
? (getStepIdByKeyForState('platform-verify', state || {}) || 10)
|
||||
: 10;
|
||||
if (state?.contributionModeExpected && !state?.contributionMode) {
|
||||
throw new Error(`步骤 ${platformVerifyStep}:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 提交。请重新进入贡献模式后再点击自动。`);
|
||||
if (state?.accountContributionExpected && !state?.accountContributionEnabled) {
|
||||
throw new Error(`步骤 ${platformVerifyStep}:当前自动流程预期使用账号贡献,但运行态 accountContributionEnabled 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 提交。请重新进入账号贡献后再点击自动。`);
|
||||
}
|
||||
if (state?.contributionMode) {
|
||||
if (state?.accountContributionEnabled) {
|
||||
return executeContributionStep10(state);
|
||||
}
|
||||
return step10Executor.executeStep10(state);
|
||||
@@ -15367,6 +15525,9 @@ chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
});
|
||||
|
||||
chrome.runtime.onStartup.addListener(() => {
|
||||
migrateLegacyAccountContributionState().catch((err) => {
|
||||
console.error(LOG_PREFIX, 'Failed to migrate legacy account contribution state on startup:', err);
|
||||
});
|
||||
restoreAutoRunTimerIfNeeded().catch((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(() => {
|
||||
migrateLegacyAccountContributionState().catch((err) => {
|
||||
console.error(LOG_PREFIX, 'Failed to migrate legacy account contribution state on install/update:', err);
|
||||
});
|
||||
restoreAutoRunTimerIfNeeded().catch((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) => {
|
||||
console.error(LOG_PREFIX, 'Failed to restore auto run timer:', err);
|
||||
});
|
||||
|
||||
@@ -431,7 +431,7 @@
|
||||
source,
|
||||
autoRunContext: source === 'auto' ? autoRunContext : null,
|
||||
plusModeEnabled: Boolean(record.plusModeEnabled),
|
||||
contributionMode: Boolean(record.contributionMode),
|
||||
accountContributionEnabled: Boolean(record.accountContributionEnabled),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@
|
||||
source,
|
||||
autoRunContext,
|
||||
plusModeEnabled: Boolean(state.plusModeEnabled),
|
||||
contributionMode: Boolean(state.contributionMode),
|
||||
accountContributionEnabled: Boolean(state.accountContributionEnabled),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -637,7 +637,7 @@
|
||||
}
|
||||
|
||||
function shouldSyncAccountRunHistorySnapshot(state = {}) {
|
||||
if (Boolean(state.contributionMode)) {
|
||||
if (Boolean(state.accountContributionEnabled)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
const CALLBACK_WAITING_STATUSES = new Set(['idle', 'waiting', 'captured', 'failed', 'submitting']);
|
||||
|
||||
const RUNTIME_DEFAULTS = {
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
accountContributionEnabled: false,
|
||||
accountContributionExpected: false,
|
||||
contributionAdapterId: '',
|
||||
flowContributionRuntime: {},
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionNickname: '',
|
||||
@@ -265,14 +267,14 @@
|
||||
return Boolean(state?.plusModeEnabled);
|
||||
}
|
||||
|
||||
function normalizeContributionModeSource(value = '') {
|
||||
function normalizeOpenAiContributionSource(value = '') {
|
||||
const normalized = normalizeString(value).toLowerCase();
|
||||
return normalized === 'sub2api' ? 'sub2api' : 'cpa';
|
||||
}
|
||||
|
||||
function resolveContributionModeRoutingState(state = {}) {
|
||||
function resolveOpenAiContributionRoutingState(state = {}) {
|
||||
const currentStatus = normalizeString(state?.contributionStatus).toLowerCase();
|
||||
const currentSource = normalizeContributionModeSource(state?.contributionSource);
|
||||
const currentSource = normalizeOpenAiContributionSource(state?.contributionSource);
|
||||
const hasActiveSession = Boolean(
|
||||
normalizeString(state?.contributionSessionId)
|
||||
&& currentStatus
|
||||
@@ -533,7 +535,7 @@
|
||||
|
||||
async function handleCapturedCallback(rawUrl, metadata = {}) {
|
||||
const currentState = await getState();
|
||||
if (!normalizeString(currentState.contributionSessionId) || !currentState.contributionMode) {
|
||||
if (!normalizeString(currentState.contributionSessionId) || !currentState.accountContributionEnabled) {
|
||||
return currentState;
|
||||
}
|
||||
if (!isContributionCallbackUrl(rawUrl, currentState)) {
|
||||
@@ -642,10 +644,10 @@
|
||||
return nextState;
|
||||
}
|
||||
|
||||
async function startContributionFlow(options = {}) {
|
||||
async function startFlowContribution(options = {}) {
|
||||
const currentState = options.stateOverride || await getState();
|
||||
const shouldOpenAuthTab = options.openAuthTab !== false;
|
||||
if (!currentState.contributionMode) {
|
||||
if (!currentState.accountContributionEnabled) {
|
||||
throw new Error('请先进入贡献模式。');
|
||||
}
|
||||
|
||||
@@ -662,7 +664,7 @@
|
||||
return pollContributionStatus({ reason: 'resume_existing' });
|
||||
}
|
||||
|
||||
const routingState = resolveContributionModeRoutingState(currentState);
|
||||
const routingState = resolveOpenAiContributionRoutingState(currentState);
|
||||
const payload = await fetchContributionJson('/start', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
@@ -744,7 +746,7 @@
|
||||
isContributionCallbackUrl,
|
||||
isContributionFinalStatus,
|
||||
pollContributionStatus,
|
||||
startContributionFlow,
|
||||
startFlowContribution,
|
||||
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,
|
||||
};
|
||||
});
|
||||
@@ -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_MAX_ATTEMPTS = 15,
|
||||
isTabAlive = async () => false,
|
||||
maybeSubmitFlowContribution = async () => null,
|
||||
KIRO_REGISTER_INJECT_FILES = null,
|
||||
KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = null,
|
||||
pollCloudflareTempEmailVerificationCode = null,
|
||||
@@ -477,6 +478,15 @@
|
||||
},
|
||||
});
|
||||
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);
|
||||
return payload;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
}
|
||||
return Boolean(state?.phoneVerificationEnabled)
|
||||
&& !Boolean(state?.plusModeEnabled)
|
||||
&& !Boolean(state?.contributionMode);
|
||||
&& !Boolean(state?.accountContributionEnabled);
|
||||
},
|
||||
resolveSignupMethod = (state = {}) => {
|
||||
const method = normalizeSignupMethod(state?.signupMethod);
|
||||
@@ -149,6 +149,7 @@
|
||||
patchMail2925Account,
|
||||
patchHotmailAccount,
|
||||
pollContributionStatus,
|
||||
submitFlowContribution,
|
||||
registerTab,
|
||||
requestStop,
|
||||
probeIpProxyExit,
|
||||
@@ -162,7 +163,7 @@
|
||||
setCurrentPayPalAccount,
|
||||
setCurrentMail2925Account,
|
||||
setCurrentHotmailAccount,
|
||||
setContributionMode,
|
||||
setAccountContributionMode,
|
||||
setEmailState,
|
||||
setEmailStateSilently,
|
||||
persistRegistrationEmailState,
|
||||
@@ -179,7 +180,7 @@
|
||||
setNodeStatus,
|
||||
skipAutoRunCountdown,
|
||||
skipNode,
|
||||
startContributionFlow,
|
||||
startFlowContribution,
|
||||
startAutoRunLoop,
|
||||
deleteMail2925Account,
|
||||
deleteMail2925Accounts,
|
||||
@@ -1154,59 +1155,61 @@
|
||||
return await setFreeReusablePhoneActivation(message.payload || {});
|
||||
}
|
||||
|
||||
case 'SET_CONTRIBUTION_MODE': {
|
||||
case 'SET_ACCOUNT_CONTRIBUTION_MODE': {
|
||||
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')) {
|
||||
throw new Error(enabled ? '当前有步骤正在执行,无法进入贡献模式。' : '当前有步骤正在执行,无法退出贡献模式。');
|
||||
throw new Error(enabled ? '当前有步骤正在执行,无法进入账号贡献。' : '当前有步骤正在执行,无法退出账号贡献。');
|
||||
}
|
||||
if (typeof setContributionMode !== 'function') {
|
||||
throw new Error('贡献模式切换能力未接入。');
|
||||
if (typeof setAccountContributionMode !== 'function') {
|
||||
throw new Error('账号贡献切换能力未接入。');
|
||||
}
|
||||
return {
|
||||
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('开始贡献');
|
||||
if (Object.values(state.nodeStatuses || {}).some((status) => status === 'running')) {
|
||||
throw new Error('当前有步骤正在执行,无法开始贡献流程。');
|
||||
}
|
||||
if (typeof startContributionFlow !== 'function') {
|
||||
if (!state?.accountContributionEnabled) {
|
||||
throw new Error('请先进入账号贡献。');
|
||||
}
|
||||
if (typeof startFlowContribution !== 'function') {
|
||||
throw new Error('贡献 OAuth 流程尚未接入。');
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
state: await startContributionFlow({
|
||||
state: await startFlowContribution({
|
||||
nickname: message.payload?.nickname,
|
||||
qq: message.payload?.qq,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case 'SET_CONTRIBUTION_PROFILE': {
|
||||
case 'SUBMIT_FLOW_CONTRIBUTION': {
|
||||
const state = await getState();
|
||||
if (!state?.contributionMode) {
|
||||
throw new Error('请先进入贡献模式。');
|
||||
if (!state?.accountContributionEnabled) {
|
||||
throw new Error('请先进入账号贡献。');
|
||||
}
|
||||
const nickname = String(message.payload?.nickname || '').trim();
|
||||
const qq = String(message.payload?.qq || '').trim();
|
||||
if (qq && !/^\d{1,20}$/.test(qq)) {
|
||||
throw new Error('QQ 只能填写数字,且长度不能超过 20 位。');
|
||||
if (typeof submitFlowContribution !== 'function') {
|
||||
throw new Error('贡献提交能力尚未接入。');
|
||||
}
|
||||
await setState({
|
||||
contributionNickname: nickname,
|
||||
contributionQq: qq,
|
||||
});
|
||||
return {
|
||||
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') {
|
||||
throw new Error('贡献状态轮询能力尚未接入。');
|
||||
}
|
||||
@@ -1285,8 +1288,11 @@
|
||||
if (message.source === 'sidepanel') {
|
||||
await lockAutomationWindowFromMessage(message, sender);
|
||||
}
|
||||
if (Boolean(message.payload?.contributionMode) && typeof setContributionMode === 'function') {
|
||||
await setContributionMode(true);
|
||||
if (Boolean(message.payload?.accountContributionEnabled) && typeof setAccountContributionMode === 'function') {
|
||||
await setAccountContributionMode(true, {
|
||||
adapterId: message.payload?.contributionAdapterId,
|
||||
flowId: message.payload?.activeFlowId || message.payload?.flowId,
|
||||
});
|
||||
if (typeof setState === 'function') {
|
||||
const contributionNickname = String(message.payload?.contributionNickname || '').trim();
|
||||
const contributionQq = String(message.payload?.contributionQq || '').trim();
|
||||
@@ -1325,8 +1331,11 @@
|
||||
if (message.source === 'sidepanel') {
|
||||
await lockAutomationWindowFromMessage(message, sender);
|
||||
}
|
||||
if (Boolean(message.payload?.contributionMode) && typeof setContributionMode === 'function') {
|
||||
await setContributionMode(true);
|
||||
if (Boolean(message.payload?.accountContributionEnabled) && typeof setAccountContributionMode === 'function') {
|
||||
await setAccountContributionMode(true, {
|
||||
adapterId: message.payload?.contributionAdapterId,
|
||||
flowId: message.payload?.activeFlowId || message.payload?.flowId,
|
||||
});
|
||||
if (typeof setState === 'function') {
|
||||
const contributionNickname = String(message.payload?.contributionNickname || '').trim();
|
||||
const contributionQq = String(message.payload?.contributionQq || '').trim();
|
||||
@@ -1444,7 +1453,7 @@
|
||||
|| Object.prototype.hasOwnProperty.call(updates, 'signupMethod')
|
||||
|| Object.prototype.hasOwnProperty.call(updates, 'panelMode')
|
||||
|| Object.prototype.hasOwnProperty.call(updates, 'activeFlowId')
|
||||
|| Object.prototype.hasOwnProperty.call(updates, 'contributionMode')
|
||||
|| Object.prototype.hasOwnProperty.call(updates, 'accountContributionEnabled')
|
||||
) {
|
||||
updates.signupMethod = resolveSignupMethod(nextSignupState);
|
||||
}
|
||||
@@ -1576,8 +1585,11 @@
|
||||
error: error?.message || String(error || '代理应用失败'),
|
||||
}));
|
||||
}
|
||||
if (Boolean(currentState?.contributionMode) && typeof setContributionMode === 'function') {
|
||||
await setContributionMode(true);
|
||||
if (Boolean(currentState?.accountContributionEnabled) && typeof setAccountContributionMode === 'function') {
|
||||
await setAccountContributionMode(true, {
|
||||
adapterId: currentState?.contributionAdapterId,
|
||||
flowId: currentState?.activeFlowId || currentState?.flowId,
|
||||
});
|
||||
}
|
||||
if (Object.keys(stateUpdates).length > 0 && typeof broadcastDataUpdate === 'function') {
|
||||
broadcastDataUpdate(stateUpdates);
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
return normalizeStep7SignupMethod(state?.signupMethod) === 'phone'
|
||||
&& Boolean(state?.phoneVerificationEnabled)
|
||||
&& !Boolean(state?.plusModeEnabled)
|
||||
&& !Boolean(state?.contributionMode);
|
||||
&& !Boolean(state?.accountContributionEnabled);
|
||||
}
|
||||
|
||||
function hasStep7PhoneSignupIdentity(state = {}) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
# 多注册流程侧边栏能力矩阵
|
||||
|
||||
> 历史说明:本文记录的是多 flow 侧边栏能力矩阵的早期设计背景。2026-05-19 起,账号贡献运行态已从历史 `contributionMode` 升级为 `accountContributionEnabled` + `contributionAdapterId`;后续开发请以 `多Flow资源入口与贡献体系升级开发方案.md` 和当前代码为准,不要继续新增或恢复 `contributionMode` 运行时逻辑。
|
||||
|
||||
本文解决的是一个很容易被低估的问题:sidepanel 现在不只是“渲染 UI”,它还承担了大量业务约束。如果未来要支持多个 flow,不能只做动态渲染,还必须把“哪些能力允许显示、允许切换、允许启动”做成统一能力矩阵。
|
||||
|
||||
## 1. 当前源码里的真实问题
|
||||
@@ -255,4 +257,3 @@ getStepDefinitions({
|
||||
- 手机号注册能力由多个开关共同决定,当前逻辑分散且重复
|
||||
- `contributionMode` 与 `panelMode` 容易混淆
|
||||
- 多 flow 之后,步骤列表也必须纳入能力矩阵,而不是继续全局写死
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createFlowCapabilitiesModule() {
|
||||
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
|
||||
const contributionRegistryApi = rootScope.MultiPageContributionRegistry || {};
|
||||
const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {};
|
||||
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
|
||||
const DEFAULT_OPENAI_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_TARGET_ID || 'cpa';
|
||||
@@ -25,6 +26,9 @@
|
||||
supportsPhoneVerificationSettings: false,
|
||||
supportsPlusMode: false,
|
||||
supportsContributionMode: false,
|
||||
supportsAccountContribution: false,
|
||||
supportsOpenAiOAuthContribution: false,
|
||||
contributionAdapterIds: [],
|
||||
supportedTargetIds: [],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
@@ -58,7 +62,7 @@
|
||||
|
||||
const MODE_SWITCH_RELEVANT_KEYS = Object.freeze([
|
||||
'activeFlowId',
|
||||
'contributionMode',
|
||||
'accountContributionEnabled',
|
||||
'panelMode',
|
||||
'phoneVerificationEnabled',
|
||||
'plusModeEnabled',
|
||||
@@ -195,6 +199,14 @@
|
||||
function getFlowCapabilities(flowId) {
|
||||
const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId);
|
||||
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'
|
||||
? normalizeOpenAiTargetList(
|
||||
entry?.supportedTargetIds || defaultFlowCapabilities.supportedTargetIds
|
||||
@@ -206,6 +218,8 @@
|
||||
...defaultFlowCapabilities,
|
||||
...(entry || {}),
|
||||
supportedTargetIds,
|
||||
contributionAdapterIds,
|
||||
supportsAccountContribution: Boolean(entry?.supportsAccountContribution || contributionAdapterIds.length > 0),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -332,7 +346,7 @@
|
||||
: defaultTargetCapabilities;
|
||||
const runtimeLocks = {
|
||||
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),
|
||||
plusModeEnabled: activeFlowId === 'openai' && flowState.supportsPlusMode && Boolean(state?.plusModeEnabled),
|
||||
settingsMenuLocked: Boolean(options?.settingsMenuLocked ?? state?.settingsMenuLocked),
|
||||
@@ -346,7 +360,7 @@
|
||||
&& Boolean(targetState.supportsPhoneSignup)
|
||||
&& runtimeLocks.phoneVerificationEnabled
|
||||
&& !runtimeLocks.plusModeEnabled
|
||||
&& !runtimeLocks.contributionMode;
|
||||
&& !runtimeLocks.accountContribution;
|
||||
if (canSelectPhoneSignup) {
|
||||
effectiveSignupMethods.push(SIGNUP_METHOD_PHONE);
|
||||
}
|
||||
@@ -390,7 +404,7 @@
|
||||
|
||||
return {
|
||||
activeFlowId,
|
||||
canShowContributionMode: activeFlowId === 'openai' && Boolean(flowState.supportsContributionMode),
|
||||
canShowContributionMode: Boolean(flowState.supportsAccountContribution),
|
||||
canShowLuckmail: activeFlowId === 'openai' && Boolean(flowState.supportsLuckmail),
|
||||
canShowPhoneSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPhoneVerificationSettings),
|
||||
canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode),
|
||||
@@ -459,7 +473,7 @@
|
||||
message: 'Plus 模式开启时不能使用手机号注册。',
|
||||
};
|
||||
}
|
||||
if (runtimeLocks.contributionMode) {
|
||||
if (runtimeLocks.accountContribution) {
|
||||
return {
|
||||
code: 'phone_signup_contribution_mode_locked',
|
||||
message: '贡献模式开启时不能使用手机号注册。',
|
||||
@@ -494,7 +508,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (Boolean(state?.contributionMode) && !capabilityState.flowCapabilities?.supportsContributionMode) {
|
||||
if (Boolean(state?.accountContributionEnabled) && !capabilityState.flowCapabilities?.supportsAccountContribution) {
|
||||
errors.push({
|
||||
code: 'contribution_mode_unsupported',
|
||||
message: '当前 flow 不支持贡献模式。',
|
||||
@@ -553,8 +567,12 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (changedKeySet.has('contributionMode') && Boolean(state?.contributionMode) && !flowState.supportsContributionMode) {
|
||||
normalizedUpdates.contributionMode = false;
|
||||
if (
|
||||
changedKeySet.has('accountContributionEnabled')
|
||||
&& Boolean(state?.accountContributionEnabled)
|
||||
&& !flowState.supportsAccountContribution
|
||||
) {
|
||||
normalizedUpdates.accountContributionEnabled = false;
|
||||
errors.push({
|
||||
code: 'contribution_mode_unsupported',
|
||||
message: '当前 flow 不支持贡献模式。',
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
supportsPhoneVerificationSettings: false,
|
||||
supportsPlusMode: false,
|
||||
supportsContributionMode: false,
|
||||
supportsAccountContribution: false,
|
||||
supportsOpenAiOAuthContribution: false,
|
||||
contributionAdapterIds: [],
|
||||
supportedTargetIds: [],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
@@ -44,6 +47,9 @@
|
||||
supportsPhoneVerificationSettings: true,
|
||||
supportsPlusMode: true,
|
||||
supportsContributionMode: true,
|
||||
supportsAccountContribution: true,
|
||||
supportsOpenAiOAuthContribution: true,
|
||||
contributionAdapterIds: ['openai-oauth', 'openai-codex-file', 'openai-sub2api-file'],
|
||||
supportedTargetIds: [...OPENAI_TARGET_IDS],
|
||||
supportsLuckmail: true,
|
||||
supportsOauthTimeoutBudget: true,
|
||||
@@ -203,6 +209,8 @@
|
||||
services: ['account', 'email', 'proxy'],
|
||||
capabilities: {
|
||||
...DEFAULT_FLOW_CAPABILITIES,
|
||||
supportsAccountContribution: true,
|
||||
contributionAdapterIds: ['kiro-builder-id'],
|
||||
supportedTargetIds: [DEFAULT_KIRO_TARGET_ID],
|
||||
stepDefinitionMode: 'kiro',
|
||||
},
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
(() => {
|
||||
const PORTAL_BASE_URL = 'https://flowpilot.qlhazycoder.top';
|
||||
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;
|
||||
|
||||
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 = {}) {
|
||||
return {
|
||||
slug: String(item?.slug || '').trim(),
|
||||
@@ -14,13 +36,20 @@
|
||||
isVisible: Boolean(item?.is_visible ?? item?.isVisible),
|
||||
updatedAt: String(item?.updated_at ?? item?.updatedAt ?? '').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)
|
||||
? 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 latestUpdatedAt = String(payload?.latest_updated_at || '').trim();
|
||||
const latestUpdatedAtDisplay = String(payload?.latest_updated_at_display || '').trim();
|
||||
@@ -33,15 +62,17 @@
|
||||
latestUpdatedAt,
|
||||
latestUpdatedAtDisplay,
|
||||
items,
|
||||
flowId: responseScope.flowId,
|
||||
targetId: responseScope.targetId,
|
||||
portalUrl: PORTAL_BASE_URL,
|
||||
apiUrl: CONTENT_SUMMARY_API_URL,
|
||||
apiUrl: buildSummaryApiUrl(responseScope),
|
||||
checkedAt: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
function readCache() {
|
||||
function readCache(scope = normalizeScope()) {
|
||||
try {
|
||||
const raw = localStorage.getItem(CACHE_KEY);
|
||||
const raw = localStorage.getItem(getCacheKey(scope));
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
@@ -57,7 +88,9 @@
|
||||
has_visible_updates: parsed.hasVisibleUpdates,
|
||||
latest_updated_at: parsed.latestUpdatedAt,
|
||||
latest_updated_at_display: parsed.latestUpdatedAtDisplay,
|
||||
});
|
||||
flow_id: parsed.flowId,
|
||||
target_id: parsed.targetId,
|
||||
}, scope);
|
||||
if (!Number.isFinite(parsed.checkedAt)) {
|
||||
return snapshot;
|
||||
}
|
||||
@@ -68,20 +101,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
function writeCache(snapshot) {
|
||||
function writeCache(snapshot, scope = normalizeScope(snapshot)) {
|
||||
try {
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(snapshot));
|
||||
localStorage.setItem(getCacheKey(scope), JSON.stringify(snapshot));
|
||||
} catch (error) {
|
||||
// Ignore cache write failures.
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchContentSummary() {
|
||||
async function fetchContentSummary(options = {}) {
|
||||
const scope = normalizeScope(options);
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
||||
|
||||
try {
|
||||
const response = await fetch(CONTENT_SUMMARY_API_URL, {
|
||||
const response = await fetch(buildSummaryApiUrl(scope), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
@@ -99,8 +133,8 @@
|
||||
throw new Error('内容摘要返回格式异常');
|
||||
}
|
||||
|
||||
const snapshot = buildSnapshot(payload);
|
||||
writeCache(snapshot);
|
||||
const snapshot = buildSnapshot(payload, scope);
|
||||
writeCache(snapshot, scope);
|
||||
return snapshot;
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError') {
|
||||
@@ -112,11 +146,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function getContentUpdateSnapshot() {
|
||||
async function getContentUpdateSnapshot(options = {}) {
|
||||
const scope = normalizeScope(options);
|
||||
try {
|
||||
return await fetchContentSummary();
|
||||
return await fetchContentSummary(scope);
|
||||
} catch (error) {
|
||||
const cached = readCache();
|
||||
const cached = readCache(scope);
|
||||
if (cached) {
|
||||
return {
|
||||
...cached,
|
||||
@@ -132,8 +167,10 @@
|
||||
latestUpdatedAt: '',
|
||||
latestUpdatedAtDisplay: '',
|
||||
items: [],
|
||||
flowId: scope.flowId,
|
||||
targetId: scope.targetId,
|
||||
portalUrl: PORTAL_BASE_URL,
|
||||
apiUrl: CONTENT_SUMMARY_API_URL,
|
||||
apiUrl: buildSummaryApiUrl(scope),
|
||||
checkedAt: Date.now(),
|
||||
errorMessage: error?.message || '内容摘要获取失败',
|
||||
};
|
||||
@@ -141,6 +178,7 @@
|
||||
}
|
||||
|
||||
window.SidepanelContributionContentService = {
|
||||
buildSummaryApiUrl,
|
||||
getContentUpdateSnapshot,
|
||||
portalUrl: PORTAL_BASE_URL,
|
||||
apiUrl: CONTENT_SUMMARY_API_URL,
|
||||
|
||||
+138
-42
@@ -81,6 +81,14 @@
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -88,12 +96,54 @@
|
||||
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()) {
|
||||
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()) {
|
||||
return isContributionModeAvailable(currentState) && Boolean(currentState.contributionMode);
|
||||
return isContributionModeAvailable(currentState) && Boolean(currentState.accountContributionEnabled);
|
||||
}
|
||||
|
||||
function hasActiveContributionSession(currentState = getLatestState()) {
|
||||
@@ -127,10 +177,10 @@
|
||||
dom.btnContributionMode.title = '当前 flow 不支持贡献模式';
|
||||
return;
|
||||
}
|
||||
dom.btnContributionMode.disabled = enabled || blocked;
|
||||
dom.btnContributionMode.title = blocked
|
||||
? '当前流程运行中,暂时不能切换贡献模式'
|
||||
: (enabled ? '当前已在贡献模式' : '进入贡献模式');
|
||||
dom.btnContributionMode.disabled = actionInFlight;
|
||||
dom.btnContributionMode.title = enabled
|
||||
? '打开当前 flow 教程;当前已在贡献模式'
|
||||
: (blocked ? '打开当前 flow 教程;当前流程运行中暂时不能进入贡献模式' : '打开当前 flow 教程并进入贡献模式');
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
@@ -163,6 +213,23 @@
|
||||
}
|
||||
|
||||
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 hasAuthUrl = Boolean(normalizeString(currentState.contributionAuthUrl));
|
||||
if (!normalizeString(currentState.contributionSessionId) || !hasAuthUrl) {
|
||||
@@ -184,6 +251,10 @@
|
||||
}
|
||||
|
||||
function getCallbackStatusText(currentState = getLatestState()) {
|
||||
if (getActiveFlowId(currentState) !== 'openai') {
|
||||
const flowRuntime = getCurrentFlowContributionRuntime(currentState);
|
||||
return normalizeString(flowRuntime.lastMessage || flowRuntime.error) || '账号产物就绪后会自动提交';
|
||||
}
|
||||
const status = normalizeCallbackStatus(currentState.contributionCallbackStatus);
|
||||
switch (status) {
|
||||
case 'captured':
|
||||
@@ -208,6 +279,9 @@
|
||||
if (statusMessage) {
|
||||
return statusMessage;
|
||||
}
|
||||
if (getActiveFlowId(currentState) !== 'openai') {
|
||||
return '当前账号将用于支持项目维护。扩展会按当前 flow 的贡献适配器收集并提交账号产物,提交过程不会依赖 OpenAI OAuth 配置。';
|
||||
}
|
||||
if (getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API) {
|
||||
const groupName = normalizeString(currentState.contributionTargetGroupName) || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME;
|
||||
return `当前账号将用于支持项目维护。贡献会通过 SUB2API 完成,并固定写入 ${groupName} 分组;如检测到回调地址,扩展会自动提交并等待服务端确认。`;
|
||||
@@ -215,11 +289,24 @@
|
||||
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() {
|
||||
return normalizeString(contributionPortalUrl);
|
||||
return normalizeString(getContributionTutorialEntry()?.portalUrl || contributionPortalUrl);
|
||||
}
|
||||
|
||||
function getContributionUploadPageUrl() {
|
||||
const currentState = getLatestState();
|
||||
if (getActiveFlowId(currentState) !== 'openai') {
|
||||
return normalizeString(getContributionTutorialEntry(currentState)?.portalUrl || contributionPortalUrl);
|
||||
}
|
||||
return normalizeString(contributionUploadUrl);
|
||||
}
|
||||
|
||||
@@ -240,28 +327,27 @@
|
||||
}
|
||||
|
||||
async function syncContributionProfile(partial = {}) {
|
||||
const payload = {
|
||||
nickname: normalizeString(partial.nickname),
|
||||
qq: normalizeString(partial.qq),
|
||||
};
|
||||
const response = await runtime.sendMessage({
|
||||
type: 'SET_CONTRIBUTION_PROFILE',
|
||||
source: 'sidepanel',
|
||||
payload,
|
||||
const nickname = normalizeString(partial.nickname);
|
||||
const qq = normalizeString(partial.qq);
|
||||
if (qq && !/^\d{1,20}$/.test(qq)) {
|
||||
throw new Error('QQ 只能填写数字,且长度不能超过 20 位。');
|
||||
}
|
||||
helpers.applySettingsState?.({
|
||||
...getLatestState(),
|
||||
contributionNickname: nickname,
|
||||
contributionQq: qq,
|
||||
});
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response?.state) {
|
||||
helpers.applySettingsState?.(response.state);
|
||||
}
|
||||
}
|
||||
|
||||
async function requestContributionMode(enabled) {
|
||||
const response = await runtime.sendMessage({
|
||||
type: 'SET_CONTRIBUTION_MODE',
|
||||
type: 'SET_ACCOUNT_CONTRIBUTION_MODE',
|
||||
source: 'sidepanel',
|
||||
payload: { enabled: Boolean(enabled) },
|
||||
payload: {
|
||||
enabled: Boolean(enabled),
|
||||
flowId: getActiveFlowId(),
|
||||
adapterId: getContributionEntryAdapterId(),
|
||||
},
|
||||
});
|
||||
|
||||
if (response?.error) {
|
||||
@@ -287,7 +373,7 @@
|
||||
pollInFlight = true;
|
||||
try {
|
||||
const response = await runtime.sendMessage({
|
||||
type: 'POLL_CONTRIBUTION_STATUS',
|
||||
type: 'POLL_FLOW_CONTRIBUTION_STATUS',
|
||||
source: 'sidepanel',
|
||||
payload: {
|
||||
reason: options.reason || 'sidepanel_poll',
|
||||
@@ -312,7 +398,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function startContributionFlow() {
|
||||
async function startAccountContributionFlow() {
|
||||
if (typeof helpers.startContributionAutoRun !== 'function') {
|
||||
throw new Error('贡献模式尚未接入主自动流程启动能力。');
|
||||
}
|
||||
@@ -323,7 +409,6 @@
|
||||
throw new Error('QQ 只能填写数字,且长度不能超过 20 位。');
|
||||
}
|
||||
await syncContributionProfile(profile);
|
||||
|
||||
const started = await helpers.startContributionAutoRun();
|
||||
if (!started) {
|
||||
return;
|
||||
@@ -348,28 +433,26 @@
|
||||
const currentState = getLatestState();
|
||||
const available = isContributionModeAvailable(currentState);
|
||||
const enabled = isContributionModeEnabled(currentState);
|
||||
const activeFlowId = getActiveFlowId(currentState);
|
||||
const blocked = available ? isModeSwitchBlocked() : false;
|
||||
const activeElement = typeof document !== 'undefined' ? document.activeElement : null;
|
||||
const sourceLabel = available ? getContributionSourceLabel(currentState) : '';
|
||||
|
||||
if (enabled && dom.selectPanelMode) {
|
||||
if (enabled && activeFlowId === 'openai' && dom.selectPanelMode) {
|
||||
dom.selectPanelMode.value = getContributionSource(currentState);
|
||||
}
|
||||
|
||||
helpers.updatePanelModeUI?.();
|
||||
helpers.updateAccountRunHistorySettingsUI?.();
|
||||
|
||||
if (dom.contributionModePanel) {
|
||||
dom.contributionModePanel.hidden = !available || !enabled;
|
||||
if (dom.accountContributionPanel) {
|
||||
dom.accountContributionPanel.hidden = !available || !enabled;
|
||||
}
|
||||
if (dom.contributionModeText) {
|
||||
dom.contributionModeText.textContent = getSummaryText({
|
||||
contributionSource: currentState.contributionSource,
|
||||
contributionTargetGroupName: currentState.contributionTargetGroupName,
|
||||
});
|
||||
if (dom.accountContributionText) {
|
||||
dom.accountContributionText.textContent = getSummaryText(currentState);
|
||||
}
|
||||
if (dom.contributionModeBadge) {
|
||||
dom.contributionModeBadge.textContent = enabled ? sourceLabel : '';
|
||||
if (dom.accountContributionBadge) {
|
||||
dom.accountContributionBadge.textContent = enabled ? sourceLabel : '';
|
||||
}
|
||||
if (dom.inputContributionNickname && activeElement !== dom.inputContributionNickname) {
|
||||
const nextNickname = normalizeString(currentState.contributionNickname);
|
||||
@@ -386,18 +469,24 @@
|
||||
if (dom.contributionOauthStatus) {
|
||||
dom.contributionOauthStatus.textContent = getOauthStatusText(currentState);
|
||||
}
|
||||
if (dom.contributionPrimaryStatusLabel) {
|
||||
dom.contributionPrimaryStatusLabel.textContent = activeFlowId === 'openai' ? 'OAUTH' : '账号产物';
|
||||
}
|
||||
if (dom.contributionCallbackStatus) {
|
||||
dom.contributionCallbackStatus.textContent = getCallbackStatusText(currentState);
|
||||
}
|
||||
if (dom.contributionModeSummary) {
|
||||
dom.contributionModeSummary.textContent = getSummaryText(currentState);
|
||||
if (dom.contributionSecondaryStatusLabel) {
|
||||
dom.contributionSecondaryStatusLabel.textContent = activeFlowId === 'openai' ? '回调' : '提交';
|
||||
}
|
||||
if (dom.accountContributionSummary) {
|
||||
dom.accountContributionSummary.textContent = getSummaryText(currentState);
|
||||
}
|
||||
|
||||
syncContributionRows(enabled);
|
||||
syncContributionRows(enabled && activeFlowId === 'openai');
|
||||
syncContributionButton(enabled, blocked, available);
|
||||
|
||||
if (dom.selectPanelMode) {
|
||||
dom.selectPanelMode.disabled = available && enabled;
|
||||
dom.selectPanelMode.disabled = activeFlowId === 'openai' && available && enabled;
|
||||
}
|
||||
|
||||
if (dom.btnStartContribution) {
|
||||
@@ -406,6 +495,7 @@
|
||||
|
||||
if (dom.btnOpenContributionUpload) {
|
||||
dom.btnOpenContributionUpload.disabled = !available;
|
||||
dom.btnOpenContributionUpload.textContent = activeFlowId === 'openai' ? '已有认证文件?前往上传' : '查看当前 flow 贡献说明';
|
||||
}
|
||||
|
||||
if (dom.btnExitContributionMode) {
|
||||
@@ -441,7 +531,13 @@
|
||||
}
|
||||
render();
|
||||
try {
|
||||
await enterContributionMode();
|
||||
if (isContributionModeEnabled()) {
|
||||
helpers.showToast?.('已打开当前 flow 教程。', 'info', 1800);
|
||||
} else if (isModeSwitchBlocked()) {
|
||||
helpers.showToast?.('已打开当前 flow 教程;当前流程运行中,暂时不能进入贡献模式。', 'warning', 2200);
|
||||
} else {
|
||||
await enterContributionMode();
|
||||
}
|
||||
} catch (error) {
|
||||
helpers.showToast?.(error.message, 'error');
|
||||
} finally {
|
||||
@@ -457,7 +553,7 @@
|
||||
actionInFlight = true;
|
||||
render();
|
||||
try {
|
||||
await startContributionFlow();
|
||||
await startAccountContributionFlow();
|
||||
} catch (error) {
|
||||
helpers.showToast?.(error.message, 'error');
|
||||
} finally {
|
||||
|
||||
@@ -152,11 +152,11 @@
|
||||
</div>
|
||||
<div class="contribution-mode-status-grid">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1857,6 +1857,7 @@
|
||||
<script src="../luckmail-utils.js"></script>
|
||||
<script src="../yyds-mail-utils.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/flow-capabilities.js"></script>
|
||||
<script src="../data/step-definitions.js"></script>
|
||||
|
||||
+87
-51
@@ -36,14 +36,16 @@ const btnIgnoreRelease = document.getElementById('btn-ignore-release');
|
||||
const btnOpenRelease = document.getElementById('btn-open-release');
|
||||
const settingsCard = document.getElementById('settings-card');
|
||||
const selectFlow = document.getElementById('select-flow');
|
||||
const contributionModePanel = document.getElementById('contribution-mode-panel');
|
||||
const contributionModeBadge = document.getElementById('contribution-mode-badge');
|
||||
const contributionModeText = document.getElementById('contribution-mode-text');
|
||||
const accountContributionPanel = document.getElementById('contribution-mode-panel');
|
||||
const accountContributionBadge = document.getElementById('contribution-mode-badge');
|
||||
const accountContributionText = document.getElementById('contribution-mode-text');
|
||||
const inputContributionNickname = document.getElementById('input-contribution-nickname');
|
||||
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 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 btnOpenContributionUpload = document.getElementById('btn-open-contribution-upload');
|
||||
const btnExitContributionMode = document.getElementById('btn-exit-contribution-mode');
|
||||
@@ -2074,7 +2076,7 @@ function shouldPromptNewUserGuide() {
|
||||
}
|
||||
if (typeof isContributionModeActiveForFlow === 'function'
|
||||
? isContributionModeActiveForFlow(latestState)
|
||||
: Boolean(latestState?.contributionMode)) {
|
||||
: Boolean(latestState?.accountContributionEnabled)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -2084,6 +2086,18 @@ function getContributionPortalUrl() {
|
||||
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() {
|
||||
return openActionModal({
|
||||
title: '新手引导',
|
||||
@@ -2112,16 +2126,29 @@ async function maybeShowNewUserGuidePrompt() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getDismissedContributionContentPromptVersion() {
|
||||
return String(localStorage.getItem(CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY) || '').trim();
|
||||
function getContributionContentPromptScope(snapshot = currentContributionContentSnapshot) {
|
||||
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 storageKey = getContributionContentPromptDismissedStorageKey(snapshot);
|
||||
if (normalized) {
|
||||
localStorage.setItem(CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY, normalized);
|
||||
localStorage.setItem(storageKey, normalized);
|
||||
} else {
|
||||
localStorage.removeItem(CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY);
|
||||
localStorage.removeItem(storageKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2301,25 +2328,25 @@ async function openAutoRunFallbackRiskConfirmModal(totalRuns) {
|
||||
|
||||
function updateConfigMenuControls() {
|
||||
const disabled = configActionInFlight || settingsSaveInFlight;
|
||||
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
? isContributionModeActiveForFlow(latestState)
|
||||
: Boolean(latestState?.contributionMode);
|
||||
if (contributionModeEnabled && configMenuOpen) {
|
||||
: Boolean(latestState?.accountContributionEnabled);
|
||||
if (accountContributionEnabled && configMenuOpen) {
|
||||
configMenuOpen = false;
|
||||
}
|
||||
const importLocked = disabled
|
||||
|| contributionModeEnabled
|
||||
|| accountContributionEnabled
|
||||
|| currentAutoRun.autoRunning
|
||||
|| Object.values(getStepStatuses()).some((status) => status === 'running');
|
||||
if (btnConfigMenu) {
|
||||
btnConfigMenu.disabled = disabled || contributionModeEnabled;
|
||||
btnConfigMenu.disabled = disabled || accountContributionEnabled;
|
||||
btnConfigMenu.setAttribute('aria-expanded', String(configMenuOpen));
|
||||
}
|
||||
if (configMenu) {
|
||||
configMenu.hidden = contributionModeEnabled || !configMenuOpen;
|
||||
configMenu.hidden = accountContributionEnabled || !configMenuOpen;
|
||||
}
|
||||
if (btnExportSettings) {
|
||||
btnExportSettings.disabled = disabled || contributionModeEnabled;
|
||||
btnExportSettings.disabled = disabled || accountContributionEnabled;
|
||||
}
|
||||
if (btnImportSettings) {
|
||||
btnImportSettings.disabled = importLocked;
|
||||
@@ -2776,7 +2803,10 @@ function isContributionModeActiveForFlow(state = latestState, flowId = undefined
|
||||
const normalizedFlowId = typeof normalizeFlowId === 'function'
|
||||
? normalizeFlowId(rawFlowId, 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;
|
||||
@@ -3775,9 +3805,9 @@ function collectSettingsPayload() {
|
||||
}
|
||||
return normalized || defaultFlowId;
|
||||
})();
|
||||
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
? isContributionModeActiveForFlow(latestState, activeFlowId)
|
||||
: (activeFlowId === defaultFlowId && Boolean(latestState?.contributionMode));
|
||||
: (activeFlowId === defaultFlowId && Boolean(latestState?.accountContributionEnabled));
|
||||
const icloudFetchModeRawValue = typeof selectIcloudFetchMode !== 'undefined'
|
||||
? String(selectIcloudFetchMode?.value || '')
|
||||
: '';
|
||||
@@ -4419,7 +4449,7 @@ function collectSettingsPayload() {
|
||||
: null;
|
||||
return {
|
||||
activeFlowId,
|
||||
...(contributionModeEnabled ? {} : {
|
||||
...(accountContributionEnabled ? {} : {
|
||||
...(activeFlowId === defaultFlowId ? { panelMode: effectivePanelMode } : {}),
|
||||
}),
|
||||
kiroTargetId: normalizeKiroTargetIdSafe(
|
||||
@@ -4528,7 +4558,7 @@ function collectSettingsPayload() {
|
||||
? inputGpcHelperLocalSmsUrl.value
|
||||
: (latestState?.gopayHelperLocalSmsHelperUrl || '')
|
||||
),
|
||||
...(contributionModeEnabled ? {} : {
|
||||
...(accountContributionEnabled ? {} : {
|
||||
customPassword: inputPassword.value,
|
||||
}),
|
||||
mailProvider: selectMailProvider.value,
|
||||
@@ -4548,7 +4578,7 @@ function collectSettingsPayload() {
|
||||
icloudFetchMode: (icloudFetchModeRawValue.trim().toLowerCase() === 'always_new'
|
||||
? 'always_new'
|
||||
: 'reuse_existing'),
|
||||
...(contributionModeEnabled ? {} : {
|
||||
...(accountContributionEnabled ? {} : {
|
||||
accountRunHistoryTextEnabled: true,
|
||||
accountRunHistoryHelperBaseUrl: normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl?.value),
|
||||
}),
|
||||
@@ -8695,9 +8725,9 @@ function canSelectPhoneSignupMethod() {
|
||||
const plusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||
? Boolean(inputPlusModeEnabled.checked)
|
||||
: Boolean(latestState?.plusModeEnabled);
|
||||
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
? isContributionModeActiveForFlow(latestState)
|
||||
: Boolean(latestState?.contributionMode);
|
||||
: Boolean(latestState?.accountContributionEnabled);
|
||||
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||
? resolveCurrentSidepanelCapabilities({
|
||||
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode,
|
||||
@@ -8705,7 +8735,7 @@ function canSelectPhoneSignupMethod() {
|
||||
...(typeof latestState !== 'undefined' ? latestState : {}),
|
||||
phoneVerificationEnabled: phoneEnabled,
|
||||
plusModeEnabled,
|
||||
contributionMode: contributionModeEnabled,
|
||||
accountContributionEnabled,
|
||||
},
|
||||
})
|
||||
: (() => {
|
||||
@@ -8721,7 +8751,7 @@ function canSelectPhoneSignupMethod() {
|
||||
...(typeof latestState !== 'undefined' ? latestState : {}),
|
||||
phoneVerificationEnabled: phoneEnabled,
|
||||
plusModeEnabled,
|
||||
contributionMode: contributionModeEnabled,
|
||||
accountContributionEnabled,
|
||||
},
|
||||
})
|
||||
: null;
|
||||
@@ -8729,7 +8759,7 @@ function canSelectPhoneSignupMethod() {
|
||||
if (capabilityState && typeof capabilityState.canSelectPhoneSignup === 'boolean') {
|
||||
return capabilityState.canSelectPhoneSignup;
|
||||
}
|
||||
return phoneEnabled && !plusModeEnabled && !contributionModeEnabled;
|
||||
return phoneEnabled && !plusModeEnabled && !accountContributionEnabled;
|
||||
}
|
||||
|
||||
function isSignupMethodSwitchLocked() {
|
||||
@@ -8751,9 +8781,9 @@ function updateSignupMethodUI(options = {}) {
|
||||
|
||||
let selectedMethod = normalizeSignupMethod(getSelectedSignupMethod());
|
||||
const phoneSelectable = canSelectPhoneSignupMethod();
|
||||
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
? isContributionModeActiveForFlow(latestState)
|
||||
: Boolean(latestState?.contributionMode);
|
||||
: Boolean(latestState?.accountContributionEnabled);
|
||||
if (!phoneSelectable && selectedMethod === SIGNUP_METHOD_PHONE) {
|
||||
selectedMethod = setSignupMethod(SIGNUP_METHOD_EMAIL);
|
||||
if (options.notify && typeof showToast === 'function') {
|
||||
@@ -8773,9 +8803,9 @@ function updateSignupMethodUI(options = {}) {
|
||||
if (!Boolean(inputPhoneVerificationEnabled?.checked)) {
|
||||
button.title = '开启接码后可选择手机号注册';
|
||||
} else if (typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled?.checked) {
|
||||
button.title = 'Plus 模式第一版暂不支持手机号注册';
|
||||
} else if (contributionModeEnabled) {
|
||||
button.title = '贡献模式第一版暂不支持手机号注册';
|
||||
button.title = 'Plus 模式暂不支持手机号注册';
|
||||
} else if (accountContributionEnabled) {
|
||||
button.title = '账号贡献开启时不能使用手机号注册';
|
||||
} else if (locked) {
|
||||
button.title = '自动流程运行中不能切换注册方式';
|
||||
} else {
|
||||
@@ -11392,12 +11422,12 @@ function shouldShowContributionUpdateHint(snapshot = currentContributionContentS
|
||||
if (!getContributionUpdatePromptLines(snapshot).length) {
|
||||
return false;
|
||||
}
|
||||
if (promptVersion === getDismissedContributionContentPromptVersion()) {
|
||||
if (promptVersion === getDismissedContributionContentPromptVersion(snapshot)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof isContributionModeActiveForFlow === 'function'
|
||||
? isContributionModeActiveForFlow(latestState)
|
||||
: Boolean(latestState?.contributionMode)) {
|
||||
: Boolean(latestState?.accountContributionEnabled)) {
|
||||
return false;
|
||||
}
|
||||
return !btnContributionMode.disabled;
|
||||
@@ -11522,7 +11552,10 @@ async function refreshContributionContentHint() {
|
||||
return contributionContentSnapshotRequestInFlight;
|
||||
}
|
||||
|
||||
contributionContentSnapshotRequestInFlight = contributionContentService.getContentUpdateSnapshot()
|
||||
contributionContentSnapshotRequestInFlight = contributionContentService.getContentUpdateSnapshot({
|
||||
flowId: getContributionContentFlowId(),
|
||||
targetId: getContributionContentTargetId(),
|
||||
})
|
||||
.then((snapshot) => {
|
||||
currentContributionContentSnapshot = snapshot;
|
||||
renderContributionUpdateHint(snapshot);
|
||||
@@ -11543,10 +11576,10 @@ async function refreshContributionContentHint() {
|
||||
}
|
||||
|
||||
function syncPasswordField(state) {
|
||||
const contributionModeEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
const accountContributionEnabled = typeof isContributionModeActiveForFlow === 'function'
|
||||
? isContributionModeActiveForFlow(state)
|
||||
: Boolean(state?.contributionMode);
|
||||
inputPassword.value = contributionModeEnabled ? '' : (state.customPassword || state.password || '');
|
||||
: Boolean(state?.accountContributionEnabled);
|
||||
inputPassword.value = accountContributionEnabled ? '' : (state.customPassword || state.password || '');
|
||||
}
|
||||
|
||||
function isCustomMailProvider(provider = selectMailProvider.value) {
|
||||
@@ -13518,7 +13551,7 @@ const bindAccountRecordEvents = accountRecordsManager?.bindEvents
|
||||
const closeAccountRecordsPanel = accountRecordsManager?.closePanel
|
||||
|| (() => { });
|
||||
bindAccountRecordEvents();
|
||||
const contributionModeManager = window.SidepanelContributionMode?.createContributionModeManager({
|
||||
const accountContributionManager = window.SidepanelContributionMode?.createContributionModeManager({
|
||||
state: {
|
||||
getLatestState: () => latestState,
|
||||
},
|
||||
@@ -13527,15 +13560,17 @@ const contributionModeManager = window.SidepanelContributionMode?.createContribu
|
||||
btnContributionMode,
|
||||
inputContributionNickname,
|
||||
inputContributionQq,
|
||||
contributionPrimaryStatusLabel,
|
||||
contributionSecondaryStatusLabel,
|
||||
contributionCallbackStatus,
|
||||
btnExitContributionMode,
|
||||
btnOpenAccountRecords,
|
||||
btnOpenContributionUpload,
|
||||
btnStartContribution,
|
||||
contributionModeBadge,
|
||||
contributionModePanel,
|
||||
contributionModeSummary,
|
||||
contributionModeText,
|
||||
accountContributionBadge,
|
||||
accountContributionPanel,
|
||||
accountContributionSummary,
|
||||
accountContributionText,
|
||||
contributionOauthStatus,
|
||||
rowAccountRunHistoryHelperBaseUrl,
|
||||
rowPhoneVerificationEnabled,
|
||||
@@ -13579,16 +13614,16 @@ const contributionModeManager = window.SidepanelContributionMode?.createContribu
|
||||
contributionUploadUrl: `${String(contributionContentService?.portalUrl || 'https://flowpilot.qlhazycoder.top').replace(/\/+$/, '')}/upload`,
|
||||
},
|
||||
});
|
||||
const baseRenderContributionMode = contributionModeManager?.render
|
||||
const baseRenderAccountContribution = accountContributionManager?.render
|
||||
|| (() => { });
|
||||
const renderContributionMode = () => {
|
||||
baseRenderContributionMode();
|
||||
baseRenderAccountContribution();
|
||||
renderContributionUpdateHint();
|
||||
updateSignupMethodUI({ notify: true });
|
||||
};
|
||||
const bindContributionModeEvents = contributionModeManager?.bindEvents
|
||||
const bindAccountContributionEvents = accountContributionManager?.bindEvents
|
||||
|| (() => { });
|
||||
bindContributionModeEvents();
|
||||
bindAccountContributionEvents();
|
||||
renderStepsList();
|
||||
|
||||
async function exportSettingsFile() {
|
||||
@@ -14094,7 +14129,7 @@ async function startAutoRunFromCurrentSettings() {
|
||||
plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||
? Boolean(inputPlusModeEnabled.checked)
|
||||
: Boolean(latestState?.plusModeEnabled),
|
||||
contributionMode: Boolean(latestState?.contributionMode),
|
||||
accountContributionEnabled: Boolean(latestState?.accountContributionEnabled),
|
||||
};
|
||||
return registry.validateAutoRunStart({
|
||||
activeFlowId: validationState.activeFlowId,
|
||||
@@ -14184,7 +14219,8 @@ async function startAutoRunFromCurrentSettings() {
|
||||
activeFlowId,
|
||||
targetId,
|
||||
autoRunSkipFailures,
|
||||
contributionMode: Boolean(latestState?.contributionMode),
|
||||
accountContributionEnabled: Boolean(latestState?.accountContributionEnabled),
|
||||
contributionAdapterId: latestState?.contributionAdapterId || '',
|
||||
contributionNickname,
|
||||
contributionQq,
|
||||
mode,
|
||||
@@ -16533,7 +16569,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
if (
|
||||
message.payload.password !== undefined
|
||||
|| message.payload.customPassword !== undefined
|
||||
|| message.payload.contributionMode !== undefined
|
||||
|| message.payload.accountContributionEnabled !== undefined
|
||||
) {
|
||||
syncPasswordField(latestState || {});
|
||||
}
|
||||
|
||||
@@ -559,7 +559,7 @@ test('auto-run restarts from confirm-oauth step after transient step10 token_exc
|
||||
stepStatuses: { 3: 'completed' },
|
||||
stepsVersion: 'ultra2.0',
|
||||
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,
|
||||
},
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
});
|
||||
|
||||
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',
|
||||
autoRunContext: null,
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
});
|
||||
|
||||
const normalized = helpers.normalizeAccountRunHistoryRecord({
|
||||
@@ -426,22 +426,22 @@ test('account run history records preserve Plus and contribution mode flags', ()
|
||||
email: 'plus@example.com',
|
||||
password: 'secret',
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
}, 'success');
|
||||
|
||||
assert.equal(record.plusModeEnabled, true);
|
||||
assert.equal(record.contributionMode, true);
|
||||
assert.equal(record.accountContributionEnabled, true);
|
||||
|
||||
const normalized = helpers.normalizeAccountRunHistoryRecord({
|
||||
email: 'plus@example.com',
|
||||
password: 'secret',
|
||||
finalStatus: 'success',
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: 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 () => {
|
||||
|
||||
@@ -69,7 +69,7 @@ test('background imports contribution oauth module and keeps contribution runtim
|
||||
|
||||
assert.match(backgroundSource, /background\/contribution-oauth\.js/);
|
||||
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', () => {
|
||||
@@ -84,14 +84,48 @@ test('contribution oauth module exposes a factory', () => {
|
||||
assert.equal(Array.isArray(api?.RUNTIME_KEYS), true);
|
||||
});
|
||||
|
||||
test('buildContributionModeState preserves active contribution runtime while keeping contribution on sub2api', () => {
|
||||
const bundle = extractFunction(backgroundSource, 'buildContributionModeState');
|
||||
test('buildAccountContributionState preserves active contribution runtime while keeping contribution on sub2api', () => {
|
||||
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 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 CONTRIBUTION_RUNTIME_DEFAULTS = {
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
accountContributionEnabled: false,
|
||||
accountContributionExpected: false,
|
||||
contributionAdapterId: '',
|
||||
flowContributionRuntime: {},
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionNickname: '',
|
||||
@@ -110,143 +144,130 @@ const CONTRIBUTION_RUNTIME_DEFAULTS = {
|
||||
};
|
||||
const CONTRIBUTION_RUNTIME_KEYS = Object.keys(CONTRIBUTION_RUNTIME_DEFAULTS);
|
||||
function isPlusModeState(state = {}) { return Boolean(state?.plusModeEnabled); }
|
||||
function normalizeContributionModeSource(value = '') {
|
||||
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号池'),
|
||||
};
|
||||
}
|
||||
${helperBundle}
|
||||
${bundle}
|
||||
return { buildContributionModeState };
|
||||
return { buildAccountContributionState };
|
||||
`)();
|
||||
|
||||
assert.deepStrictEqual(
|
||||
api.buildContributionModeState(true, {
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionStatus: 'waiting',
|
||||
contributionCallbackStatus: 'waiting',
|
||||
}),
|
||||
{
|
||||
contributionMode: true,
|
||||
contributionModeExpected: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionNickname: '',
|
||||
contributionQq: '',
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionAuthState: '',
|
||||
contributionCallbackUrl: '',
|
||||
contributionStatus: 'waiting',
|
||||
contributionStatusMessage: '',
|
||||
contributionLastPollAt: 0,
|
||||
contributionCallbackStatus: 'waiting',
|
||||
contributionCallbackMessage: '',
|
||||
contributionAuthOpenedAt: 0,
|
||||
contributionAuthTabId: 0,
|
||||
panelMode: 'sub2api',
|
||||
customPassword: '',
|
||||
accountRunHistoryTextEnabled: false,
|
||||
}
|
||||
);
|
||||
const enabledState = api.buildAccountContributionState(true, {
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionStatus: 'waiting',
|
||||
contributionCallbackStatus: 'waiting',
|
||||
});
|
||||
assert.equal(enabledState.accountContributionEnabled, true);
|
||||
assert.equal(enabledState.accountContributionExpected, true);
|
||||
assert.equal(enabledState.contributionAdapterId, 'openai-oauth');
|
||||
assert.deepStrictEqual(enabledState.flowContributionRuntime, {
|
||||
openai: { enabled: true, adapterId: 'openai-oauth' },
|
||||
});
|
||||
assert.equal(enabledState.contributionSessionId, 'session-001');
|
||||
assert.equal(enabledState.panelMode, 'sub2api');
|
||||
assert.equal(enabledState.customPassword, '');
|
||||
assert.equal(enabledState.accountRunHistoryTextEnabled, false);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
api.buildContributionModeState(false, {
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionStatus: 'waiting',
|
||||
}),
|
||||
{
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionNickname: '',
|
||||
contributionQq: '',
|
||||
contributionSessionId: '',
|
||||
contributionAuthUrl: '',
|
||||
contributionAuthState: '',
|
||||
contributionCallbackUrl: '',
|
||||
contributionStatus: '',
|
||||
contributionStatusMessage: '',
|
||||
contributionLastPollAt: 0,
|
||||
contributionCallbackStatus: 'idle',
|
||||
contributionCallbackMessage: '',
|
||||
contributionAuthOpenedAt: 0,
|
||||
contributionAuthTabId: 0,
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}
|
||||
);
|
||||
const disabledState = api.buildAccountContributionState(false, {
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionStatus: 'waiting',
|
||||
});
|
||||
assert.equal(disabledState.accountContributionEnabled, false);
|
||||
assert.equal(disabledState.accountContributionExpected, false);
|
||||
assert.equal(disabledState.contributionAdapterId, '');
|
||||
assert.deepStrictEqual(disabledState.flowContributionRuntime, {});
|
||||
assert.equal(disabledState.contributionSessionId, '');
|
||||
assert.equal(disabledState.panelMode, 'sub2api');
|
||||
assert.equal(disabledState.customPassword, 'Secret123!');
|
||||
|
||||
assert.deepStrictEqual(
|
||||
api.buildContributionModeState(true, {
|
||||
panelMode: 'cpa',
|
||||
plusModeEnabled: true,
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {}),
|
||||
{
|
||||
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,
|
||||
}
|
||||
);
|
||||
const plusContributionState = api.buildAccountContributionState(true, {
|
||||
panelMode: 'cpa',
|
||||
plusModeEnabled: true,
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {});
|
||||
assert.equal(plusContributionState.contributionTargetGroupName, 'openai-plus');
|
||||
assert.equal(plusContributionState.panelMode, 'sub2api');
|
||||
});
|
||||
|
||||
test('resetState preserves contribution runtime across reset', () => {
|
||||
assert.match(backgroundSource, /CONTRIBUTION_RUNTIME_KEYS/);
|
||||
assert.match(backgroundSource, /const contributionModeState = buildContributionModeState/);
|
||||
assert.match(backgroundSource, /\.\.\.contributionModeState/);
|
||||
assert.match(backgroundSource, /const accountContributionState = buildAccountContributionState/);
|
||||
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 () => {
|
||||
@@ -258,23 +279,23 @@ test('message router handles contribution mode, start flow, and status polling m
|
||||
const router = api.createMessageRouter({
|
||||
ensureManualInteractionAllowed: async () => ({
|
||||
stepStatuses: { 1: 'pending', 2: 'completed' },
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
}),
|
||||
pollContributionStatus: async (options) => {
|
||||
calls.push({ type: 'poll', options });
|
||||
return { contributionStatus: 'waiting' };
|
||||
},
|
||||
setContributionMode: async (enabled) => {
|
||||
setAccountContributionMode: async (enabled) => {
|
||||
calls.push({ type: 'toggle', enabled });
|
||||
return {
|
||||
contributionMode: Boolean(enabled),
|
||||
accountContributionEnabled: Boolean(enabled),
|
||||
panelMode: 'cpa',
|
||||
};
|
||||
},
|
||||
startContributionFlow: async (options) => {
|
||||
startFlowContribution: async (options) => {
|
||||
calls.push({ type: 'start', options });
|
||||
return {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionSessionId: 'session-001',
|
||||
contributionStatus: 'started',
|
||||
};
|
||||
@@ -282,15 +303,15 @@ test('message router handles contribution mode, start flow, and status polling m
|
||||
});
|
||||
|
||||
const enableResponse = await router.handleMessage({
|
||||
type: 'SET_CONTRIBUTION_MODE',
|
||||
type: 'SET_ACCOUNT_CONTRIBUTION_MODE',
|
||||
payload: { enabled: true },
|
||||
});
|
||||
const startResponse = await router.handleMessage({
|
||||
type: 'START_CONTRIBUTION_FLOW',
|
||||
type: 'START_FLOW_CONTRIBUTION',
|
||||
payload: { nickname: '阿青', qq: '123456' },
|
||||
});
|
||||
const pollResponse = await router.handleMessage({
|
||||
type: 'POLL_CONTRIBUTION_STATUS',
|
||||
type: 'POLL_FLOW_CONTRIBUTION_STATUS',
|
||||
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 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: () => {},
|
||||
getPendingAutoRunTimerPlan: () => null,
|
||||
getState: async () => ({
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
stepStatuses: {},
|
||||
}),
|
||||
normalizeRunCount: (value) => Number(value) || 1,
|
||||
setContributionMode: async (enabled) => {
|
||||
setAccountContributionMode: async (enabled) => {
|
||||
calls.push({ type: 'toggle', enabled });
|
||||
return { contributionMode: true };
|
||||
return { accountContributionEnabled: true };
|
||||
},
|
||||
setState: async (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,
|
||||
autoRunSkipFailures: true,
|
||||
mode: 'restart',
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionNickname: '阿青',
|
||||
contributionQq: '123456',
|
||||
},
|
||||
@@ -433,7 +454,7 @@ test('account run history snapshot sync is disabled in contribution mode', () =>
|
||||
|
||||
assert.equal(
|
||||
helpers.shouldSyncAccountRunHistorySnapshot({
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
accountRunHistoryTextEnabled: true,
|
||||
accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373',
|
||||
}),
|
||||
@@ -442,7 +463,7 @@ test('account run history snapshot sync is disabled in contribution mode', () =>
|
||||
|
||||
assert.equal(
|
||||
helpers.shouldSyncAccountRunHistorySnapshot({
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
accountRunHistoryTextEnabled: true,
|
||||
accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373',
|
||||
}),
|
||||
@@ -458,7 +479,7 @@ test('contribution oauth manager starts session, opens auth url, submits callbac
|
||||
const closeCallbackCalls = [];
|
||||
let statusPollCount = 0;
|
||||
let currentState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
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.contributionAuthState, 'oauth-state-001');
|
||||
assert.equal(startedState.contributionStatus, 'waiting');
|
||||
@@ -577,7 +598,7 @@ test('contribution oauth manager deduplicates concurrent callback captures for t
|
||||
let submitCallCount = 0;
|
||||
let resolveSubmitRequest = null;
|
||||
let currentState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionSessionId: 'session-001',
|
||||
@@ -664,7 +685,7 @@ test('contribution oauth manager ignores tabs.onUpdated events without a real ur
|
||||
const fetchCalls = [];
|
||||
const addedListeners = {};
|
||||
let currentState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionSessionId: 'session-001',
|
||||
@@ -756,7 +777,7 @@ test('contribution oauth manager switches Plus contribution traffic to sub2api o
|
||||
const globalScope = {};
|
||||
const fetchCalls = [];
|
||||
let currentState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionSource: 'sub2api',
|
||||
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 || ''), /"target_group_name":"openai-plus"/);
|
||||
@@ -836,7 +857,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
calls.push({ type: 'log', message, level, options });
|
||||
};
|
||||
globalThis.contributionOAuthManager = {
|
||||
async startContributionFlow(options) {
|
||||
async startFlowContribution(options) {
|
||||
calls.push({ type: 'contribution', options });
|
||||
return {
|
||||
contributionAuthUrl: 'https://auth.example.com/oauth?state=oauth-state-001',
|
||||
@@ -854,7 +875,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
globalThis.LOG_PREFIX = '[test]';
|
||||
|
||||
const oauthUrl = await api.refreshOAuthUrlBeforeStep6({
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
email: 'user@example.com',
|
||||
});
|
||||
|
||||
@@ -862,7 +883,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
assert.deepStrictEqual(calls, [
|
||||
{
|
||||
type: 'log',
|
||||
message: 'contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...',
|
||||
message: '账号贡献已开启,走公开贡献接口,正在申请 OAuth 登录地址...',
|
||||
level: 'info',
|
||||
options: { step: 7, stepKey: 'oauth-login' },
|
||||
},
|
||||
@@ -872,7 +893,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
nickname: '',
|
||||
openAuthTab: false,
|
||||
stateOverride: {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
email: 'user@example.com',
|
||||
},
|
||||
},
|
||||
@@ -894,7 +915,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
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 calls = [];
|
||||
|
||||
@@ -907,7 +928,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
calls.push({ type: 'log', message, level, options });
|
||||
};
|
||||
globalThis.contributionOAuthManager = {
|
||||
async startContributionFlow() {
|
||||
async startFlowContribution() {
|
||||
calls.push({ type: 'contribution' });
|
||||
return {
|
||||
contributionAuthUrl: 'https://auth.example.com/oauth?state=unexpected',
|
||||
@@ -925,7 +946,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
globalThis.LOG_PREFIX = '[test]';
|
||||
|
||||
const oauthUrl = await api.refreshOAuthUrlBeforeStep6({
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'sub2api',
|
||||
email: 'user@example.com',
|
||||
});
|
||||
@@ -934,7 +955,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
assert.deepStrictEqual(calls, [
|
||||
{
|
||||
type: 'log',
|
||||
message: 'contributionMode=false,走普通 CPA / SUB2API / Codex2API 链路(当前面板:SUB2API),正在刷新 OAuth 登录地址...',
|
||||
message: '账号贡献未开启,走普通 CPA / SUB2API / Codex2API 链路(当前面板:SUB2API),正在刷新 OAuth 登录地址...',
|
||||
level: 'info',
|
||||
options: { step: 7, stepKey: 'oauth-login' },
|
||||
},
|
||||
@@ -956,7 +977,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
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 api = new Function(`
|
||||
@@ -973,10 +994,10 @@ return { executeStep10 };
|
||||
|
||||
await assert.rejects(
|
||||
() => api.executeStep10({
|
||||
contributionModeExpected: true,
|
||||
contributionMode: false,
|
||||
accountContributionExpected: true,
|
||||
accountContributionEnabled: false,
|
||||
}),
|
||||
/步骤 10:当前自动流程预期使用贡献模式/
|
||||
/步骤 10:当前自动流程预期使用账号贡献/
|
||||
);
|
||||
|
||||
delete globalThis.executeContributionStep10;
|
||||
|
||||
@@ -674,7 +674,7 @@ return {
|
||||
|
||||
test('resetState preserves LuckMail session config, used map, and preserve tag cache while clearing runtime purchase state', async () => {
|
||||
const bundle = [
|
||||
extractFunction('buildContributionModeState'),
|
||||
extractFunction('buildAccountContributionState'),
|
||||
extractFunction('resetState'),
|
||||
].join('\n');
|
||||
|
||||
@@ -702,7 +702,7 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
' email: null,',
|
||||
'};',
|
||||
'const CONTRIBUTION_RUNTIME_DEFAULTS = {',
|
||||
' contributionMode: false,',
|
||||
' accountContributionEnabled: false,',
|
||||
" contributionSessionId: '',",
|
||||
" contributionAuthUrl: '',",
|
||||
" contributionAuthState: '',",
|
||||
|
||||
@@ -100,7 +100,7 @@ function createRouterWithFinalNode(options = {}) {
|
||||
selectLuckmailPurchase: async () => {},
|
||||
setCurrentHotmailAccount: async () => {},
|
||||
setCurrentMail2925Account: async () => {},
|
||||
setContributionMode: async () => {},
|
||||
setAccountContributionMode: async () => {},
|
||||
setEmailState: async () => {},
|
||||
setEmailStateSilently: async () => {},
|
||||
setIcloudAliasPreservedState: async () => {},
|
||||
|
||||
@@ -58,7 +58,7 @@ let state = {
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
resolvedSignupMethod: null,
|
||||
};
|
||||
async function getState() { return { ...state }; }
|
||||
|
||||
@@ -61,7 +61,7 @@ function getErrorMessage(error) {
|
||||
}
|
||||
async function getState() {
|
||||
events.push({ type: 'getState' });
|
||||
return { nodeStatuses: {}, contributionMode: true };
|
||||
return { nodeStatuses: {}, accountContributionEnabled: true };
|
||||
}
|
||||
function getLastNodeIdForState() {
|
||||
return lastNodeId;
|
||||
|
||||
@@ -8,6 +8,7 @@ function createContributionContentService(options = {}) {
|
||||
const cache = new Map();
|
||||
const windowObject = {};
|
||||
let fetchCalls = 0;
|
||||
const fetchUrls = [];
|
||||
|
||||
const localStorage = {
|
||||
getItem(key) {
|
||||
@@ -23,7 +24,7 @@ function createContributionContentService(options = {}) {
|
||||
|
||||
if (options.cachedSnapshot) {
|
||||
cache.set(
|
||||
'multipage-contribution-content-summary-v1',
|
||||
options.cacheKey || 'multipage-contribution-content-summary-v2:openai:cpa',
|
||||
JSON.stringify(options.cachedSnapshot)
|
||||
);
|
||||
}
|
||||
@@ -44,6 +45,7 @@ function createContributionContentService(options = {}) {
|
||||
|
||||
const wrappedFetch = async (...args) => {
|
||||
fetchCalls += 1;
|
||||
fetchUrls.push(String(args[0] || ''));
|
||||
return fetchImpl(...args);
|
||||
};
|
||||
|
||||
@@ -69,11 +71,14 @@ function createContributionContentService(options = {}) {
|
||||
getFetchCalls() {
|
||||
return fetchCalls;
|
||||
},
|
||||
getFetchUrls() {
|
||||
return fetchUrls.slice();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('getContentUpdateSnapshot returns a prompt version for visible contribution content updates', async () => {
|
||||
const { api } = createContributionContentService({
|
||||
const { api, getFetchUrls } = createContributionContentService({
|
||||
fetchImpl: async () => ({
|
||||
ok: true,
|
||||
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.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.latestUpdatedAt, '2026-04-21T12:05:00Z');
|
||||
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',
|
||||
apiUrl: 'https://flowpilot.qlhazycoder.top/api/content-summary',
|
||||
apiUrl: 'https://flowpilot.qlhazycoder.top/api/content-summary?flow=openai&target=cpa',
|
||||
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.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);
|
||||
});
|
||||
|
||||
@@ -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/
|
||||
);
|
||||
});
|
||||
@@ -3,6 +3,7 @@ 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');
|
||||
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
|
||||
const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8');
|
||||
|
||||
@@ -10,7 +11,7 @@ function loadApi() {
|
||||
const scope = {};
|
||||
return new Function(
|
||||
'self',
|
||||
`${flowRegistrySource}; ${settingsSchemaSource}; ${source}; return self.MultiPageFlowCapabilities;`
|
||||
`${flowRegistrySource}; ${contributionRegistrySource}; ${settingsSchemaSource}; ${source}; return self.MultiPageFlowCapabilities;`
|
||||
)(scope);
|
||||
}
|
||||
|
||||
@@ -24,7 +25,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
signupMethod: 'phone',
|
||||
},
|
||||
});
|
||||
@@ -40,7 +41,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
signupMethod: 'phone',
|
||||
},
|
||||
});
|
||||
@@ -61,7 +62,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
|
||||
openaiIntegrationTargetId: 'codex2api',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
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.canShowPhoneSettings, false);
|
||||
assert.equal(capabilityState.canShowPlusSettings, false);
|
||||
assert.equal(capabilityState.canShowContributionMode, true);
|
||||
assert.equal(capabilityState.effectiveSignupMethod, 'email');
|
||||
assert.equal(capabilityState.effectiveTargetId, 'kiro-rs');
|
||||
assert.deepEqual(capabilityState.flowCapabilities.contributionAdapterIds, ['kiro-builder-id']);
|
||||
assert.deepEqual(
|
||||
capabilityState.visibleGroupIds,
|
||||
['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',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -159,14 +162,14 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
},
|
||||
changedKeys: [
|
||||
'openaiIntegrationTargetId',
|
||||
'signupMethod',
|
||||
'phoneVerificationEnabled',
|
||||
'plusModeEnabled',
|
||||
'contributionMode',
|
||||
'accountContributionEnabled',
|
||||
],
|
||||
});
|
||||
|
||||
@@ -178,7 +181,8 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
signupMethod: 'email',
|
||||
phoneVerificationEnabled: false,
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
accountContributionEnabled: false,
|
||||
});
|
||||
assert.deepEqual(
|
||||
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']
|
||||
);
|
||||
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', () => {
|
||||
|
||||
@@ -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 DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const latestState = {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
@@ -266,7 +266,7 @@ const latestState = {
|
||||
activeFlowId: 'site-a',
|
||||
panelMode: 'cpa',
|
||||
signupMethod: 'phone',
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
phoneVerificationEnabled: true,
|
||||
};
|
||||
const inputAutoSkipFailures = { checked: false };
|
||||
|
||||
@@ -4,6 +4,8 @@ const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
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() {
|
||||
return {
|
||||
@@ -19,7 +21,10 @@ function createElement() {
|
||||
},
|
||||
},
|
||||
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 dom = {
|
||||
btnContributionMode: createElement(),
|
||||
contributionModePanel: createElement(),
|
||||
contributionModeText: createElement(),
|
||||
contributionModeBadge: createElement(),
|
||||
accountContributionPanel: createElement(),
|
||||
accountContributionText: createElement(),
|
||||
accountContributionBadge: createElement(),
|
||||
contributionPrimaryStatusLabel: createElement(),
|
||||
contributionSecondaryStatusLabel: createElement(),
|
||||
contributionOauthStatus: createElement(),
|
||||
contributionCallbackStatus: createElement(),
|
||||
contributionModeSummary: createElement(),
|
||||
accountContributionSummary: createElement(),
|
||||
inputContributionNickname: createElement(),
|
||||
inputContributionQq: createElement(),
|
||||
btnStartContribution: createElement(),
|
||||
@@ -57,7 +64,9 @@ test('contribution mode manager does not project openai-only ui state into kiro
|
||||
getLatestState: () => ({
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
supportsAccountContribution: true,
|
||||
contributionAdapterId: 'kiro-builder-id',
|
||||
contributionSource: 'cpa',
|
||||
}),
|
||||
},
|
||||
@@ -80,11 +89,107 @@ test('contribution mode manager does not project openai-only ui state into kiro
|
||||
|
||||
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.btnContributionMode.disabled, true);
|
||||
assert.equal(dom.btnContributionMode.title, '当前 flow 不支持贡献模式');
|
||||
assert.equal(dom.btnStartContribution.disabled, true);
|
||||
assert.equal(dom.btnOpenContributionUpload.disabled, true);
|
||||
assert.equal(dom.selectPanelMode.value, '');
|
||||
assert.equal(dom.btnContributionMode.disabled, false);
|
||||
assert.equal(dom.btnContributionMode.title, '打开当前 flow 教程;当前已在贡献模式');
|
||||
assert.equal(dom.btnStartContribution.disabled, false);
|
||||
assert.equal(dom.btnOpenContributionUpload.disabled, false);
|
||||
assert.equal(dom.btnOpenContributionUpload.textContent, '查看当前 flow 贡献说明');
|
||||
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');
|
||||
});
|
||||
|
||||
@@ -193,7 +193,7 @@ test('collectSettingsPayload omits custom password and local sync settings in co
|
||||
const bundle = extractFunction('collectSettingsPayload');
|
||||
|
||||
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
|
||||
let latestState = { contributionMode: true };
|
||||
let latestState = { accountContributionEnabled: true };
|
||||
const window = {};
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
@@ -337,7 +337,7 @@ return {
|
||||
assert.equal(contributionPayload.phoneVerificationEnabled, true);
|
||||
assert.equal(contributionPayload.cloudflareTempEmailUseRandomSubdomain, true);
|
||||
|
||||
api.setLatestState({ contributionMode: false });
|
||||
api.setLatestState({ accountContributionEnabled: false });
|
||||
const normalPayload = api.collectSettingsPayload();
|
||||
assert.equal(normalPayload.panelMode, 'cpa');
|
||||
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');
|
||||
|
||||
let latestState = {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'sub2api',
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
@@ -401,13 +401,13 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
btnOpenAccountRecords: createElement(),
|
||||
btnOpenContributionUpload: createElement(),
|
||||
btnStartContribution: createElement(),
|
||||
contributionModeBadge: createElement(),
|
||||
accountContributionBadge: createElement(),
|
||||
inputContributionNickname: createElement({ value: '贡献者昵称' }),
|
||||
inputContributionQq: createElement({ value: '123456' }),
|
||||
contributionCallbackStatus: createElement(),
|
||||
contributionModePanel: createElement({ hidden: true }),
|
||||
contributionModeSummary: createElement(),
|
||||
contributionModeText: createElement(),
|
||||
accountContributionPanel: createElement({ hidden: true }),
|
||||
accountContributionSummary: createElement(),
|
||||
accountContributionText: createElement(),
|
||||
contributionOauthStatus: createElement(),
|
||||
rowAccountRunHistoryHelperBaseUrl: createElement(),
|
||||
rowAccountRunHistoryTextEnabled: createElement(),
|
||||
@@ -492,11 +492,11 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
runtime: {
|
||||
sendMessage: async (message) => {
|
||||
sentMessages.push(message);
|
||||
if (message.type === 'SET_CONTRIBUTION_MODE') {
|
||||
if (message.type === 'SET_ACCOUNT_CONTRIBUTION_MODE') {
|
||||
return {
|
||||
state: message.payload.enabled
|
||||
? {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
panelMode: 'sub2api',
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
@@ -512,7 +512,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
email: latestState.email,
|
||||
}
|
||||
: {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'cpa',
|
||||
contributionSource: 'cpa',
|
||||
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 {
|
||||
state: {
|
||||
...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 {};
|
||||
},
|
||||
},
|
||||
@@ -560,21 +552,21 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
});
|
||||
|
||||
manager.render();
|
||||
assert.equal(dom.contributionModePanel.hidden, true);
|
||||
assert.equal(dom.accountContributionPanel.hidden, true);
|
||||
assert.equal(dom.btnContributionMode.disabled, false);
|
||||
assert.equal(dom.contributionModeBadge.textContent, '');
|
||||
assert.equal(dom.accountContributionBadge.textContent, '');
|
||||
|
||||
manager.bindEvents();
|
||||
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.disabled, true);
|
||||
assert.equal(dom.contributionModeBadge.textContent, 'SUB2API');
|
||||
assert.equal(dom.accountContributionBadge.textContent, 'SUB2API');
|
||||
assert.equal(dom.btnOpenAccountRecords.disabled, true);
|
||||
assert.equal(dom.contributionOauthStatus.textContent, '\u672a\u751f\u6210\u767b\u5f55\u5730\u5740');
|
||||
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.rowVpsUrl.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(latestState.contributionSessionId, 'session-002');
|
||||
assert.equal(latestState.contributionStatus, 'started');
|
||||
const contributionProfileMessage = sentMessages.find((message) => message.type === 'SET_CONTRIBUTION_PROFILE');
|
||||
assert.deepStrictEqual(contributionProfileMessage?.payload, { nickname: '贡献者昵称', qq: '123456' });
|
||||
assert.equal(latestState.contributionNickname, '贡献者昵称');
|
||||
assert.equal(latestState.contributionQq, '123456');
|
||||
assert.equal(timers.length > 0, true);
|
||||
|
||||
await manager.pollOnce({ reason: 'test_poll' });
|
||||
assert.equal(statusState.contributionStatus, 'processing');
|
||||
assert.equal(dom.contributionOauthStatus.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();
|
||||
assert.deepStrictEqual(openedUrls, ['https://flowpilot.qlhazycoder.top', 'https://flowpilot.qlhazycoder.top/upload']);
|
||||
|
||||
await dom.btnExitContributionMode.listeners.click();
|
||||
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.selectPanelMode.disabled, false);
|
||||
assert.equal(dom.rowVpsUrl.classList.contains('is-contribution-hidden'), false);
|
||||
assert.deepStrictEqual(
|
||||
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(
|
||||
toasts.map((item) => item.message),
|
||||
@@ -625,7 +617,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
|
||||
blocked = true;
|
||||
latestState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
panelMode: 'sub2api',
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
@@ -640,7 +632,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
};
|
||||
manager.render();
|
||||
assert.equal(dom.selectPanelMode.value, 'sub2api');
|
||||
assert.equal(dom.contributionModeBadge.textContent, 'SUB2API');
|
||||
assert.equal(dom.accountContributionBadge.textContent, 'SUB2API');
|
||||
assert.equal(dom.btnExitContributionMode.disabled, true);
|
||||
manager.stopPolling();
|
||||
});
|
||||
|
||||
@@ -84,7 +84,7 @@ test('collectSettingsPayload persists icloud target mailbox settings', () => {
|
||||
const bundle = extractFunction('collectSettingsPayload');
|
||||
|
||||
const api = new Function(`
|
||||
let latestState = { contributionMode: false };
|
||||
let latestState = { accountContributionEnabled: false };
|
||||
const window = {};
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
|
||||
@@ -154,7 +154,7 @@ test('collectSettingsPayload persists currentMail2925AccountId for 2925 account
|
||||
|
||||
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
|
||||
let latestState = {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
mail2925UseAccountPool: true,
|
||||
currentMail2925AccountId: 'acc-2',
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ const localStorage = {
|
||||
},
|
||||
};
|
||||
const btnContributionMode = { disabled: false };
|
||||
let latestState = { contributionMode: false };
|
||||
let latestState = { accountContributionEnabled: false };
|
||||
${bundle}
|
||||
return {
|
||||
shouldPromptNewUserGuide,
|
||||
@@ -82,8 +82,8 @@ return {
|
||||
setButtonDisabled(value) {
|
||||
btnContributionMode.disabled = Boolean(value);
|
||||
},
|
||||
setContributionMode(value) {
|
||||
latestState = { contributionMode: Boolean(value) };
|
||||
setAccountContributionMode(value) {
|
||||
latestState = { accountContributionEnabled: Boolean(value) };
|
||||
},
|
||||
};
|
||||
`)();
|
||||
@@ -98,7 +98,7 @@ return {
|
||||
assert.equal(api.shouldPromptNewUserGuide(), false);
|
||||
|
||||
api.setButtonDisabled(false);
|
||||
api.setContributionMode(true);
|
||||
api.setAccountContributionMode(true);
|
||||
assert.equal(api.shouldPromptNewUserGuide(), false);
|
||||
});
|
||||
|
||||
@@ -129,7 +129,7 @@ const localStorage = {
|
||||
},
|
||||
};
|
||||
const btnContributionMode = { disabled: false };
|
||||
const latestState = { contributionMode: false };
|
||||
const latestState = { accountContributionEnabled: false };
|
||||
const contributionContentService = { portalUrl: 'https://flowpilot.qlhazycoder.top' };
|
||||
const openedUrls = [];
|
||||
let modalOptions = null;
|
||||
|
||||
@@ -337,7 +337,7 @@ const window = {
|
||||
};
|
||||
let latestState = {
|
||||
activeFlowId: 'site-a',
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'cpa',
|
||||
};
|
||||
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 DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
let latestState = {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
mail2925UseAccountPool: false,
|
||||
currentMail2925AccountId: '',
|
||||
fiveSimCountryOrder: ['thailand', 'england'],
|
||||
|
||||
Reference in New Issue
Block a user