dsh-mobile-plugin 0.1.87 → 0.1.88

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.
@@ -2,7 +2,10 @@
2
2
 
3
3
  /**
4
4
  * 局域网直连代理:手机在同一个 Wi-Fi 下直接访问
5
- * http://<电脑IP>:<lanPort>,经配对码校验后代理到本机 dsh web。
5
+ * http://<电脑IP>:<lanPort>,带上配对拿到的令牌后代理到本机 dsh web。
6
+ *
7
+ * 只服务 App:未认证的请求一律 JSON 401,不给任何网页。曾经的 HTML 配对页
8
+ * 与配对后能进入的网页本体已删除(见 docs/decisions.md 2026-10-19)。
6
9
  *
7
10
  * 与中转隧道的关系:同网用本代理(零延迟、不依赖公网),
8
11
  * 异地走 dsh-mobile-server 中转。两者互不影响,可同时开启。
@@ -154,69 +157,15 @@ function createLanProxy(opts) {
154
157
  + `; Path=/; HttpOnly; SameSite=Lax; Max-Age=${core.MOBILE_TOKEN_TTL_SEC}`,
155
158
  );
156
159
  }
157
- function clearCookie(res) {
158
- // 与 setCookie 保持同一组属性:属性不一致时部分客户端会当成另一个 cookie,删不掉。
159
- res.setHeader('set-cookie', 'dsh_lan=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0');
160
- }
161
-
162
- const PAGE = `<!DOCTYPE html>
163
- <html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
164
- <title>连接 DeepSeek Harness</title>
165
- <style>
166
- *{box-sizing:border-box;margin:0;padding:0}
167
- body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC',sans-serif;background:#0f1115;color:#e6e8eb;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
168
- .card{width:min(380px,100%);background:#171a21;border:1px solid #262b36;border-radius:16px;padding:28px}
169
- h1{font-size:18px;margin-bottom:6px}
170
- .sub{font-size:13px;color:#8b93a1;margin-bottom:20px;line-height:1.6}
171
- label{display:block;font-size:12px;color:#8b93a1;margin:14px 0 6px}
172
- input{width:100%;padding:10px 12px;font-size:15px;background:#0f1115;color:#e6e8eb;border:1px solid #2c3340;border-radius:10px;outline:none}
173
- input:focus{border-color:#4d6bfe}
174
- button{width:100%;margin-top:20px;padding:11px;font-size:15px;background:#4d6bfe;color:#fff;border:none;border-radius:10px;cursor:pointer}
175
- .err{margin-top:14px;font-size:13px;color:#f85149;min-height:18px}
176
- </style></head><body>
177
- <div class="card">
178
- <h1>连接 DeepSeek Harness</h1>
179
- <div class="sub">局域网直连 · 实例 <code>${instanceId}</code><br>请输入桌面端「远程连接」面板显示的 6 位配对码</div>
180
- <form id="f"><label for="code">配对码</label><input id="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" autocomplete="one-time-code">
181
- <button type="submit">连接</button></form>
182
- <div class="err" id="err"></div>
183
- </div>
184
- <script>
185
- document.getElementById('f').addEventListener('submit', async (e) => {
186
- e.preventDefault();
187
- const code = document.getElementById('code').value.trim();
188
- if (!/^\d{6}$/.test(code)) {
189
- document.getElementById('err').textContent = '请输入 6 位数字配对码';
190
- return;
191
- }
192
- const r = await fetch('/__lan_pair', { method:'POST', headers:{'content-type':'application/json'},
193
- body: JSON.stringify({ code }) });
194
- if (r.ok) { location.href = '/'; }
195
- else { document.getElementById('err').textContent = '配对码错误或尝试过于频繁'; }
196
- });
197
- </script></body></html>`;
198
-
199
- /**
200
- * 是否是「程序化 API 请求」。
201
- *
202
- * 未认证时给浏览器导航回配对页(200 HTML)是刻意的:用户直接在手机浏览器里
203
- * 打开局域网地址即可输入配对码。但**绝不能**这样答复 API 请求 ——
204
- * `/api/mobile/v1/*` 被答成 200 + HTML 时,App 的 `app/src/api.ts` 会把它当成
205
- * 成功(`response.ok` 为真 → `publishRoute` 把连接通道钉在局域网、并重置局域网
206
- * 熔断),随后 `res.json()` 解析失败,`unwrapResult` 抛出「请求失败
207
- * (HTTP 200)」。结果是桌面端换过 lanSecret、或设备凭据被吊销之后,App 会永久
208
- * 卡在局域网通道上:既不会回退到中转,也不会提示重新配对。
209
- *
210
- * 判定规则:路径以 `/api/` 开头,或客户端明确不接受 `text/html`。
211
- */
212
- function isApiRequest(req, url) {
213
- if (url.pathname.startsWith('/api/')) return true;
214
- return !/\btext\/html\b/i.test(String(req.headers.accept || ''));
215
- }
216
-
217
160
  /**
218
- * 未认证的 API 请求:回 JSON 401,与移动 API 的鉴权失败保持同一种形状,
161
+ * 未认证的请求一律回 JSON 401,与移动 API 的鉴权失败保持同一种形状 ——
219
162
  * App 据此显示可读的「缺少或无效的移动 token」,而不是「请求失败 (HTTP 200)」。
163
+ *
164
+ * 浏览器导航也走这条路:这里**不再**提供网页版的配对页与配对后进入的网页本体,
165
+ * 手机端只有 App 一条路。给网页回 200 HTML 曾是刻意行为,但 App 的
166
+ * `app/src/api.ts` 会把 `response.ok` 当成功、把连接通道钉在局域网并重置熔断,
167
+ * 随后 `res.json()` 解析失败,桌面端换过 lanSecret 或凭据被吊销之后就永久卡住 ——
168
+ * 一个「谁都能打开的页面」换来的这点便利不值这条风险。
220
169
  */
