dsh-remote-web 0.6.4-beta.1 → 0.6.4-beta.2

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 (2) hide show
  1. package/lib/index.js +70 -16
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -228,9 +228,20 @@ function applySaaSMode(cfg) {
228
228
  * 老版本(无该鉴权)下 connection 服务没有 authenticatedUrl → 静默跳过,行为不变。
229
229
  */
230
230
  const HARNESS_COOKIE_FILE = ".harness-cookie.json";
231
+ /**
232
+ * 手机端 401「dsh web authentication required; reopen the URL printed by dsh web」的根因就在这里:
233
+ * 该 Cookie 由本插件在进程内经 ?token= 换取后交给 bridge 代持,**dsh web 每次重启都会换签名密钥**,
234
+ * 旧 Cookie 立即失效。旧实现只在启动后重试 20 次(约 60s)就放弃、之后 6 小时才刷新一次 ——
235
+ * 一旦启动那一刻 connection 服务还没就绪(或用户按引导重启 harness 后插件先于服务就绪),
236
+ * Cookie 就会长时间不可用,手机端看到那段英文提示且**无法自行恢复**(他打不开电脑上打印的 URL)。
237
+ * 现在改为:① 启动后持续重试 10 分钟;② 每 30 分钟主动刷新;③ 面板每次查状态时按需补齐;
238
+ * ④ bridge 撞到 401 会写「作废」标记,插件下次查状态立即重取。四处叠加后用户无需任何操作。
239
+ */
231
240
  const HARNESS_AUTH_RETRY_MS = 3000;
232
- const HARNESS_AUTH_RETRY_MAX = 20; // 最多约 60s connection 服务就绪
233
- const HARNESS_AUTH_REFRESH_MS = 6 * 3600 * 1000;
241
+ const HARNESS_AUTH_RETRY_WINDOW_MS = 10 * 60 * 1000; // 持续尝试 10 分钟,不再 60s 后放弃
242
+ const HARNESS_AUTH_REFRESH_MS = 30 * 60 * 1000; // 主动刷新周期(本地一次 fetch,成本可忽略)
243
+ const HARNESS_COOKIE_STALE_MS = 30 * 60 * 1000; // 超过视为陈旧 → 按需重取
244
+ const HARNESS_COOKIE_REVOKED_FILE = ".harness-cookie-revoked"; // bridge 撞 401 时写的标记
234
245
 
235
246
  /** 读取响应里的 set-cookie(兼容 getSetCookie / get 两种实现)。 */
236
247
  function setCookieOf(res) {
@@ -270,24 +281,63 @@ async function mintHarnessCookie(ctx, relayDir) {
270
281
  }
271
282
  }
272
283
 
273
- /** 后台调度:启动重试直到换取成功,成功后每 6h 刷新(与插件生命周期同进退)。 */
284
+ /** 读取当前 Cookie 文件状态:{cookie, authority, mintedAt} 或 null。 */
285
+ function readHarnessCookie(relayDir) {
286
+ try {
287
+ const j = JSON.parse(readFileSync(join(relayDir, HARNESS_COOKIE_FILE), "utf8"));
288
+ return j && typeof j.cookie === "string" && j.cookie ? j : null;
289
+ } catch {
290
+ return null;
291
+ }
292
+ }
293
+
294
+ let harnessMintInflight = false;
295
+ let harnessMintLastAt = 0;
296
+
297
+ /**
298
+ * 按需确保 Cookie 可用(去重 + 限频):
299
+ * 缺失 / 超过 HARNESS_COOKIE_STALE_MS / bridge 写了作废标记 → 重取一次。
300
+ * 被 /dsh-remote/status 与 /dsh-remote/bridge-status 调用(面板轮询 2.5~30s),
301
+ * 因此只要面板开着,手机端 401 会在秒级内自愈;不需要用户做任何事。
302
+ * @param {object} ctx cordis 上下文
303
+ * @param {string} relayDir 配置目录
304
+ * @param {{force?: boolean}} [opts]
305
+ * @returns {Promise<boolean>} 本次是否确认 Cookie 可用
306
+ */
307
+ async function ensureHarnessCookie(ctx, relayDir, opts) {
308
+ const force = !!(opts && opts.force);
309
+ const revoked = existsSync(join(relayDir, HARNESS_COOKIE_REVOKED_FILE));
310
+ if (!force && !revoked) {
311
+ const cur = readHarnessCookie(relayDir);
312
+ if (cur && Date.now() - Number(cur.mintedAt || 0) < HARNESS_COOKIE_STALE_MS) return true;
313
+ }
314
+ if (harnessMintInflight) return false; // 去重:同一时刻只换一次
315
+ if (Date.now() - harnessMintLastAt < 5000) return false; // 限频:5s 内不重复换
316
+ harnessMintInflight = true;
317
+ harnessMintLastAt = Date.now();
318
+ try {
319
+ const ok = await mintHarnessCookie(ctx, relayDir);
320
+ if (ok || revoked) {
321
+ try { rmSync(join(relayDir, HARNESS_COOKIE_REVOKED_FILE), { force: true }); } catch { /* ignore */ }
322
+ }
323
+ if (!ok && revoked) console.warn("[dsh-remote-web] 浏览器会话 Cookie 已被 dsh web 作废,重取失败,下次查状态再试");
324
+ return ok;
325
+ } finally {
326
+ harnessMintInflight = false;
327
+ }
328
+ }
329
+
330
+ /** 后台调度:启动后持续重试(10 分钟窗口) + 每 30 分钟主动刷新(与插件生命周期同进退)。 */
274
331
  function scheduleHarnessMint(ctx, relayDir) {
275
- let succeeded = false;
276
- let retries = 0;
277
- const attempt = async () => {
278
- if (succeeded) return;
279
- if (await mintHarnessCookie(ctx, relayDir)) succeeded = true;
280
- };
332
+ const startedAt = Date.now();
281
333
  const bootIv = setInterval(() => {
282
- if (succeeded || ++retries > HARNESS_AUTH_RETRY_MAX) {
283
- clearInterval(bootIv);
284
- return;
285
- }
286
- void attempt();
334
+ if (Date.now() - startedAt > HARNESS_AUTH_RETRY_WINDOW_MS) { clearInterval(bootIv); return; }
335
+ void ensureHarnessCookie(ctx, relayDir, { force: true }).catch(() => {});
287
336
  }, HARNESS_AUTH_RETRY_MS);
288
337
  bootIv.unref?.();
338
+ void ensureHarnessCookie(ctx, relayDir, { force: true }).catch(() => {}); // 立刻来一次
289
339
  const refreshIv = setInterval(() => {
290
- void mintHarnessCookie(ctx, relayDir).catch(() => {});
340
+ void ensureHarnessCookie(ctx, relayDir).catch(() => {});
291
341
  }, HARNESS_AUTH_REFRESH_MS);
292
342
  refreshIv.unref?.();
293
343
  return () => {
@@ -1673,7 +1723,7 @@ const PLUGIN_ID = "dsh-remote-web";
1673
1723
  const PLUGIN_LEGACY_IDS = ["dsh-remote-ui"];
1674
1724
  const PLUGIN_ALL_IDS = [PLUGIN_ID, ...PLUGIN_LEGACY_IDS];
1675
1725
  /** 插件自身发布版本(与 dsh-remote 根包同步递增)。 */
1676
- const PLUGIN_VERSION = "0.6.4-beta.1";
1726
+ const PLUGIN_VERSION = "0.6.4-beta.2";
1677
1727
  const UPDATE_LOG = ".dsh-update.log";
1678
1728
  const UPDATE_MARKER = ".dsh-update-running";
1679
1729
 
@@ -2325,6 +2375,9 @@ function registerRoutes(ctx, relayDir) {
2325
2375
  method: "GET",
2326
2376
  path: "/dsh-remote/status",
2327
2377
  handler: async (_req, res) => {
2378
+ // 按需补齐浏览器会话 Cookie(不 await:状态接口不能被本地换 Cookie 拖慢)。
2379
+ // 面板轮询会持续触达本接口 → 手机端的 401 会在秒级内自愈。
2380
+ void ensureHarnessCookie(ctx, relayDir).catch(() => {});
2328
2381
  sendJson(res, 200, await composeStatus(relayDir));
2329
2382
  },
2330
2383
  },
@@ -2337,6 +2390,7 @@ function registerRoutes(ctx, relayDir) {
2337
2390
  handler: async (_req, res) => {
2338
2391
  ensureConnection(relayDir);
2339
2392
  maybeReportInstall(relayDir);
2393
+ void ensureHarnessCookie(ctx, relayDir).catch(() => {}); // 同上:手机端授权会话自愈
2340
2394
  sendJson(res, 200, { ok: true, connect: await composeConnect(relayDir) });
2341
2395
  },
2342
2396
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-web",
3
- "version": "0.6.4-beta.1",
3
+ "version": "0.6.4-beta.2",
4
4
  "description": "公网远程控制 DeepSeek Harness(dsh web):安装即得专属加密地址,人在外面也能用手机访问电脑上的 dsh——无需同一局域网/WiFi、无需公网 IP 与内网穿透,全程加密;手机端 100% 还原电脑体验(对话/工具/审批/设置)。技术用户可选自建服务,流量走自己的服务器。Remote control DeepSeek Harness (dsh web) from anywhere over the public internet — install, get an encrypted URL, use it from your phone on any network (dual-half cordis plugin: Settings panel + same-origin /dsh-remote routes). (2026-09 由 dsh-remote-ui 更名 / renamed from dsh-remote-ui; dsh-remote 的 dsh web 插件半,不是纯 UI/皮肤插件)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",