auvezy-terminal-remote 0.4.4 → 0.4.5

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.
package/README.md CHANGED
@@ -1,107 +1,297 @@
1
1
  # auvezy-terminal-remote
2
2
 
3
- > 局域网内通过手机 / 平板浏览器远程控制 PC 上的任意终端程序(zsh / bash / claude / 任何 CLI)。
3
+ **English** | [简体中文](https://github.com/jjj201200/auvezy-terminal-remote/blob/main/README.zh-CN.md)
4
+
5
+ > Remote-control any terminal program on your PC (zsh / bash / claude / any CLI)
6
+ > from a phone or tablet browser over LAN.
4
7
  >
5
- > 一行命令 `atr <program>`,多终端多实例自动出现在浏览器顶栏 tab 切换。
8
+ > One command — `atr <program>` — and every instance shows up as a tab in your
9
+ > browser's top bar.
10
+
11
+ > **License: [PolyForm Noncommercial 1.0.0](./LICENSE)** —
12
+ > free for personal, educational, and nonprofit use, including modification and
13
+ > redistribution. Commercial use requires a separate license.
14
+
15
+ ## What is this
6
16
 
7
- ## 这是什么
17
+ You're on the couch with your phone. A long-running CLI on your PC
18
+ (Claude Code / a deploy script / a debug session…) is doing its thing and
19
+ you want to:
8
20
 
9
- 你坐在沙发上拿着手机,PC 上某个 CLI(Claude Code / 部署脚本 / 调试会话…)正在跑一个长任务。你希望:
21
+ - See its live output (ANSI colors included)
22
+ - Type the next command, hit arrow keys
23
+ - Get a phone notification when Claude triggers an approval hook
24
+ - Not open any port to the public internet, not depend on a cloud service
10
25
 
11
- - 实时看到终端输出(包括 ANSI 颜色)
12
- - 输入下一条指令、按方向键
13
- - Claude 触发审批 hook 时,手机锁屏弹通知
14
- - 不开公网、不依赖云
26
+ That's exactly what this project does. PTY output is bridged over WebSocket
27
+ to a webapp; webapp input is bridged back to the PTY. Listens on LAN IPs only,
28
+ authed by token + local cookie.
15
29
 
16
- 这正是这个项目要做的。把 PTY 输出经 WebSocket 桥到 webapp,
17
- 把 webapp 输入桥回 PTY。仅绑 LAN IP,用 token + 本地 cookie 鉴权。
30
+ ## Quick start
18
31
 
19
- ## 安装
32
+ ### Global install (npm users)
20
33
 
21
34
  ```bash
22
- npm install -g auvezy-terminal-remote
35
+ npm install -g auvezy-terminal-remote # -g is required
23
36
  ```
24
37
 
25
- ## 使用
38
+ > ⚠️ The default `npm i auvezy-terminal-remote` shown at the top right of the
39
+ > npm package page is **missing `-g`**. This is a CLI tool — without `-g` the
40
+ > `atr` binary won't be on your PATH. Use the command above.
41
+
42
+ Then in any terminal:
26
43
 
27
44
  ```bash
28
- atr # 跑当前 $SHELLzsh / bash 自动检测)
29
- atr claude # claude
30
- atr zsh # zsh
31
- atr claude --resume foo # 透传任意参数给子进程
45
+ atr # runs your $SHELL (auto-detects zsh / bash)
46
+ atr claude # runs claude
47
+ atr zsh # runs zsh
48
+ atr claude --resume foo # passes any args through to the child process
32
49
  ```
33
50
 
34
- 启动后扫终端打印的二维码 webapp 自动登录(token `~/.auvezy/terminal-remote/config.json`)。
51
+ After it starts, scan the QR code printed in the terminal — the webapp logs in
52
+ automatically (token lives in `~/.auvezy/terminal-remote/config.json`).
35
53
 
36
- **多实例**:在不同终端多次 `atr <prog>`,每次会自动占一个新端口(3000、3001、3002…),
37
- 浏览器顶栏会自动出现新 tab,点击即可切换。
54
+ **Multiple instances**: Run `atr <prog>` in different terminals; each grabs the
55
+ next available port (3000, 3001, 3002…). Tabs for new instances appear in the
56
+ browser's top bar automatically — click to switch.
38
57
 
39
58
  ```bash
40
- atr list # 列出本机所有实例
41
- atr stop # 停止本机所有实例
42
- atr attach <url> # 命令行接管已有实例
59
+ atr list # list all running instances on this machine
60
+ atr stop # stop all instances on this machine
61
+ atr attach <url> # take over a running instance from the command line
43
62
  ```
44
63
 
45
- ## 启动选项
64
+ ### From source (development or self-build)
65
+
66
+ ```bash
67
+ # GitHub (primary)
68
+ git clone https://github.com/jjj201200/auvezy-terminal-remote.git
69
+ # or Gitee mirror (faster from mainland China)
70
+ git clone https://gitee.com/drowsyflesh/auvezy-terminal-remote.git
46
71
 
72
+ cd auvezy-terminal-remote
73
+ bash install.sh # checks Node 20+ / pnpm 9+ / build deps → installs → builds
74
+ node backend/dist/cli.js # equivalent to `atr`
47
75
  ```
48
- atr [子命令] [选项]
49
-
50
- 子命令:
51
- start 启动 backend(默认)
52
- attach attach 到运行中的实例(命令行接管)
53
- list 列出本机所有运行中实例
54
- stop 停止本机所有实例
55
-
56
- 选项:
57
- -p, --port <n> 端口(默认 3000,多实例自动递增;除非 -S)
58
- -S, --strict-port 严格端口模式:被占即报错退出,不自适应
59
- --spawn-timeout <s> PTY spawn 兜底秒数(默认 30;0=不超时;
60
- 首个浏览器连入 / Enter / 超时三选一触发)
61
- --wait-confirm 强制必须按 Enter spawn(覆盖浏览器/超时触发)
62
- --name <s> 实例名(用于 webapp 显示)
63
- --no-terminal 不打印二维码(CI / 守护进程友好)
64
- --command <cmd> PTY 启动命令(默认当前 $SHELL)
65
- --args <json> 命令参数(JSON 数组字符串)
66
- -h, --help 显示帮助
67
- -v, --version 显示版本号
76
+
77
+
78
+ ## Feature matrix
79
+
80
+ | Feature | How it's implemented |
81
+ |---|---|
82
+ | PTY bridge | node-pty + xterm.js 5 |
83
+ | Auth | timingSafeEqual token + Session Cookie (port-bound) |
84
+ | Multi-instance | port-finder auto-increment + cookie-name suffix isolation |
85
+ | Reconnect / replay | OutputBuffer + history_sync (alt-screen filtered by default) |
86
+ | Approval push | Web Push (VAPID, 3 priorities) + iOS Safari LocalNotification fallback |
87
+ | IP drift detection | 30s polling + stability threshold + ip_changed broadcast |
88
+ | Config rewrite | Webapp Settings dialog → /api/config |
89
+ | `attach` subcommand | Master arbitration (webapp > attach > PC) |
90
+
91
+ ## Configuration
92
+
93
+ On startup the backend reads `~/.auvezy/terminal-remote/config.json`:
94
+
95
+ ```json
96
+ {
97
+ "token": "<64-char hex, auto-generated>",
98
+ "shortcuts": [
99
+ { "label": "ESC", "data": "" },
100
+ { "label": "↑", "data": "[A" }
101
+ ],
102
+ "command": null,
103
+ "args": null,
104
+ "rateLimitPerMinute": 10,
105
+ "sessionTtlMs": 86400000
106
+ }
107
+ ```
108
+
109
+ VAPID keys live in the same directory: `vapid.json` (mode 0o600, auto-generated
110
+ or read from env vars `VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY`).
111
+
112
+ Subscriptions are in `push-subscriptions.json`; the multi-instance registry is in
113
+ `instances/<port>.json`.
114
+
115
+ ## Startup options
116
+
68
117
  ```
118
+ atr [subcommand] [options]
69
119
 
70
- 环境变量:
120
+ Subcommands:
121
+ start start the backend (default)
122
+ attach attach to a running instance from the command line
123
+ list list all running instances on this machine
124
+ stop stop all instances on this machine
71
125
 
72
- | 变量 | 用途 |
126
+ Options:
127
+ -p, --port <n> port (default 3000, auto-increments unless -S)
128
+ -S, --strict-port strict port mode: fail if port is taken, no fallback
129
+ --spawn-timeout <s> PTY spawn fallback seconds (default 30; 0 = no timeout;
130
+ first browser connect / Enter / timeout — whichever first)
131
+ --wait-confirm require Enter to spawn (overrides browser/timeout triggers)
132
+ --name <s> instance name (shown in webapp)
133
+ --no-terminal don't print QR code (CI / daemon-friendly)
134
+ --command <cmd> PTY command (default: 'claude')
135
+ --args <json> command args (JSON array string)
136
+ -h, --help show help
137
+ -v, --version show version
138
+ ```
139
+
140
+ Environment variables:
141
+
142
+ | Variable | Purpose |
73
143
  |---|---|
74
- | `OCR_COMMAND` | 子进程命令(默认 `$SHELL`,没有则 `/bin/sh`;显式设为 `claude` Claude)|
75
- | `OCR_ARGS` | 命令参数(JSON 数组字符串,如 `'["-c","tail -f /dev/null"]'`)|
76
- | `OCR_CWD` | 子进程工作目录(默认 `process.cwd()`)|
77
- | `OCR_ANSI_FILTER` | 是否过滤 alt-screen 输出(默认 `false`)。设 `true` vim/htop 退出后重连回放更干净;但全程 alt-screen TUI(claude/tmux/...)仍受内置黑名单保护 |
78
- | `OCR_ANSI_FILTER_TUI_NAMES` | 追加自家 alt-screen TUI 黑名单(逗号分隔),例如 `"lazygit,k9s,gh-dash"` |
79
- | `VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY` | 注入 VAPID(高优先级,跳过文件)|
80
- | `PORT` | `--port` |
81
- | `STRICT_PORT` | `--strict-port`(设 `true` 启用严格模式)|
82
- | `OCR_SPAWN_TIMEOUT` | `--spawn-timeout`(秒;0 = 无超时)|
83
- | `AUTH_TOKEN` | 指定 token(默认自动生成)|
84
- | `LOG_LEVEL` | pino 级别(默认 info)|
144
+ | `OCR_COMMAND` | Child command (default `$SHELL`, or `/bin/sh`; set to `claude` to run Claude) |
145
+ | `OCR_ARGS` | Command args (JSON array string, e.g. `'["-c","tail -f /dev/null"]'`) |
146
+ | `OCR_CWD` | Child process working directory (default: `process.cwd()`) |
147
+ | `OCR_ANSI_FILTER` | Filter alt-screen output (default `false`). Set `true` for cleaner reconnect replay after vim/htop exits; full-time alt-screen TUIs (claude/tmux/...) are still protected by built-in blocklist |
148
+ | `OCR_ANSI_FILTER_TUI_NAMES` | Append to your own alt-screen TUI blocklist (comma-separated), e.g. `"lazygit,k9s,gh-dash"` |
149
+ | `VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY` | Inject VAPID keys (highest priority, skips file) |
150
+ | `PORT` | Same as `--port` |
151
+ | `STRICT_PORT` | Same as `--strict-port` (set `true` to enable strict mode) |
152
+ | `OCR_SPAWN_TIMEOUT` | Same as `--spawn-timeout` (seconds; 0 = no timeout) |
153
+ | `AUTH_TOKEN` | Specify token (default: auto-generated) |
154
+ | `LOG_LEVEL` | pino level (default `info`) |
155
+
156
+ > Legacy names `CLAUDE_COMMAND` / `CLAUDE_ARGS` / `CLAUDE_CWD` still work
157
+ > (warned once at startup). Renamed to make it clear: this project is not tied
158
+ > to Claude — it can run any PTY program.
159
+
160
+ ## Install as a PWA (recommended on mobile)
161
+
162
+ The webapp ships with a manifest. "Add to Home Screen" gives you a near-native
163
+ app experience:
164
+
165
+ - **Android Chrome**: top-right ⋮ → "Install app" (or the address bar shows an
166
+ "Install" prompt)
167
+ - **iOS Safari**: share button → "Add to Home Screen"
168
+
169
+ After install: no browser UI (no address bar, no bottom nav), independent task
170
+ card, status bar matches the app color.
171
+
172
+ > **Web Push limitations**: browsers require Push to be in a secure context
173
+ > (HTTPS / localhost). LAN HTTP (http://192.168.x.x) cannot subscribe to push.
174
+ > The settings panel will display "HTTPS required". Workarounds: use Tailscale /
175
+ > Cloudflare Tunnel to put HTTPS in front of the backend, or deploy with a
176
+ > self-signed certificate.
177
+
178
+ ## Running in WSL, accessing from Windows browser
179
+
180
+ WSL2 has two network modes that behave differently:
181
+
182
+ - **Mirrored mode** (Win11 22H2+ default): WSL gets the Windows LAN IP directly
183
+ (e.g. `192.168.x.x`). Windows browsers can use the IP printed on the banner,
184
+ no extra config.
185
+ - **NAT mode** (default): WSL is on `172.x.x.x` private network — Windows
186
+ browsers can't connect directly. The backend detects this on startup and
187
+ prints PowerShell config commands at the end of the banner.
188
+
189
+ **One-shot auto config** (admin PowerShell):
190
+
191
+ ```powershell
192
+ # Forward common port range (default 3000–3010)
193
+ .\scripts\wsl-port-forward.ps1
194
+
195
+ # Forward specific ports only
196
+ .\scripts\wsl-port-forward.ps1 -Ports 3000,3001
197
+
198
+ # Register to re-forward on login (no manual re-run when WSL IP changes)
199
+ .\scripts\wsl-port-forward.ps1 -Persist
200
+
201
+ # Cleanup
202
+ .\scripts\wsl-port-forward.ps1 -Reset
203
+ ```
204
+
205
+ ## Architecture / decisions
206
+
207
+ - Design doc: [`docs/plans/open-claude-remote-clone/design.md`](./docs/plans/open-claude-remote-clone/design.md)
208
+ - Module diagram and data flow: [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md)
209
+ - Architecture decision records (ADRs):
210
+ [`docs/plans/open-claude-remote-clone/adrs/`](./docs/plans/open-claude-remote-clone/adrs/)
211
+
212
+ ## Development
213
+
214
+ ```bash
215
+ pnpm install
216
+ pnpm dev # backend (tsx watch) + frontend (vite) in parallel
217
+ pnpm test # shared + backend + frontend unit tests
218
+ pnpm typecheck
219
+ pnpm build # full build artifacts (frontend copied into backend/frontend-dist)
220
+ ```
221
+
222
+
223
+ ## Roadmap
224
+
225
+ ### Tier 1 (must-have for mobile, low effort, big UX win)
226
+
227
+ 1. **Local Echo** (Mosh / Blink / code-server)
228
+ Input lag killer on mobile 4G/weak networks. xterm prediction plugin shows
229
+ keystrokes immediately, PTY response replaces.
230
+ 2. **Multi-line paste warning + bracketed paste** (VS Code, Tabby)
231
+ Mobile users paste 5-line commands from WeChat/email — currently goes
232
+ straight to PTY, dangerous. Detect multi-line → confirm dialog.
233
+ 3. **Shell Integration subset (OSC 633/133)**
234
+ - Command decorations (green/red dot)
235
+ - Run Recent Command — fuzzy cross-session history quick pick
236
+ - Both extremely friendly on mobile (slow typing → cross-session history
237
+ search is core)
238
+ 4. **Auto Reply** (VS Code)
239
+ Match prompt → auto-respond y/N. Mobile users hate typing `[y/N]`.
240
+ 5. **Process Revive** (VS Code terminal revive)
241
+ You already have instances.json; serialize scrollback into it. After restart
242
+ webapp can see the previous content. The only hard part on the LAN-only
243
+ route is serialization size — bumping to 5MB is fine.
85
244
 
86
- ## 配置
245
+ ### Tier 2 (mobile UX bonus)
87
246
 
88
- 启动时自动读 `~/.auvezy/terminal-remote/config.json`(首次启动自动生成)。
89
- VAPID `~/.auvezy/terminal-remote/vapid.json`,多实例注册表在
90
- `~/.auvezy/terminal-remote/instances/<port>.json`。
247
+ 6. **SmartKeys long-press menu** (Blink)
248
+ On-screen keyboard expansion row: long-press Tab → Shift+Tab; long-press Esc
249
+ → `^[`; long-press Ctrl → sticky until next key. We already have a Toolbar
250
+ shortcut panel, missing "long-press menu" + "modifier sticky".
251
+ 7. **Thumb-drag cursor strip** (Termius: long-press space as trackpad)
252
+ Bottom 8px transparent strip on the terminal area; drag = arrow key
253
+ sequence. Best solution for precise cursor movement on mobile.
254
+ 8. **OSC 8 hyperlinks + word-link / file-link** (VS Code)
255
+ xterm.js native LinkProvider — a few lines makes `src/foo.ts:42` clickable.
256
+ 9. **Multi-chord shortcuts / modifier sticky** (Tabby, Blink)
257
+ Mobile virtual modifier + Cmd-K Cmd-S two-step combos save more screen than
258
+ a wall of buttons.
259
+ 10. **Quick Fixes** (VS Code)
260
+ Scan output, suggest fixes. `fatal: ... --set-upstream` one-click apply.
261
+ High effort but very flashy.
91
262
 
92
- ## WSL 中跑、Windows 浏览器访问
263
+ ### Tier 3 (write permission / security / collaboration)
93
264
 
94
- WSL2 的两种网络模式行为不同:
265
+ 11. **Writable / Read-only split** (ttyd -W, gotty -w)
266
+ When multiple devices connect to one instance, others can be set to
267
+ read-only. Very low effort (distinguish at WS handshake).
268
+ 12. **Broadcast Input** (Termius: simultaneous input on multiple terminals)
269
+ When multiple webapps connect to one instance, broadcast the same input to
270
+ all PTYs. Easy to add to our multi-instance arch.
271
+ 13. **TLS self-signed cert** (ttyd -S, gotty -t)
272
+ HTTPS on LAN lets Web Push API work in more browsers (currently restricted
273
+ on LAN HTTP).
274
+ 14. **OAuth / client cert auth** (ttyd client cert)
275
+ On top of our token, add client cert for hardware auth. Low priority —
276
+ token is already enough.
95
277
 
96
- - **mirrored 模式**(Win11 22H2+ 默认):WSL 直接拿 Windows LAN IP(如 `192.168.x.x`),
97
- Windows 浏览器可以直接用 banner 上的 IP 访问,无需任何额外配置
98
- - **NAT 模式**(默认):WSL 在 `172.x.x.x` 私网,Windows 浏览器无法直连。
99
- backend 启动时会自动检测并在 banner 末尾打印 PowerShell 配置命令
278
+ ### Tier 4 (explicitly NOT copying)
100
279
 
101
- ## 系统要求
280
+ - ❌ Plugin system (Tabby): unnecessary for a LAN-only single binary
281
+ - ❌ Cloud Settings Sync (VS Code): conflicts with the LAN-only red line
282
+ - ❌ Sixel/iTerm image protocols: low value on mobile, xterm.js doesn't
283
+ natively support them
284
+ - ❌ asciinema public sharing: conflicts with LAN-only; if anything we'd only
285
+ do local `.cast` export
286
+ - ❌ SFTP/SCP file management (Termius/Wetty): outside the "remote PTY control"
287
+ scope
288
+ - ❌ End-to-end encrypted Vault: home LAN users don't need this
102
289
 
103
- - Node.js ≥ 20
290
+ ---
104
291
 
105
- ## 许可
292
+ ## Pain points unique to us (others haven't done these)
106
293
 
107
- 专有软件,保留所有权利。详见 LICENSE。
294
+ - **Tailscale / VPN QR code labeling**: we already do dual LAN+Tailscale codes —
295
+ a thoughtful detail on the LAN-only route
296
+ - **Webapp toast notification + iOS LocalNotification fallback**: under iOS PWA
297
+ push restrictions, this fallback strategy isn't considered by anyone else
package/dist/cli.js CHANGED
@@ -37,7 +37,7 @@ function isServerMessage(value) {
37
37
  if (!value || typeof value !== "object")
38
38
  return false;
39
39
  const type = value.type;
40
- return type === "terminal_output" || type === "status_update" || type === "history_sync" || type === "heartbeat" || type === "error" || type === "session_ended" || type === "terminal_resize" || type === "ip_changed";
40
+ return type === "terminal_output" || type === "status_update" || type === "history_sync" || type === "heartbeat" || type === "error" || type === "session_ended" || type === "terminal_resize" || type === "ip_changed" || type === "alt_screen_change";
41
41
  }
42
42
  var init_ws_protocol = __esm({
43
43
  "shared/dist/ws-protocol.js"() {
@@ -1755,7 +1755,7 @@ var init_pty_manager = __esm({
1755
1755
  */
1756
1756
  write(data) {
1757
1757
  if (!this.process) {
1758
- logger.warn({ dataLength: data.length }, "\u5C1D\u8BD5\u5199\u5165 PTY \u4F46\u8FDB\u7A0B\u672A\u8FD0\u884C");
1758
+ logger.debug({ dataLength: data.length }, "\u5C1D\u8BD5\u5199\u5165 PTY \u4F46\u8FDB\u7A0B\u672A\u8FD0\u884C");
1759
1759
  return;
1760
1760
  }
1761
1761
  this.process.write(data);
@@ -1864,6 +1864,7 @@ var init_pty_manager = __esm({
1864
1864
  if (this._inAltScreen !== isEnter) {
1865
1865
  this._inAltScreen = isEnter;
1866
1866
  logger.debug({ inAltScreen: isEnter }, "PTY alt-screen \u72B6\u6001\u5207\u6362");
1867
+ this.emit("altScreenChange", isEnter);
1867
1868
  }
1868
1869
  }
1869
1870
  }
@@ -1974,7 +1975,7 @@ var init_ws_server = __esm({
1974
1975
  }
1975
1976
  const clientType = this.opts.authenticate ? this.opts.authenticate(req) : "webapp";
1976
1977
  if (clientType === null) {
1977
- logger.warn({ url: req.url }, "WS upgrade \u88AB\u9274\u6743\u62D2\u7EDD");
1978
+ logger.debug({ url: req.url }, "WS upgrade \u88AB\u9274\u6743\u62D2\u7EDD");
1978
1979
  socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
1979
1980
  socket.destroy();
1980
1981
  return;
@@ -2433,6 +2434,9 @@ var init_session_controller = __esm({
2433
2434
  this.pty.on("resize", (cols, rows) => {
2434
2435
  this.ws.broadcast({ type: "terminal_resize", cols, rows });
2435
2436
  });
2437
+ this.pty.on("altScreenChange", (inAltScreen) => {
2438
+ this.ws.broadcast({ type: "alt_screen_change", inAltScreen });
2439
+ });
2436
2440
  }
2437
2441
  /**
2438
2442
  * 入队一段 PTY 输出,按三阈值决定是否立即 flush
@@ -2931,7 +2935,7 @@ function createWsAuthenticate(authModule) {
2931
2935
  logger.info({ remoteAddress: req.socket.remoteAddress }, "WS \u901A\u8FC7 URL token \u8BA4\u8BC1\uFF08attach\uFF09");
2932
2936
  return "attach";
2933
2937
  }
2934
- logger.warn({ remoteAddress: req.socket.remoteAddress }, "WS URL token \u65E0\u6548");
2938
+ logger.debug({ remoteAddress: req.socket.remoteAddress }, "WS URL token \u65E0\u6548");
2935
2939
  return null;
2936
2940
  }
2937
2941
  const cookieHeader = req.headers.cookie ?? "";
@@ -2939,7 +2943,7 @@ function createWsAuthenticate(authModule) {
2939
2943
  if (sid && authModule.validateSession(sid)) {
2940
2944
  return "webapp";
2941
2945
  }
2942
- logger.warn({
2946
+ logger.debug({
2943
2947
  remoteAddress: req.socket.remoteAddress,
2944
2948
  cookieNames: cookieHeader.split(";").map((c) => c.trim().split("=")[0]).filter(Boolean),
2945
2949
  expectedCookie: authModule.getCookieName()
@@ -4747,11 +4751,11 @@ var init_attach_client = __esm({
4747
4751
  try {
4748
4752
  parsed = JSON.parse(raw.toString());
4749
4753
  } catch {
4750
- logger.warn("\u6536\u5230\u975E JSON WS \u6D88\u606F\uFF0C\u5FFD\u7565");
4754
+ logger.debug("\u6536\u5230\u975E JSON WS \u6D88\u606F\uFF0C\u5FFD\u7565");
4751
4755
  return;
4752
4756
  }
4753
4757
  if (!isServerMessage(parsed)) {
4754
- logger.warn("\u6536\u5230\u4E0D\u8BC6\u522B\u7684 server message\uFF0C\u5FFD\u7565");
4758
+ logger.debug("\u6536\u5230\u4E0D\u8BC6\u522B\u7684 server message\uFF0C\u5FFD\u7565");
4755
4759
  return;
4756
4760
  }
4757
4761
  this.handleServerMessage(parsed);