feat: 支持从 CFTE 同步 Temp 域名列表

This commit is contained in:
QLHazyCoder
2026-05-15 02:52:48 +08:00
parent d681972724
commit a7ea261093
3 changed files with 365 additions and 27 deletions
@@ -65,6 +65,7 @@ test('sidepanel html places cloudflare temp email controls in a standalone secti
assert.match(html, /id="btn-cloudflare-temp-email-github"/);
assert.match(html, /id="row-temp-email-random-subdomain-toggle"/);
assert.match(html, /id="input-temp-email-use-random-subdomain"/);
assert.match(html, /id="btn-temp-email-domain-mode"[^>]*>更新</);
assert.doesNotMatch(html, /id="row-temp-email-random-subdomain-domain"/);
});
@@ -173,6 +174,215 @@ return {
assert.deepEqual(api.calls.domainEditMode, [{ editing: false, options: { clearInput: true } }]);
});
test('setCloudflareTempEmailDomainEditMode keeps the selector visible and the button in update mode', () => {
const bundle = extractFunction('setCloudflareTempEmailDomainEditMode');
const api = new Function(`
let cloudflareTempEmailDomainEditMode = true;
const tempEmailDomainPicker = {
visibility: [],
setVisible(value) {
this.visibility.push(value);
},
};
const inputTempEmailDomain = { value: 'old.example.com', style: { display: '' } };
const btnTempEmailDomainMode = { textContent: '' };
${bundle}
return {
setCloudflareTempEmailDomainEditMode,
tempEmailDomainPicker,
inputTempEmailDomain,
btnTempEmailDomainMode,
getMode() {
return cloudflareTempEmailDomainEditMode;
},
};
`)();
api.setCloudflareTempEmailDomainEditMode(true, { clearInput: true });
assert.equal(api.getMode(), false);
assert.deepEqual(api.tempEmailDomainPicker.visibility, [true]);
assert.equal(api.inputTempEmailDomain.style.display, 'none');
assert.equal(api.inputTempEmailDomain.value, '');
assert.equal(api.btnTempEmailDomainMode.textContent, '更新');
});
test('syncCloudflareTempEmailDomainsFromService merges domains from open settings and preserves the current selection', async () => {
const bundle = [
extractFunction('joinCloudflareTempEmailSettingsUrl'),
extractFunction('buildCloudflareTempEmailSyncHeaders'),
extractFunction('requestCloudflareTempEmailSyncPayload'),
extractFunction('mergeCloudflareTempEmailDomains'),
extractFunction('fetchCloudflareTempEmailAvailableDomains'),
extractFunction('syncCloudflareTempEmailDomainsFromService'),
].join('\n');
const api = new Function(`
const fetchCalls = [];
const saveCalls = [];
const toastCalls = [];
const btnTempEmailDomainMode = { textContent: '更新', disabled: false };
const inputTempEmailBaseUrl = { value: 'https://temp.example.com' };
const inputTempEmailCustomAuth = { value: 'custom-secret' };
const inputTempEmailAdminAuth = { value: 'admin-secret' };
const selectTempEmailDomain = { value: 'old.example.com' };
function normalizeCloudflareTempEmailBaseUrlValue(value = '') {
const raw = String(value || '').trim();
if (!raw) return '';
const candidate = /^[a-zA-Z][a-zA-Z\\d+\\-.]*:\\/\\//.test(raw) ? raw : \`https://\${raw}\`;
const parsed = new URL(candidate);
parsed.hash = '';
parsed.search = '';
const pathname = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\\/+$/, '');
return \`\${parsed.origin}\${pathname}\`;
}
function normalizeCloudflareTempEmailDomainValue(value = '') {
let normalized = String(value || '').trim().toLowerCase();
if (!normalized) return '';
normalized = normalized.replace(/^@+/, '');
normalized = normalized.replace(/^https?:\\/\\//, '');
normalized = normalized.replace(/\\/.*$/, '');
return /^[a-z0-9.-]+\\.[a-z]{2,}$/i.test(normalized) ? normalized : '';
}
function normalizeCloudflareTempEmailDomains(values = []) {
const seen = new Set();
const domains = [];
for (const value of Array.isArray(values) ? values : []) {
const normalized = normalizeCloudflareTempEmailDomainValue(value);
if (!normalized || seen.has(normalized)) continue;
seen.add(normalized);
domains.push(normalized);
}
return domains;
}
async function fetch(url, options = {}) {
fetchCalls.push({ url, options });
return {
ok: true,
status: 200,
async text() {
return JSON.stringify({
domains: ['new.example.com', 'old.example.com'],
});
},
};
}
function getCloudflareTempEmailDomainsFromState() {
return {
domains: ['old.example.com', 'local-only.example.com'],
activeDomain: 'old.example.com',
};
}
async function saveCloudflareTempEmailDomainSettings(domains, activeDomain, options) {
saveCalls.push({ domains, activeDomain, options });
}
function showToast(message, type, duration) {
toastCalls.push({ message, type, duration });
}
${bundle}
return {
syncCloudflareTempEmailDomainsFromService,
fetchCalls,
saveCalls,
toastCalls,
btnTempEmailDomainMode,
};
`)();
const result = await api.syncCloudflareTempEmailDomainsFromService();
assert.equal(result.source, 'open_api/settings');
assert.deepEqual(api.fetchCalls.map((item) => item.url), ['https://temp.example.com/open_api/settings']);
assert.deepEqual(api.saveCalls, [{
domains: ['new.example.com', 'old.example.com', 'local-only.example.com'],
activeDomain: 'old.example.com',
options: { silent: true },
}]);
assert.match(api.toastCalls[0].message, /新增 1 个/);
assert.equal(api.toastCalls[0].type, 'success');
assert.equal(api.btnTempEmailDomainMode.disabled, false);
assert.equal(api.btnTempEmailDomainMode.textContent, '更新');
});
test('fetchCloudflareTempEmailAvailableDomains falls back to admin worker configs when open settings has no domains', async () => {
const bundle = [
extractFunction('joinCloudflareTempEmailSettingsUrl'),
extractFunction('buildCloudflareTempEmailSyncHeaders'),
extractFunction('requestCloudflareTempEmailSyncPayload'),
extractFunction('fetchCloudflareTempEmailAvailableDomains'),
].join('\n');
const api = new Function(`
const fetchCalls = [];
const inputTempEmailCustomAuth = { value: 'custom-secret' };
const inputTempEmailAdminAuth = { value: 'admin-secret' };
function normalizeCloudflareTempEmailBaseUrlValue(value = '') {
const raw = String(value || '').trim();
if (!raw) return '';
const candidate = /^[a-zA-Z][a-zA-Z\\d+\\-.]*:\\/\\//.test(raw) ? raw : \`https://\${raw}\`;
const parsed = new URL(candidate);
parsed.hash = '';
parsed.search = '';
const pathname = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\\/+$/, '');
return \`\${parsed.origin}\${pathname}\`;
}
function normalizeCloudflareTempEmailDomainValue(value = '') {
let normalized = String(value || '').trim().toLowerCase();
if (!normalized) return '';
normalized = normalized.replace(/^@+/, '');
normalized = normalized.replace(/^https?:\\/\\//, '');
normalized = normalized.replace(/\\/.*$/, '');
return /^[a-z0-9.-]+\\.[a-z]{2,}$/i.test(normalized) ? normalized : '';
}
function normalizeCloudflareTempEmailDomains(values = []) {
const seen = new Set();
const domains = [];
for (const value of Array.isArray(values) ? values : []) {
const normalized = normalizeCloudflareTempEmailDomainValue(value);
if (!normalized || seen.has(normalized)) continue;
seen.add(normalized);
domains.push(normalized);
}
return domains;
}
async function fetch(url, options = {}) {
fetchCalls.push({ url, options });
if (url.endsWith('/open_api/settings')) {
return {
ok: true,
status: 200,
async text() {
return JSON.stringify({ domains: [] });
},
};
}
return {
ok: true,
status: 200,
async text() {
return JSON.stringify({ DOMAINS: ['admin.example.com'] });
},
};
}
${bundle}
return {
fetchCloudflareTempEmailAvailableDomains,
fetchCalls,
};
`)();
const result = await api.fetchCloudflareTempEmailAvailableDomains('https://temp.example.com');
assert.equal(result.source, 'admin/worker/configs');
assert.deepEqual(result.domains, ['admin.example.com']);
assert.deepEqual(api.fetchCalls.map((item) => item.url), [
'https://temp.example.com/open_api/settings',
'https://temp.example.com/admin/worker/configs',
]);
assert.equal(api.fetchCalls[1].options.headers['x-admin-auth'], 'admin-secret');
});
test('updateMailProviderUI keeps the temp domain selector visible and updates the hint when random subdomain is enabled', () => {
const bundle = extractFunction('updateMailProviderUI');