feat: 支持从 CFTE 同步 Temp 域名列表
This commit is contained in:
@@ -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');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user