重构duckduckgo邮箱获取

This commit is contained in:
QLHazyCoder
2026-05-12 02:26:46 +08:00
parent 745010d6c9
commit f81cc74c29
16 changed files with 492 additions and 53 deletions
@@ -96,6 +96,7 @@ test('generated email helper forwards the previous email to Duck generation as a
},
getCloudflareTempEmailAddressFromResponse: () => '',
getCloudflareTempEmailConfig: () => ({ baseUrl: '', adminAuth: '', domain: '' }),
getRegistrationEmailBaseline: (state, options = {}) => options.preferredEmail || options.fallbackEmail || state.email,
getState: async () => ({
email: 'Previous@Duck.com',
emailGenerator: 'duck',
@@ -132,7 +133,82 @@ test('generated email helper forwards the previous email to Duck generation as a
assert.equal(requests[0].type, 'FETCH_DUCK_EMAIL');
assert.deepEqual(requests[0].payload, {
generateNew: true,
previousEmail: 'previous@duck.com',
baselineEmail: 'previous@duck.com',
});
});
test('generated email helper prefers current UI email over preserved runtime baseline for Duck generation', async () => {
const api = loadGeneratedEmailHelpersApi();
const requests = [];
const helpers = api.createGeneratedEmailHelpers({
addLog: async () => {},
buildGeneratedAliasEmail: () => {
throw new Error('should not build alias');
},
buildCloudflareTempEmailHeaders: () => ({}),
CLOUDFLARE_TEMP_EMAIL_GENERATOR: 'cloudflare-temp-email',
DUCK_AUTOFILL_URL: 'https://duckduckgo.com/email',
fetch: async () => ({ ok: true, text: async () => '{}' }),
fetchIcloudHideMyEmail: async () => {
throw new Error('should not use icloud generator');
},
getCloudflareTempEmailAddressFromResponse: () => '',
getCloudflareTempEmailConfig: () => ({ baseUrl: '', adminAuth: '', domain: '' }),
getRegistrationEmailBaseline: (state, options = {}) => (
String(options.preferredEmail || '').trim()
|| String(state?.registrationEmailState?.previous || '').trim()
|| String(options.fallbackEmail || '').trim()
),
getState: async () => ({
email: '',
registrationEmailState: {
current: '',
previous: 'preserved@duck.com',
source: 'generated:duck',
updatedAt: 1,
},
emailGenerator: 'duck',
mailProvider: 'gmail',
}),
ensureMail2925AccountForFlow: async () => {
throw new Error('should not allocate 2925 account');
},
joinCloudflareTempEmailUrl: () => '',
normalizeCloudflareDomain: () => '',
normalizeCloudflareTempEmailAddress: () => '',
normalizeEmailGenerator: (value) => String(value || '').trim().toLowerCase(),
isGeneratedAliasProvider: () => false,
reuseOrCreateTab: async () => {},
sendToContentScript: async (_source, message) => {
requests.push(message);
return { email: 'fresh@duck.com', generated: true };
},
setEmailState: async () => {},
throwIfStopped: () => {},
});
const email = await helpers.fetchGeneratedEmail({
email: '',
registrationEmailState: {
current: '',
previous: 'preserved@duck.com',
source: 'generated:duck',
updatedAt: 1,
},
emailGenerator: 'duck',
mailProvider: 'gmail',
}, {
generator: 'duck',
generateNew: true,
currentEmail: 'visible@duck.com',
});
assert.equal(email, 'fresh@duck.com');
assert.equal(requests.length, 1);
assert.deepEqual(requests[0].payload, {
generateNew: true,
baselineEmail: 'visible@duck.com',
});
});
@@ -0,0 +1,58 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadRegistrationEmailStateApi() {
const source = fs.readFileSync('background/registration-email-state.js', 'utf8');
const globalScope = {};
return new Function('self', `${source}; return self.MultiPageRegistrationEmailState;`)(globalScope);
}
test('registration email state preserves the previous baseline when current email is cleared for recovery', () => {
const api = loadRegistrationEmailStateApi();
const helpers = api.createRegistrationEmailStateHelpers();
const updates = helpers.buildRegistrationEmailStateUpdates({
email: 'old.user@example.com',
registrationEmailState: {
current: 'old.user@example.com',
previous: 'old.user@example.com',
source: 'generated:duck',
updatedAt: 1,
},
}, {
currentEmail: null,
preservePrevious: true,
source: 'step8_recovery',
});
assert.deepStrictEqual(updates, {
email: null,
registrationEmailState: {
current: '',
previous: 'old.user@example.com',
source: 'generated:duck',
updatedAt: updates.registrationEmailState.updatedAt,
},
});
assert.ok(updates.registrationEmailState.updatedAt > 0);
});
test('registration email baseline prefers the current UI email over preserved runtime state', () => {
const api = loadRegistrationEmailStateApi();
const helpers = api.createRegistrationEmailStateHelpers();
const baseline = helpers.getRegistrationEmailBaseline({
email: '',
registrationEmailState: {
current: '',
previous: 'preserved@duck.com',
source: 'generated:duck',
updatedAt: 1,
},
}, {
preferredEmail: 'visible@duck.com',
});
assert.equal(baseline, 'visible@duck.com');
});
@@ -775,7 +775,6 @@ test('signup flow helper can generate an email on demand when add-email starts f
assert.equal(fetchedStates[0].options.preserveAccountIdentity, true);
assert.deepStrictEqual(setStateCalls, [
{
email: 'duck.generated@example.com',
accountIdentifierType: 'phone',
accountIdentifier: '+447780579093',
signupPhoneNumber: '+447780579093',
+107
View File
@@ -352,6 +352,113 @@ test('step 8 submits add-email before polling the email verification code', asyn
]);
});
test('step 8 email_in_use recovery preserves the previous registration baseline', async () => {
const calls = {
contentCalls: 0,
setStates: [],
updatedUrls: [],
};
const executor = api.createStep8Executor({
addLog: async () => {},
chrome: {
tabs: {
update: async (_tabId, payload) => {
calls.updatedUrls.push(payload.url);
},
},
},
buildRegistrationEmailStateUpdates: () => ({
email: null,
registrationEmailState: {
current: '',
previous: 'old.user@example.com',
source: 'step8_recovery',
updatedAt: 123,
},
}),
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
confirmCustomVerificationStepBypass: async () => {},
ensureStep8VerificationPageReady: async () => ({
state: 'add_email_page',
url: 'https://auth.openai.com/add-email',
}),
getOAuthFlowRemainingMs: async () => 5000,
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
getMailConfig: () => ({
provider: 'qq',
label: 'QQ 閭',
source: 'mail-qq',
url: 'https://mail.qq.com',
navigateOnReuse: false,
}),
getState: async () => ({
email: '',
registrationEmailState: {
current: '',
previous: 'old.user@example.com',
source: 'step8_recovery',
updatedAt: 123,
},
oauthUrl: 'https://auth.openai.com/add-email',
password: 'secret',
}),
getTabId: async () => 1,
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
LUCKMAIL_PROVIDER: 'luckmail-api',
resolveSignupEmailForFlow: async () => 'new.user@example.com',
resolveVerificationStep: async () => {},
rerunStep7ForStep8Recovery: async () => {},
reuseOrCreateTab: async () => {},
sendToContentScriptResilient: async () => {
calls.contentCalls += 1;
if (calls.contentCalls === 1) {
throw new Error('STEP8_EMAIL_IN_USE::old.user@example.com');
}
return {
submitted: true,
displayedEmail: 'new.user@example.com',
url: 'https://auth.openai.com/email-verification',
};
},
setState: async (payload) => {
calls.setStates.push(payload);
},
shouldUseCustomRegistrationEmail: () => false,
sleepWithStop: async () => {},
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 3,
throwIfStopped: () => {},
});
await executor.executeStep8({
email: 'old.user@example.com',
registrationEmailState: {
current: 'old.user@example.com',
previous: 'old.user@example.com',
source: 'generated:duck',
updatedAt: 1,
},
oauthUrl: 'https://auth.openai.com/add-email',
password: 'secret',
visibleStep: 8,
});
assert.deepStrictEqual(calls.setStates[0], {
email: null,
registrationEmailState: {
current: '',
previous: 'old.user@example.com',
source: 'step8_recovery',
updatedAt: 123,
},
step8VerificationTargetEmail: '',
loginVerificationRequestedAt: null,
});
});
test('Plus login-code step reuses step 8 verification logic but completes visible step 11', async () => {
let resolvedStep = null;
let resolvedOptions = null;
+1 -1
View File
@@ -176,7 +176,7 @@ test('fetchDuckEmail falls back to the previous Duck email when the page baselin
const result = await api.fetchDuckEmail({
generateNew: true,
previousEmail: 'previous@duck.com',
baselineEmail: 'previous@duck.com',
});
assert.deepEqual(result, {