feat: 添加本地 CPA 第 9 步策略切换功能
This commit is contained in:
@@ -369,6 +369,9 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。
|
||||
|
||||
- 步骤 9 会拒绝任何不是真实 `/auth/callback`,或缺少 `code` / `state` 的本地回调地址
|
||||
- 成功后的清理只会针对 `/auth` 这一类真实回调标签页,不会再泛化清理任意 localhost 路径
|
||||
- 侧边栏可切换“本地 CPA”策略,默认是 `全部回调`
|
||||
- 选择 `全部回调` 时,即使 CPA 部署在本地,也会执行步骤 9
|
||||
- 选择 `跳过第9步` 时,仅当本地 CPA 且步骤 8 已拿到回调地址时,才会直接跳过步骤 9
|
||||
|
||||
回到 CPA 面板:
|
||||
|
||||
|
||||
+16
-2
@@ -39,6 +39,7 @@ const DEFAULT_SUB2API_REDIRECT_URI = 'http://localhost:1455/auth/callback';
|
||||
const AUTO_RUN_ALARM_NAME = 'scheduled-auto-run';
|
||||
const AUTO_RUN_DELAY_MIN_MINUTES = 1;
|
||||
const AUTO_RUN_DELAY_MAX_MINUTES = 1440;
|
||||
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
||||
|
||||
initializeSessionStorageAccess();
|
||||
|
||||
@@ -50,6 +51,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
panelMode: 'cpa', // Step 1 / Step 9 的来源模式:cpa | sub2api。
|
||||
vpsUrl: '', // VPS 面板地址,可手动填写。
|
||||
vpsPassword: '', // VPS 面板登录密码,可手动填写。
|
||||
localCpaStep9Mode: DEFAULT_LOCAL_CPA_STEP9_MODE, // 本地 CPA 的第 9 步策略:submit | bypass。
|
||||
sub2apiUrl: DEFAULT_SUB2API_URL, // SUB2API 管理后台地址。
|
||||
sub2apiEmail: '', // SUB2API 登录邮箱。
|
||||
sub2apiPassword: '', // SUB2API 登录密码。
|
||||
@@ -139,6 +141,12 @@ function normalizeEmailGenerator(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'cloudflare' ? 'cloudflare' : 'duck';
|
||||
}
|
||||
|
||||
function normalizeLocalCpaStep9Mode(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'bypass'
|
||||
? 'bypass'
|
||||
: DEFAULT_LOCAL_CPA_STEP9_MODE;
|
||||
}
|
||||
|
||||
function normalizeCloudflareDomain(rawValue = '') {
|
||||
let value = String(rawValue || '').trim().toLowerCase();
|
||||
if (!value) return '';
|
||||
@@ -158,6 +166,7 @@ async function getPersistedSettings() {
|
||||
autoRunDelayEnabled: Boolean(stored.autoRunDelayEnabled ?? PERSISTED_SETTING_DEFAULTS.autoRunDelayEnabled),
|
||||
autoRunDelayMinutes: normalizeAutoRunDelayMinutes(stored.autoRunDelayMinutes ?? PERSISTED_SETTING_DEFAULTS.autoRunDelayMinutes),
|
||||
emailGenerator: normalizeEmailGenerator(stored.emailGenerator ?? PERSISTED_SETTING_DEFAULTS.emailGenerator),
|
||||
localCpaStep9Mode: normalizeLocalCpaStep9Mode(stored.localCpaStep9Mode ?? PERSISTED_SETTING_DEFAULTS.localCpaStep9Mode),
|
||||
hotmailAccounts: normalizeHotmailAccounts(stored.hotmailAccounts),
|
||||
};
|
||||
}
|
||||
@@ -198,6 +207,8 @@ async function setPersistentSettings(updates) {
|
||||
persistedUpdates[key] = Boolean(updates[key]);
|
||||
} else if (key === 'autoRunDelayMinutes') {
|
||||
persistedUpdates[key] = normalizeAutoRunDelayMinutes(updates[key]);
|
||||
} else if (key === 'localCpaStep9Mode') {
|
||||
persistedUpdates[key] = normalizeLocalCpaStep9Mode(updates[key]);
|
||||
} else if (key === 'hotmailAccounts') {
|
||||
persistedUpdates[key] = normalizeHotmailAccounts(updates[key]);
|
||||
} else {
|
||||
@@ -983,7 +994,9 @@ function isLocalCpaUrl(rawUrl) {
|
||||
}
|
||||
|
||||
function shouldBypassStep9ForLocalCpa(state) {
|
||||
return Boolean(state?.localhostUrl) && isLocalCpaUrl(state?.vpsUrl);
|
||||
return normalizeLocalCpaStep9Mode(state?.localCpaStep9Mode) === 'bypass'
|
||||
&& Boolean(state?.localhostUrl)
|
||||
&& isLocalCpaUrl(state?.vpsUrl);
|
||||
}
|
||||
|
||||
function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
|
||||
@@ -2439,6 +2452,7 @@ async function handleMessage(message, sender) {
|
||||
if (message.payload.panelMode !== undefined) updates.panelMode = message.payload.panelMode;
|
||||
if (message.payload.vpsUrl !== undefined) updates.vpsUrl = message.payload.vpsUrl;
|
||||
if (message.payload.vpsPassword !== undefined) updates.vpsPassword = message.payload.vpsPassword;
|
||||
if (message.payload.localCpaStep9Mode !== undefined) updates.localCpaStep9Mode = normalizeLocalCpaStep9Mode(message.payload.localCpaStep9Mode);
|
||||
if (message.payload.sub2apiUrl !== undefined) updates.sub2apiUrl = message.payload.sub2apiUrl;
|
||||
if (message.payload.sub2apiEmail !== undefined) updates.sub2apiEmail = message.payload.sub2apiEmail;
|
||||
if (message.payload.sub2apiPassword !== undefined) updates.sub2apiPassword = message.payload.sub2apiPassword;
|
||||
@@ -4420,7 +4434,7 @@ async function executeCpaStep9(state) {
|
||||
}
|
||||
|
||||
if (shouldBypassStep9ForLocalCpa(state)) {
|
||||
await addLog('步骤 9:检测到本地 CPA,步骤 8 完成后已自动添加,无需重复提交回调地址。', 'info');
|
||||
await addLog('步骤 9:检测到本地 CPA,且当前策略为“跳过第9步”,本轮不再重复提交回调地址。', 'info');
|
||||
await completeStepFromBackground(9, {
|
||||
localhostUrl: state.localhostUrl,
|
||||
verifiedStatus: 'local-auto',
|
||||
|
||||
@@ -378,6 +378,41 @@ header {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.choice-group {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.choice-btn {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 7px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-base);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: border-color var(--transition), background var(--transition), color var(--transition), box-shadow var(--transition);
|
||||
}
|
||||
|
||||
.choice-btn:hover {
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
background: var(--blue-soft);
|
||||
}
|
||||
|
||||
.choice-btn.is-active {
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
background: var(--blue-soft);
|
||||
box-shadow: 0 0 0 1px var(--blue-glow);
|
||||
}
|
||||
|
||||
#btn-fetch-email,
|
||||
#btn-save-settings {
|
||||
padding-inline: 10px;
|
||||
|
||||
@@ -83,6 +83,13 @@
|
||||
<span class="data-label">管理密钥</span>
|
||||
<input type="password" id="input-vps-password" class="data-input" placeholder="请输入 CPA 管理密钥" />
|
||||
</div>
|
||||
<div class="data-row" id="row-local-cpa-step9-mode">
|
||||
<span class="data-label">本地 CPA</span>
|
||||
<div id="local-cpa-step9-mode-group" class="choice-group" role="group" aria-label="本地 CPA 第 9 步策略">
|
||||
<button type="button" class="choice-btn" data-local-cpa-step9-mode="submit">全部回调</button>
|
||||
<button type="button" class="choice-btn" data-local-cpa-step9-mode="bypass">跳过第9步</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-sub2api-url" style="display:none;">
|
||||
<span class="data-label">SUB2API</span>
|
||||
<input type="text" id="input-sub2api-url" class="data-input" placeholder="https://sub2api.hisence.fun/admin/accounts" />
|
||||
|
||||
@@ -38,6 +38,8 @@ const rowVpsUrl = document.getElementById('row-vps-url');
|
||||
const inputVpsUrl = document.getElementById('input-vps-url');
|
||||
const rowVpsPassword = document.getElementById('row-vps-password');
|
||||
const inputVpsPassword = document.getElementById('input-vps-password');
|
||||
const rowLocalCpaStep9Mode = document.getElementById('row-local-cpa-step9-mode');
|
||||
const localCpaStep9ModeButtons = Array.from(document.querySelectorAll('[data-local-cpa-step9-mode]'));
|
||||
const rowSub2ApiUrl = document.getElementById('row-sub2api-url');
|
||||
const inputSub2ApiUrl = document.getElementById('input-sub2api-url');
|
||||
const rowSub2ApiEmail = document.getElementById('row-sub2api-email');
|
||||
@@ -96,6 +98,7 @@ const SKIPPABLE_STEPS = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
const AUTO_DELAY_MIN_MINUTES = 1;
|
||||
const AUTO_DELAY_MAX_MINUTES = 1440;
|
||||
const AUTO_DELAY_DEFAULT_MINUTES = 30;
|
||||
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
||||
|
||||
let latestState = null;
|
||||
let currentAutoRun = {
|
||||
@@ -492,6 +495,7 @@ function collectSettingsPayload() {
|
||||
panelMode: selectPanelMode.value,
|
||||
vpsUrl: inputVpsUrl.value.trim(),
|
||||
vpsPassword: inputVpsPassword.value,
|
||||
localCpaStep9Mode: getSelectedLocalCpaStep9Mode(),
|
||||
sub2apiUrl: inputSub2ApiUrl.value.trim(),
|
||||
sub2apiEmail: inputSub2ApiEmail.value.trim(),
|
||||
sub2apiPassword: inputSub2ApiPassword.value,
|
||||
@@ -509,6 +513,26 @@ function collectSettingsPayload() {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeLocalCpaStep9Mode(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'bypass'
|
||||
? 'bypass'
|
||||
: DEFAULT_LOCAL_CPA_STEP9_MODE;
|
||||
}
|
||||
|
||||
function getSelectedLocalCpaStep9Mode() {
|
||||
const activeButton = localCpaStep9ModeButtons.find((button) => button.classList.contains('is-active'));
|
||||
return normalizeLocalCpaStep9Mode(activeButton?.dataset.localCpaStep9Mode);
|
||||
}
|
||||
|
||||
function setLocalCpaStep9Mode(mode) {
|
||||
const resolvedMode = normalizeLocalCpaStep9Mode(mode);
|
||||
localCpaStep9ModeButtons.forEach((button) => {
|
||||
const active = button.dataset.localCpaStep9Mode === resolvedMode;
|
||||
button.classList.toggle('is-active', active);
|
||||
button.setAttribute('aria-pressed', String(active));
|
||||
});
|
||||
}
|
||||
|
||||
function markSettingsDirty(isDirty = true) {
|
||||
settingsDirty = isDirty;
|
||||
updateSaveButtonState();
|
||||
@@ -677,6 +701,7 @@ async function restoreState() {
|
||||
if (state.vpsPassword) {
|
||||
inputVpsPassword.value = state.vpsPassword;
|
||||
}
|
||||
setLocalCpaStep9Mode(state.localCpaStep9Mode);
|
||||
if (state.panelMode) {
|
||||
selectPanelMode.value = state.panelMode;
|
||||
}
|
||||
@@ -1051,6 +1076,7 @@ function updatePanelModeUI() {
|
||||
const useSub2Api = selectPanelMode.value === 'sub2api';
|
||||
rowVpsUrl.style.display = useSub2Api ? 'none' : '';
|
||||
rowVpsPassword.style.display = useSub2Api ? 'none' : '';
|
||||
rowLocalCpaStep9Mode.style.display = useSub2Api ? 'none' : '';
|
||||
rowSub2ApiUrl.style.display = useSub2Api ? '' : 'none';
|
||||
rowSub2ApiEmail.style.display = useSub2Api ? '' : 'none';
|
||||
rowSub2ApiPassword.style.display = useSub2Api ? '' : 'none';
|
||||
@@ -1698,6 +1724,18 @@ btnToggleVpsUrl.addEventListener('click', () => {
|
||||
syncVpsUrlToggleLabel();
|
||||
});
|
||||
|
||||
localCpaStep9ModeButtons.forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const nextMode = button.dataset.localCpaStep9Mode;
|
||||
if (getSelectedLocalCpaStep9Mode() === normalizeLocalCpaStep9Mode(nextMode)) {
|
||||
return;
|
||||
}
|
||||
setLocalCpaStep9Mode(nextMode);
|
||||
markSettingsDirty(true);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
});
|
||||
});
|
||||
|
||||
btnSaveSettings.addEventListener('click', async () => {
|
||||
if (!settingsDirty) {
|
||||
showToast('配置已是最新', 'info', 1400);
|
||||
@@ -2147,6 +2185,7 @@ initializeManualStepActions();
|
||||
initTheme();
|
||||
initHotmailListExpandedState();
|
||||
updateSaveButtonState();
|
||||
setLocalCpaStep9Mode(DEFAULT_LOCAL_CPA_STEP9_MODE);
|
||||
restoreState().then(() => {
|
||||
syncPasswordToggleLabel();
|
||||
syncVpsUrlToggleLabel();
|
||||
|
||||
@@ -28,29 +28,42 @@ function extractFunction(name) {
|
||||
}
|
||||
|
||||
const bundle = [
|
||||
'const DEFAULT_LOCAL_CPA_STEP9_MODE = "submit";',
|
||||
extractFunction('parseUrlSafely'),
|
||||
extractFunction('isLocalCpaUrl'),
|
||||
extractFunction('normalizeLocalCpaStep9Mode'),
|
||||
extractFunction('shouldBypassStep9ForLocalCpa'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`${bundle}; return { isLocalCpaUrl, shouldBypassStep9ForLocalCpa };`)();
|
||||
const api = new Function(`${bundle}; return { isLocalCpaUrl, normalizeLocalCpaStep9Mode, shouldBypassStep9ForLocalCpa };`)();
|
||||
|
||||
assert.strictEqual(api.isLocalCpaUrl('http://127.0.0.1:8317/management.html#/oauth'), true, '127.0.0.1 应视为本地 CPA');
|
||||
assert.strictEqual(api.isLocalCpaUrl('http://localhost:1455/management.html#/oauth'), true, 'localhost 应视为本地 CPA');
|
||||
assert.strictEqual(api.isLocalCpaUrl('https://example.com/management.html#/oauth'), false, '远程域名不应视为本地 CPA');
|
||||
assert.strictEqual(api.isLocalCpaUrl('notaurl'), false, '非法 URL 不应视为本地 CPA');
|
||||
assert.strictEqual(api.normalizeLocalCpaStep9Mode('submit'), 'submit', 'submit 应保持为 submit');
|
||||
assert.strictEqual(api.normalizeLocalCpaStep9Mode('bypass'), 'bypass', 'bypass 应保持为 bypass');
|
||||
assert.strictEqual(api.normalizeLocalCpaStep9Mode('other'), 'submit', '未知模式应回退为 submit');
|
||||
|
||||
assert.strictEqual(api.shouldBypassStep9ForLocalCpa({
|
||||
vpsUrl: 'http://127.0.0.1:8317/management.html#/oauth',
|
||||
localhostUrl: 'http://127.0.0.1:8317/codex/callback?code=abc&state=xyz',
|
||||
}), true, '本地 CPA 且已有 callback 时应跳过远程提交流程');
|
||||
}), false, '默认模式下,本地 CPA 也应执行步骤 9');
|
||||
|
||||
assert.strictEqual(api.shouldBypassStep9ForLocalCpa({
|
||||
localCpaStep9Mode: 'bypass',
|
||||
vpsUrl: 'http://127.0.0.1:8317/management.html#/oauth',
|
||||
localhostUrl: 'http://127.0.0.1:8317/codex/callback?code=abc&state=xyz',
|
||||
}), true, '切换为 bypass 后,本地 CPA 且已有 callback 时应跳过步骤 9');
|
||||
|
||||
assert.strictEqual(api.shouldBypassStep9ForLocalCpa({
|
||||
localCpaStep9Mode: 'bypass',
|
||||
vpsUrl: 'https://example.com/management.html#/oauth',
|
||||
localhostUrl: 'http://127.0.0.1:8317/codex/callback?code=abc&state=xyz',
|
||||
}), false, '远程 CPA 不应跳过步骤 9');
|
||||
|
||||
assert.strictEqual(api.shouldBypassStep9ForLocalCpa({
|
||||
localCpaStep9Mode: 'bypass',
|
||||
vpsUrl: 'http://127.0.0.1:8317/management.html#/oauth',
|
||||
localhostUrl: '',
|
||||
}), false, '没有 callback 时不应跳过步骤 9');
|
||||
|
||||
Reference in New Issue
Block a user