From a7ea261093c7fca5ed1f1fff7fac0cf8a1745c17 Mon Sep 17 00:00:00 2001
From: QLHazyCoder <2825305047@qq.com>
Date: Fri, 15 May 2026 02:52:48 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E4=BB=8E=20CFTE=20?=
=?UTF-8?q?=E5=90=8C=E6=AD=A5=20Temp=20=E5=9F=9F=E5=90=8D=E5=88=97?=
=?UTF-8?q?=E8=A1=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
sidepanel/sidepanel.html | 4 +-
sidepanel/sidepanel.js | 178 ++++++++++++---
...dflare-temp-email-random-subdomain.test.js | 210 ++++++++++++++++++
3 files changed, 365 insertions(+), 27 deletions(-)
diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html
index 3db3046..92edae9 100644
--- a/sidepanel/sidepanel.html
+++ b/sidepanel/sidepanel.html
@@ -725,14 +725,14 @@
-
+
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js
index 9e29c62..9061f0b 100644
--- a/sidepanel/sidepanel.js
+++ b/sidepanel/sidepanel.js
@@ -1310,7 +1310,7 @@ const tempEmailDomainPicker = createEditableListPicker({
trigger: btnTempEmailDomainMenu,
current: tempEmailDomainCurrent,
menu: tempEmailDomainMenu,
- emptyLabel: '请先添加域名',
+ emptyLabel: '请先更新域名',
itemLabel: '域名',
normalizeItems: normalizeCloudflareTempEmailDomains,
normalizeValue: normalizeCloudflareTempEmailDomainValue,
@@ -2981,16 +2981,11 @@ function setCloudflareDomainEditMode(editing, options = {}) {
function setCloudflareTempEmailDomainEditMode(editing, options = {}) {
const { clearInput = false } = options;
- cloudflareTempEmailDomainEditMode = Boolean(editing);
- tempEmailDomainPicker.setVisible(!cloudflareTempEmailDomainEditMode);
- inputTempEmailDomain.style.display = cloudflareTempEmailDomainEditMode ? '' : 'none';
- btnTempEmailDomainMode.textContent = cloudflareTempEmailDomainEditMode ? '保存' : '添加';
- if (cloudflareTempEmailDomainEditMode) {
- if (clearInput) {
- inputTempEmailDomain.value = '';
- }
- inputTempEmailDomain.focus();
- } else if (clearInput) {
+ cloudflareTempEmailDomainEditMode = false;
+ tempEmailDomainPicker.setVisible(true);
+ inputTempEmailDomain.style.display = 'none';
+ btnTempEmailDomainMode.textContent = '更新';
+ if (clearInput) {
inputTempEmailDomain.value = '';
}
}
@@ -10427,6 +10422,152 @@ async function saveCloudflareTempEmailDomainSettings(domains, activeDomain, opti
}
}
+function joinCloudflareTempEmailSettingsUrl(baseUrl, path) {
+ const normalizedBaseUrl = normalizeCloudflareTempEmailBaseUrlValue(baseUrl);
+ const normalizedPath = String(path || '').trim();
+ if (!normalizedBaseUrl || !normalizedPath) {
+ return normalizedBaseUrl || '';
+ }
+ return `${normalizedBaseUrl}${normalizedPath.startsWith('/') ? '' : '/'}${normalizedPath}`;
+}
+
+function buildCloudflareTempEmailSyncHeaders(options = {}) {
+ const { includeAdminAuth = false } = options;
+ const headers = {
+ Accept: 'application/json',
+ };
+ const customAuth = String(inputTempEmailCustomAuth?.value || '').trim();
+ if (customAuth) {
+ headers['x-custom-auth'] = customAuth;
+ }
+ if (includeAdminAuth) {
+ const adminAuth = String(inputTempEmailAdminAuth?.value || '').trim();
+ if (adminAuth) {
+ headers['x-admin-auth'] = adminAuth;
+ }
+ }
+ return headers;
+}
+
+async function requestCloudflareTempEmailSyncPayload(baseUrl, path, options = {}) {
+ const url = joinCloudflareTempEmailSettingsUrl(baseUrl, path);
+ if (!url) {
+ throw new Error('Cloudflare Temp Email 服务地址为空或格式无效。');
+ }
+
+ const response = await fetch(url, {
+ cache: 'no-store',
+ headers: buildCloudflareTempEmailSyncHeaders(options),
+ });
+ const rawText = await response.text();
+ let payload = rawText;
+ try {
+ payload = rawText ? JSON.parse(rawText) : {};
+ } catch {
+ payload = rawText;
+ }
+
+ if (!response.ok) {
+ const message = typeof payload === 'object' && payload
+ ? (payload.message || payload.error || payload.msg)
+ : '';
+ throw new Error(message || `HTTP ${response.status}`);
+ }
+
+ return payload;
+}
+
+function mergeCloudflareTempEmailDomains(existingDomains, fetchedDomains) {
+ const mergedDomains = [];
+ const seen = new Set();
+ for (const domain of normalizeCloudflareTempEmailDomains(fetchedDomains)) {
+ if (seen.has(domain)) continue;
+ seen.add(domain);
+ mergedDomains.push(domain);
+ }
+ for (const domain of normalizeCloudflareTempEmailDomains(existingDomains)) {
+ if (seen.has(domain)) continue;
+ seen.add(domain);
+ mergedDomains.push(domain);
+ }
+ return mergedDomains;
+}
+
+async function fetchCloudflareTempEmailAvailableDomains(baseUrl) {
+ let openSettingsError = null;
+ try {
+ const payload = await requestCloudflareTempEmailSyncPayload(baseUrl, '/open_api/settings');
+ const domains = normalizeCloudflareTempEmailDomains(payload?.domains || []);
+ if (domains.length) {
+ return {
+ domains,
+ source: 'open_api/settings',
+ };
+ }
+ openSettingsError = new Error('公开设置未返回可用域名。');
+ } catch (error) {
+ openSettingsError = error;
+ }
+
+ const adminAuth = String(inputTempEmailAdminAuth?.value || '').trim();
+ if (!adminAuth) {
+ throw openSettingsError || new Error('未获取到可用域名。');
+ }
+
+ const payload = await requestCloudflareTempEmailSyncPayload(baseUrl, '/admin/worker/configs', {
+ includeAdminAuth: true,
+ });
+ const domains = normalizeCloudflareTempEmailDomains(payload?.DOMAINS || []);
+ if (!domains.length) {
+ throw openSettingsError || new Error('管理配置未返回可用域名。');
+ }
+ return {
+ domains,
+ source: 'admin/worker/configs',
+ };
+}
+
+async function syncCloudflareTempEmailDomainsFromService() {
+ const normalizedBaseUrl = normalizeCloudflareTempEmailBaseUrlValue(inputTempEmailBaseUrl?.value || '');
+ if (!normalizedBaseUrl) {
+ throw new Error('请先填写有效的 Cloudflare Temp Email 服务地址。');
+ }
+
+ const previousButtonText = btnTempEmailDomainMode.textContent;
+ btnTempEmailDomainMode.disabled = true;
+ btnTempEmailDomainMode.textContent = '更新中...';
+
+ try {
+ const { domains: fetchedDomains, source } = await fetchCloudflareTempEmailAvailableDomains(normalizedBaseUrl);
+ const { domains: existingDomains, activeDomain } = getCloudflareTempEmailDomainsFromState();
+ const currentDomain = normalizeCloudflareTempEmailDomainValue(selectTempEmailDomain.value || activeDomain);
+ const mergedDomains = mergeCloudflareTempEmailDomains(existingDomains, fetchedDomains);
+ const nextActiveDomain = mergedDomains.includes(currentDomain)
+ ? currentDomain
+ : (fetchedDomains[0] || mergedDomains[0] || '');
+ const existingSet = new Set(normalizeCloudflareTempEmailDomains(existingDomains));
+ const addedCount = fetchedDomains.filter((domain) => !existingSet.has(domain)).length;
+
+ await saveCloudflareTempEmailDomainSettings(mergedDomains, nextActiveDomain, { silent: true });
+ if (addedCount > 0) {
+ showToast(`已更新 Cloudflare Temp Email 域名,新增 ${addedCount} 个。`, 'success', 2200);
+ } else {
+ showToast('已同步 Cloudflare Temp Email 域名,暂无新增项。', 'info', 2200);
+ }
+ return {
+ domains: mergedDomains,
+ activeDomain: nextActiveDomain,
+ source,
+ };
+ } catch (error) {
+ const message = error?.message || String(error || '未知错误');
+ throw new Error(`更新 Cloudflare Temp Email 域名失败:${message}`);
+ } finally {
+ btnTempEmailDomainMode.disabled = false;
+ btnTempEmailDomainMode.textContent = previousButtonText || '更新';
+ }
+}
+
async function handleDeleteCloudflareDomain(domain) {
const targetDomain = normalizeCloudflareDomainValue(domain);
if (!targetDomain) {
@@ -12736,20 +12877,7 @@ btnCfDomainMode.addEventListener('click', async () => {
btnTempEmailDomainMode.addEventListener('click', async () => {
try {
- if (!cloudflareTempEmailDomainEditMode) {
- setCloudflareTempEmailDomainEditMode(true, { clearInput: true });
- return;
- }
-
- const newDomain = normalizeCloudflareTempEmailDomainValue(inputTempEmailDomain.value);
- if (!newDomain) {
- showToast('请输入有效的 Cloudflare Temp Email 域名。', 'warn');
- inputTempEmailDomain.focus();
- return;
- }
-
- const { domains } = getCloudflareTempEmailDomainsFromState();
- await saveCloudflareTempEmailDomainSettings([...domains, newDomain], newDomain);
+ await syncCloudflareTempEmailDomainsFromService();
} catch (err) {
showToast(err.message, 'error');
}
diff --git a/tests/sidepanel-cloudflare-temp-email-random-subdomain.test.js b/tests/sidepanel-cloudflare-temp-email-random-subdomain.test.js
index d553d40..303a298 100644
--- a/tests/sidepanel-cloudflare-temp-email-random-subdomain.test.js
+++ b/tests/sidepanel-cloudflare-temp-email-random-subdomain.test.js
@@ -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');