重构修改2925获取邮箱
This commit is contained in:
@@ -51,9 +51,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeStep8VerificationTargetEmail(value) {
|
||||||
|
return String(value || '').trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
async function runStep8Attempt(state) {
|
async function runStep8Attempt(state) {
|
||||||
const mail = getMailConfig(state);
|
const mail = getMailConfig(state);
|
||||||
if (mail.error) throw new Error(mail.error);
|
if (mail.error) throw new Error(mail.error);
|
||||||
|
|
||||||
const stepStartedAt = Date.now();
|
const stepStartedAt = Date.now();
|
||||||
const authTabId = await getTabId('signup-page');
|
const authTabId = await getTabId('signup-page');
|
||||||
|
|
||||||
@@ -67,10 +72,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
await ensureStep8VerificationPageReady({
|
const pageState = await ensureStep8VerificationPageReady({
|
||||||
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪'),
|
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪'),
|
||||||
});
|
});
|
||||||
|
const shouldCompareVerificationEmail = mail.provider !== '2925';
|
||||||
|
const displayedVerificationEmail = shouldCompareVerificationEmail
|
||||||
|
? normalizeStep8VerificationTargetEmail(pageState?.displayedEmail)
|
||||||
|
: '';
|
||||||
|
const fixedTargetEmail = shouldCompareVerificationEmail
|
||||||
|
? (displayedVerificationEmail || normalizeStep8VerificationTargetEmail(state?.email))
|
||||||
|
: '';
|
||||||
|
|
||||||
|
await setState({
|
||||||
|
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||||||
|
});
|
||||||
|
|
||||||
await addLog('步骤 8:登录验证码页面已就绪,开始获取验证码。', 'info');
|
await addLog('步骤 8:登录验证码页面已就绪,开始获取验证码。', 'info');
|
||||||
|
if (shouldCompareVerificationEmail && displayedVerificationEmail) {
|
||||||
|
await addLog(`步骤 8:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info');
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldUseCustomRegistrationEmail(state)) {
|
if (shouldUseCustomRegistrationEmail(state)) {
|
||||||
await confirmCustomVerificationStepBypass(8);
|
await confirmCustomVerificationStepBypass(8);
|
||||||
@@ -78,7 +98,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
if (mail.provider === HOTMAIL_PROVIDER || mail.provider === LUCKMAIL_PROVIDER || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
|
if (
|
||||||
|
mail.provider === HOTMAIL_PROVIDER
|
||||||
|
|| mail.provider === LUCKMAIL_PROVIDER
|
||||||
|
|| mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER
|
||||||
|
) {
|
||||||
await addLog(`步骤 8:正在通过 ${mail.label} 轮询验证码...`);
|
await addLog(`步骤 8:正在通过 ${mail.label} 轮询验证码...`);
|
||||||
} else {
|
} else {
|
||||||
await addLog(`步骤 8:正在打开${mail.label}...`);
|
await addLog(`步骤 8:正在打开${mail.label}...`);
|
||||||
@@ -102,10 +126,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await resolveVerificationStep(8, state, mail, {
|
await resolveVerificationStep(8, {
|
||||||
|
...state,
|
||||||
|
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||||||
|
}, mail, {
|
||||||
filterAfterTimestamp: stepStartedAt,
|
filterAfterTimestamp: stepStartedAt,
|
||||||
getRemainingTimeMs: getStep8RemainingTimeResolver(),
|
getRemainingTimeMs: getStep8RemainingTimeResolver(),
|
||||||
requestFreshCodeFirst: false,
|
requestFreshCodeFirst: false,
|
||||||
|
targetEmail: fixedTargetEmail,
|
||||||
resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
|
resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
|
||||||
? 0
|
? 0
|
||||||
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
const is2925Provider = state?.mailProvider === '2925';
|
const is2925Provider = state?.mailProvider === '2925';
|
||||||
if (step === 4) {
|
if (step === 4) {
|
||||||
return {
|
return {
|
||||||
filterAfterTimestamp: getHotmailVerificationRequestTimestamp(4, state),
|
filterAfterTimestamp: is2925Provider ? 0 : getHotmailVerificationRequestTimestamp(4, state),
|
||||||
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'duckduckgo', 'forward'],
|
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'duckduckgo', 'forward'],
|
||||||
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm'],
|
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm'],
|
||||||
targetEmail: state.email,
|
targetEmail: state.email,
|
||||||
@@ -124,10 +124,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filterAfterTimestamp: getHotmailVerificationRequestTimestamp(8, state),
|
filterAfterTimestamp: is2925Provider ? 0 : getHotmailVerificationRequestTimestamp(8, state),
|
||||||
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
|
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
|
||||||
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
|
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
|
||||||
targetEmail: state.email,
|
targetEmail: String(state?.step8VerificationTargetEmail || '').trim() || state.email,
|
||||||
maxAttempts: is2925Provider ? MAIL_2925_VERIFICATION_MAX_ATTEMPTS : 5,
|
maxAttempts: is2925Provider ? MAIL_2925_VERIFICATION_MAX_ATTEMPTS : 5,
|
||||||
intervalMs: is2925Provider ? MAIL_2925_VERIFICATION_INTERVAL_MS : 3000,
|
intervalMs: is2925Provider ? MAIL_2925_VERIFICATION_INTERVAL_MS : 3000,
|
||||||
...overrides,
|
...overrides,
|
||||||
@@ -236,6 +236,33 @@
|
|||||||
return requestedAt;
|
return requestedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function triggerPostSuccessMailboxCleanup(step, mail) {
|
||||||
|
if (mail?.provider !== '2925') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.resolve().then(async () => {
|
||||||
|
try {
|
||||||
|
await sendToMailContentScriptResilient(
|
||||||
|
mail,
|
||||||
|
{
|
||||||
|
type: 'DELETE_ALL_EMAILS',
|
||||||
|
step,
|
||||||
|
source: 'background',
|
||||||
|
payload: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeoutMs: 10000,
|
||||||
|
responseTimeoutMs: 5000,
|
||||||
|
maxRecoveryAttempts: 1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
// Best-effort cleanup only.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides = {}) {
|
async function pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides = {}) {
|
||||||
const stateKey = getVerificationCodeStateKey(step);
|
const stateKey = getVerificationCodeStateKey(step);
|
||||||
const rejectedCodes = new Set();
|
const rejectedCodes = new Set();
|
||||||
@@ -656,6 +683,7 @@
|
|||||||
emailTimestamp: result.emailTimestamp,
|
emailTimestamp: result.emailTimestamp,
|
||||||
code: result.code,
|
code: result.code,
|
||||||
});
|
});
|
||||||
|
triggerPostSuccessMailboxCleanup(step, mail);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+259
-148
@@ -1,4 +1,4 @@
|
|||||||
// content/mail-2925.js — Content script for 2925 Mail (steps 4, 7)
|
// content/mail-2925.js - Content script for 2925 Mail (steps 4, 8)
|
||||||
// Injected dynamically on: 2925.com
|
// Injected dynamically on: 2925.com
|
||||||
|
|
||||||
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
|
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
|
||||||
@@ -37,19 +37,28 @@ async function persistSeenCodes() {
|
|||||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
if (message.type === 'POLL_EMAIL') {
|
if (message.type === 'POLL_EMAIL') {
|
||||||
resetStopState();
|
resetStopState();
|
||||||
handlePollEmail(message.step, message.payload).then(result => {
|
handlePollEmail(message.step, message.payload).then((result) => {
|
||||||
sendResponse(result);
|
sendResponse(result);
|
||||||
}).catch(err => {
|
}).catch((err) => {
|
||||||
if (isStopError(err)) {
|
if (isStopError(err)) {
|
||||||
log(`步骤 ${message.step}:已被用户停止。`, 'warn');
|
log(`步骤 ${message.step}:已被用户停止。`, 'warn');
|
||||||
sendResponse({ stopped: true, error: err.message });
|
sendResponse({ stopped: true, error: err.message });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`步骤 ${message.step}:邮箱轮询失败:${err.message}`, 'warn');
|
log(`步骤 ${message.step}:邮箱轮询失败:${err.message}`, 'warn');
|
||||||
sendResponse({ error: err.message });
|
sendResponse({ error: err.message });
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.type === 'DELETE_ALL_EMAILS') {
|
||||||
|
Promise.resolve(deleteAllMailboxEmails(message.step)).catch(() => {});
|
||||||
|
sendResponse({ ok: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const MAIL_ITEM_SELECTORS = [
|
const MAIL_ITEM_SELECTORS = [
|
||||||
@@ -64,13 +73,67 @@ const MAIL_ITEM_SELECTORS = [
|
|||||||
'[class*="list-item"]',
|
'[class*="list-item"]',
|
||||||
'li[class*="mail"]',
|
'li[class*="mail"]',
|
||||||
];
|
];
|
||||||
const MAIL_REFRESH_SELECTOR = '[class*="refresh"], [title*="鍒锋柊"], [aria-label*="鍒锋柊"], [class*="Refresh"]';
|
const MAIL_ITEM_SELECTOR_GROUP = MAIL_ITEM_SELECTORS.join(', ');
|
||||||
|
const MAIL_REFRESH_SELECTORS = [
|
||||||
|
'[class*="refresh"]',
|
||||||
|
'[title*="刷新"]',
|
||||||
|
'[aria-label*="刷新"]',
|
||||||
|
'[class*="Refresh"]',
|
||||||
|
];
|
||||||
const MAIL_INBOX_SELECTORS = [
|
const MAIL_INBOX_SELECTORS = [
|
||||||
'a[href*="mailList"]',
|
'a[href*="mailList"]',
|
||||||
'[class*="inbox"]',
|
'[class*="inbox"]',
|
||||||
'[class*="Inbox"]',
|
'[class*="Inbox"]',
|
||||||
'[title*="鏀朵欢绠?]',
|
'[title*="收件箱"]',
|
||||||
];
|
];
|
||||||
|
const MAIL_DELETE_SELECTORS = [
|
||||||
|
'[class*="delete"]',
|
||||||
|
'[title*="删除"]',
|
||||||
|
'[aria-label*="删除"]',
|
||||||
|
'[class*="Delete"]',
|
||||||
|
];
|
||||||
|
const MAIL_SELECT_ALL_SELECTORS = [
|
||||||
|
'input[type="checkbox"]',
|
||||||
|
'[role="checkbox"]',
|
||||||
|
'.el-checkbox__input',
|
||||||
|
'.el-checkbox',
|
||||||
|
'label[class*="checkbox"]',
|
||||||
|
'[class*="checkbox"]',
|
||||||
|
];
|
||||||
|
const MAIL_ACTION_CANDIDATE_SELECTORS = 'button, [role="button"], a, label, span, div';
|
||||||
|
|
||||||
|
function normalizeNodeText(value) {
|
||||||
|
return String(value || '').replace(/\s+/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVisibleNode(node) {
|
||||||
|
if (!node) return false;
|
||||||
|
if (node.hidden) return false;
|
||||||
|
|
||||||
|
const style = typeof window.getComputedStyle === 'function'
|
||||||
|
? window.getComputedStyle(node)
|
||||||
|
: null;
|
||||||
|
if (style && (style.display === 'none' || style.visibility === 'hidden')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = typeof node.getBoundingClientRect === 'function'
|
||||||
|
? node.getBoundingClientRect()
|
||||||
|
: null;
|
||||||
|
if (rect && rect.width <= 0 && rect.height <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMailItemNode(node) {
|
||||||
|
return Boolean(node?.closest?.(MAIL_ITEM_SELECTOR_GROUP));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveActionTarget(node) {
|
||||||
|
return node?.closest?.('button, [role="button"], a, label, .el-checkbox, .el-checkbox__input') || node || null;
|
||||||
|
}
|
||||||
|
|
||||||
function findMailItems() {
|
function findMailItems() {
|
||||||
for (const selector of MAIL_ITEM_SELECTORS) {
|
for (const selector of MAIL_ITEM_SELECTORS) {
|
||||||
@@ -82,20 +145,81 @@ function findMailItems() {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function findRefreshButton() {
|
function findActionBySelectors(selectors = []) {
|
||||||
return document.querySelector(MAIL_REFRESH_SELECTOR);
|
for (const selector of selectors) {
|
||||||
}
|
const candidates = document.querySelectorAll(selector);
|
||||||
|
for (const candidate of candidates) {
|
||||||
function findInboxLink() {
|
const target = resolveActionTarget(candidate);
|
||||||
for (const selector of MAIL_INBOX_SELECTORS) {
|
if (!isVisibleNode(target) || isMailItemNode(target)) {
|
||||||
const node = document.querySelector(selector);
|
continue;
|
||||||
if (node) {
|
}
|
||||||
return node;
|
return target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findToolbarActionButton(patterns = [], selectors = []) {
|
||||||
|
const directMatch = findActionBySelectors(selectors);
|
||||||
|
if (directMatch) {
|
||||||
|
return directMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = document.querySelectorAll(MAIL_ACTION_CANDIDATE_SELECTORS);
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const target = resolveActionTarget(candidate);
|
||||||
|
if (!isVisibleNode(target) || isMailItemNode(target)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = normalizeNodeText(target.innerText || target.textContent || '');
|
||||||
|
const label = normalizeNodeText(target.getAttribute?.('aria-label') || target.getAttribute?.('title') || '');
|
||||||
|
if (patterns.some((pattern) => pattern.test(text) || pattern.test(label))) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRefreshButton() {
|
||||||
|
return findToolbarActionButton([
|
||||||
|
/刷新/i,
|
||||||
|
/refresh/i,
|
||||||
|
], MAIL_REFRESH_SELECTORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findInboxLink() {
|
||||||
|
return findActionBySelectors(MAIL_INBOX_SELECTORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findDeleteButton() {
|
||||||
|
return findToolbarActionButton([
|
||||||
|
/删除/i,
|
||||||
|
/delete/i,
|
||||||
|
], MAIL_DELETE_SELECTORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSelectAllControl() {
|
||||||
|
return findActionBySelectors(MAIL_SELECT_ALL_SELECTORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCheckboxChecked(node) {
|
||||||
|
const checkbox = node?.matches?.('input[type="checkbox"], [role="checkbox"]')
|
||||||
|
? node
|
||||||
|
: node?.querySelector?.('input[type="checkbox"], [role="checkbox"]');
|
||||||
|
if (checkbox?.checked === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (String(checkbox?.getAttribute?.('aria-checked') || '').toLowerCase() === 'true') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Boolean(
|
||||||
|
node?.classList?.contains('is-checked')
|
||||||
|
|| node?.classList?.contains('checked')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getMailItemText(item) {
|
function getMailItemText(item) {
|
||||||
if (!item) return '';
|
if (!item) return '';
|
||||||
const contentCell = item.querySelector('td.content, .content, .mail-content');
|
const contentCell = item.querySelector('td.content, .content, .mail-content');
|
||||||
@@ -112,11 +236,11 @@ function getMailItemText(item) {
|
|||||||
|
|
||||||
function getMailItemTimeText(item) {
|
function getMailItemTimeText(item) {
|
||||||
const timeEl = item?.querySelector('.date-time-text, [class*="date-time"], [class*="time"], td.time');
|
const timeEl = item?.querySelector('.date-time-text, [class*="date-time"], [class*="time"], td.time');
|
||||||
return (timeEl?.textContent || '').replace(/\s+/g, ' ').trim();
|
return normalizeNodeText(timeEl?.textContent || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeMailIdentityPart(value) {
|
function normalizeMailIdentityPart(value) {
|
||||||
return (value || '').replace(/\s+/g, ' ').trim().toLowerCase();
|
return normalizeNodeText(value).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMailItemId(item, index = 0) {
|
function getMailItemId(item, index = 0) {
|
||||||
@@ -148,82 +272,36 @@ function getCurrentMailIds(items = []) {
|
|||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeMinuteTimestamp(timestamp) {
|
|
||||||
if (!Number.isFinite(timestamp) || timestamp <= 0) return 0;
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
date.setSeconds(0, 0);
|
|
||||||
return date.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesMailFilters(text, senderFilters, subjectFilters) {
|
function matchesMailFilters(text, senderFilters, subjectFilters) {
|
||||||
const lower = (text || '').toLowerCase();
|
const lower = String(text || '').toLowerCase();
|
||||||
const senderMatch = senderFilters.some(filter => lower.includes(filter.toLowerCase()));
|
const senderMatch = senderFilters.some((filter) => lower.includes(String(filter || '').toLowerCase()));
|
||||||
const subjectMatch = subjectFilters.some(filter => lower.includes(filter.toLowerCase()));
|
const subjectMatch = subjectFilters.some((filter) => lower.includes(String(filter || '').toLowerCase()));
|
||||||
return senderMatch || subjectMatch;
|
return senderMatch || subjectMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
|
function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
|
||||||
if (strictChatGPTCodeOnly) {
|
if (strictChatGPTCodeOnly) {
|
||||||
const strictMatch = text.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
const strictMatch = String(text || '').match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
||||||
return strictMatch ? strictMatch[1] : null;
|
return strictMatch ? strictMatch[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
const normalized = String(text || '');
|
||||||
|
|
||||||
|
const matchCn = normalized.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||||
if (matchCn) return matchCn[1];
|
if (matchCn) return matchCn[1];
|
||||||
|
|
||||||
const matchChatGPT = text.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
const matchChatGPT = normalized.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
||||||
if (matchChatGPT) return matchChatGPT[1];
|
if (matchChatGPT) return matchChatGPT[1];
|
||||||
|
|
||||||
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
const matchEn = normalized.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||||
if (matchEn) return matchEn[1] || matchEn[2];
|
if (matchEn) return matchEn[1] || matchEn[2];
|
||||||
|
|
||||||
const match6 = text.match(/\b(\d{6})\b/);
|
const match6 = normalized.match(/\b(\d{6})\b/);
|
||||||
if (match6) return match6[1];
|
if (match6) return match6[1];
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractEmails(text) {
|
|
||||||
const matches = text.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig) || [];
|
|
||||||
return [...new Set(matches.map(item => item.toLowerCase()))];
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailMatchesTarget(candidate, targetEmail) {
|
|
||||||
const normalizedCandidate = String(candidate || '').trim().toLowerCase();
|
|
||||||
const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
|
|
||||||
return Boolean(normalizedCandidate && normalizedTarget && normalizedCandidate === normalizedTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTargetEmailMatchState(text, targetEmail) {
|
|
||||||
const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
|
|
||||||
if (!normalizedTarget) {
|
|
||||||
return { matches: true, hasExplicitEmail: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedText = String(text || '').toLowerCase();
|
|
||||||
if (normalizedText.includes(normalizedTarget)) {
|
|
||||||
return { matches: true, hasExplicitEmail: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const atIndex = normalizedTarget.indexOf('@');
|
|
||||||
if (atIndex > 0) {
|
|
||||||
const encodedTarget = `${normalizedTarget.slice(0, atIndex)}=${normalizedTarget.slice(atIndex + 1)}`;
|
|
||||||
if (normalizedText.includes(encodedTarget)) {
|
|
||||||
return { matches: true, hasExplicitEmail: true };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const emails = extractEmails(text);
|
|
||||||
if (!emails.length) {
|
|
||||||
return { matches: false, hasExplicitEmail: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
matches: emails.some(email => emailMatchesTarget(email, normalizedTarget)),
|
|
||||||
hasExplicitEmail: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseMailItemTimestamp(item) {
|
function parseMailItemTimestamp(item) {
|
||||||
const timeText = getMailItemTimeText(item);
|
const timeText = getMailItemTimeText(item);
|
||||||
if (!timeText) return null;
|
if (!timeText) return null;
|
||||||
@@ -236,7 +314,7 @@ function parseMailItemTimestamp(item) {
|
|||||||
return now.getTime();
|
return now.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
match = timeText.match(/(\d+)\s*分(?:钟)?前/);
|
match = timeText.match(/(\d+)\s*分钟前/);
|
||||||
if (match) {
|
if (match) {
|
||||||
return now.getTime() - Number(match[1]) * 60 * 1000;
|
return now.getTime() - Number(match[1]) * 60 * 1000;
|
||||||
}
|
}
|
||||||
@@ -324,19 +402,70 @@ async function openMailAndGetMessageText(item) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteCurrentMailboxEmail(step) {
|
||||||
|
try {
|
||||||
|
const deleteButton = findDeleteButton();
|
||||||
|
if (!deleteButton) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
simulateClick(deleteButton);
|
||||||
|
await sleepRandom(200, 500);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(MAIL2925_PREFIX, `Step ${step}: delete-current cleanup failed:`, err?.message || err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openMailAndDeleteAfterRead(item, step) {
|
||||||
|
simulateClick(item);
|
||||||
|
try {
|
||||||
|
await sleepRandom(1200, 2200);
|
||||||
|
return document.body?.textContent || '';
|
||||||
|
} finally {
|
||||||
|
await deleteCurrentMailboxEmail(step);
|
||||||
|
await returnToInbox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAllMailboxEmails(step) {
|
||||||
|
try {
|
||||||
|
await returnToInbox();
|
||||||
|
|
||||||
|
const selectAllControl = findSelectAllControl();
|
||||||
|
if (!selectAllControl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCheckboxChecked(selectAllControl)) {
|
||||||
|
simulateClick(selectAllControl);
|
||||||
|
await sleepRandom(200, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteButton = findDeleteButton();
|
||||||
|
if (!deleteButton) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
simulateClick(deleteButton);
|
||||||
|
await sleepRandom(200, 500);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(MAIL2925_PREFIX, `Step ${step}: delete-all cleanup failed:`, err?.message || err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshInbox() {
|
async function refreshInbox() {
|
||||||
const refreshBtn = document.querySelector(
|
const refreshBtn = findRefreshButton();
|
||||||
'[class*="refresh"], [title*="刷新"], [aria-label*="刷新"], [class*="Refresh"]'
|
|
||||||
);
|
|
||||||
if (refreshBtn) {
|
if (refreshBtn) {
|
||||||
simulateClick(refreshBtn);
|
simulateClick(refreshBtn);
|
||||||
await sleepRandom(700, 1200);
|
await sleepRandom(700, 1200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inboxLink = document.querySelector(
|
const inboxLink = findInboxLink();
|
||||||
'a[href*="mailList"], [class*="inbox"], [class*="Inbox"], [title*="收件箱"]'
|
|
||||||
);
|
|
||||||
if (inboxLink) {
|
if (inboxLink) {
|
||||||
simulateClick(inboxLink);
|
simulateClick(inboxLink);
|
||||||
await sleepRandom(700, 1200);
|
await sleepRandom(700, 1200);
|
||||||
@@ -349,27 +478,26 @@ async function handlePollEmail(step, payload) {
|
|||||||
subjectFilters,
|
subjectFilters,
|
||||||
maxAttempts,
|
maxAttempts,
|
||||||
intervalMs,
|
intervalMs,
|
||||||
filterAfterTimestamp = 0,
|
|
||||||
excludeCodes = [],
|
excludeCodes = [],
|
||||||
strictChatGPTCodeOnly = false,
|
strictChatGPTCodeOnly = false,
|
||||||
targetEmail = '',
|
} = payload || {};
|
||||||
} = payload;
|
|
||||||
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||||
const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0);
|
|
||||||
|
|
||||||
log(`步骤 ${step}:开始轮询 2925 邮箱(最多 ${maxAttempts} 次)`);
|
log(`步骤 ${step}:开始轮询 2925 邮箱(最多 ${maxAttempts} 次)`);
|
||||||
if (filterAfterMinute) {
|
|
||||||
log(`步骤 ${step}:仅尝试 ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} 及之后时间的邮件。`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let initialItems = [];
|
let initialItems = [];
|
||||||
for (let i = 0; i < 20; i++) {
|
let initialLoadUsedRefresh = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < 20; i += 1) {
|
||||||
initialItems = findMailItems();
|
initialItems = findMailItems();
|
||||||
if (initialItems.length > 0) break;
|
if (initialItems.length > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initialItems.length === 0) {
|
if (initialItems.length === 0) {
|
||||||
|
initialLoadUsedRefresh = true;
|
||||||
await returnToInbox();
|
await returnToInbox();
|
||||||
await refreshInbox();
|
await refreshInbox();
|
||||||
await sleep(2000);
|
await sleep(2000);
|
||||||
@@ -380,16 +508,16 @@ async function handlePollEmail(step, payload) {
|
|||||||
throw new Error('2925 邮箱列表未加载完成,请确认当前已打开收件箱。');
|
throw new Error('2925 邮箱列表未加载完成,请确认当前已打开收件箱。');
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingMailIds = getCurrentMailIds(initialItems);
|
const knownMailIds = getCurrentMailIds(initialItems);
|
||||||
log(`步骤 ${step}:邮件列表已加载,共 ${initialItems.length} 封邮件`);
|
log(`步骤 ${step}:邮件列表已加载,共 ${initialItems.length} 封邮件`);
|
||||||
log(`步骤 ${step}:已记录当前 ${existingMailIds.size} 封旧邮件快照`);
|
log(`步骤 ${step}:已记录当前 ${knownMailIds.size} 封旧邮件快照`);
|
||||||
|
|
||||||
const FALLBACK_AFTER = 3;
|
const FALLBACK_AFTER = 3;
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
log(`步骤 ${step}:正在轮询 2925 邮箱,第 ${attempt}/${maxAttempts} 次`);
|
log(`步骤 ${step}:正在轮询 2925 邮箱,第 ${attempt}/${maxAttempts} 次`);
|
||||||
|
|
||||||
if (attempt > 1) {
|
if (attempt > 1 || !initialLoadUsedRefresh) {
|
||||||
await returnToInbox();
|
await returnToInbox();
|
||||||
await refreshInbox();
|
await refreshInbox();
|
||||||
await sleepRandom(900, 1500);
|
await sleepRandom(900, 1500);
|
||||||
@@ -398,78 +526,61 @@ async function handlePollEmail(step, payload) {
|
|||||||
const items = findMailItems();
|
const items = findMailItems();
|
||||||
if (items.length > 0) {
|
if (items.length > 0) {
|
||||||
const useFallback = attempt > FALLBACK_AFTER;
|
const useFallback = attempt > FALLBACK_AFTER;
|
||||||
|
const newMailIds = new Set();
|
||||||
|
|
||||||
for (let index = 0; index < items.length; index++) {
|
items.forEach((item, index) => {
|
||||||
|
const itemId = getMailItemId(item, index);
|
||||||
|
if (!knownMailIds.has(itemId)) {
|
||||||
|
newMailIds.add(itemId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let index = 0; index < items.length; index += 1) {
|
||||||
const item = items[index];
|
const item = items[index];
|
||||||
const itemId = getMailItemId(item, index);
|
const itemId = getMailItemId(item, index);
|
||||||
|
const isNewMail = newMailIds.has(itemId);
|
||||||
const itemTimestamp = parseMailItemTimestamp(item);
|
const itemTimestamp = parseMailItemTimestamp(item);
|
||||||
const itemMinute = normalizeMinuteTimestamp(itemTimestamp || 0);
|
|
||||||
const passesTimeFilter = !filterAfterMinute || (itemMinute && itemMinute >= filterAfterMinute);
|
|
||||||
const shouldBypassOldSnapshot = Boolean(filterAfterMinute && passesTimeFilter && itemMinute > 0);
|
|
||||||
|
|
||||||
if (!passesTimeFilter) {
|
if (!useFallback && !isNewMail) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useFallback && !shouldBypassOldSnapshot && existingMailIds.has(itemId)) {
|
const previewText = getMailItemText(item);
|
||||||
|
if (!matchesMailFilters(previewText, senderFilters, subjectFilters)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = getMailItemText(item);
|
const previewCode = extractVerificationCode(previewText, strictChatGPTCodeOnly);
|
||||||
if (!matchesMailFilters(text, senderFilters, subjectFilters)) {
|
const openedText = await openMailAndDeleteAfterRead(item, step);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const previewEmails = extractEmails(text);
|
|
||||||
const previewTargetState = getTargetEmailMatchState(text, targetEmail);
|
|
||||||
const previewMatchesTarget = previewTargetState.matches;
|
|
||||||
if (targetEmail && previewEmails.length > 0 && !previewMatchesTarget) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const code = extractVerificationCode(text, strictChatGPTCodeOnly);
|
|
||||||
if (code && previewMatchesTarget) {
|
|
||||||
if (excludedCodeSet.has(code)) {
|
|
||||||
log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (seenCodes.has(code)) {
|
|
||||||
log(`步骤 ${step}:跳过已处理过的验证码:${code}`, 'info');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seenCodes.add(code);
|
|
||||||
persistSeenCodes();
|
|
||||||
const source = useFallback && existingMailIds.has(itemId) ? '回退匹配邮件' : '新邮件';
|
|
||||||
const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : '';
|
|
||||||
log(`步骤 ${step}:已找到验证码:${code}(来源:${source}${timeLabel})`, 'ok');
|
|
||||||
await sleep(1000);
|
|
||||||
return { ok: true, code, emailTimestamp: Date.now() };
|
|
||||||
}
|
|
||||||
|
|
||||||
const openedText = await openMailAndGetMessageText(item);
|
|
||||||
const bodyCode = extractVerificationCode(openedText, strictChatGPTCodeOnly);
|
const bodyCode = extractVerificationCode(openedText, strictChatGPTCodeOnly);
|
||||||
const openedTargetState = getTargetEmailMatchState(openedText, targetEmail);
|
const candidateCode = bodyCode || previewCode;
|
||||||
if (targetEmail && openedTargetState.hasExplicitEmail && !openedTargetState.matches) {
|
|
||||||
|
if (!candidateCode) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (bodyCode) {
|
|
||||||
if (excludedCodeSet.has(bodyCode)) {
|
if (excludedCodeSet.has(candidateCode)) {
|
||||||
log(`步骤 ${step}:跳过排除的验证码:${bodyCode}`, 'info');
|
log(`步骤 ${step}:跳过排除的验证码:${candidateCode}`, 'info');
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
if (seenCodes.has(bodyCode)) {
|
|
||||||
log(`步骤 ${step}:跳过已处理过的验证码:${bodyCode}`, 'info');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seenCodes.add(bodyCode);
|
|
||||||
persistSeenCodes();
|
|
||||||
const source = useFallback && existingMailIds.has(itemId) ? '回退匹配邮件正文' : '新邮件正文';
|
|
||||||
const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : '';
|
|
||||||
log(`步骤 ${step}:已在邮件正文中找到验证码:${bodyCode}(来源:${source}${timeLabel})`, 'ok');
|
|
||||||
await sleep(1000);
|
|
||||||
return { ok: true, code: bodyCode, emailTimestamp: Date.now() };
|
|
||||||
}
|
}
|
||||||
|
if (seenCodes.has(candidateCode)) {
|
||||||
|
log(`步骤 ${step}:跳过已处理过的验证码:${candidateCode}`, 'info');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
seenCodes.add(candidateCode);
|
||||||
|
persistSeenCodes();
|
||||||
|
const source = useFallback && !isNewMail
|
||||||
|
? (bodyCode ? '回退匹配邮件正文' : '回退匹配邮件')
|
||||||
|
: (bodyCode ? '新邮件正文' : '新邮件');
|
||||||
|
const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : '';
|
||||||
|
log(`步骤 ${step}:已找到验证码:${candidateCode}(来源:${source}${timeLabel})`, 'ok');
|
||||||
|
return { ok: true, code: candidateCode, emailTimestamp: Date.now() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items.forEach((item, index) => {
|
||||||
|
knownMailIds.add(getMailItemId(item, index));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attempt === FALLBACK_AFTER + 1) {
|
if (attempt === FALLBACK_AFTER + 1) {
|
||||||
|
|||||||
@@ -695,6 +695,12 @@ function getPageTextSnapshot() {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLoginVerificationDisplayedEmail() {
|
||||||
|
const pageText = getPageTextSnapshot();
|
||||||
|
const matches = pageText.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig) || [];
|
||||||
|
return matches[0] ? String(matches[0]).trim().toLowerCase() : '';
|
||||||
|
}
|
||||||
|
|
||||||
function getOAuthConsentForm() {
|
function getOAuthConsentForm() {
|
||||||
return document.querySelector(OAUTH_CONSENT_FORM_SELECTOR);
|
return document.querySelector(OAUTH_CONSENT_FORM_SELECTOR);
|
||||||
}
|
}
|
||||||
@@ -1076,6 +1082,7 @@ function inspectLoginAuthState() {
|
|||||||
state: 'unknown',
|
state: 'unknown',
|
||||||
url: location.href,
|
url: location.href,
|
||||||
path: location.pathname || '',
|
path: location.pathname || '',
|
||||||
|
displayedEmail: getLoginVerificationDisplayedEmail(),
|
||||||
retryButton: retryState?.retryButton || null,
|
retryButton: retryState?.retryButton || null,
|
||||||
retryEnabled: Boolean(retryState?.retryEnabled),
|
retryEnabled: Boolean(retryState?.retryEnabled),
|
||||||
titleMatched: Boolean(retryState?.titleMatched),
|
titleMatched: Boolean(retryState?.titleMatched),
|
||||||
@@ -1141,6 +1148,7 @@ function serializeLoginAuthState(snapshot) {
|
|||||||
state: snapshot?.state || 'unknown',
|
state: snapshot?.state || 'unknown',
|
||||||
url: snapshot?.url || location.href,
|
url: snapshot?.url || location.href,
|
||||||
path: snapshot?.path || location.pathname || '',
|
path: snapshot?.path || location.pathname || '',
|
||||||
|
displayedEmail: snapshot?.displayedEmail || '',
|
||||||
retryEnabled: Boolean(snapshot?.retryEnabled),
|
retryEnabled: Boolean(snapshot?.retryEnabled),
|
||||||
titleMatched: Boolean(snapshot?.titleMatched),
|
titleMatched: Boolean(snapshot?.titleMatched),
|
||||||
detailMatched: Boolean(snapshot?.detailMatched),
|
detailMatched: Boolean(snapshot?.detailMatched),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
|||||||
executeStep7: 0,
|
executeStep7: 0,
|
||||||
sleep: [],
|
sleep: [],
|
||||||
resolveOptions: null,
|
resolveOptions: null,
|
||||||
|
setStates: [],
|
||||||
};
|
};
|
||||||
const realDateNow = Date.now;
|
const realDateNow = Date.now;
|
||||||
Date.now = () => 123456;
|
Date.now = () => 123456;
|
||||||
@@ -29,7 +30,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
|||||||
ensureStep8VerificationPageReady: async (options) => {
|
ensureStep8VerificationPageReady: async (options) => {
|
||||||
calls.ensureReady += 1;
|
calls.ensureReady += 1;
|
||||||
calls.ensureReadyOptions.push(options || null);
|
calls.ensureReadyOptions.push(options || null);
|
||||||
return { state: 'verification_page' };
|
return { state: 'verification_page', displayedEmail: 'display.user@example.com' };
|
||||||
},
|
},
|
||||||
executeStep7: async () => {
|
executeStep7: async () => {
|
||||||
calls.executeStep7 += 1;
|
calls.executeStep7 += 1;
|
||||||
@@ -53,7 +54,9 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
|||||||
calls.resolveOptions = options;
|
calls.resolveOptions = options;
|
||||||
},
|
},
|
||||||
reuseOrCreateTab: async () => {},
|
reuseOrCreateTab: async () => {},
|
||||||
setState: async () => {},
|
setState: async (payload) => {
|
||||||
|
calls.setStates.push(payload);
|
||||||
|
},
|
||||||
setStepStatus: async () => {},
|
setStepStatus: async () => {},
|
||||||
shouldUseCustomRegistrationEmail: () => false,
|
shouldUseCustomRegistrationEmail: () => false,
|
||||||
sleepWithStop: async (ms) => {
|
sleepWithStop: async (ms) => {
|
||||||
@@ -82,6 +85,10 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
|||||||
assert.equal(typeof calls.resolveOptions.getRemainingTimeMs, 'function');
|
assert.equal(typeof calls.resolveOptions.getRemainingTimeMs, 'function');
|
||||||
assert.equal(await calls.resolveOptions.getRemainingTimeMs({ actionLabel: '登录验证码流程' }), 5000);
|
assert.equal(await calls.resolveOptions.getRemainingTimeMs({ actionLabel: '登录验证码流程' }), 5000);
|
||||||
assert.equal(calls.resolveOptions.resendIntervalMs, 25000);
|
assert.equal(calls.resolveOptions.resendIntervalMs, 25000);
|
||||||
|
assert.equal(calls.resolveOptions.targetEmail, 'display.user@example.com');
|
||||||
|
assert.deepStrictEqual(calls.setStates, [
|
||||||
|
{ step8VerificationTargetEmail: 'display.user@example.com' },
|
||||||
|
]);
|
||||||
assert.deepStrictEqual(calls.ensureReadyOptions, [
|
assert.deepStrictEqual(calls.ensureReadyOptions, [
|
||||||
{ timeoutMs: 5000 },
|
{ timeoutMs: 5000 },
|
||||||
]);
|
]);
|
||||||
@@ -136,10 +143,62 @@ test('step 8 disables resend interval for 2925 mailbox polling', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(capturedOptions.resendIntervalMs, 0);
|
assert.equal(capturedOptions.resendIntervalMs, 0);
|
||||||
|
assert.equal(capturedOptions.targetEmail, '');
|
||||||
assert.equal(capturedOptions.beforeSubmit, undefined);
|
assert.equal(capturedOptions.beforeSubmit, undefined);
|
||||||
assert.equal(typeof capturedOptions.getRemainingTimeMs, 'function');
|
assert.equal(typeof capturedOptions.getRemainingTimeMs, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('step 8 falls back to the run email when the verification page does not expose a displayed email', async () => {
|
||||||
|
let capturedOptions = null;
|
||||||
|
|
||||||
|
const executor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
|
ensureStep8VerificationPageReady: async () => ({ state: 'verification_page', displayedEmail: '' }),
|
||||||
|
executeStep7: async () => {},
|
||||||
|
getOAuthFlowRemainingMs: async () => 8000,
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => Math.min(defaultTimeoutMs, 8000),
|
||||||
|
getMailConfig: () => ({
|
||||||
|
provider: 'qq',
|
||||||
|
label: 'QQ 邮箱',
|
||||||
|
source: 'mail-qq',
|
||||||
|
url: 'https://mail.qq.com',
|
||||||
|
navigateOnReuse: false,
|
||||||
|
}),
|
||||||
|
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||||
|
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
isVerificationMailPollingError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
resolveVerificationStep: async (_step, _state, _mail, options) => {
|
||||||
|
capturedOptions = options;
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => {},
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
shouldUseCustomRegistrationEmail: () => false,
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||||
|
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 8,
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeStep8({
|
||||||
|
email: 'user@example.com',
|
||||||
|
password: 'secret',
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(capturedOptions.targetEmail, 'user@example.com');
|
||||||
|
});
|
||||||
|
|
||||||
test('step 8 does not rerun step 7 when verification submit lands on add-phone', async () => {
|
test('step 8 does not rerun step 7 when verification submit lands on add-phone', async () => {
|
||||||
const calls = {
|
const calls = {
|
||||||
executeStep7: 0,
|
executeStep7: 0,
|
||||||
|
|||||||
+245
-49
@@ -51,37 +51,38 @@ function extractFunction(name) {
|
|||||||
return source.slice(start, end);
|
return source.slice(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
test('handlePollEmail returns to inbox before initial refresh when 2925 opens on a detail page', async () => {
|
test('handlePollEmail establishes a baseline after opening from detail view and only picks mail from a later refresh', async () => {
|
||||||
const bundle = extractFunction('handlePollEmail');
|
const bundle = extractFunction('handlePollEmail');
|
||||||
|
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
let detailMode = true;
|
let state = 'detail';
|
||||||
|
let refreshCalls = 0;
|
||||||
const clickOrder = [];
|
const clickOrder = [];
|
||||||
|
const readAndDeleteCalls = [];
|
||||||
const seenCodes = new Set();
|
const seenCodes = new Set();
|
||||||
const mailItem = { text: 'OpenAI verification code 654321' };
|
const baselineMail = { id: 'baseline', text: 'OpenAI newsletter without code' };
|
||||||
|
const newMail = { id: 'new', text: 'OpenAI verification code 654321' };
|
||||||
|
|
||||||
function findMailItems() {
|
function findMailItems() {
|
||||||
return detailMode ? [] : [mailItem];
|
if (state === 'detail') return [];
|
||||||
|
if (state === 'baseline') return [baselineMail];
|
||||||
|
return [baselineMail, newMail];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMailItemId() {
|
function getMailItemId(item) {
|
||||||
return 'mail-1';
|
return item.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentMailIds(items = []) {
|
function getCurrentMailIds(items = []) {
|
||||||
return new Set(items.map(() => 'mail-1'));
|
return new Set(items.map((item) => item.id));
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeMinuteTimestamp(value) {
|
|
||||||
return Number(value) || 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseMailItemTimestamp() {
|
function parseMailItemTimestamp() {
|
||||||
return Date.now();
|
return Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchesMailFilters() {
|
function matchesMailFilters(text) {
|
||||||
return true;
|
return /openai|verification/i.test(String(text || ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMailItemText(item) {
|
function getMailItemText(item) {
|
||||||
@@ -93,29 +94,26 @@ function extractVerificationCode(text) {
|
|||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractEmails() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailMatchesTarget() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTargetEmailMatchState() {
|
|
||||||
return { matches: true, hasExplicitEmail: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sleep() {}
|
async function sleep() {}
|
||||||
async function sleepRandom() {}
|
async function sleepRandom() {}
|
||||||
|
|
||||||
async function returnToInbox() {
|
async function returnToInbox() {
|
||||||
clickOrder.push('inbox');
|
clickOrder.push('inbox');
|
||||||
detailMode = false;
|
state = 'baseline';
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshInbox() {
|
async function refreshInbox() {
|
||||||
clickOrder.push('refresh');
|
clickOrder.push('refresh');
|
||||||
|
refreshCalls += 1;
|
||||||
|
if (refreshCalls >= 2) {
|
||||||
|
state = 'with-new';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openMailAndDeleteAfterRead(item) {
|
||||||
|
readAndDeleteCalls.push(item.id);
|
||||||
|
return item.id === 'new' ? 'Your ChatGPT code is 654321' : 'No code here';
|
||||||
}
|
}
|
||||||
|
|
||||||
function persistSeenCodes() {}
|
function persistSeenCodes() {}
|
||||||
@@ -128,38 +126,113 @@ return {
|
|||||||
getClickOrder() {
|
getClickOrder() {
|
||||||
return clickOrder.slice();
|
return clickOrder.slice();
|
||||||
},
|
},
|
||||||
|
getReadAndDeleteCalls() {
|
||||||
|
return readAndDeleteCalls.slice();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
`)();
|
`)();
|
||||||
|
|
||||||
const result = await api.handlePollEmail(4, {
|
const result = await api.handlePollEmail(4, {
|
||||||
senderFilters: ['openai'],
|
senderFilters: ['openai'],
|
||||||
subjectFilters: ['verification'],
|
subjectFilters: ['verification'],
|
||||||
maxAttempts: 1,
|
maxAttempts: 2,
|
||||||
intervalMs: 1,
|
intervalMs: 1,
|
||||||
filterAfterTimestamp: Date.now(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.code, '654321');
|
assert.equal(result.code, '654321');
|
||||||
assert.deepEqual(api.getClickOrder(), ['inbox', 'refresh']);
|
assert.deepEqual(api.getClickOrder(), ['inbox', 'refresh', 'inbox', 'refresh']);
|
||||||
|
assert.deepEqual(api.getReadAndDeleteCalls(), ['new']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handlePollEmail ignores targetEmail and still tests any matching ChatGPT mail', async () => {
|
||||||
|
const bundle = extractFunction('handlePollEmail');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
let state = 'empty';
|
||||||
|
const seenCodes = new Set();
|
||||||
|
const readAndDeleteCalls = [];
|
||||||
|
const matchingMail = {
|
||||||
|
id: 'mail-1',
|
||||||
|
text: 'ChatGPT verification code 112233 for another.user@example.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
function findMailItems() {
|
||||||
|
return state === 'ready' ? [matchingMail] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMailItemId(item) {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMailIds(items = []) {
|
||||||
|
return new Set(items.map((item) => item.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMailItemTimestamp() {
|
||||||
|
return Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesMailFilters(text) {
|
||||||
|
return /chatgpt|openai|verification/i.test(String(text || ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMailItemText(item) {
|
||||||
|
return item.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractVerificationCode(text) {
|
||||||
|
const match = String(text || '').match(/(\\d{6})/);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sleep() {}
|
||||||
|
async function sleepRandom() {}
|
||||||
|
async function returnToInbox() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
async function refreshInbox() {
|
||||||
|
state = 'ready';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openMailAndDeleteAfterRead(item) {
|
||||||
|
readAndDeleteCalls.push(item.id);
|
||||||
|
return item.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistSeenCodes() {}
|
||||||
|
function log() {}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
handlePollEmail,
|
||||||
|
getReadAndDeleteCalls() {
|
||||||
|
return readAndDeleteCalls.slice();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const result = await api.handlePollEmail(8, {
|
||||||
|
senderFilters: ['chatgpt'],
|
||||||
|
subjectFilters: ['verification'],
|
||||||
|
maxAttempts: 4,
|
||||||
|
intervalMs: 1,
|
||||||
|
targetEmail: 'expected@example.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.code, '112233');
|
||||||
|
assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('openMailAndGetMessageText always returns to inbox after opening a 2925 message', async () => {
|
test('openMailAndGetMessageText always returns to inbox after opening a 2925 message', async () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('findInboxLink'),
|
|
||||||
extractFunction('returnToInbox'),
|
extractFunction('returnToInbox'),
|
||||||
extractFunction('openMailAndGetMessageText'),
|
extractFunction('openMailAndGetMessageText'),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
const MAIL_INBOX_SELECTORS = [
|
|
||||||
'a[href*="mailList"]',
|
|
||||||
'[class*="inbox"]',
|
|
||||||
'[class*="Inbox"]',
|
|
||||||
'[title*="鏀朵欢绠?]',
|
|
||||||
];
|
|
||||||
const clickOrder = [];
|
const clickOrder = [];
|
||||||
const mailItem = { kind: 'mail' };
|
const mailItem = { kind: 'mail' };
|
||||||
const inboxLink = { kind: 'inbox' };
|
|
||||||
let listVisible = true;
|
let listVisible = true;
|
||||||
let bodyText = '';
|
let bodyText = '';
|
||||||
|
|
||||||
@@ -169,18 +242,16 @@ const document = {
|
|||||||
return bodyText;
|
return bodyText;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
querySelector(selector) {
|
|
||||||
if (selector.includes('mailList') || selector.includes('inbox') || selector.includes('Inbox')) {
|
|
||||||
return inboxLink;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function findMailItems() {
|
function findMailItems() {
|
||||||
return listVisible ? [mailItem] : [];
|
return listVisible ? [mailItem] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findInboxLink() {
|
||||||
|
return { kind: 'inbox' };
|
||||||
|
}
|
||||||
|
|
||||||
function simulateClick(node) {
|
function simulateClick(node) {
|
||||||
if (node === mailItem) {
|
if (node === mailItem) {
|
||||||
clickOrder.push('mail');
|
clickOrder.push('mail');
|
||||||
@@ -188,12 +259,8 @@ function simulateClick(node) {
|
|||||||
bodyText = 'Your ChatGPT code is 731091';
|
bodyText = 'Your ChatGPT code is 731091';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (node === inboxLink) {
|
clickOrder.push('inbox');
|
||||||
clickOrder.push('inbox');
|
listVisible = true;
|
||||||
listVisible = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new Error('unexpected node');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sleep() {}
|
async function sleep() {}
|
||||||
@@ -219,3 +286,132 @@ return {
|
|||||||
assert.deepEqual(api.getClickOrder(), ['mail', 'inbox']);
|
assert.deepEqual(api.getClickOrder(), ['mail', 'inbox']);
|
||||||
assert.equal(api.isListVisible(), true);
|
assert.equal(api.isListVisible(), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('openMailAndDeleteAfterRead deletes the opened message before returning to inbox', async () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('deleteCurrentMailboxEmail'),
|
||||||
|
extractFunction('returnToInbox'),
|
||||||
|
extractFunction('openMailAndDeleteAfterRead'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const calls = [];
|
||||||
|
const mailItem = { kind: 'mail' };
|
||||||
|
const deleteButton = { kind: 'delete' };
|
||||||
|
let listVisible = true;
|
||||||
|
let bodyText = '';
|
||||||
|
|
||||||
|
const document = {
|
||||||
|
body: {
|
||||||
|
get textContent() {
|
||||||
|
return bodyText;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function findMailItems() {
|
||||||
|
return listVisible ? [mailItem] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function findDeleteButton() {
|
||||||
|
return deleteButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findInboxLink() {
|
||||||
|
return { kind: 'inbox' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function simulateClick(node) {
|
||||||
|
if (node === mailItem) {
|
||||||
|
calls.push('mail');
|
||||||
|
listVisible = false;
|
||||||
|
bodyText = 'Your ChatGPT code is 778899';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node === deleteButton) {
|
||||||
|
calls.push('delete');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
calls.push('inbox');
|
||||||
|
listVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sleep() {}
|
||||||
|
async function sleepRandom() {}
|
||||||
|
const console = { warn() {} };
|
||||||
|
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mailItem,
|
||||||
|
openMailAndDeleteAfterRead,
|
||||||
|
getCalls() {
|
||||||
|
return calls.slice();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const text = await api.openMailAndDeleteAfterRead(api.mailItem, 8);
|
||||||
|
|
||||||
|
assert.match(text, /778899/);
|
||||||
|
assert.deepEqual(api.getCalls(), ['mail', 'delete', 'inbox']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleteAllMailboxEmails selects all messages and clicks delete', async () => {
|
||||||
|
const bundle = extractFunction('deleteAllMailboxEmails');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const calls = [];
|
||||||
|
const selectAllControl = { kind: 'select-all' };
|
||||||
|
const deleteButton = { kind: 'delete' };
|
||||||
|
|
||||||
|
async function returnToInbox() {
|
||||||
|
calls.push('inbox');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSelectAllControl() {
|
||||||
|
return selectAllControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCheckboxChecked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findDeleteButton() {
|
||||||
|
return deleteButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
function simulateClick(node) {
|
||||||
|
if (node === selectAllControl) {
|
||||||
|
calls.push('select-all');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node === deleteButton) {
|
||||||
|
calls.push('delete');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error('unexpected node');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sleepRandom() {}
|
||||||
|
|
||||||
|
const console = { warn() {} };
|
||||||
|
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleteAllMailboxEmails,
|
||||||
|
getCalls() {
|
||||||
|
return calls.slice();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const result = await api.deleteAllMailboxEmails(4);
|
||||||
|
|
||||||
|
assert.equal(result, true);
|
||||||
|
assert.deepEqual(api.getCalls(), ['inbox', 'select-all', 'delete']);
|
||||||
|
});
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ function extractFunction(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bundle = [
|
const bundle = [
|
||||||
|
extractFunction('getPageTextSnapshot'),
|
||||||
|
extractFunction('getLoginVerificationDisplayedEmail'),
|
||||||
extractFunction('inspectLoginAuthState'),
|
extractFunction('inspectLoginAuthState'),
|
||||||
extractFunction('normalizeStep6Snapshot'),
|
extractFunction('normalizeStep6Snapshot'),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
@@ -62,6 +64,13 @@ const location = {
|
|||||||
pathname: ${JSON.stringify(overrides.pathname || '/log-in')},
|
pathname: ${JSON.stringify(overrides.pathname || '/log-in')},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const document = {
|
||||||
|
body: {
|
||||||
|
innerText: ${JSON.stringify(overrides.pageText || '')},
|
||||||
|
textContent: ${JSON.stringify(overrides.pageText || '')},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function getLoginTimeoutErrorPageState() {
|
function getLoginTimeoutErrorPageState() {
|
||||||
return ${JSON.stringify(overrides.retryState || null)};
|
return ${JSON.stringify(overrides.retryState || null)};
|
||||||
}
|
}
|
||||||
@@ -127,6 +136,16 @@ return {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const api = createApi({
|
||||||
|
verificationTarget: { id: 'otp' },
|
||||||
|
pageText: 'We emailed a code to display.user@example.com. Enter it below.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = api.inspectLoginAuthState();
|
||||||
|
assert.strictEqual(snapshot.displayedEmail, 'display.user@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const api = createApi({
|
const api = createApi({
|
||||||
oauthConsentPage: true,
|
oauthConsentPage: true,
|
||||||
|
|||||||
@@ -37,8 +37,10 @@ test('verification flow extends 2925 polling window', () => {
|
|||||||
const step4Payload = helpers.getVerificationPollPayload(4, { email: 'user@example.com', mailProvider: '2925' });
|
const step4Payload = helpers.getVerificationPollPayload(4, { email: 'user@example.com', mailProvider: '2925' });
|
||||||
const step8Payload = helpers.getVerificationPollPayload(8, { email: 'user@example.com', mailProvider: '2925' });
|
const step8Payload = helpers.getVerificationPollPayload(8, { email: 'user@example.com', mailProvider: '2925' });
|
||||||
|
|
||||||
|
assert.equal(step4Payload.filterAfterTimestamp, 0);
|
||||||
assert.equal(step4Payload.maxAttempts, 15);
|
assert.equal(step4Payload.maxAttempts, 15);
|
||||||
assert.equal(step4Payload.intervalMs, 15000);
|
assert.equal(step4Payload.intervalMs, 15000);
|
||||||
|
assert.equal(step8Payload.filterAfterTimestamp, 0);
|
||||||
assert.equal(step8Payload.maxAttempts, 15);
|
assert.equal(step8Payload.maxAttempts, 15);
|
||||||
assert.equal(step8Payload.intervalMs, 15000);
|
assert.equal(step8Payload.intervalMs, 15000);
|
||||||
});
|
});
|
||||||
@@ -109,6 +111,67 @@ test('verification flow runs beforeSubmit hook before filling the code', async (
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('verification flow triggers 2925 mailbox cleanup only after code submission succeeds', async () => {
|
||||||
|
const mailMessages = [];
|
||||||
|
|
||||||
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||||
|
getHotmailVerificationPollConfig: () => ({}),
|
||||||
|
getHotmailVerificationRequestTimestamp: () => 0,
|
||||||
|
getState: async () => ({}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isStopError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||||
|
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||||
|
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||||
|
pollHotmailVerificationCode: async () => ({}),
|
||||||
|
pollLuckmailVerificationCode: async () => ({}),
|
||||||
|
sendToContentScript: async (_source, message) => {
|
||||||
|
if (message.type === 'FILL_CODE') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
sendToMailContentScriptResilient: async (_mail, message) => {
|
||||||
|
mailMessages.push(message.type);
|
||||||
|
if (message.type === 'POLL_EMAIL') {
|
||||||
|
return { code: '654321', emailTimestamp: 123 };
|
||||||
|
}
|
||||||
|
return { ok: true };
|
||||||
|
},
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
await helpers.resolveVerificationStep(
|
||||||
|
8,
|
||||||
|
{
|
||||||
|
email: 'user@example.com',
|
||||||
|
mailProvider: '2925',
|
||||||
|
lastLoginCode: null,
|
||||||
|
},
|
||||||
|
{ provider: '2925', label: '2925 邮箱' },
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|
||||||
|
assert.deepStrictEqual(mailMessages, ['POLL_EMAIL', 'DELETE_ALL_EMAILS']);
|
||||||
|
});
|
||||||
|
|
||||||
test('verification flow treats add-phone after login code submit as fatal instead of completing step 8', async () => {
|
test('verification flow treats add-phone after login code submit as fatal instead of completing step 8', async () => {
|
||||||
const events = [];
|
const events = [];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user