rdsh-gateway 0.11.1 → 0.11.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/dist/join.js +34 -20
  2. package/package.json +1 -1
package/dist/join.js CHANGED
@@ -481,27 +481,19 @@ export function startJoin(opts) {
481
481
  send(encodeFrame(FRAME_TYPE.ERROR, frame.streamId, jsonPayload({ code: "BAD_OPEN", message: "malformed open" })));
482
482
  return;
483
483
  }
484
- // ---- raw 门禁 + 候选端点(仅 raw inner dispatcher:dio.rawGate 定义时)----
485
- if (dio?.rawGate !== undefined) {
486
- // raw 门禁(仅设口令时):授权前只接受 authorize 请求,否则 fail-closed 关流(修 raw 绕过,R7)
487
- if (gate.accessCode !== null && !rawAuthorized) {
488
- if (path.startsWith("/__rdsh/authorize")) {
489
- const token = new URL(path, "http://rdsh.local").searchParams.get("token") ?? "";
490
- if (token !== "" && verifyGateCookie(gate.accessCode, token)) {
491
- rawAuthorized = true;
492
- sendSyntheticHttp(send, frame.streamId, 204, {}, Buffer.alloc(0));
493
- return;
494
- }
484
+ // ---- raw 门禁(仅 raw inner dispatcher:dio.rawGate 定义时)----
485
+ if (dio?.rawGate !== undefined && gate.accessCode !== null && !rawAuthorized) {
486
+ // 授权前只接受 authorize 请求,否则 fail-closed 关流(修 raw 绕过,R7)
487
+ if (path.startsWith("/__rdsh/authorize")) {
488
+ const token = new URL(path, "http://rdsh.local").searchParams.get("token") ?? "";
489
+ if (token !== "" && verifyGateCookie(gate.accessCode, token)) {
490
+ rawAuthorized = true;
491
+ sendSyntheticHttp(send, frame.streamId, 204, {}, Buffer.alloc(0));
492
+ return;
495
493
  }
496
- send(encodeFrame(FRAME_TYPE.CLOSE, frame.streamId, jsonPayload({ code: 403, message: "raw stream not authorized" })));
497
- return;
498
- }
499
- // 候选 + 一次性直连票(无论是否设口令都提供;经 E2EE,hub 不可见,R3/R4)
500
- if (path === "/__rdsh/direct-candidates" || path.startsWith("/__rdsh/direct-candidates?")) {
501
- const body = Buffer.from(JSON.stringify({ candidates: dio.rawGate.candidates(), ticket: dio.rawGate.mintTicket() }));
502
- sendSyntheticHttp(send, frame.streamId, 200, { "content-type": "application/json" }, body);
503
- return;
504
494
  }
495
+ send(encodeFrame(FRAME_TYPE.CLOSE, frame.streamId, jsonPayload({ code: 403, message: "raw stream not authorized" })));
496
+ return;
505
497
  }
506
498
  // ---- 访问口令 gate(仅 plain dispatcher:dio.gate=true 且已设 accessCode)----
507
499
  if (dio?.gate === true && gate.accessCode !== null) {
@@ -525,6 +517,21 @@ export function startJoin(opts) {
525
517
  return;
526
518
  }
527
519
  }
520
+ // ---- 直连候选 + 一次性票(**raw 与 plain 两条路径都要提供**)----
521
+ // 位置在**两道门禁之后**:raw 已过 rawGate,plain 已过访问口令(未过则上面已 return),
522
+ // 因此既不会泄露内网候选地址、也不会在未授权时签发直连票(R3/R4/R7)。
523
+ // 为什么 plain 也要提供:plain 页同样注入了直连 bootstrap(见 plainDispatcher 的 htmlInject),
524
+ // 缺了这个端点,页面里的 fetch("/__rdsh/direct-candidates") 会被转发给 dsh web → 404,
525
+ // 于是 window.__rdshDirectInfo 永不赋值 —— 即 2026-10-04 真机 "no __rdshDirectInfo" 的根因。
526
+ if (dio?.directCandidates !== undefined &&
527
+ (path === "/__rdsh/direct-candidates" || path.startsWith("/__rdsh/direct-candidates?"))) {
528
+ const body = Buffer.from(JSON.stringify({
529
+ candidates: dio.directCandidates.candidates(),
530
+ ticket: dio.directCandidates.mintTicket(),
531
+ }));
532
+ sendSyntheticHttp(send, frame.streamId, 200, { "content-type": "application/json" }, body);
533
+ return;
534
+ }
528
535
  if (kind === "ws") {
529
536
  openWsStream(frame.streamId, path, headers);
530
537
  return;
@@ -563,7 +570,11 @@ export function startJoin(opts) {
563
570
  delete outHeaders["transfer-encoding"];
564
571
  if (!adapterInjectionLogged) {
565
572
  adapterInjectionLogged = true;
566
- console.log("[rdsh] rdsh webview api injected (window.__rdshWebViewApi v1)");
573
+ // 按**实际注入的内容**打日志:raw 路径只注入直连 bootstrap,不含 WebView API。
574
+ // (旧文案一律说 "webview api injected",在 raw 路径上是假的,曾误导排查。)
575
+ console.log(inject.includes("__rdshWebViewApi")
576
+ ? "[rdsh] html inject: webview api + direct bootstrap (plain path)"
577
+ : "[rdsh] html inject: direct bootstrap only (raw/E2EE path)");
567
578
  }
568
579
  send(encodeFrame(FRAME_TYPE.OPEN, streamId, jsonPayload({ kind: "http", status, reason: upRes.statusMessage, headers: outHeaders })));
569
580
  sendChunkedBody(send, streamId, Buffer.from(html));
@@ -731,6 +742,8 @@ export function startJoin(opts) {
731
742
  const plainDispatcher = makeInnerDispatcher(sendTunnelFrame, {
732
743
  jsPatch: () => uiCompat.trustE2EEAsLoopback,
733
744
  gate: true,
745
+ // plain 页也要能取直连候选:它同样注入了直连 bootstrap,端点由下方统一提供
746
+ directCandidates: rawGate,
734
747
  htmlInject: () => {
735
748
  // 直连 bootstrap 始终注入(join 模式);页面授权 token 仅设口令时生成(无口令则跳过 raw 授权、只取候选)
736
749
  return RDSH_WEBVIEW_API + pageAuthorizeScript(gate.accessCode);
@@ -770,6 +783,7 @@ export function startJoin(opts) {
770
783
  }, {
771
784
  jsPatch: () => uiCompat.trustE2EEAsLoopback,
772
785
  rawGate,
786
+ directCandidates: rawGate,
773
787
  // raw 门禁的解锁脚本:与 plain 同一来源(pageAuthorizeScript)。
774
788
  // raw 流不转发 cookie,这里是页面唯一的授权入口;缺了它 raw 门禁必然 fail-closed。
775
789
  htmlInject: () => pageAuthorizeScript(gate.accessCode),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rdsh-gateway",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "rdsh host-side component: LAN auth gateway + outbound tunnel endpoint (spawns dsh web)",
5
5
  "license": "MIT",
6
6
  "type": "module",