From 001f133963e669aa9fc26180d8e56c334b91b490 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Thu, 28 May 2026 04:30:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(email):=20=E4=BD=BF=E7=94=A8=E8=8B=B1?= =?UTF-8?q?=E6=96=87=E5=90=8D=E6=97=B6=E9=97=B4=E7=94=9F=E6=88=90=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E9=82=AE=E7=AE=B1=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 1 + background/cloudmail-provider.js | 13 ++- background/email-local-part-helpers.js | 78 +++++++++++++ background/generated-email-helpers.js | 17 ++- .../background-generated-email-module.test.js | 106 +++++++++++++++++- tests/cloudmail-provider.test.js | 54 +++++++++ 6 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 background/email-local-part-helpers.js diff --git a/background.js b/background.js index 7799ffb6..23049716 100644 --- a/background.js +++ b/background.js @@ -45,6 +45,7 @@ importScripts( 'flows/kiro/background/desktop-authorize-runner.js', 'flows/kiro/background/publisher-kiro-rs.js', 'flows/grok/background/publisher-webchat2api.js', + 'background/email-local-part-helpers.js', 'background/generated-email-helpers.js', 'background/signup-flow-helpers.js', 'background/mail-rule-registry.js', diff --git a/background/cloudmail-provider.js b/background/cloudmail-provider.js index 175d8f72..8d53f937 100644 --- a/background/cloudmail-provider.js +++ b/background/cloudmail-provider.js @@ -1,6 +1,6 @@ (function cloudMailProviderModule(root, factory) { - root.MultiPageBackgroundCloudMailProvider = factory(); -})(typeof self !== 'undefined' ? self : globalThis, function createCloudMailProviderModule() { + root.MultiPageBackgroundCloudMailProvider = factory(root); +})(typeof self !== 'undefined' ? self : globalThis, function createCloudMailProviderModule(root = globalThis) { function createCloudMailProvider(deps = {}) { const { addLog = async () => {}, @@ -18,6 +18,7 @@ normalizeCloudMailDomains, normalizeCloudMailMailApiMessages, persistRegistrationEmailState = null, + buildRandomNameDateTimeLocalPart = root.MultiPageEmailLocalPartHelpers?.buildRandomNameDateTimeLocalPart, pickVerificationMessageWithTimeFallback, setEmailState = async () => {}, setPersistentSettings = async () => {}, @@ -172,6 +173,12 @@ return chars.join(''); } + function buildCloudMailNameDateTimeLocalPart(date = new Date()) { + return typeof buildRandomNameDateTimeLocalPart === 'function' + ? buildRandomNameDateTimeLocalPart(date) + : ''; + } + async function fetchCloudMailAddress(state, options = {}) { throwIfStopped(); const latestState = state || await getState(); @@ -181,6 +188,7 @@ requireDomain: true, }); const requestedLocal = String(options.localPart || options.name || '').trim().toLowerCase() + || buildCloudMailNameDateTimeLocalPart(options.date) || generateCloudMailAliasLocalPart(); const address = `${requestedLocal}@${ensuredConfig.domain}`.toLowerCase(); const payload = { list: [{ email: address }] }; @@ -318,6 +326,7 @@ } return { + buildCloudMailNameDateTimeLocalPart, ensureCloudMailConfig, ensureCloudMailToken, fetchCloudMailAddress, diff --git a/background/email-local-part-helpers.js b/background/email-local-part-helpers.js new file mode 100644 index 00000000..8b6ec61c --- /dev/null +++ b/background/email-local-part-helpers.js @@ -0,0 +1,78 @@ +(function attachEmailLocalPartHelpers(root, factory) { + root.MultiPageEmailLocalPartHelpers = factory(root); +})(typeof self !== 'undefined' ? self : globalThis, function createEmailLocalPartHelpersModule(root = globalThis) { + const ENGLISH_NAME_PREFIXES = [ + 'james', 'john', 'robert', 'michael', 'william', 'david', 'richard', 'joseph', + 'thomas', 'charles', 'mary', 'patricia', 'jennifer', 'linda', 'elizabeth', + 'barbara', 'susan', 'jessica', 'sarah', 'karen', 'daniel', 'matthew', + 'anthony', 'mark', 'donald', 'steven', 'paul', 'andrew', 'joshua', 'kevin', + 'brian', 'george', 'edward', 'ronald', 'timothy', 'jason', 'jeffrey', 'ryan', + 'jacob', 'gary', 'nicholas', 'eric', 'jonathan', 'stephen', 'larry', 'justin', + 'scott', 'brandon', 'benjamin', 'samuel', 'gregory', 'alexander', 'patrick', + 'frank', 'raymond', 'jack', 'dennis', 'jerry', 'tyler', 'aaron', 'henry', + 'douglas', 'peter', 'adam', 'zachary', 'nathan', 'walter', 'harold', 'kyle', + 'carl', 'arthur', 'gerald', 'roger', 'alice', 'emma', 'olivia', 'sophia', + 'isabella', 'mia', 'amelia', 'harper', 'evelyn', 'abigail', 'emily', 'ella', + 'scarlett', 'grace', 'chloe', 'victoria', 'riley', 'aria', 'lily', 'nora', + ]; + + const RANDOM_SUFFIX_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789'; + + function getRandomInt(maxExclusive) { + const max = Math.floor(Number(maxExclusive)); + if (!Number.isFinite(max) || max <= 0) return 0; + const cryptoApi = root.crypto; + if (cryptoApi && typeof cryptoApi.getRandomValues === 'function') { + const buffer = new Uint32Array(1); + cryptoApi.getRandomValues(buffer); + return buffer[0] % max; + } + return Math.floor(Math.random() * max); + } + + function pickRandomEnglishNamePrefix() { + return ENGLISH_NAME_PREFIXES[getRandomInt(ENGLISH_NAME_PREFIXES.length)] || 'james'; + } + + function formatDateTimeDigits(date = new Date()) { + const current = new Date(date); + if (Number.isNaN(current.getTime())) { + return ''; + } + const year = String(current.getFullYear()); + const month = String(current.getMonth() + 1).padStart(2, '0'); + const day = String(current.getDate()).padStart(2, '0'); + const hour = String(current.getHours()).padStart(2, '0'); + const minute = String(current.getMinutes()).padStart(2, '0'); + const second = String(current.getSeconds()).padStart(2, '0'); + const millisecond = String(current.getMilliseconds()).padStart(3, '0'); + return `${year}${month}${day}${hour}${minute}${second}${millisecond}`; + } + + function buildRandomAlphaNumericSuffix(length = 4) { + const size = Math.max(0, Math.floor(Number(length) || 0)); + const chars = []; + for (let index = 0; index < size; index += 1) { + chars.push(RANDOM_SUFFIX_CHARS[getRandomInt(RANDOM_SUFFIX_CHARS.length)] || 'a'); + } + return chars.join(''); + } + + function buildRandomNameDateTimeLocalPart(date = new Date(), options = {}) { + const dateTimeDigits = formatDateTimeDigits(date); + if (!dateTimeDigits) { + return ''; + } + const suffixLength = Number.isFinite(Number(options.suffixLength)) + ? Math.max(0, Math.floor(Number(options.suffixLength))) + : 4; + return `${pickRandomEnglishNamePrefix()}${dateTimeDigits}${buildRandomAlphaNumericSuffix(suffixLength)}`; + } + + return { + buildRandomAlphaNumericSuffix, + buildRandomNameDateTimeLocalPart, + formatDateTimeDigits, + pickRandomEnglishNamePrefix, + }; +}); diff --git a/background/generated-email-helpers.js b/background/generated-email-helpers.js index e6f6ddb3..3864b148 100644 --- a/background/generated-email-helpers.js +++ b/background/generated-email-helpers.js @@ -1,6 +1,6 @@ (function attachGeneratedEmailHelpers(root, factory) { - root.MultiPageGeneratedEmailHelpers = factory(); -})(typeof self !== 'undefined' ? self : globalThis, function createGeneratedEmailHelpersModule() { + root.MultiPageGeneratedEmailHelpers = factory(root); +})(typeof self !== 'undefined' ? self : globalThis, function createGeneratedEmailHelpersModule(root = globalThis) { function createGeneratedEmailHelpers(deps = {}) { const { addLog, @@ -24,6 +24,7 @@ normalizeEmailGenerator, isGeneratedAliasProvider, persistRegistrationEmailState = null, + buildRandomNameDateTimeLocalPart = root.MultiPageEmailLocalPartHelpers?.buildRandomNameDateTimeLocalPart, reuseOrCreateTab, sendToContentScript, setEmailState, @@ -59,6 +60,12 @@ return chars.join(''); } + function buildDefaultGeneratedEmailLocalPart(date = new Date()) { + return typeof buildRandomNameDateTimeLocalPart === 'function' + ? buildRandomNameDateTimeLocalPart(date) + : ''; + } + async function fetchCloudflareEmail(state, options = {}) { throwIfStopped(); const latestState = state || await getState(); @@ -165,7 +172,9 @@ requireAdminAuth: true, requireDomain: true, }); - const requestedName = String(options.localPart || options.name || '').trim().toLowerCase() || generateCloudflareAliasLocalPart(); + const requestedName = String(options.localPart || options.name || '').trim().toLowerCase() + || buildDefaultGeneratedEmailLocalPart(options.date) + || generateCloudflareAliasLocalPart(); const effectiveDomain = config.effectiveDomain || ( typeof buildCloudflareTempEmailEffectiveDomain === 'function' ? buildCloudflareTempEmailEffectiveDomain(config) @@ -377,6 +386,8 @@ fetchCloudflareTempEmailAddress, fetchDuckEmail, fetchGeneratedEmail, + buildDefaultGeneratedEmailLocalPart, + buildRandomNameDateTimeLocalPart, generateCloudflareAliasLocalPart, requestCloudflareTempEmailJson, }; diff --git a/tests/background-generated-email-module.test.js b/tests/background-generated-email-module.test.js index 8fde2689..2b9c3544 100644 --- a/tests/background-generated-email-module.test.js +++ b/tests/background-generated-email-module.test.js @@ -3,16 +3,38 @@ const assert = require('node:assert/strict'); const fs = require('node:fs'); function loadGeneratedEmailHelpersApi() { + const localPartHelpersSource = fs.readFileSync('background/email-local-part-helpers.js', 'utf8'); const source = fs.readFileSync('background/generated-email-helpers.js', 'utf8'); const globalScope = {}; - return new Function('self', `${source}; return self.MultiPageGeneratedEmailHelpers;`)(globalScope); + return new Function('self', `${localPartHelpersSource}; ${source}; return self.MultiPageGeneratedEmailHelpers;`)(globalScope); } test('background imports generated email helper module', () => { const source = fs.readFileSync('background.js', 'utf8'); + assert.match(source, /importScripts\([\s\S]*'background\/email-local-part-helpers\.js'/); assert.match(source, /importScripts\([\s\S]*'background\/generated-email-helpers\.js'/); }); +test('email local-part helper builds english name, date-time, and random suffix', () => { + const source = fs.readFileSync('background/email-local-part-helpers.js', 'utf8'); + let randomValues = []; + const globalScope = { + crypto: { + getRandomValues: (buffer) => { + buffer[0] = randomValues.shift() || 0; + return buffer; + }, + }, + }; + const api = new Function('self', `${source}; return self.MultiPageEmailLocalPartHelpers;`)(globalScope); + + assert.equal(api.formatDateTimeDigits('2026-05-17T08:09:10.123'), '20260517080910123'); + randomValues = [0, 1, 2, 3]; + assert.equal(api.buildRandomAlphaNumericSuffix(4), 'abcd'); + randomValues = [0, 0, 1, 2, 3]; + assert.equal(api.buildRandomNameDateTimeLocalPart('2026-05-17T08:09:10.123'), 'james20260517080910123abcd'); +}); + test('generated email helper module exposes a factory', () => { const api = loadGeneratedEmailHelpersApi(); @@ -481,7 +503,7 @@ test('generated email helper uses the regular temp email domain when random subd name: requests[0].body.name, domain: 'mail.example.com', }); - assert.match(requests[0].body.name, /^[a-z0-9]+$/); + assert.match(requests[0].body.name, /^[a-z]+[0-9]{17}[a-z0-9]{4}$/); }); test('generated email helper requests random subdomain creation while preserving the returned address', async () => { @@ -639,6 +661,86 @@ test('generated email helper uses fixed subdomain as the effective temp email do }); }); +test('generated email helper combines default name format with fixed subdomain payload', async () => { + const api = loadGeneratedEmailHelpersApi(); + const requests = []; + const savedEmails = []; + + const helpers = api.createGeneratedEmailHelpers({ + addLog: async () => {}, + buildGeneratedAliasEmail: () => { + throw new Error('should not build managed alias'); + }, + buildCloudflareTempEmailEffectiveDomain: (config) => `${config.subdomainPrefix}.${config.domain}`, + buildCloudflareTempEmailHeaders: () => ({ 'x-admin-auth': 'admin-secret' }), + CLOUDFLARE_TEMP_EMAIL_GENERATOR: 'cloudflare-temp-email', + DUCK_AUTOFILL_URL: 'https://duckduckgo.com/email', + fetch: async (url, options = {}) => { + requests.push({ + url, + method: options.method, + body: options.body ? JSON.parse(options.body) : null, + }); + return { + ok: true, + text: async () => JSON.stringify({ address: `${requests[0].body.name}@team.mail.example.com` }), + }; + }, + fetchIcloudHideMyEmail: async () => { + throw new Error('should not use icloud generator'); + }, + getCloudflareTempEmailAddressFromResponse: (payload) => payload.address, + getCloudflareTempEmailConfig: () => ({ + baseUrl: 'https://temp.example.com', + adminAuth: 'admin-secret', + customAuth: '', + effectiveDomain: 'team.mail.example.com', + useRandomSubdomain: true, + useFixedSubdomain: true, + subdomainPrefix: 'team', + domain: 'mail.example.com', + }), + getState: async () => ({ + mailProvider: '163', + emailGenerator: 'cloudflare-temp-email', + }), + ensureMail2925AccountForFlow: async () => { + throw new Error('should not allocate mail2925 account'); + }, + joinCloudflareTempEmailUrl: (baseUrl, path) => `${baseUrl}${path}`, + normalizeCloudflareDomain: () => '', + normalizeCloudflareTempEmailAddress: (value) => String(value || '').trim().toLowerCase(), + normalizeEmailGenerator: (value) => String(value || '').trim().toLowerCase(), + isGeneratedAliasProvider: () => false, + reuseOrCreateTab: async () => {}, + sendToContentScript: async () => { + throw new Error('should not use duck generator'); + }, + setEmailState: async (email) => { + savedEmails.push(email); + }, + throwIfStopped: () => {}, + }); + + const email = await helpers.fetchGeneratedEmail({ + emailGenerator: 'cloudflare-temp-email', + }, { + generator: 'cloudflare-temp-email', + date: '2026-05-17T08:09:10.123', + }); + + assert.match(email, /^[a-z]+20260517080910123[a-z0-9]{4}@team\.mail\.example\.com$/); + assert.deepEqual(savedEmails, [email]); + assert.equal(requests.length, 1); + assert.deepEqual(requests[0].body, { + enablePrefix: true, + enableRandomSubdomain: false, + name: requests[0].body.name, + domain: 'team.mail.example.com', + }); + assert.match(requests[0].body.name, /^[a-z]+20260517080910123[a-z0-9]{4}$/); +}); + test('generated email helper honors iCloud always-new fetch mode', async () => { const api = loadGeneratedEmailHelpersApi(); const icloudOptions = []; diff --git a/tests/cloudmail-provider.test.js b/tests/cloudmail-provider.test.js index 57445c53..972f8496 100644 --- a/tests/cloudmail-provider.test.js +++ b/tests/cloudmail-provider.test.js @@ -1,6 +1,7 @@ const test = require('node:test'); const assert = require('node:assert/strict'); +require('../background/email-local-part-helpers.js'); require('../background/cloudmail-provider.js'); function createProviderApi(options = {}) { @@ -228,3 +229,56 @@ test('fetchCloudMailAddress preserves phone identity through the shared persiste }, ]); }); + +test('fetchCloudMailAddress builds english name, date-time, and suffix local part by default', async () => { + const api = globalThis.MultiPageBackgroundCloudMailProvider.createCloudMailProvider({ + addLog: async () => {}, + buildCloudMailHeaders: () => ({}), + CLOUD_MAIL_DEFAULT_PAGE_SIZE: 20, + CLOUD_MAIL_GENERATOR: 'cloudmail', + CLOUD_MAIL_PROVIDER: 'cloudmail', + fetchImpl: async (url) => { + if (String(url).includes('/api/public/addUser')) { + return { + ok: true, + text: async () => JSON.stringify({ code: 200 }), + }; + } + return { + ok: true, + text: async () => JSON.stringify({ code: 200, data: { token: 'token' } }), + }; + }, + getCloudMailTokenFromResponse: () => 'token', + getState: async () => ({}), + joinCloudMailUrl: (baseUrl, path) => `${baseUrl}${path}`, + normalizeCloudMailAddress: (value) => String(value || '').trim().toLowerCase(), + normalizeCloudMailBaseUrl: (value) => String(value || '').trim(), + normalizeCloudMailDomain: (value) => String(value || '').trim(), + normalizeCloudMailDomains: (values) => values || [], + normalizeCloudMailMailApiMessages: () => [], + persistRegistrationEmailState: async () => {}, + pickVerificationMessageWithTimeFallback: () => ({ + match: null, + usedRelaxedFilters: false, + usedTimeFallback: false, + }), + setEmailState: async () => {}, + setPersistentSettings: async () => {}, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + }); + + const state = { + cloudMailBaseUrl: 'https://mail.example.com', + cloudMailAdminEmail: 'admin@example.com', + cloudMailAdminPassword: 'secret', + cloudMailToken: 'token', + cloudMailDomain: 'example.com', + }; + const email = await api.fetchCloudMailAddress(state, { + date: '2026-05-17T08:09:10.123', + }); + + assert.match(email, /^[a-z]+20260517080910123[a-z0-9]{4}@example\.com$/); +});