feat: 新增 YYDS Mail 邮箱服务端点
This commit is contained in:
@@ -688,12 +688,15 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
" luckmailBaseUrl: 'https://mails.luckyous.com',",
|
||||
" luckmailEmailType: 'ms_graph',",
|
||||
" luckmailDomain: '',",
|
||||
" yydsMailApiKey: '',",
|
||||
" yydsMailBaseUrl: 'https://maliapi.215.im/v1',",
|
||||
" panelMode: 'cpa',",
|
||||
' luckmailUsedPurchases: {},',
|
||||
' luckmailPreserveTagId: 0,',
|
||||
" luckmailPreserveTagName: '保留',",
|
||||
" currentLuckmailPurchase: { token: 'stale' },",
|
||||
" currentLuckmailMailCursor: { messageId: 'stale' },",
|
||||
" currentYydsMailInbox: { address: 'stale@example.com', token: 'stale-token' },",
|
||||
' currentPhoneActivation: null,',
|
||||
' reusablePhoneActivation: null,',
|
||||
' email: null,',
|
||||
@@ -725,6 +728,12 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
'function normalizeLuckmailUsedPurchases(value) {',
|
||||
' return value || {};',
|
||||
'}',
|
||||
'function normalizeYydsMailApiKey(value) {',
|
||||
" return String(value || '').trim();",
|
||||
'}',
|
||||
'function normalizeYydsMailBaseUrl(value) {',
|
||||
" return (String(value || '').trim() || 'https://maliapi.215.im/v1').replace(/\\/$/, '');",
|
||||
'}',
|
||||
'async function getPersistedSettings() {',
|
||||
" return { mailProvider: '163' };",
|
||||
'}',
|
||||
@@ -752,6 +761,8 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
" luckmailUsedPurchases: { 88: true },",
|
||||
' luckmailPreserveTagId: 9,',
|
||||
" luckmailPreserveTagName: '保留',",
|
||||
" yydsMailApiKey: 'AC-session',",
|
||||
" yydsMailBaseUrl: 'https://maliapi.215.im/v1/',",
|
||||
' };',
|
||||
' },',
|
||||
' async clear() {',
|
||||
@@ -786,6 +797,9 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
assert.equal(snapshot.storedPayload.luckmailPreserveTagName, '保留');
|
||||
assert.equal(snapshot.storedPayload.currentLuckmailPurchase, null);
|
||||
assert.equal(snapshot.storedPayload.currentLuckmailMailCursor, null);
|
||||
assert.equal(snapshot.storedPayload.yydsMailApiKey, 'AC-session');
|
||||
assert.equal(snapshot.storedPayload.yydsMailBaseUrl, 'https://maliapi.215.im/v1');
|
||||
assert.equal(snapshot.storedPayload.currentYydsMailInbox, null);
|
||||
assert.deepStrictEqual(snapshot.storedPayload.reusablePhoneActivation, {
|
||||
activationId: 'rx-001',
|
||||
phoneNumber: '66951112222',
|
||||
|
||||
@@ -15,3 +15,38 @@ test('verification flow module exposes a factory', () => {
|
||||
|
||||
assert.equal(typeof api?.createVerificationFlowHelpers, 'function');
|
||||
});
|
||||
|
||||
test('verification flow routes YYDS Mail provider to background poller', async () => {
|
||||
const source = fs.readFileSync('background/verification-flow.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundVerificationFlow;`)(globalScope);
|
||||
const pollCalls = [];
|
||||
const helpers = api.createVerificationFlowHelpers({
|
||||
addLog: async () => {},
|
||||
buildVerificationPollPayload: () => ({ maxAttempts: 1, intervalMs: 1 }),
|
||||
getState: async () => ({}),
|
||||
getTabId: async () => 1,
|
||||
isStopError: () => false,
|
||||
pollYydsMailVerificationCode: async (step, state, payload) => {
|
||||
pollCalls.push({ step, state, payload });
|
||||
return { ok: true, code: '123456', emailTimestamp: 1, mailId: 'msg-1' };
|
||||
},
|
||||
sendToContentScript: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
YYDS_MAIL_PROVIDER: 'yyds-mail',
|
||||
});
|
||||
|
||||
const result = await helpers.pollFreshVerificationCode(
|
||||
4,
|
||||
{ mailProvider: 'yyds-mail' },
|
||||
{ provider: 'yyds-mail', label: 'YYDS Mail' },
|
||||
{ disableTimeBudgetCap: true }
|
||||
);
|
||||
|
||||
assert.equal(result.code, '123456');
|
||||
assert.equal(pollCalls.length, 1);
|
||||
assert.equal(pollCalls[0].step, 4);
|
||||
assert.equal(pollCalls[0].payload.maxAttempts, 1);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
HOTMAIL_PROVIDER,
|
||||
YYDS_MAIL_PROVIDER,
|
||||
getIcloudForwardMailConfig,
|
||||
getIcloudForwardMailProviderOptions,
|
||||
getMailProviderConfig,
|
||||
@@ -14,6 +15,7 @@ const {
|
||||
test('normalizeMailProvider accepts 126 and falls back to 163', () => {
|
||||
assert.equal(normalizeMailProvider('126'), '126');
|
||||
assert.equal(normalizeMailProvider('163-vip'), '163-vip');
|
||||
assert.equal(normalizeMailProvider(YYDS_MAIL_PROVIDER), YYDS_MAIL_PROVIDER);
|
||||
assert.equal(normalizeMailProvider('unknown-provider'), '163');
|
||||
});
|
||||
|
||||
@@ -38,6 +40,16 @@ test('getMailProviderConfig preserves the hotmail provider sentinel', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('getMailProviderConfig preserves the YYDS Mail provider sentinel', () => {
|
||||
assert.deepEqual(
|
||||
getMailProviderConfig({ mailProvider: YYDS_MAIL_PROVIDER }),
|
||||
{
|
||||
provider: YYDS_MAIL_PROVIDER,
|
||||
label: 'YYDS Mail',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('iCloud forward mailbox helpers normalize and expose supported providers', () => {
|
||||
assert.equal(normalizeIcloudTargetMailboxType('forward-mailbox'), 'forward-mailbox');
|
||||
assert.equal(normalizeIcloudTargetMailboxType('unknown'), 'icloud-inbox');
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const utils = require('../yyds-mail-utils.js');
|
||||
require('../background/yyds-mail-provider.js');
|
||||
|
||||
function createProviderApi(options = {}) {
|
||||
const {
|
||||
state = {
|
||||
yydsMailApiKey: 'AC-demo',
|
||||
yydsMailBaseUrl: 'https://maliapi.215.im/v1',
|
||||
currentYydsMailInbox: null,
|
||||
email: '',
|
||||
},
|
||||
fetchImpl,
|
||||
} = options;
|
||||
let currentState = { ...state };
|
||||
const logs = [];
|
||||
const persistCalls = [];
|
||||
const stateUpdates = [];
|
||||
const calls = [];
|
||||
|
||||
const api = globalThis.MultiPageBackgroundYydsMailProvider.createYydsMailProvider({
|
||||
addLog: async (message, level) => logs.push({ message, level }),
|
||||
buildYydsMailHeaders: utils.buildYydsMailHeaders,
|
||||
DEFAULT_YYDS_MAIL_BASE_URL: utils.DEFAULT_YYDS_MAIL_BASE_URL,
|
||||
fetchImpl: fetchImpl || (async (url, request = {}) => {
|
||||
calls.push({
|
||||
url: String(url),
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.body ? JSON.parse(request.body) : undefined,
|
||||
});
|
||||
if (String(url).endsWith('/accounts')) {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
id: 'inbox-1',
|
||||
address: 'fresh@example.com',
|
||||
token: 'temp-token',
|
||||
expiresAt: '2026-03-15T12:00:00Z',
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (String(url).includes('/messages/msg-1')) {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
id: 'msg-1',
|
||||
from: { address: 'noreply@tm.openai.com' },
|
||||
subject: 'OpenAI verification code',
|
||||
text: 'Your ChatGPT code is 987654.',
|
||||
createdAt: '2026-03-14T12:30:00Z',
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (String(url).includes('/messages')) {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
messages: [{
|
||||
id: 'msg-1',
|
||||
from: { address: 'noreply@tm.openai.com' },
|
||||
subject: 'OpenAI verification code',
|
||||
createdAt: '2026-03-14T12:30:00Z',
|
||||
}],
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected URL ${url}`);
|
||||
}),
|
||||
getState: async () => currentState,
|
||||
joinYydsMailUrl: utils.joinYydsMailUrl,
|
||||
normalizeYydsMailAddress: utils.normalizeYydsMailAddress,
|
||||
normalizeYydsMailApiKey: utils.normalizeYydsMailApiKey,
|
||||
normalizeYydsMailBaseUrl: utils.normalizeYydsMailBaseUrl,
|
||||
normalizeYydsMailCurrentInbox: utils.normalizeYydsMailCurrentInbox,
|
||||
normalizeYydsMailInbox: utils.normalizeYydsMailInbox,
|
||||
normalizeYydsMailMessageDetail: utils.normalizeYydsMailMessageDetail,
|
||||
normalizeYydsMailMessages: utils.normalizeYydsMailMessages,
|
||||
persistRegistrationEmailState: async (callState, email, persistOptions) => {
|
||||
persistCalls.push({ state: callState, email, options: persistOptions });
|
||||
currentState = { ...currentState, email };
|
||||
},
|
||||
pickVerificationMessageWithTimeFallback: (messages) => {
|
||||
const match = messages.find((message) => message.verification_code);
|
||||
return match
|
||||
? {
|
||||
match: {
|
||||
code: match.verification_code,
|
||||
receivedAt: Date.parse(match.receivedDateTime),
|
||||
message: match,
|
||||
},
|
||||
usedRelaxedFilters: false,
|
||||
usedTimeFallback: false,
|
||||
}
|
||||
: { match: null, usedRelaxedFilters: false, usedTimeFallback: false };
|
||||
},
|
||||
setState: async (updates) => {
|
||||
stateUpdates.push(updates);
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
YYDS_MAIL_PROVIDER: utils.YYDS_MAIL_PROVIDER,
|
||||
});
|
||||
|
||||
return {
|
||||
...api,
|
||||
snapshot() {
|
||||
return { calls, currentState, logs, persistCalls, stateUpdates };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('fetchYydsMailAddress creates an inbox with localPart only and stores the returned address/token', async () => {
|
||||
const api = createProviderApi();
|
||||
const email = await api.fetchYydsMailAddress(null, { localPart: 'fresh' });
|
||||
const snapshot = api.snapshot();
|
||||
|
||||
assert.equal(email, 'fresh@example.com');
|
||||
assert.equal(snapshot.calls[0].url, 'https://maliapi.215.im/v1/accounts');
|
||||
assert.equal(snapshot.calls[0].method, 'POST');
|
||||
assert.deepEqual(snapshot.calls[0].body, { localPart: 'fresh' });
|
||||
assert.equal(snapshot.calls[0].headers['X-API-Key'], 'AC-demo');
|
||||
assert.equal(snapshot.calls[0].headers.Authorization, undefined);
|
||||
assert.equal(snapshot.currentState.currentYydsMailInbox.address, 'fresh@example.com');
|
||||
assert.equal(snapshot.currentState.currentYydsMailInbox.token, 'temp-token');
|
||||
assert.equal(snapshot.persistCalls[0].email, 'fresh@example.com');
|
||||
});
|
||||
|
||||
test('pollYydsMailVerificationCode lists messages with temp token and reads detail before matching code', async () => {
|
||||
const api = createProviderApi({
|
||||
state: {
|
||||
yydsMailApiKey: 'AC-demo',
|
||||
yydsMailBaseUrl: 'https://maliapi.215.im/v1',
|
||||
currentYydsMailInbox: {
|
||||
id: 'inbox-1',
|
||||
address: 'fresh@example.com',
|
||||
token: 'temp-token',
|
||||
},
|
||||
email: 'fresh@example.com',
|
||||
},
|
||||
});
|
||||
|
||||
const result = await api.pollYydsMailVerificationCode(4, null, {
|
||||
maxAttempts: 1,
|
||||
intervalMs: 1,
|
||||
senderFilters: ['openai'],
|
||||
subjectFilters: ['code'],
|
||||
});
|
||||
const snapshot = api.snapshot();
|
||||
const listCall = snapshot.calls.find((call) => call.url.includes('/messages?'));
|
||||
const detailCall = snapshot.calls.find((call) => call.url.includes('/messages/msg-1'));
|
||||
|
||||
assert.equal(result.code, '987654');
|
||||
assert.equal(listCall.headers.Authorization, 'Bearer temp-token');
|
||||
assert.equal(listCall.headers['X-API-Key'], undefined);
|
||||
assert.match(listCall.url, /address=fresh%40example\.com/);
|
||||
assert.equal(detailCall.headers.Authorization, 'Bearer temp-token');
|
||||
});
|
||||
|
||||
test('clearYydsMailRuntimeState clears current inbox and optionally current email', async () => {
|
||||
const api = createProviderApi({
|
||||
state: {
|
||||
currentYydsMailInbox: { address: 'fresh@example.com', token: 'temp-token' },
|
||||
email: 'fresh@example.com',
|
||||
},
|
||||
});
|
||||
|
||||
await api.clearYydsMailRuntimeState({ clearEmail: true });
|
||||
assert.deepEqual(api.snapshot().stateUpdates.at(-1), {
|
||||
currentYydsMailInbox: null,
|
||||
email: null,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
DEFAULT_YYDS_MAIL_BASE_URL,
|
||||
buildYydsMailHeaders,
|
||||
joinYydsMailUrl,
|
||||
normalizeYydsMailBaseUrl,
|
||||
normalizeYydsMailInbox,
|
||||
normalizeYydsMailMessageDetail,
|
||||
normalizeYydsMailMessages,
|
||||
} = require('../yyds-mail-utils.js');
|
||||
|
||||
test('normalizeYydsMailBaseUrl defaults to official v1 endpoint and trims trailing slash', () => {
|
||||
assert.equal(normalizeYydsMailBaseUrl(''), DEFAULT_YYDS_MAIL_BASE_URL);
|
||||
assert.equal(normalizeYydsMailBaseUrl('maliapi.215.im/v1/'), DEFAULT_YYDS_MAIL_BASE_URL);
|
||||
assert.equal(normalizeYydsMailBaseUrl('http://127.0.0.1:8787/v1/'), 'http://127.0.0.1:8787/v1');
|
||||
});
|
||||
|
||||
test('buildYydsMailHeaders separates API key creation auth from temp-token read auth', () => {
|
||||
assert.deepEqual(buildYydsMailHeaders({ apiKey: 'AC-demo' }, { json: true }), {
|
||||
'X-API-Key': 'AC-demo',
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
buildYydsMailHeaders(
|
||||
{ apiKey: 'AC-demo', token: 'temp-token' },
|
||||
{ tempToken: 'temp-token', includeConfigApiKey: false }
|
||||
),
|
||||
{
|
||||
Authorization: 'Bearer temp-token',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('joinYydsMailUrl appends query parameters to the normalized base URL', () => {
|
||||
assert.equal(
|
||||
joinYydsMailUrl('https://maliapi.215.im/v1/', '/messages', { address: 'a+b@example.com', limit: 20 }),
|
||||
'https://maliapi.215.im/v1/messages?address=a%2Bb%40example.com&limit=20'
|
||||
);
|
||||
});
|
||||
|
||||
test('normalizeYydsMailInbox keeps the final returned address and temp token', () => {
|
||||
assert.deepEqual(normalizeYydsMailInbox({
|
||||
id: 'inbox-1',
|
||||
address: 'User@Example.com',
|
||||
token: 'jwt-token',
|
||||
expiresAt: '2026-03-15T12:00:00Z',
|
||||
}), {
|
||||
id: 'inbox-1',
|
||||
address: 'user@example.com',
|
||||
token: 'jwt-token',
|
||||
expiresAt: '2026-03-15T12:00:00Z',
|
||||
isActive: true,
|
||||
createdAt: null,
|
||||
raw: {
|
||||
id: 'inbox-1',
|
||||
address: 'User@Example.com',
|
||||
token: 'jwt-token',
|
||||
expiresAt: '2026-03-15T12:00:00Z',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('normalizeYydsMailMessages and detail expose Graph-like fields for verification matching', () => {
|
||||
const messages = normalizeYydsMailMessages({
|
||||
messages: [{
|
||||
id: 'msg-1',
|
||||
from: { address: 'noreply@tm.openai.com' },
|
||||
to: [{ address: 'User@Example.com' }],
|
||||
subject: 'OpenAI verification code',
|
||||
createdAt: '2026-03-14T12:30:00Z',
|
||||
}],
|
||||
});
|
||||
assert.equal(messages[0].id, 'msg-1');
|
||||
assert.equal(messages[0].address, 'user@example.com');
|
||||
assert.equal(messages[0].from.emailAddress.address, 'noreply@tm.openai.com');
|
||||
|
||||
const detail = normalizeYydsMailMessageDetail({
|
||||
id: 'msg-1',
|
||||
subject: 'OpenAI verification code',
|
||||
from: { address: 'noreply@tm.openai.com' },
|
||||
text: 'Your ChatGPT code is 654321.',
|
||||
createdAt: '2026-03-14T12:30:00Z',
|
||||
});
|
||||
assert.equal(detail.verification_code, '654321');
|
||||
assert.match(detail.bodyPreview, /654321/);
|
||||
});
|
||||
Reference in New Issue
Block a user