dsh-remote-web 0.6.18-beta.1 → 0.6.18-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-web",
3
- "version": "0.6.18-beta.1",
3
+ "version": "0.6.18-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",
@@ -710,7 +710,7 @@
710
710
  /** 生成 window.WebSocket 的替换构造函数:命中策略才套 SeE2eeWs,否则原样放行。 */
711
711
  function seMakeWsCtor(NativeWS, ctx) {
712
712
  function SeWs(url, protocols) {
713
- var full = "", should = false, scoped = String(url);
713
+ var full = "", should = false, scoped = String(url), passUrl = url;
714
714
  try {
715
715
  var base = ctx.origin || (typeof window !== "undefined" ? window.location.href : undefined);
716
716
  var uo = new URL(url, base);
@@ -727,12 +727,19 @@
727
727
  scoped = seScopeToDevice(uo.pathname + uo.search, ctx.deviceId);
728
728
  var so = scoped === (uo.pathname + uo.search) ? uo : new URL(scoped, base);
729
729
  full = so.pathname + so.search;
730
- should = seShouldEncryptWs(full);
730
+ /* ★ 关键:改写结果要**留住**。原来不改写分支 return 的是原始 url,等于把
731
+ `/remote/<dev>/api/remote.mux` 又丢回根路径 —— 那正是 404 的来源。 */
732
+ passUrl = so.href;
733
+ /* ⚠️ 必须同时要求"有会话":URL 改写在无会话时也要做(这是 2026-09-26 事故的正题),
734
+ 但**加密**没有会话就做不了 —— 少了这个判断,未解锁的页面上任何
735
+ `new WebSocket("/api/…")` 都会在 ctx.sess.sessId 上抛 TypeError,
736
+ 把整个页面打挂(比原来"不装补丁"还糟)。 */
737
+ should = !!ctx.sess && seShouldEncryptWs(full);
731
738
  }
732
739
  } catch (e) { should = false; }
733
- if (!should) return new NativeWS(url, protocols);
740
+ if (!should) return new NativeWS(passUrl, protocols);
734
741
  var target = seBuildWsTarget(scoped, ctx.sess.sessId, seRandomHex(8), base);
735
- if (!target) return new NativeWS(url, protocols);
742
+ if (!target) return new NativeWS(passUrl, protocols);
736
743
  return new SeE2eeWs(NativeWS, target.href, protocols, { sess: ctx.sess, wsLabel: target.wsLabel });
737
744
  }
738
745
  SeWs.OPEN = 1; SeWs.CONNECTING = 0; SeWs.CLOSING = 2; SeWs.CLOSED = 3;
@@ -875,9 +882,28 @@
875
882
  + "一直这样请返回设备列表重新解锁(会自动重建加密通道)。");
876
883
  }
877
884
 
878
- /* ---- 安装 fetch/WS 加密补丁(有会话才安装) ---- */
885
+ /* ---- 补丁安装:**URL 改写永远装,加密按会话开关** ----
886
+ *
887
+ * 🔴 2026-09-27 修(P0-1 收尾):原来 fetch/WS 补丁只在"有会话"时安装(`_install(sess)`),
888
+ * 可**这正是它最需要生效的场景**:镜像页没有会话 → 不装补丁 → 官方客户端把 mux WS 拼成根路径
889
+ * `/api/remote.mux` → 中继 `resolveRoute` 只认 `dsh_device` cookie,cookie 过期/缺失就 404
890
+ * (`rejectUpgrade` 写状态行后立刻 destroy → nginx 只看到不透明的 009)→
891
+ * `e2ee-hello` 永远送不到宿主机 → **会话永远建不起来** → 之后每个受保护请求都是
892
+ * `unknown_session` 502。**死循环**:没会话→不装补丁→建不了会话。
893
+ * 生产已有实锤:上线 reject 分支日志后中继立刻打出
894
+ * `WS 升级被拒: reason=route_unresolved status=404 path=/api/remote.mux`。
895
+ *
896
+ * 现在:`_install()` 在 boot 阶段**无条件**安装(`ctx.sess` 先为 null),会话到了再填进去。
897
+ * 补丁里"不改写就原样放行"的分支也会带上改写后的 URL(见 seMakeWsCtor 的 passUrl)——
898
+ * 否则改写算出来又被丢掉,等于没做。
899
+ */
879
900
  var _installed = false;
901
+ var _ctx = null;
880
902
  function _install(sess) {
903
+ if (sess) {
904
+ // 会话后到:只更新 ctx(补丁已装好,闭包读的是同一个对象)
905
+ if (_ctx) { _ctx.sess = sess; return; }
906
+ }
881
907
  if (_installed) return;
882
908
  _installed = true;
883
909
  seFailureHook = _reportFail;
@@ -886,7 +912,8 @@
886
912
  /* deviceId 取自**页面路径**(`/remote/<deviceId>/…`,2026-09-19 起的标准入口):
887
913
  WS 侧用它把根路径的 mux 改写成设备作用域形态,从而不再依赖会过期的 dsh_device cookie
888
914
  (见 seScopeToDevice 的长注释与 2026-09-26 的 nginx 日志证据)。读不到就留空 = 不改写。 */
889
- var ctx = { sess: sess, origin: window.location.origin, deviceId: seDeviceIdOfPath(window.location.pathname) };
915
+ _ctx = { sess: sess || null, origin: window.location.origin, deviceId: seDeviceIdOfPath(window.location.pathname) };
916
+ var ctx = _ctx;
890
917
  try {
891
918
  if (window.fetch) {
892
919
  var origFetch = window.fetch.bind(window);
@@ -906,6 +933,7 @@
906
933
  function _boot() {
907
934
  try {
908
935
  if (!(window.crypto && window.crypto.subtle && window.crypto.getRandomValues)) {
936
+ _install(null);
909
937
  _badge("warn", "未加密:环境不支持", "当前浏览器环境不支持 WebCrypto(需 HTTPS 安全上下文),镜像页数据流保持明文。");
910
938
  return;
911
939
  }
@@ -936,12 +964,16 @@
936
964
  return;
937
965
  }
938
966
  }
967
+ /* ⚠️ 即使没有会话也必须先装补丁:WS 的设备作用域改写与加密无关(见 _install 长注释)。
968
+ 否则这里 return 掉 → mux 打根路径 → 404 → 会话永远建不起来(死循环)。 */
969
+ _install(null);
939
970
  _badge("warn", "未加密:未解锁(明文)", "本次为明文连接。密钥只存内存;请返回设备列表重新点选设备解锁后,镜像页的数据请求与 WebSocket 将自动加密。");
940
971
  return;
941
972
  }
942
973
  var sess = null;
943
974
  try { sess = seSessionOfHandover(raw); } catch (e) { sess = null; }
944
975
  if (!sess) {
976
+ _install(null);
945
977
  _badge("warn", "未加密:交接单无效", "一次性交接单无法解析(可能已损坏或过期)。请返回设备列表重新解锁。");
946
978
  return;
947
979
  }