dsh-mesh-access 1.0.2 → 1.0.4
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/CHANGELOG.md +16 -0
- package/README.en.md +39 -6
- package/README.md +38 -2
- package/lib/index.js +10 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 1.0.4 — 把「性能与限制」写清楚
|
|
4
|
+
|
|
5
|
+
功能不变。补了一节实测表格与判断方法,用来回答「卡是不是插件的问题」:
|
|
6
|
+
|
|
7
|
+
- 同局域网 1–3 ms / P2P 30–80 ms / UDP 中继 60–150 ms / **TCP 中继 840–990 ms(最坏 3.7 s)**;
|
|
8
|
+
- 解释了 TCP 中继为什么慢(TCP 套 TCP),以及手机 CGNAT 换地址导致 P2P 被隔离的日志长什么样;
|
|
9
|
+
- 给出自查命令 `grep -E 'SN-RTT|path=SN-TCP|peer-info-address-changed' /usr/local/lib/stars/res/stars.log`;
|
|
10
|
+
- 明确:心跳超时不是插件能修的,要稳定远程就上公网 VPS 中转。
|
|
11
|
+
|
|
12
|
+
## 1.0.3 — 静态资源长缓存(给慢链路减负)
|
|
13
|
+
|
|
14
|
+
上游返回的 `/assets/*.js|css` **没有** `cache-control`,也没有 `etag` / `last-modified`,浏览器无法判断新鲜度,重载时只能整套重新下载(一套 1.5–3 MB)。在组网这种 250 KB/s、往返 500 ms 的链路上,一次重连就等于重传一遍,越重连越慢、越慢越容易再断。
|
|
15
|
+
|
|
16
|
+
- 反代对 `/assets/<带哈希>.(js|css|woff2|png|svg|webp)` 补上 `cache-control: public, max-age=31536000, immutable`;
|
|
17
|
+
- 文件名本身带内容哈希,所以长缓存是安全的(换版本 = 换文件名)。
|
|
18
|
+
|
|
3
19
|
## 1.0.2 — 把卖点改成「手机远程控制」
|
|
4
20
|
|
|
5
21
|
功能没变,只是把话说对:这东西最主要的用法是**用手机远程控制电脑上的 DSH**(同一个实例、同一份会话),
|
package/README.en.md
CHANGED
|
@@ -92,12 +92,45 @@ Corollary: change the mesh IP or the port and you must open a fresh token URL on
|
|
|
92
92
|
|
|
93
93
|
## Limitations
|
|
94
94
|
|
|
95
|
-
-
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
- The plugin cannot move **its own process's** listen address. The mesh port is a reverse proxy, not a second bind.
|
|
96
|
+
- IPv4 mesh addresses only.
|
|
97
|
+
- One `~/.dsh` still admits only one task-board lock holder; that is deliberate, and this plugin exists to avoid needing a second instance.
|
|
98
|
+
|
|
99
|
+
## Performance and limits (measured)
|
|
100
|
+
|
|
101
|
+
Whether this feels good depends on **how the mesh picks a path**, not on the plugin. Numbers below were measured
|
|
102
|
+
on 2026-11-07 on a real link, so you can tell "is it the plugin?" — almost always it is not.
|
|
103
|
+
|
|
104
|
+
| Path | One-way latency | Note |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| Same LAN (`10.x.y.z:3081`) | **1–3 ms** | direct, feels local |
|
|
107
|
+
| Mesh direct (`path=P2P`) | 30–80 ms | best case, needs both sides hole-punchable |
|
|
108
|
+
| Mesh UDP relay | 60–150 ms | workable |
|
|
109
|
+
| Mesh **TCP relay** (`path=SN-TCP`) | **840–990 ms**, worst seen 3.7 s | painful, see below |
|
|
110
|
+
|
|
111
|
+
The TCP relay is TCP inside TCP: one lost packet stalls the whole ordered stream, so latency is amplified ~10×.
|
|
112
|
+
Worse, a phone on carrier-grade NAT keeps changing its address; the client then isolates the P2P link and falls back:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
[P2P] isolated the old direct link, waiting for a new P2P confirmation; policy picks Mesh/Supernode
|
|
116
|
+
[SN-RTT] Supernode round trip is slow: ... rtt=891ms path=SN-TCP
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
On such a link the DSH realtime channel's heartbeat times out periodically, which the UI shows as
|
|
120
|
+
an endless **"reconnecting…"** banner. The plugin already removes two of the amplifiers:
|
|
121
|
+
|
|
122
|
+
1. **rewrites `Origin`** — otherwise the WebSocket upgrade is rejected by DSH's own origin check with 403 and never connects;
|
|
123
|
+
2. **adds `immutable` caching for `/assets/*`** — upstream sends no cache headers at all, so every reload used to re-transfer ~2.4 MB.
|
|
124
|
+
|
|
125
|
+
Heartbeat timeouts themselves are not fixable in the plugin. Check the server side first:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
grep -E 'SN-RTT|path=SN-TCP|peer-info-address-changed' /usr/local/lib/stars/res/stars.log | tail
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- `path=SN-TCP` with `rtt` stuck above 800 ms → change the link, not the plugin;
|
|
132
|
+
- for dependable remote access, put a VPS (ideally in the same country) in front and let the PC dial out to it,
|
|
133
|
+
instead of relying on a mesh where both peers sit behind NAT and can only relay.
|
|
101
134
|
|
|
102
135
|
## Not just phones
|
|
103
136
|
|
package/README.md
CHANGED
|
@@ -102,10 +102,46 @@ curl -s http://127.0.0.1:3080/api/dsh-mesh-access/status
|
|
|
102
102
|
|
|
103
103
|
- 插件动不了**自己进程**的监听地址。组网端口是反代,不是把主服务再绑一次。
|
|
104
104
|
- 只考虑 IPv4 组网地址。
|
|
105
|
-
- 仍然依赖隧道本身的质量。星空组网走中继时实测单向约 0.4 MB/s,`[SN-RTT]` 会抖到几秒,
|
|
106
|
-
长回答可能中途停住,刷新一下能接回来。
|
|
107
105
|
- 同一份 `~/.dsh` 只允许一个实例持有 task-board 锁;这是刻意的,本插件就是为避开它而存在。
|
|
108
106
|
|
|
107
|
+
## 性能与限制(实测,别指望它像局域网)
|
|
108
|
+
|
|
109
|
+
这套东西能不能用得舒服,只取决于**组网隧道怎么给你选路**。下面是 2026-11-07 在一条真实链路上量到的一组数字,
|
|
110
|
+
用来判断「是不是插件的问题」——绝大多数时候不是。
|
|
111
|
+
|
|
112
|
+
| 场景 | 单程延迟 | 说明 |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| 同一局域网(`10.x.y.z:3081`) | **1–3 ms** | 直连,等于本地 |
|
|
115
|
+
| 组网直连(`path=P2P`) | 30–80 ms | 最好情况,但需要双方都能被打洞 |
|
|
116
|
+
| 组网 UDP 中继 | 60–150 ms | 可用 |
|
|
117
|
+
| 组网 **TCP 中继**(`path=SN-TCP`) | **840–990 ms**,最坏见过 3.7 s | 会很卡,见下 |
|
|
118
|
+
|
|
119
|
+
TCP 中继的坑在于它是 **TCP 套 TCP**:一个丢包会因为重传把整条流按顺序卡住,延迟被放大十几倍。
|
|
120
|
+
更麻烦的是手机侧的地址会变(运营商 CGNAT),客户端每次发现「对端地址变了」就会隔离 P2P 直连、回落中继:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
[P2P] 隔离旧直连,等待新的 P2P 确认;业务由 Mesh/Supernode 策略选路
|
|
124
|
+
reason=peer-info-address-changed
|
|
125
|
+
[SN-RTT] Supernode 业务包往返偏慢: ... rtt=891ms path=SN-TCP
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
这种链路上,DSH 的实时通道心跳会周期性超时,界面上表现为 **「重新连接中…」反复出现**。
|
|
129
|
+
插件做过两件事把代价压到最低:
|
|
130
|
+
|
|
131
|
+
1. **改写了 `Origin`**(否则 WebSocket 升级会被 DSH 的同源校验直接回 403,一次都握不上手);
|
|
132
|
+
2. **给 `/assets/*` 加了 `immutable` 缓存**(上游原本没有任何缓存头,每次重载都要重传约 2.4 MB)。
|
|
133
|
+
|
|
134
|
+
但**心跳超时本身救不了**。判断标准很简单:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# 在服务端看组网客户端的日志,确认走的是不是 TCP 中继
|
|
138
|
+
grep -E 'SN-RTT|path=SN-TCP|peer-info-address-changed' /usr/local/lib/stars/res/stars.log | tail
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- 出现 `path=SN-TCP` 且 `rtt` 常年 800 ms 以上 → 不是插件的问题,换链路才有用;
|
|
142
|
+
- 想稳定远程,请在公网放一台国内 VPS 做端口转发/隧道(手机走普通公网 TCP,PC 主动出站),
|
|
143
|
+
而不是指望一个「双方都在 NAT 后面、只能中继」的组网。
|
|
144
|
+
|
|
109
145
|
## 不只手机
|
|
110
146
|
|
|
111
147
|
名字叫 phone,但插件本身跟“手机”没有关系 —— 它做的是**把组网地址上的请求转给本进程**。
|
package/lib/index.js
CHANGED
|
@@ -88,6 +88,9 @@ function createProxy({ listenAddress, listenPort, upstream, upstreamPort }) {
|
|
|
88
88
|
// 上游地址,就必须把 Origin 一起改写成上游的 origin;否则浏览器的 WebSocket 升级
|
|
89
89
|
// 永远 403,界面上表现成「一直重新连接中」。
|
|
90
90
|
const upstreamOrigin = `http://${target}`
|
|
91
|
+
// 带内容哈希的静态资源(/assets/index-BXV-oxvX.js 这种)
|
|
92
|
+
const CACHEABLE = /\/assets\/[^?]+\.(?:js|css|woff2?|png|svg|webp)$/
|
|
93
|
+
|
|
91
94
|
const rewrite = (headers) => {
|
|
92
95
|
const h = { ...headers }
|
|
93
96
|
const incomingHost = headers.host ?? ''
|
|
@@ -102,7 +105,13 @@ function createProxy({ listenAddress, listenPort, upstream, upstreamPort }) {
|
|
|
102
105
|
const up = http.request(
|
|
103
106
|
{ host: upstream, port: upstreamPort, method: req.method, path: req.url, headers: rewrite(req.headers) },
|
|
104
107
|
(r) => {
|
|
105
|
-
|
|
108
|
+
const headers = { ...r.headers }
|
|
109
|
+
// 前端资源文件名带内容哈希,可以放心长缓存。上游没给 cache-control / etag,
|
|
110
|
+
// 浏览器就只能每次重新下载(一套 1.5–3 MB),在组网这种慢链路上等于每次重连都重传一遍。
|
|
111
|
+
if (r.statusCode === 200 && CACHEABLE.test(req.url || '')) {
|
|
112
|
+
headers['cache-control'] = 'public, max-age=31536000, immutable'
|
|
113
|
+
}
|
|
114
|
+
res.writeHead(r.statusCode ?? 502, headers)
|
|
106
115
|
r.pipe(res)
|
|
107
116
|
},
|
|
108
117
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mesh-access",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Control your DSH from your phone: opens a port on the mesh/VPN interface from inside the running server process, so a phone browser gets the same instance and the same session list — no SSH, no second dsh. Also reachable from another PC, a GUI NAS, or a TV box.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|