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.
- package/dist/join.js +34 -20
- 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
|
|
485
|
-
if (dio?.rawGate !== undefined) {
|
|
486
|
-
//
|
|
487
|
-
if (
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
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),
|