feat: add frontend development guidelines and structure documentation

- Introduced API guidelines for interface contracts and request handling.
- Added design tokens usage guidelines for consistent styling across the project.
- Established DTO guidelines for defining request parameters and response data types.
- Created frontend structure guidelines to clarify directory organization and code placement rules.
- Compiled a comprehensive frontend development guideline document covering various aspects of the development process.
- Implemented quality guidelines to ensure code maintainability and adherence to best practices.
This commit is contained in:
yuxuanhui
2026-07-25 22:20:25 +08:00
commit 91861565bb
78 changed files with 5001 additions and 0 deletions
@@ -0,0 +1,331 @@
# MonoProxy 订阅信息获取工作流
本文记录如何从 macOS 版 MonoProxy 的本地配置中获取自有账户的节点信息,并生成不包含节点密码的 Clash YAML 列表。
## 适用范围
- 应用路径:`/Applications/MonoProxyMac.app`
- 配置路径:`~/Library/Application Support/MonoProxy/config.json`
- 已验证日期:2026-07-24
- 输出字段:`name`、`server`、`port`、`type`、`cipher`、`udp`
- 明确排除:节点 `password`、登录令牌、刷新令牌和账户信息
仅应处理自己拥有或获授权访问的账户与配置。不要上传或公开分享原始 `config.json`,其中还包含账户令牌和加密后的节点密码。
## 结论
MonoProxy 将节点数组保存在 `config.json` 的 `mn_service_<service-id>_servers` 字段中。该字段是 Base64 字符串,解码后的数据布局为:
```text
salt(16 字节)
+ IV(16 字节)
+ HMAC-SHA256(32 字节)
+ AES-256-CBC ciphertext(剩余字节)
```
密钥派生参数:
```text
算法:PBKDF2-HMAC-SHA256
迭代次数:100000
密钥长度:32 字节
配置封装口令:MonoProxyMac.MNLocalManager.Services.v1
```
这里的“配置封装口令”是应用二进制中用于保护本地配置结构的固定值,不是 Shadowsocks 节点的 `password`。
解密流程必须先验证 HMAC,再执行 AES 解密。HMAC 不匹配时应立即停止,不能忽略校验继续处理。
## 步骤一:让 MonoProxy 刷新本地配置
1. 启动 MonoProxy 并登录自己的账户。
2. 等待节点列表完成刷新;是否开启系统代理不影响离线读取,但刷新过程需要网络。
3. 检查配置文件是否刚刚更新:
```bash
stat -f '%N | modified=%Sm | size=%z' \
-t '%Y-%m-%d %H:%M:%S %z' \
"$HOME/Library/Application Support/MonoProxy/config.json"
```
如果文件不存在,先确认应用是否已登录并成功获取服务信息。
## 步骤二:确认节点字段
只查看字段名称,不输出令牌或节点密码:
```bash
jq -r 'keys[] | select(test("^mn_service_.*_servers$"))' \
"$HOME/Library/Application Support/MonoProxy/config.json"
```
正常情况下会得到类似:
```text
mn_service_2462_servers
```
服务 ID 可能随账户或后端迁移而变化,因此提取脚本不应硬编码数字部分。
## 步骤三:使用离线脚本生成无密码 YAML
下面的脚本仅使用 Node.js 内置的 `fs` 和 `crypto` 模块,不需要安装第三方依赖,也不会联网。脚本会:
1. 自动查找 `mn_service_<id>_servers` 字段;
2. Base64 解码数据;
3. 使用 PBKDF2-SHA256 派生密钥;
4. 验证 HMAC-SHA256;
5. 使用 AES-256-CBC 解密节点 JSON;
6. 输出不含 `password` 的 Clash YAML。
保存为 `extract-monoproxy.js`:
```javascript
const fs = require("fs");
const crypto = require("crypto");
const configPath =
process.argv[2] ||
`${process.env.HOME}/Library/Application Support/MonoProxy/config.json`;
const configEnvelopePassword =
"MonoProxyMac.MNLocalManager.Services.v1";
/**
* 使用 YAML 单引号格式转义字符串,避免节点名称中的特殊字符破坏 YAML。
* @param {unknown} value 需要编码的值。
* @returns {string} 可安全写入 YAML 的单引号字符串。
*/
function yamlString(value) {
return `'${String(value).replaceAll("'", "''")}'`;
}
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
const serverKey = Object.keys(config).find((key) =>
/^mn_service_.*_servers$/.test(key),
);
if (!serverKey) {
throw new Error("未找到 mn_service_<id>_servers 字段");
}
const envelope = Buffer.from(config[serverKey], "base64");
if (envelope.length <= 64) {
throw new Error("节点密文长度异常");
}
const salt = envelope.subarray(0, 16);
const iv = envelope.subarray(16, 32);
const storedHmac = envelope.subarray(32, 64);
const ciphertext = envelope.subarray(64);
const key = crypto.pbkdf2Sync(
configEnvelopePassword,
salt,
100000,
32,
"sha256",
);
const calculatedHmac = crypto
.createHmac("sha256", key)
.update(Buffer.concat([salt, iv, ciphertext]))
.digest();
if (
storedHmac.length !== calculatedHmac.length ||
!crypto.timingSafeEqual(storedHmac, calculatedHmac)
) {
throw new Error(
"HMAC 校验失败:配置可能损坏,或 MonoProxy 已更改加密格式",
);
}
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
const plaintext = Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]).toString("utf8");
const nodes = JSON.parse(plaintext);
if (!Array.isArray(nodes)) {
throw new Error("解密结果不是节点数组");
}
console.log("proxies:");
for (const node of nodes) {
if (!node.alias || !node.hostname || !node.port || !node.encryption) {
throw new Error("节点缺少 alias/hostname/port/encryption 字段");
}
console.log(` - name: ${yamlString(node.alias)}`);
console.log(` server: ${yamlString(node.hostname)}`);
console.log(` port: ${Number(node.port)}`);
console.log(" type: ss");
console.log(` cipher: ${yamlString(node.encryption)}`);
console.log(" udp: true");
}
```
执行:
```bash
node extract-monoproxy.js \
"$HOME/Library/Application Support/MonoProxy/config.json" \
> monoproxy-subscription-without-password.yaml
```
检查输出中没有密码字段:
```bash
rg -n 'password|access_token|refresh_token' \
monoproxy-subscription-without-password.yaml
```
正常结果应无任何输出。再检查 YAML 的节点数量:
```bash
rg -c '^ - name:' monoproxy-subscription-without-password.yaml
```
当前快照应输出 `15`。
## 字段映射
| MonoProxy 节点字段 | Clash YAML 字段 | 说明 |
|---|---|---|
| `alias` | `name` | 节点显示名称 |
| `hostname` | `server` | 节点域名或地址 |
| `port` | `port` | 节点端口 |
| 服务类型 Shadowsocks | `type: ss` | `type` 不在每个节点对象中单独存储 |
| `encryption` | `cipher` | 当前均为 `chacha20-ietf-poly1305` |
| 未单独存储 | `udp: true` | 按现有 Clash Shadowsocks 配置补充 |
| `password` | 不输出 | 本工作流明确排除 |
## 当前完整 YAML 快照(不含 password)
数据来自 2026-07-24 15:03:55 更新的本地 `config.json`。HMAC 校验、AES 解密及 JSON 解析均已通过。
```yaml
proxies:
- name: 'Relay-HK1'
server: 'scott.mydarkcloud.info'
port: 1904
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-HK2'
server: 'andrew.mydarkcloud.info'
port: 2004
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-HK3'
server: 'ethan.mydarkcloud.info'
port: 3204
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-HK4'
server: 'lucas.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-SG1'
server: 'tyler.mydarkcloud.info'
port: 2604
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-SG2'
server: 'tyler.mydarkcloud.info'
port: 2704
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-JP1'
server: 'patrick.mydarkcloud.info'
port: 1504
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-JP2'
server: 'ava.mydarkcloud.info'
port: 995
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-TW1'
server: 'kevin.mydarkcloud.info'
port: 2104
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-TW2'
server: 'kevin.mydarkcloud.info'
port: 2204
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-US1'
server: 'nathan.mydarkcloud.info'
port: 1204
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-US2'
server: 'nathan.mydarkcloud.info'
port: 1304
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'JP3'
server: 'noah.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'TW1'
server: 'tw1.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'TR1'
server: 'tr1.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
```
## 验证与故障处理
### HMAC 校验失败
不要跳过校验。常见原因:
- `config.json` 正在被应用写入,读取到了不完整内容;
- MonoProxy 更新后更改了封装口令、迭代次数或加密格式;
- 读取了其他应用或旧版本生成的配置文件。
先等待应用完成刷新并重新执行。如果仍失败,需要重新检查当前二进制中的:
- `MNLocalManager -_encryptedJSONObjectForKey:`
- `Ctor +d:p:e:`
- `CCKeyDerivationPBKDF` 参数
- `AES256CBCDecryptData:key:iv:error:`
- `HMACSHA256WithData:key:`
### 输出节点为空或字段缺失
- 确认账户仍有有效服务;
- 确认找到的是当前 `mn_service_<id>_servers` 字段;
- 不要把旧版 `~/Library/Preferences/com.MonoCloud.MonoProxyMac.plist` 当作最新数据源;
- 优先以刚刷新过的 `~/Library/Application Support/MonoProxy/config.json` 为准。
### YAML 无法直接连接
本文输出刻意删除了 `password`,因此它是用于审阅、比对和更新 `server/port` 的安全快照,并不是可直接连接的完整凭据文件。需要实际连接时,应在本地私密环境中补回自己已有的密码,且不要提交到 Git 或同步到公开笔记库。