78 lines
3.5 KiB
JavaScript
78 lines
3.5 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* 邮件服务器「账号接口」客户端(对接服务端 v2.2.0 的账号体系)。
|
||
*
|
||
* 走的是服务器上**只放账号类接口**的公网 HTTPS 入口(默认 https://mail.example.com:9443):
|
||
* 注册、邮箱验证码、找回密码、登录换 token、改密码、会话与资料。
|
||
* 邮件读写(读信/发件/队列)**不经过这里**,仍然走 IMAP 993 / SMTP 587 ——
|
||
* 那个入口对邮件与管理接口一律返回 404(可在服务器上用 /api/messages 验证)。
|
||
*
|
||
* 零依赖:用 Node 内置的全局 fetch(Node 18+)。
|
||
*/
|
||
|
||
const DEFAULT_API_BASE = 'https://mail.example.com:9443';
|
||
|
||
/** 服务器给的错误信息本身就是中文说明,直接透传给 UI;只补上网络层失败的可读提示。 */
|
||
async function call(base, path, options = {}) {
|
||
const { method = 'GET', body, token, timeoutMs = 20000 } = options;
|
||
const root = String(base || DEFAULT_API_BASE).replace(/\/+$/, '');
|
||
const url = root + path;
|
||
const controller = new AbortController();
|
||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||
try {
|
||
const headers = { 'Content-Type': 'application/json; charset=utf-8' };
|
||
if (token) headers.Authorization = `Bearer ${token}`;
|
||
const res = await fetch(url, {
|
||
method,
|
||
headers,
|
||
body: body === undefined ? undefined : JSON.stringify(body),
|
||
signal: controller.signal,
|
||
});
|
||
const text = await res.text();
|
||
let data = null;
|
||
try {
|
||
data = text ? JSON.parse(text) : null;
|
||
} catch {
|
||
data = { error: text };
|
||
}
|
||
if (!res.ok) {
|
||
const err = new Error((data && data.error) || `账号接口返回 HTTP ${res.status}`);
|
||
err.status = res.status;
|
||
err.data = data;
|
||
throw err;
|
||
}
|
||
return data;
|
||
} catch (err) {
|
||
if (err && err.status) throw err;
|
||
if (err && err.name === 'AbortError') throw new Error(`账号接口超时:${url}`);
|
||
throw new Error(`连不上账号接口 ${url}:${err && err.message}`);
|
||
} finally {
|
||
clearTimeout(timer);
|
||
}
|
||
}
|
||
|
||
function createAccountApi(base) {
|
||
const apiBase = String(base || DEFAULT_API_BASE).replace(/\/+$/, '');
|
||
return {
|
||
apiBase,
|
||
policy: () => call(apiBase, '/api/auth/policy'),
|
||
register: (body) => call(apiBase, '/api/register', { method: 'POST', body }),
|
||
verifyRegistration: (body) => call(apiBase, '/api/register/verify', { method: 'POST', body }),
|
||
resendCode: (body) => call(apiBase, '/api/register/resend', { method: 'POST', body }),
|
||
forgot: (email) => call(apiBase, '/api/auth/forgot', { method: 'POST', body: { email } }),
|
||
reset: (body) => call(apiBase, '/api/auth/reset', { method: 'POST', body }),
|
||
login: (email, password) => call(apiBase, '/api/login', { method: 'POST', body: { email, password } }),
|
||
logout: (token) => call(apiBase, '/api/logout', { method: 'POST', body: {}, token }),
|
||
changePassword: (token, body) => call(apiBase, '/api/account/password', { method: 'POST', body, token }),
|
||
profile: (token) => call(apiBase, '/api/me', { token }),
|
||
updateProfile: (token, displayName) =>
|
||
call(apiBase, '/api/account/profile', { method: 'PATCH', body: { displayName }, token }),
|
||
sessions: (token) => call(apiBase, '/api/account/sessions', { token }),
|
||
revokeSessions: (token, body) => call(apiBase, '/api/account/sessions/revoke', { method: 'POST', body, token }),
|
||
audit: (token, limit = 30) => call(apiBase, `/api/account/audit?limit=${limit}`, { token }),
|
||
};
|
||
}
|
||
|
||
module.exports = { DEFAULT_API_BASE, createAccountApi };
|