dsh-bridge-gateway 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +408 -0
  4. package/README.md +430 -0
  5. package/client/build.mjs +43 -0
  6. package/client/client.js +5209 -0
  7. package/client/index.js +4967 -0
  8. package/cordis.patch.yml +4 -0
  9. package/docs/banner.jpg +0 -0
  10. package/docs/custom-tunnel.md +220 -0
  11. package/docs/feishu-usage.md +116 -0
  12. package/docs/fix-tunnel-sse-and-session-list.md +134 -0
  13. package/docs/platform-abstraction-design.md +473 -0
  14. package/docs/qq-usage.md +387 -0
  15. package/docs/screenshots/admin-lock-screen.jpg +0 -0
  16. package/docs/screenshots/feishu-bot-config.jpg +0 -0
  17. package/docs/screenshots/feishu-chat.jpg +0 -0
  18. package/docs/screenshots/lan-access.jpg +0 -0
  19. package/docs/screenshots/mobile-chat.jpg +0 -0
  20. package/docs/screenshots/mobile-drawer.jpg +0 -0
  21. package/docs/screenshots/mobile-remote-settings.jpg +0 -0
  22. package/docs/screenshots/mobile-settings-im.jpg +0 -0
  23. package/docs/screenshots/mobile-settings-lan.jpg +0 -0
  24. package/docs/screenshots/mobile-settings-security.jpg +0 -0
  25. package/docs/screenshots/mobile-settings-tunnel.jpg +0 -0
  26. package/docs/screenshots/mobile-workspace-picker.jpg +0 -0
  27. package/docs/screenshots/qq-bot-config.jpg +0 -0
  28. package/docs/screenshots/qq-chat.jpg +0 -0
  29. package/docs/screenshots/qq-group.jpg +0 -0
  30. package/docs/screenshots/qr-scan.jpg +0 -0
  31. package/docs/screenshots/remote-auth-login.jpg +0 -0
  32. package/docs/screenshots/remote-web-mobile.jpg +0 -0
  33. package/docs/screenshots/security-auth-config.jpg +0 -0
  34. package/docs/screenshots/telegram-bot-config.jpg +0 -0
  35. package/docs/screenshots/tunnel-access.jpg +0 -0
  36. package/docs/screenshots/wechat-bot-config.jpg +0 -0
  37. package/docs/screenshots/wechat-chat.jpg +0 -0
  38. package/docs/telegram-usage.md +93 -0
  39. package/docs/wechat-usage.md +104 -0
  40. package/lib/auth/login-template.js +382 -0
  41. package/lib/auth/manager.js +470 -0
  42. package/lib/bridge-rpc-constants.js +55 -0
  43. package/lib/bridge-rpc.js +553 -0
  44. package/lib/cloudflared-manager.mjs +345 -0
  45. package/lib/feishu/gateway.js +550 -0
  46. package/lib/feishu/index.js +222 -0
  47. package/lib/feishu/lark-bundled.mjs +125547 -0
  48. package/lib/feishu/node.js +409 -0
  49. package/lib/gateway/cert.js +214 -0
  50. package/lib/index.js +1963 -0
  51. package/lib/platform/base.js +156 -0
  52. package/lib/platform/conversation-bridge.js +1570 -0
  53. package/lib/platform/index.js +10 -0
  54. package/lib/platform/manager.js +74 -0
  55. package/lib/qq/gateway.js +616 -0
  56. package/lib/qq/index.js +309 -0
  57. package/lib/qq/node.js +533 -0
  58. package/lib/security/path-validator.js +208 -0
  59. package/lib/security/rate-limiter.js +93 -0
  60. package/lib/telegram/gateway.js +630 -0
  61. package/lib/telegram/index.js +213 -0
  62. package/lib/telegram/node.js +351 -0
  63. package/lib/tunnel-client.mjs +402 -0
  64. package/lib/wechat/gateway.js +960 -0
  65. package/lib/wechat/index.js +241 -0
  66. package/lib/wechat/media.js +281 -0
  67. package/lib/wechat/node.js +350 -0
  68. package/package.json +102 -0