221
170
  function sendUnauthorized(res) {
222
171
  try {
@@ -225,11 +174,6 @@ document.getElementById('f').addEventListener('submit', async (e) => {
225
174
  } catch { /* 客户端已断开 */ }
226
175
  }
227
176
 
228
- function sendPage(res, status) {
229
- res.writeHead(status, { 'content-type': 'text/html; charset=utf-8' });
230
- res.end(PAGE);
231
- }
232
-
233
177
  function proxyToUpstream(req, res) {
234
178
  // 读取隔离:命中本地镜像快照时直接回,不再连主进程。
235
179
  // 例外:带 ?fresh=1 的写后刷新必须回源——镜像最多旧 500ms,撞上写操作会把刚做完的
@@ -440,12 +384,6 @@ document.getElementById('f').addEventListener('submit', async (e) => {
440
384
  server = http.createServer((req, res) => {
441
385
  const url = new URL(req.url, 'http://lan.local');
442
386
  try {
443
- if (url.pathname === '/__lan_logout') {
444
- clearCookie(res);
445
- res.writeHead(302, { location: '/' });
446
- res.end();
447
- return;
448
- }
449
387
  if (url.pathname === '/__lan_pair' && req.method === 'POST') {
450
388
  const ip = req.socket.remoteAddress || '';
451
389
  let b = '';
@@ -546,9 +484,7 @@ document.getElementById('f').addEventListener('submit', async (e) => {
546
484
  return;
547
485
  }
548
486
  if (!authorized(req)) {
549
- // API 请求必须回 JSON 401(见 isApiRequest 注释);只有浏览器导航才给配对页。
550
- if (isApiRequest(req, url)) sendUnauthorized(res);
551
- else sendPage(res, 200);
487
+ sendUnauthorized(res);
552
488
  return;
553
489
  }
554
490
  const c = credential(req);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile-plugin",
3
- "version": "0.1.87",
3
+ "version": "0.1.88",
4
4
  "description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (0.2.0-rc.2 compatible, peers resolved by the host)",
5
5
  "license": "MIT",
6
6
  "type": "module",