Merge pull request #290 from QLHazyCoder/codex/pr280-reimplement

feat(email): 使用英文名时间生成默认邮箱前缀
This commit is contained in:
QLHazyCoder
2026-05-28 04:58:47 +08:00
committed by GitHub
6 changed files with 262 additions and 7 deletions
+1
View File
@@ -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',
+11 -2
View File
@@ -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,
+78
View File
@@ -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,
};
});
+14 -3
View File
@@ -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,
};
+104 -2
View File
@@ -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 = [];
+54
View File
@@ -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$/);
});