@@ -0,0 +1,4 @@
1
+ # dsh-bridge bundle patch
2
+ - insert:
3
+ - id: dsh-bridge
4
+ name: '@wenbin_wb/dsh-bridge'
Binary file
@@ -0,0 +1,220 @@
1
+ # 自建隧道服务器搭建教程
2
+
3
+ dsh-bridge 的「自建隧道」功能需要一台有公网 IP 的服务器来中转流量。本教程提供一键部署脚本,执行完成后直接复制输出的配置信息填入 dsh-bridge 即可。
4
+
5
+ ## 前置条件
6
+
7
+ - 一台运行 Linux 的公网服务器(VPS、云主机均可,国内外皆可)
8
+ - 服务器有 root 权限
9
+ - Node.js 18+ 会**自动安装**,无需手动准备
10
+
11
+ ---
12
+
13
+ ## 一键部署(推荐)
14
+
15
+ 在服务器上以 root 身份执行以下命令:
16
+
17
+ ```bash
18
+ bash <(curl -fsSL https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/scripts/install-tunnel-server.sh)
19
+ ```
20
+
21
+ 脚本会自动完成:
22
+
23
+ - 检测并安装 Node.js 22 LTS
24
+ - 自动获取服务器公网 IP,自动选择可用端口
25
+ - 随机生成 16 位访问路径 + 32 位连接令牌(双重保护)
26
+ - 部署隧道服务端到 `/opt/dsh-tunnel`
27
+ - 注册 systemd 服务并设为开机自启
28
+ - 放行防火墙端口(支持 ufw / firewalld)
29
+ - 验证服务是否正常运行
30
+
31
+ 执行完成后,终端会打印如下信息:
32
+
33
+ ```
34
+ ┌──────────────────────────────────────────────────────────────┐
35
+ │ DSH 设置 → 远程访问 → 自建隧道 │
36
+ ├──────────────────────────────────────────────────────────────┤
37
+ │ WebSocket 地址 │
38
+ │ ws://YOUR_IP:3000/connect │
39
+ │ │
40
+ │ 访问令牌 │
41
+ │ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
42
+ └──────────────────────────────────────────────────────────────┘
43
+
44
+ 连接成功后,dsh-bridge 会显示以下公网访问地址:
45
+ http://YOUR_IP:3000/a7f2k9m3x1b4c8d2
46
+
47
+ 安全说明:该地址含随机路径(/a7f2k9m3x1b4c8d2),不知道路径无法访问。
48
+ 端口扫描只能看到 404,不会暴露 DSH 的存在。
49
+ ```
50
+
51
+ 将 WebSocket 地址和访问令牌复制到 DSH 设置页「远程访问 → 自建隧道」,保存后点「开启」即可。
52
+
53
+ ### 安全设计说明
54
+
55
+ 脚本部署的服务端采用**双重保护**机制:
56
+
57
+ | 层 | 机制 | 说明 |
58
+ |----|------|------|
59
+ | WebSocket 连接层 | 连接令牌(TOKEN) | dsh-bridge 连接服务端时验证,防止他人建立隧道连接 |
60
+ | HTTP 访问层 | 随机路径前缀 | 所有不含该路径的请求返回 404,端口扫描看不出服务存在 |
61
+
62
+ 最终对外的公网地址形如 `http://IP:PORT/a7f2k9m3x1b4c8d2`,路径本身即是密钥——不知道完整地址就无法访问你的 DSH。
63
+
64
+ ### 自定义参数(可选)
65
+
66
+ 如需自定义端口、令牌或指定域名,可通过环境变量传入:
67
+
68
+ ```bash
69
+ # 自定义端口
70
+ PORT=8080 bash <(curl -fsSL ...)
71
+
72
+ # 自定义令牌
73
+ TOKEN=my-secret-token bash <(curl -fsSL ...)
74
+
75
+ # 有域名时指定(会自动使用 wss:// 安全连接)
76
+ DOMAIN=tunnel.example.com bash <(curl -fsSL ...)
77
+ ```
78
+
79
+ ### 日常管理
80
+
81
+ ```bash
82
+ # 查看服务状态
83
+ systemctl status dsh-tunnel
84
+
85
+ # 查看实时日志
86
+ journalctl -u dsh-tunnel -f
87
+
88
+ # 重启服务
89
+ systemctl restart dsh-tunnel
90
+
91
+ # 修改配置后重启
92
+ nano /opt/dsh-tunnel/.env
93
+ systemctl restart dsh-tunnel
94
+ ```
95
+
96
+ ---
97
+
98
+ ## 启用 HTTPS / WSS(可选,有域名时推荐)
99
+
100
+ 默认部署使用 HTTP,如果你有域名并配置了 SSL 证书,可以通过 Nginx 反向代理启用 HTTPS,连接更安全稳定。
101
+
102
+ ### 安装 Nginx 和 Certbot
103
+
104
+ ```bash
105
+ # Debian/Ubuntu
106
+ apt-get install -y nginx certbot python3-certbot-nginx
107
+
108
+ # CentOS/Rocky
109
+ dnf install -y nginx certbot python3-certbot-nginx
110
+ ```
111
+
112
+ ### 申请证书并配置 Nginx
113
+
114
+ ```bash
115
+ # 申请免费 Let's Encrypt 证书
116
+ certbot --nginx -d tunnel.example.com
117
+ ```
118
+
119
+ Nginx 配置 `/etc/nginx/sites-available/dsh-tunnel`(Debian 系):
120
+
121
+ ```nginx
122
+ server {
123
+ listen 443 ssl;
124
+ server_name tunnel.example.com;
125
+
126
+ ssl_certificate /etc/letsencrypt/live/tunnel.example.com/fullchain.pem;
127
+ ssl_certificate_key /etc/letsencrypt/live/tunnel.example.com/privkey.pem;
128
+
129
+ location / {
130
+ proxy_pass http://127.0.0.1:3000;
131
+ proxy_http_version 1.1;
132
+ proxy_set_header Upgrade $http_upgrade;
133
+ proxy_set_header Connection "upgrade";
134
+ proxy_set_header Host $host;
135
+ proxy_set_header X-Real-IP $remote_addr;
136
+ proxy_read_timeout 3600s;
137
+ }
138
+ }
139
+
140
+ server {
141
+ listen 80;
142
+ server_name tunnel.example.com;
143
+ return 301 https://$host$request_uri;
144
+ }
145
+ ```
146
+
147
+ 启用并重启 Nginx:
148
+
149
+ ```bash
150
+ ln -s /etc/nginx/sites-available/dsh-tunnel /etc/nginx/sites-enabled/
151
+ nginx -t && systemctl restart nginx
152
+ ```
153
+
154
+ 配置完成后,修改 `/opt/dsh-tunnel/.env`:
155
+
156
+ ```
157
+ PUBLIC_URL=https://tunnel.example.com
158
+ ```
159
+
160
+ 重启服务并更新 dsh-bridge 中的地址为 `wss://tunnel.example.com/connect`:
161
+
162
+ ```bash
163
+ systemctl restart dsh-tunnel
164
+ ```
165
+
166
+ ---
167
+
168
+ ## 工作原理
169
+
170
+ ```
171
+ 手机/外网设备
172
+ │ HTTPS 请求
173
+ ▼
174
+ 隧道服务端(你的公网服务器)
175
+ │ WebSocket 实时转发
176
+ ▼
177
+ dsh-bridge(你的本地电脑,主动发起连接)
178
+ │ HTTP 本地转发
179
+ ▼
180
+ DSH(127.0.0.1:3080)
181
+ ```
182
+
183
+ 本地电脑主动连接到服务端,**本地无需开放任何端口**,防火墙不需要特殊配置。
184
+
185
+ ---
186
+
187
+ ## 常见问题
188
+
189
+ **连接超时 / 无法连接**
190
+
191
+ 1. 检查服务器**云控制台安全组**是否放行了端口(默认 3000)——云主机有两层防火墙:云安全组(控制台配置)和系统防火墙(ufw/firewalld),两层都要放行
192
+ 2. 确认服务正在运行:`systemctl status dsh-tunnel`
193
+ 3. 验证健康检查(在服务器本机执行):`curl http://127.0.0.1:3000/healthz`,应返回 `{"ok":true,...}`
194
+ 4. 外网访问 `healthz` 返回 404 是正常的(安全设计,防止探测)
195
+
196
+ **历史记录加载失败 / 提示 "The user aborted a request."**
197
+
198
+ 历史消息较多时,API 响应时间较长。请确保服务端版本 ≥ 1.0.6(脚本已将 API 请求超时提升至 120s)。重新运行安装脚本即可更新:
199
+
200
+ ```bash
201
+ bash <(curl -fsSL https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/scripts/install-tunnel-server.sh)
202
+ ```
203
+
204
+ **重装后端口变了,安全组不匹配**
205
+
206
+ 插件会自动保留已有配置(令牌/端口/路径不变)。若出现端口变化,通常是上次重装时旧进程仍在占用端口导致自动选了新端口。脚本 1.0.6 起已修复:清理旧进程提前到端口检测之前,重装时始终复用原端口。
207
+
208
+ WebSocket 长连接需要较长的超时配置。如果使用 Nginx,确保 `proxy_read_timeout` 设为 `3600s`。
209
+
210
+ **页面加载缓慢**
211
+
212
+ 自建隧道的速度取决于服务器带宽和网络延迟。国内用户建议选择国内云服务器节点。
213
+
214
+ **重新部署 / 更新**
215
+
216
+ 重新执行一键部署命令即可,会覆盖旧版本并保留配置文件:
217
+
218
+ ```bash
219
+ bash <(curl -fsSL https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/scripts/install-tunnel-server.sh)
220
+ ```
@@ -0,0 +1,116 @@
1
+ # 飞书 (Feishu / Lark) 机器人接入指南
2
+
3
+ > 通过飞书官方开放平台 **WebSocket 长连接(免公网 IP)** 模式,将本地 DeepSeek Harness 接入飞书,支持单聊、群聊、表格命令与原生交互卡片权限审批。
4
+
5
+ ---
6
+
7
+ ## 🌟 核心优势
8
+
9
+ - **100% 免公网 IP**:基于飞书官方最新 WebSocket 长连接协议,无需配置公网服务器、无需域名、无需内网穿透工具。
10
+ - **免验签与加密配置**:长连接由飞书官方 SDK 在建立通道时完成认证,无需开发者手动处理 HTTP 回调验签。
11
+ - **飞书原生交互卡片**:Agent 触发敏感工具审批时,飞书端下发带点击按钮的交互卡片,手机/电脑端单手一键点击即可完成批准或拒绝。
12
+ - **多工作区与会话持久化**:支持 `/sessions` 表格查看历史会话、`/use <序号>` 极速切换、`/workspaces` 工作区调度。
13
+
14
+ ---
15
+
16
+ ## 🛠️ 第一步:创建飞书企业自建应用
17
+
18
+ 1. 登录 [飞书开放平台开发者后台](https://open.feishu.cn/app);
19
+ 2. 点击右上角 **「创建企业自建应用」**,填写应用名称(如 `DeepSeek Harness`)与应用描述,上传机器人头像;
20
+ 3. 创建完成后,在左侧导航栏点击 **「凭证与基础信息」**,即可查看到:
21
+ - **App ID**(格式如 `cli_a1b2c3d4...`)
22
+ - **App Secret**(点击复制密钥)
23
+
24
+ ---
25
+
26
+ ## 🤖 第二步:添加机器人能力
27
+
28
+ 1. 在应用详情页左侧导航栏,点击 **「添加应用能力」**;
29
+ 2. 找到 **「机器人」**,点击 **「添加」** 开启机器人能力。
30
+
31
+ ---
32
+
33
+ ## 🔐 第三步:开通必要权限
34
+
35
+ 在左侧导航栏点击 **「权限管理」**,搜索并开通以下权限:
36
+
37
+ | 权限名称 | 权限 Key | 权限说明 |
38
+ | :--- | :--- | :--- |
39
+ | **获取与发送单聊/群聊消息** | `im:message` | 基础消息收发权限 |
40
+ | **以应用的身份发消息** | `im:message:send_as_bot` | 允许机器人向用户/群回复消息 |
41
+ | **获取群组中所有消息** | `im:message.group_msg` | 允许机器人在群聊中被 `@` 时接收消息 |
42
+ | **获取用户 user ID** | `contact:user.id:readonly` | (可选) 获取用户信息 |
43
+
44
+ ---
45
+
46
+ ## ⚡ 第四步:开启 WebSocket 长连接事件订阅
47
+
48
+ 1. 在左侧导航栏点击 **「事件与回调」**;
49
+ 2. 在 **「事件配置」** 页面,将事件接收方式切换为 **「使用长连接接收事件」**(WebSocket 模式);
50
+ 3. 点击 **「添加事件」**,勾选并添加:
51
+ - **`im.message.receive_v1`**(接收消息)
52
+ - **`card.action.trigger`**(消息卡片回传交互 / 审批按钮点击)
53
+ 4. 保存配置。
54
+
55
+ ---
56
+
57
+ ## 🚀 第五步:版本发布
58
+
59
+ 1. 在左侧导航栏点击 **「版本管理与发布」**;
60
+ 2. 点击 **「创建版本」**,填写版本号(如 `1.0.0`),设置应用可用范围(如「所有员工」或「仅自己」);
61
+ 3. 点击 **「申请发布」**(自建应用通常由企业管理员直接免审或一键通过)。
62
+
63
+ ---
64
+
65
+ ## 📱 第六步:在 DSH 客户端中连接
66
+
67
+ 1. 打开 DeepSeek Harness,进入「设置」➔「远程访问」➔「IM 机器人」➔ 选择 **「飞书」**;
68
+ 2. 在表单中填入刚才复制的 **App ID** 和 **App Secret**;
69
+ 3. 点击 **「保存并连接」**;
70
+ 4. 状态显示为绿色 **「已连接」** 即表示长连接成功建立!
71
+
72
+ ![飞书机器人配置](screenshots/feishu-bot-config.jpg)
73
+
74
+ ---
75
+
76
+ ## 💬 常用操作与指令
77
+
78
+ 在飞书与机器人单聊或在群里 `@机器人` 即可开始对话:
79
+
80
+ ### 1. 会话与工作区管理
81
+ | 指令 | 说明 | 示例 |
82
+ | :--- | :--- | :--- |
83
+ | `/sessions` | 查看所有历史会话表格 | `/sessions` 或 `/list` |
84
+ | `/use <编号>` | 切换到指定编号会话 | `/use 1` 或 `/resume 1` |
85
+ | `/new <提示词>` | 在当前工作区创建新会话并开始 | `/new 帮我写个脚本` |
86
+ | `/new <词> @N` | 在指定工作区新建会话 | `/new 帮我写个脚本 @1` |
87
+ | `/rename <新标题>` | 重命名当前活动会话 | `/rename 优化登录交互` |
88
+ | `/workspaces` | 查看所有已注册的工作区列表 | `/workspaces` |
89
+ | `/addworkspace <路径>` | 注册添加新的电脑工作区目录 | `/addworkspace D:\projects\app` |
90
+ | `/status` | 查看 Agent 运行状态看板 | `/status` |
91
+ | `/stop` | 中断停止当前正在执行的任务 | `/stop` |
92
+ | `/end` | 结束当前会话回到空闲状态 | `/end` |
93
+
94
+ ### 2. 权限审批交互
95
+ 当 Agent 尝试执行需授权的操作(如终端命令、写敏感文件)时,飞书端会自动推送 **原生交互卡片**:
96
+ - 点击卡片上的 **「✓ 批准执行」** 或 **「✕ 拒绝执行」** 按钮即可一键处理;
97
+ - 亦可直接回复文字 `/yes` (或 `1`) / `/no` (或 `2`)。
98
+
99
+ ![飞书对话与卡片审批](screenshots/feishu-chat.jpg)
100
+
101
+ ---
102
+
103
+ ## ❓ 常见问题 (FAQ)
104
+
105
+ ### Q1: 点击卡片上的「批准」/「拒绝」按钮提示无权限或无反应?
106
+ 请依次核对以下三项飞书开放平台配置:
107
+ 1. **是否订阅了卡片交互事件**:在开放平台后台 **「事件与回调」➔「事件配置」** 中,必须添加 **`card.action.trigger`**(消息卡片回传交互)事件。若只添加了接收消息事件,卡片点击不会下发回调。
108
+ 2. **是否发布了新版本生效**:飞书平台的所有权限与事件变更,**必须在「版本管理与发布」中「创建版本」并申请发布通过后才会生效**。
109
+ 3. **应用可用范围**:在「版本管理与发布」中,确认应用可用范围包含了您当前的飞书账号(建议设为「所有员工」或将自己加入可用成员)。
110
+ 4. **快速应急处理**:如果卡片按钮暂时受网络或配置影响,可直接在聊天中回复文字 **`/yes`**(或 `1`)批准,回复 **`/no`**(或 `2`)拒绝。
111
+
112
+ ### Q2: 机器人无法在群聊中回复消息?
113
+ 1. 确保在「权限管理」中开通了 **`im:message.group_msg`**(获取群组中所有消息)权限;
114
+ 2. 确保在「版本管理与发布」中发布了包含该权限的新版本;
115
+ 3. 将机器人拉入群聊后,需要 **`@机器人`** 唤醒并发送指令。
116
+
@@ -0,0 +1,134 @@
1
+ # Fix: 自建隧道 SSE 504 超时 & 会话列表不显示
2
+
3
+ ## 问题现象
4
+
5
+ 通过自建 WebSocket 隧道(`wss://` 中转服务器)远程访问 DSH 时,出现两个问题:
6
+
7
+ 1. **`/plugins/events` SSE 端点返回 504 超时** — HMR 插件的 Server-Sent Events 连接永远不结束,隧道服务器超时后返回 504
8
+ 2. **Web 侧边栏会话列表不显示** — 页面其他功能正常,唯独会话列表空白
9
+
10
+ ## 根因分析
11
+
12
+ ### 问题 1:SSE 504 超时
13
+
14
+ 隧道协议将 HTTP 请求/响应封装为 WebSocket 消息:tunnel client 向本地 DSH 发起 HTTP 请求,**缓冲完整响应**后再通过 WebSocket 回传给 tunnel server,再由 tunnel server 转发给浏览器。
15
+
16
+ SSE(`text/event-stream`)响应是**永不结束**的流式连接 — 服务器持续推送事件,不调用 `res.end()`。原代码的 `res.on('end')` 回调永远不会触发,tunnel server 的超时(30s 非 API 路径 / 120s API 路径)到期后返回 504。
17
+
18
+ ### 问题 2:会话列表不显示
19
+
20
+ DSH 的 `session.list` RPC 返回所有会话的完整投影数据。在会话数量较多时(233 个会话),响应体高达 **63MB**,其中:
21
+
22
+ | 字段 | 大小 | 占比 |
23
+ |------|------|------|
24
+ | `contextHeaders` | 57.4 MB | 92% |
25
+ | `contextTimeline` | 5.0 MB | 8% |
26
+ | 其他投影字段 | ~0.2 MB | <1% |
27
+
28
+ `contextHeaders` 包含每个会话的完整对话上下文头(系统提示、消息头等),侧边栏列表**完全不需要**这些数据 — 打开会话时通过 WebSocket 实时获取即可。
29
+
30
+ 即使 gzip 压缩后仍有 13.5MB,通过 WebSocket 隧道传输需要 ~33s,超过 DSH 客户端的 `AbortSignal.timeout(30000)` 默认超时,导致请求被中止。
31
+
32
+ ## 修复方案
33
+
34
+ ### 修复 1:SSE 流式响应提前返回
35
+
36
+ ### 修复 1:SSE 流式响应提前返回
37
+
38
+ 在 `_handleHttpRequest()` 中检测 `text/event-stream` content-type,收集初始数据(2 个 chunk 或 500ms 超时)后立即返回响应,不等 `res.end()`:
39
+
40
+ ```javascript
41
+ if (isSSE) {
42
+ let sseSent = false;
43
+ const sseTimer = setTimeout(() => { /* 发送已收集数据 */ }, 500);
44
+ res.on('data', (c) => {
45
+ if (sseSent) return;
46
+ chunks.push(c);
47
+ if (chunks.length >= 2) {
48
+ clearTimeout(sseTimer);
49
+ sseSent = true;
50
+ // 立即发送已收集的初始数据
51
+ this._sendMessage({ type: 'response', ... });
52
+ res.destroy();
53
+ }
54
+ });
55
+ // ... error/end 处理
56
+ return;
57
+ }
58
+ ```
59
+
60
+ ### 修复 2:剥离 session.list 中的大字段
61
+
62
+ 对 `/api/session.list` 的 200 响应,解析 JSON 并删除每个会话投影中的 `contextHeaders` 和 `contextTimeline`:
63
+
64
+ ```javascript
65
+ if (path === '/api/session.list' && res.statusCode === 200) {
66
+ try {
67
+ const json = JSON.parse(bodyBuf.toString('utf8'));
68
+ if (json?.result?.ok && json.result.value?.items) {
69
+ for (const item of json.result.value.items) {
70
+ const proj = item?.projections?.values;
71
+ if (proj) {
72
+ delete proj.contextHeaders;
73
+ delete proj.contextTimeline;
74
+ }
75
+ }
76
+ bodyBuf = Buffer.from(JSON.stringify(json), 'utf8');
77
+ }
78
+ } catch {}
79
+ }
80
+ ```
81
+
82
+ ### 修复 3:大响应 gzip 压缩
83
+
84
+ 对超过 100KB 的可压缩响应(`text/*`、`application/json` 等)自动 gzip 压缩,设置 `content-encoding: gzip` 头:
85
+
86
+ ```javascript
87
+ if (bodyBuf.length > GZIP_THRESHOLD && compressible && !alreadyEncoded) {
88
+ bodyBuf = gzipSync(bodyBuf);
89
+ respHeaders['content-encoding'] = 'gzip';
90
+ respHeaders['content-length'] = String(bodyBuf.length);
91
+ }
92
+ ```
93
+
94
+ ### 修复 4:启用 WebSocket per-message 压缩
95
+
96
+ 将隧道 WebSocket 的 `perMessageDeflate` 从 `false` 改为启用,进一步减少隧道传输量:
97
+
98
+ ```javascript
99
+ this.ws = new WebSocket(url.toString(), {
100
+ handshakeTimeout: 10000,
101
+ perMessageDeflate: {
102
+ clientNoContextTakeover: true,
103
+ serverNoContextTakeover: true,
104
+ clientMaxWindowBits: 15,
105
+ serverMaxWindowBits: 15,
106
+ },
107
+ });
108
+ ```
109
+
110
+ ## 效果
111
+
112
+ | 指标 | 修复前 | 修复后 |
113
+ |------|--------|--------|
114
+ | `session.list` 响应大小 | 63 MB | ~290 KB (剥离后) / 31 KB (gzip 后) |
115
+ | `session.list` 隧道传输时间 | 33s (超时) | 0.74s |
116
+ | `/plugins/events` SSE | 504 超时 | 正常返回初始数据 |
117
+ | 侧边栏会话列表 | 不显示 | 正常显示 |
118
+
119
+ ## 影响范围
120
+
121
+ - **非隧道模式不受影响**:所有修改仅在 `_handleHttpRequest()` 中,只影响自建隧道的 HTTP 代理路径
122
+ - **WebSocket 透传不受影响**:`/api/events.mux` 和 `/api/events.host` 的 WebSocket 升级走独立的 `_handleWsOpen()` 路径,不受此修改影响
123
+ - **非 session.list 请求不受影响**:contextHeaders 剥离仅对 `/api/session.list` 路径生效
124
+ - **小响应不受影响**:gzip 压缩仅对超过 100KB 的可压缩响应生效
125
+ - **已有 content-encoding 的响应不受影响**:不重复压缩
126
+
127
+ ## 测试验证
128
+
129
+ - ✅ 本地 WebSocket 直连 DSH(`/api/events.mux`)正常接收 `session/subscribed` 事件
130
+ - ✅ 通过隧道的 WebSocket(`wss://ds.missus.top/api/events.mux`)正常接收 26+ 条消息
131
+ - ✅ 通过隧道的 `session.list` 请求:31KB / 0.74s(修复前 63MB / 33s 超时)
132
+ - ✅ 响应包含 234 个会话,`contextHeaders` 和 `contextTimeline` 已剥离,`title` 等字段完整
133
+ - ✅ TLS 证书有效,gzip content-encoding 头正确传递
134
+ >>>>>>> pr-21