Initial commit: WpywMail 桌面客户端:Node 零依赖本地服务 + React 19 / shadcn-ui 界面,支持收发信、注册、找回密码、会话与账号管理
This commit is contained in:
@@ -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 };
|
||||
Reference in New Issue
Block a user