mn-ws 0.0.0-stage → 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.
- package/README.md +250 -2
- package/bin/index.js +714 -0
- package/client/bridge-client.js +160 -0
- package/client/web.js +234 -0
- package/client/ws-client.js +191 -0
- package/lib/apps.js +237 -0
- package/lib/auth.js +319 -0
- package/lib/config.js +534 -0
- package/lib/index.js +15 -0
- package/lib/logger.js +63 -0
- package/lib/protocol.js +208 -0
- package/lib/store.js +66 -0
- package/package.json +43 -4
- package/server/auth.js +274 -0
- package/server/server.js +752 -0
package/README.md
CHANGED
|
@@ -1,3 +1,251 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mn-ws —— HTTP ⇄ WebSocket 双向桥
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
把本机跑的 **nx 系列服务**(nx-kv / nx-rh / nx-sk …)经一条 WebSocket 长连接,
|
|
4
|
+
桥给**只能发普通 HTTP 的远端客户端**。
|
|
5
|
+
|
|
6
|
+
**不修改任何现有 nx 服务。**
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
┌──────────────────────────┐
|
|
10
|
+
│ 客户端(curl / fr / Worker)│
|
|
11
|
+
└────────────┬─────────────┘
|
|
12
|
+
│ HTTP(带鉴权头 + 应用标识)
|
|
13
|
+
▼
|
|
14
|
+
┌────────────────────────────────────────┐
|
|
15
|
+
│ mn-ws server(公网机) :7777 │
|
|
16
|
+
│ HTTP 面:/health /stats /clients │
|
|
17
|
+
│ /<app>/* ← 桥接入口 │
|
|
18
|
+
│ WS 面:/ws(与 HTTP 同端口共存) │
|
|
19
|
+
└────────────┬───────────────────────────┘
|
|
20
|
+
│ WebSocket(出向连接,穿 NAT)
|
|
21
|
+
▼
|
|
22
|
+
┌────────────────────────────────────────┐
|
|
23
|
+
│ mn-ws client(本机,跑在 nx 那台机器) │
|
|
24
|
+
│ 收到帧 → 还原 HTTP → 打 127.0.0.1:端口 │
|
|
25
|
+
└────────────┬───────────────────────────┘
|
|
26
|
+
▼ HTTP (loopback)
|
|
27
|
+
nx-kv / nx-rh / …
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 快速上手
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# ── 公网机:起中继服务(必须有鉴权,否则拒绝启动)──
|
|
36
|
+
npx mn-ws server --host 0.0.0.0 --port 7777 --token <门禁TOKEN>
|
|
37
|
+
|
|
38
|
+
# ── 本机:起转发,把要桥的应用列成白名单 ──
|
|
39
|
+
npx mn-ws client --url ws://<服务器>:7777/ws --token <门禁TOKEN> \
|
|
40
|
+
--apps nx-kv,nx-rh
|
|
41
|
+
|
|
42
|
+
# ── 远端客户端:直接当普通 HTTP 服务用 ──
|
|
43
|
+
curl -H 'X-Mn-Ws-Token: <门禁TOKEN>' \
|
|
44
|
+
'http://<服务器>:7777/nx-kv/api/todo/list'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**本地开发**(还没发布到 npm 时):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cd mn-ws && npm install
|
|
51
|
+
node bin/index.js server --port 7777 --token dev
|
|
52
|
+
node bin/index.js client --url ws://127.0.0.1:7777/ws --token dev --apps nx-kv
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 命令
|
|
58
|
+
|
|
59
|
+
| 命令 | 说明 |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `mn-ws server` | 启动中继服务(公网半):HTTP 与 WS 同端口共存 |
|
|
62
|
+
| `mn-ws client` | 启动本机转发:连上 server 并注册可桥接的应用 |
|
|
63
|
+
| `mn-ws client web` | 本地 Web 面板(只监听 127.0.0.1):可桥接清单 + 状态 + API 试验台 |
|
|
64
|
+
| `mn-ws client apps` | 列出可桥接的应用(白名单 + 本地端口存活探测) |
|
|
65
|
+
| `mn-ws client catalog` | 拉取远端的元信息目录(登录后看哪些能路由) |
|
|
66
|
+
| `mn-ws client status` | 查看桥接连接状态 |
|
|
67
|
+
| `mn-ws auth login` | 统一登录(go-ah auth-hub),与 mn-rt 的 `auth login` 同款流程 |
|
|
68
|
+
| `mn-ws auth list\|switch` | 查看 / 切换本机已存账号 |
|
|
69
|
+
| `mn-ws auth status\|logout` | 查看 / 清除登录凭据 |
|
|
70
|
+
| `mn-ws doctor` | 环境自检:端口占用 / 鉴权 / 应用可达性 / server 连通性 |
|
|
71
|
+
|
|
72
|
+
常用选项:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
--url <ws://...> server 半地址
|
|
76
|
+
--server-http <url> 统一登录用的 HTTP 基址(默认从 --url 推导)
|
|
77
|
+
--host <h> 监听地址(server 默认 0.0.0.0;本地面板固定 127.0.0.1)
|
|
78
|
+
--port <n> server 端口(默认 7777)
|
|
79
|
+
--web-port <n> 本地面板端口(默认 7777)
|
|
80
|
+
--token <TOKEN> 注册门禁(server 与 client 配同一个)
|
|
81
|
+
--apps <a,b,c> 只桥接这些应用(白名单;不给就一个都不桥)
|
|
82
|
+
--ports <p,q> 只桥接这些本地端口(从已知表反查应用名)
|
|
83
|
+
--app-map <a=url,...> 指定应用的本地基址(如 nx-kv=http://127.0.0.1:7999)
|
|
84
|
+
--device-name <名> 桥的显示名
|
|
85
|
+
--no-open auth login 只打印授权地址、不弹浏览器
|
|
86
|
+
--insecure server 允许无鉴权启动(**仅限本机自测**)
|
|
87
|
+
--log-level <level> debug | info | warn | error
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 远端怎么用:先看目录,再选,再路由
|
|
93
|
+
|
|
94
|
+
远端客户端拿到凭据后的标准动作是**先拉目录**(知道能路由到什么),再按应用标识发起请求:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# ① 拉元信息目录(哪些能路由、分别在哪个端口、由哪台桥承载)
|
|
98
|
+
curl -H 'X-Mn-Ws-Token: <门禁TOKEN>' http://<服务器>:7777/api/catalog
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"ok": true,
|
|
104
|
+
"count": 2,
|
|
105
|
+
"apps": [
|
|
106
|
+
{"app":"nx-kv","title":"KV 清单中枢","port":7877,"base":"http://127.0.0.1:7877",
|
|
107
|
+
"bridgeId":"host-a","connectedAt":"2026-11-12T11:00:00.000Z"},
|
|
108
|
+
{"app":"nx-rh","title":"仓库与 Skill 中枢","port":7800,"base":"http://127.0.0.1:7800",
|
|
109
|
+
"bridgeId":"host-b","connectedAt":"2026-11-12T11:00:01.000Z"}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
命令行等价物:`npx mn-ws client catalog --url ws://<服务器>:7777/ws --token <TOKEN>`
|
|
115
|
+
|
|
116
|
+
② 按目录里选中的应用发起请求 —— **路径前缀**与**请求头**两种写法等价:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# 路径前缀
|
|
120
|
+
curl -H 'X-Mn-Ws-Token: $TOKEN' 'http://<服务器>:7777/nx-kv/api/todo/list'
|
|
121
|
+
|
|
122
|
+
# 请求头(路径原样,适合已有客户端不想改 URL)
|
|
123
|
+
curl -H 'X-Mn-Ws-Token: $TOKEN' -H 'X-Nx-App: nx-kv' \
|
|
124
|
+
'http://<服务器>:7777/api/todo/list'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
两者冲突 → `400 app_conflict`(**不猜**:猜错等于把请求打到别的服务上)。
|
|
128
|
+
|
|
129
|
+
### 多路复用
|
|
130
|
+
|
|
131
|
+
每条 client half 的 WS 连接是一个独立的「桥」。server 半维护 `app → ws` 的路由表,
|
|
132
|
+
**请求按应用标识落到对应的那条 WS 上**:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
┌── WS#1(host-a:nx-kv)──▶ 127.0.0.1:7877
|
|
136
|
+
客户端 ──HTTP──▶ server ─┤
|
|
137
|
+
└── WS#2(host-b:nx-rh)──▶ 127.0.0.1:7800
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
同机跑两个 client、或多台机器各跑一个,都由这张表分流,互不串台。
|
|
141
|
+
多个 client 声明同一个 app 时**后来者顶替前者**(同机跑两份 nx 是异常状态,
|
|
142
|
+
顶替比随机命中更可预期),被顶替的 client 会收到 `replaced` 通知。
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 应用标识(app)
|
|
147
|
+
|
|
148
|
+
一个请求该去哪个本地端口,靠**应用标识**区分。两种都支持,**路径前缀优先**:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
① POST /nx-kv/api/todo/add → app=nx-kv, path=/api/todo/add
|
|
152
|
+
② POST /api/todo/add (X-Nx-App: nx-kv) → app=nx-kv, path=/api/todo/add
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- 路径前缀优先:客户端零改造(curl 直接打)、自定义头不会被中间反代剥掉、一个请求只对应一个 app
|
|
156
|
+
- 两者冲突 → `400 app_conflict`(**不猜**:猜错等于把请求打到别的服务上)
|
|
157
|
+
- 都缺 → `400 app_required`
|
|
158
|
+
|
|
159
|
+
### 哪些可以桥接
|
|
160
|
+
|
|
161
|
+
**白名单说了算**:只有 `--apps` / `--app-map` 里列出的应用才会被桥。
|
|
162
|
+
端口探测(`mn-ws client apps`)**只用来显示状态,不会自动开桥** —— 扫到端口在监听就自动开桥,等于把整台机器暴露出去。
|
|
163
|
+
|
|
164
|
+
已知 nx 服务与默认端口(2026-11-12 实测各仓 `core/paths.js`):
|
|
165
|
+
|
|
166
|
+
| app | 端口 | 服务 |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| nx-rh | 7800 | 仓库与 Skill 中枢 |
|
|
169
|
+
| nx-rp | 7820 | 外部上下文 |
|
|
170
|
+
| nx-sk | 7866 | 个人资源与密钥 |
|
|
171
|
+
| nx-ft | 7869 | 前端资源管理 |
|
|
172
|
+
| nx-kv | 7877 | KV 清单中枢 |
|
|
173
|
+
| nx-nx | 7880 | 项目生成器 |
|
|
174
|
+
| nx-kn | 7881 | Obsidian 知识库 |
|
|
175
|
+
| nx-ditto | 7883 | ditto |
|
|
176
|
+
| nx-pg | 7888 | 画布工作台 |
|
|
177
|
+
|
|
178
|
+
端口变了就用 `--app-map nx-kv=http://127.0.0.1:7999` 覆盖。
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 三层鉴权
|
|
183
|
+
|
|
184
|
+
| 层 | 管什么 | 载体 |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| **L1 通道层** | 谁能用这个桥 | `X-Mn-Ws-Token` 头(设备令牌 或 静态门禁) |
|
|
187
|
+
| **L2 路由层** | 请求的 app 必须在该桥的注册表里,且属主匹配 | 应用标识 |
|
|
188
|
+
| **L3 应用层** | nx 自己的那套鉴权 | `Authorization` / `Cookie` **原样透传** |
|
|
189
|
+
|
|
190
|
+
**两层凭据必须分开** —— `Authorization` 是 nx 的地盘(nx-kv 就是读 `Authorization: Bearer`),
|
|
191
|
+
L1 走独立头 `X-Mn-Ws-Token`,否则两边令牌互相打架 → 401。
|
|
192
|
+
|
|
193
|
+
`X-Mn-Ws-Token` 是桥接层内部事务,**不会**被透传给目标服务。
|
|
194
|
+
|
|
195
|
+
### 设备令牌 vs 静态门禁
|
|
196
|
+
|
|
197
|
+
| | 设备令牌(统一登录) | 静态门禁 `--token` |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| 隔离 | 按用户 —— 只能访问自己名下的桥 | 共享密钥 —— 等价全量访问 |
|
|
200
|
+
| 吊销 | 可单独吊销 | 换密钥才算换 |
|
|
201
|
+
| 建议 | **生产用这个** | 仅自测 / 单人使用 |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 安全
|
|
206
|
+
|
|
207
|
+
- **server 无鉴权直接拒绝启动**(`--insecure` 才放行,且会打醒目警告)
|
|
208
|
+
- **无鉴权时不允许绑 0.0.0.0**
|
|
209
|
+
- **端口占用预检**:被占就明确报错退出,**不静默顺延**(顺延会让客户端连错端口,两边对不上账)
|
|
210
|
+
- **本地面板只绑 127.0.0.1**
|
|
211
|
+
- **每次经桥的请求落审计**(`~/.mn-ws/data/requests.jsonl`)
|
|
212
|
+
- **跨站防护不被绕过**:Origin 头在桥接时被剥离 —— 桥接请求本来就来自本机,
|
|
213
|
+
剥掉是恢复真实语义(不剥的话 nx-kv 的 `originAllowed()` 会 403)
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 开发
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npm install
|
|
221
|
+
node test/unit.js # 协议 + 端到端透传 + 多路复用 + 元信息目录 + 默认端口(34 例)
|
|
222
|
+
node test/smoke-cli.js # 真实起两个进程打穿(11 例)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
设计文档:[docs/design.md](docs/design.md) · 部署手册:[docs/DEPLOY.md](docs/DEPLOY.md)
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 部署(server 半)
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# 本地构建镜像
|
|
233
|
+
docker build -f Dockerfile -t mn-ws-server .
|
|
234
|
+
|
|
235
|
+
# 起服务(没有鉴权会被拒绝启动)
|
|
236
|
+
docker run -d --name mn_ws_server -p 7777:7777 \
|
|
237
|
+
-e MN_WS_TOKEN=<门禁> -e MN_WS_JWT_SECRET=<随机> \
|
|
238
|
+
-e MN_WS_AUTH_ENABLED=1 -e MN_WS_ISSUER=http://47.110.80.47:8082 \
|
|
239
|
+
-v mn_ws_data:/app/data mn-ws-server:latest
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
线上部署走 GitHub Actions(`.github/workflows/deploy.yml`):
|
|
243
|
+
push 到 `main` → 构建 → `docker save` + gzip → scp → 服务器 `docker load` → 重启 → 验活。
|
|
244
|
+
**镜像不走 GHCR** —— 服务器 pull 实测太慢,改成镜像直传,部署时间可控。
|
|
245
|
+
|
|
246
|
+
发版走附注 tag(`.github/workflows/release.yml`):
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
git tag -a v0.1.0 -m "v0.1.0" && git push origin v0.1.0
|
|
250
|
+
# → 校验 tag 与 package.json 版本一致 → 跑测试 → npm publish → 建 GitHub Release
|
|
251
|
+
```
|