Initial commit: WpywMail 桌面客户端:Node 零依赖本地服务 + React 19 / shadcn-ui 界面,支持收发信、注册、找回密码、会话与账号管理

This commit is contained in:
WpyQwq
2026-09-19 11:20:43 +08:00
commit c7fb8f8f68
47 changed files with 11573 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
'use strict';
/**
* 账户配置的读取与保存。
*
* 位置优先级:
* 1. 环境变量 WPYWMAIL_ACCOUNT_FILE 指定的文件(自测/多账户时用)
* 2. %LOCALAPPDATA%\WpywMailClient\account.json(默认,跟着用户走)
*
* 安全说明(不粉饰):密码以明文存在这个文件里。这是自研客户端的常见做法
* (Thunderbird 之类会用系统凭据库/主密码,那需要额外依赖)。本文件只放在
* 当前用户自己的 profile 目录下,不做任何网络传输;若在意,可把
* account.savePassword 设为 false,每次启动时手动输入密码。
*/
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const DEFAULTS = {
host: 'mail.example.com',
imapPort: 993,
smtpPort: 587,
implicitTls: true, // 993 隐式 TLS
startTls: true, // 587 先 STARTTLS 再 AUTH
user: '',
password: '',
displayName: '',
domain: 'wpy.email',
savePassword: true,
// 服务器「账号接口」入口(注册 / 找回密码 / 会话与资料)。
// 只放账号类接口,邮件读写仍然走 IMAP/SMTP —— 这里看不到任何邮件数据。
apiBase: 'https://mail.example.com:9443',
// 界面默认值
pageSize: 50,
folders: { inbox: 'INBOX', sent: 'Sent', drafts: 'Drafts', archive: 'Archive', trash: 'Trash', junk: 'Junk' },
};
function defaultDir() {
if (process.env.WPYWMAIL_CONFIG_DIR) return process.env.WPYWMAIL_CONFIG_DIR;
const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
return path.join(base, 'WpywMailClient');
}
function accountFile() {
if (process.env.WPYWMAIL_ACCOUNT_FILE) return process.env.WPYWMAIL_ACCOUNT_FILE;
return path.join(defaultDir(), 'account.json');
}
function loadAccount() {
const file = accountFile();
let data = {};
try {
if (fs.existsSync(file)) data = JSON.parse(fs.readFileSync(file, 'utf8'));
} catch (err) {
throw new Error(`账户配置读取失败(${file}):${err.message}`);
}
return Object.assign({}, DEFAULTS, data, { _file: file });
}
function saveAccount(account) {
const file = accountFile();
const clean = Object.assign({}, account);
delete clean._file;
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, JSON.stringify(clean, null, 2), { encoding: 'utf8', mode: 0o600 });
return file;
}
/** 供 UI 与自测复用:把配置变成两个客户端 */
function connectionOptions(account) {
return {
imap: {
host: account.host,
port: account.imapPort,
user: account.user,
password: account.password,
implicitTls: !!account.implicitTls,
startTls: !!account.startTls,
},
smtp: {
host: account.host,
port: account.smtpPort,
user: account.user,
password: account.password,
startTls: !!account.startTls,
heloName: account.heloName || undefined,
},
};
}
module.exports = { DEFAULTS, accountFile, defaultDir, loadAccount, saveAccount, connectionOptions };