feat(email): 使用英文名时间生成默认邮箱前缀
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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$/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user