dsh-bridge-gateway 0.1.1 → 0.1.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/CHANGELOG.md +8 -0
- package/client/client.js +24 -10
- package/client/index.js +24 -10
- package/lib/index.js +96 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
本项目 `dsh-bridge-gateway` 是基于 [dsh-bridge](https://github.com/wenbin-wb/dsh-bridge) 的移植增强分支,在保留原版能力之外,重点新增「公网直连网关」。
|
|
4
4
|
|
|
5
|
+
## [0.1.2] - 待发布
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
- 公网直连网关新增插件 host 反向代理:`/points-checkin/*` 路径转发到本机 points-checkin bridge,使积分签到插件在公网 HTTPS 网关页面下同步可用。
|
|
9
|
+
|
|
10
|
+
### 修复
|
|
11
|
+
- 修复移动端模型/权限选择下拉在底部输入栏被挤出视口、仅剩右下角横条的问题:改为固定在输入栏上方的底部浮层。
|
|
12
|
+
|
|
5
13
|
## [0.1.1] - 待发布
|
|
6
14
|
|
|
7
15
|
### 修复
|
package/client/client.js
CHANGED
|
@@ -3963,7 +3963,7 @@ function injectMobileStyles() {
|
|
|
3963
3963
|
min-width: 0 !important;
|
|
3964
3964
|
}
|
|
3965
3965
|
|
|
3966
|
-
/* \u79FB\u52A8\u7AEF\uFF1A\u6A21\u578B/\u6743\u9650\u9009\u62E9\u5F39\u5C42\
|
|
3966
|
+
/* \u79FB\u52A8\u7AEF\uFF1A\u6A21\u578B/\u6743\u9650\u9009\u62E9\u5F39\u5C42\u56FA\u5B9A\u4E3A\u8F93\u5165\u680F\u4E0A\u65B9\u7684\u5E95\u90E8\u6D6E\u5C42\uFF0C\u4FDD\u8BC1\u5B8C\u6574\u53EF\u89C1\uFF08\u4FEE\u590D\u771F\u673A\u4E0B\u62C9\u53EA\u5269\u53F3\u4E0B\u89D2\u6A2A\u6761\u3001\u5185\u5BB9\u88AB\u6324\u51FA\u89C6\u53E3\uFF09 */
|
|
3967
3967
|
div[class*="_7KE1Ra_root"] > div[class*="dropdown"],
|
|
3968
3968
|
div[class*="_7KE1Ra_root"] > div[class*="listbox"],
|
|
3969
3969
|
div[class*="_7KE1Ra_root"] > div[class*="popup"],
|
|
@@ -3973,15 +3973,20 @@ function injectMobileStyles() {
|
|
|
3973
3973
|
div[class*="Sh0Q9G_root"] > div[class*="listbox"],
|
|
3974
3974
|
div[class*="Sh0Q9G_root"] > div[class*="popup"],
|
|
3975
3975
|
div[class*="Sh0Q9G_root"] > div[class*="menu"] {
|
|
3976
|
+
position: fixed !important;
|
|
3977
|
+
left: 8px !important;
|
|
3978
|
+
right: 8px !important;
|
|
3979
|
+
top: auto !important;
|
|
3980
|
+
bottom: calc(64px + var(--dsh-mobile-safe-bottom)) !important;
|
|
3981
|
+
width: auto !important;
|
|
3976
3982
|
min-width: 0 !important;
|
|
3977
3983
|
max-width: calc(100vw - 16px) !important;
|
|
3978
|
-
|
|
3979
|
-
max-height: 55vh !important;
|
|
3984
|
+
max-height: 48vh !important;
|
|
3980
3985
|
overflow-x: auto !important;
|
|
3981
3986
|
overflow-y: auto !important;
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3987
|
+
z-index: 10005 !important;
|
|
3988
|
+
transform: none !important;
|
|
3989
|
+
margin: 0 !important;
|
|
3985
3990
|
box-sizing: border-box !important;
|
|
3986
3991
|
-webkit-overflow-scrolling: touch !important;
|
|
3987
3992
|
}
|
|
@@ -4233,7 +4238,7 @@ function injectMobileStyles() {
|
|
|
4233
4238
|
pointer-events: auto !important;
|
|
4234
4239
|
}
|
|
4235
4240
|
|
|
4236
|
-
/* \u515C\u5E95\uFF1APortal \u6E32\u67D3\u5230 overlayAnchor \u91CC\u7684\u6A21\u578B/\u6743\u9650\u9009\u62E9\u5F39\u5C42\u4E5F\
|
|
4241
|
+
/* \u515C\u5E95\uFF1APortal \u6E32\u67D3\u5230 overlayAnchor \u91CC\u7684\u6A21\u578B/\u6743\u9650\u9009\u62E9\u5F39\u5C42\u4E5F\u56FA\u5B9A\u4E3A\u5E95\u90E8\u6D6E\u5C42\uFF0C\u675C\u7EDD\u6574\u4F53\u8DD1\u51FA\u89C6\u53E3\u53EA\u5269\u6A2A\u6761 */
|
|
4237
4242
|
div[class*="uV2eYG_overlayAnchor"] > div[class*="dropdown"],
|
|
4238
4243
|
div[class*="uV2eYG_overlayAnchor"] > div[class*="popup"],
|
|
4239
4244
|
div[class*="uV2eYG_overlayAnchor"] > div[class*="menu"],
|
|
@@ -4241,13 +4246,22 @@ function injectMobileStyles() {
|
|
|
4241
4246
|
div[class*="uV2eYG_overlayAnchor"] > div[role="listbox"],
|
|
4242
4247
|
div[class*="overlayLayer"] > div[class*="dropdown"],
|
|
4243
4248
|
div[class*="overlayLayer"] > div[class*="popup"] {
|
|
4244
|
-
|
|
4249
|
+
position: fixed !important;
|
|
4250
|
+
left: 8px !important;
|
|
4251
|
+
right: 8px !important;
|
|
4252
|
+
top: auto !important;
|
|
4253
|
+
bottom: calc(64px + var(--dsh-mobile-safe-bottom)) !important;
|
|
4254
|
+
width: auto !important;
|
|
4245
4255
|
min-width: 0 !important;
|
|
4246
|
-
max-
|
|
4256
|
+
max-width: calc(100vw - 16px) !important;
|
|
4257
|
+
max-height: 50vh !important;
|
|
4247
4258
|
overflow-x: auto !important;
|
|
4248
4259
|
overflow-y: auto !important;
|
|
4260
|
+
z-index: 10005 !important;
|
|
4261
|
+
transform: none !important;
|
|
4262
|
+
margin: 0 !important;
|
|
4249
4263
|
box-sizing: border-box !important;
|
|
4250
|
-
transform-origin: left
|
|
4264
|
+
transform-origin: left bottom !important;
|
|
4251
4265
|
}
|
|
4252
4266
|
}
|
|
4253
4267
|
|
package/client/index.js
CHANGED
|
@@ -3561,7 +3561,7 @@ function injectMobileStyles() {
|
|
|
3561
3561
|
min-width: 0 !important;
|
|
3562
3562
|
}
|
|
3563
3563
|
|
|
3564
|
-
/*
|
|
3564
|
+
/* 移动端:模型/权限选择弹层固定为输入栏上方的底部浮层,保证完整可见(修复真机下拉只剩右下角横条、内容被挤出视口) */
|
|
3565
3565
|
div[class*="_7KE1Ra_root"] > div[class*="dropdown"],
|
|
3566
3566
|
div[class*="_7KE1Ra_root"] > div[class*="listbox"],
|
|
3567
3567
|
div[class*="_7KE1Ra_root"] > div[class*="popup"],
|
|
@@ -3571,15 +3571,20 @@ function injectMobileStyles() {
|
|
|
3571
3571
|
div[class*="Sh0Q9G_root"] > div[class*="listbox"],
|
|
3572
3572
|
div[class*="Sh0Q9G_root"] > div[class*="popup"],
|
|
3573
3573
|
div[class*="Sh0Q9G_root"] > div[class*="menu"] {
|
|
3574
|
+
position: fixed !important;
|
|
3575
|
+
left: 8px !important;
|
|
3576
|
+
right: 8px !important;
|
|
3577
|
+
top: auto !important;
|
|
3578
|
+
bottom: calc(64px + var(--dsh-mobile-safe-bottom)) !important;
|
|
3579
|
+
width: auto !important;
|
|
3574
3580
|
min-width: 0 !important;
|
|
3575
3581
|
max-width: calc(100vw - 16px) !important;
|
|
3576
|
-
|
|
3577
|
-
max-height: 55vh !important;
|
|
3582
|
+
max-height: 48vh !important;
|
|
3578
3583
|
overflow-x: auto !important;
|
|
3579
3584
|
overflow-y: auto !important;
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3585
|
+
z-index: 10005 !important;
|
|
3586
|
+
transform: none !important;
|
|
3587
|
+
margin: 0 !important;
|
|
3583
3588
|
box-sizing: border-box !important;
|
|
3584
3589
|
-webkit-overflow-scrolling: touch !important;
|
|
3585
3590
|
}
|
|
@@ -3831,7 +3836,7 @@ function injectMobileStyles() {
|
|
|
3831
3836
|
pointer-events: auto !important;
|
|
3832
3837
|
}
|
|
3833
3838
|
|
|
3834
|
-
/* 兜底:Portal 渲染到 overlayAnchor
|
|
3839
|
+
/* 兜底:Portal 渲染到 overlayAnchor 里的模型/权限选择弹层也固定为底部浮层,杜绝整体跑出视口只剩横条 */
|
|
3835
3840
|
div[class*="uV2eYG_overlayAnchor"] > div[class*="dropdown"],
|
|
3836
3841
|
div[class*="uV2eYG_overlayAnchor"] > div[class*="popup"],
|
|
3837
3842
|
div[class*="uV2eYG_overlayAnchor"] > div[class*="menu"],
|
|
@@ -3839,13 +3844,22 @@ function injectMobileStyles() {
|
|
|
3839
3844
|
div[class*="uV2eYG_overlayAnchor"] > div[role="listbox"],
|
|
3840
3845
|
div[class*="overlayLayer"] > div[class*="dropdown"],
|
|
3841
3846
|
div[class*="overlayLayer"] > div[class*="popup"] {
|
|
3842
|
-
|
|
3847
|
+
position: fixed !important;
|
|
3848
|
+
left: 8px !important;
|
|
3849
|
+
right: 8px !important;
|
|
3850
|
+
top: auto !important;
|
|
3851
|
+
bottom: calc(64px + var(--dsh-mobile-safe-bottom)) !important;
|
|
3852
|
+
width: auto !important;
|
|
3843
3853
|
min-width: 0 !important;
|
|
3844
|
-
max-
|
|
3854
|
+
max-width: calc(100vw - 16px) !important;
|
|
3855
|
+
max-height: 50vh !important;
|
|
3845
3856
|
overflow-x: auto !important;
|
|
3846
3857
|
overflow-y: auto !important;
|
|
3858
|
+
z-index: 10005 !important;
|
|
3859
|
+
transform: none !important;
|
|
3860
|
+
margin: 0 !important;
|
|
3847
3861
|
box-sizing: border-box !important;
|
|
3848
|
-
transform-origin: left
|
|
3862
|
+
transform-origin: left bottom !important;
|
|
3849
3863
|
}
|
|
3850
3864
|
}
|
|
3851
3865
|
|
package/lib/index.js
CHANGED
|
@@ -223,6 +223,9 @@ function getDshLoopbackCookie(targetPort) {
|
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
/** points-checkin 本机 host bridge 的候选端口(与其 client 端探测顺序一致) */
|
|
227
|
+
const POINTS_CHECKIN_PORTS = [27182, 27183, 27184, 27185, 27186, 27187, 27188, 27189, 27190, 27191];
|
|
228
|
+
|
|
226
229
|
/** 把请求头中的 Host 和 Origin 改写成 loopback,让 DSH 的安全栅栏放行 */
|
|
227
230
|
function loopbackHeaders(headers, targetPort) {
|
|
228
231
|
const authority = `127.0.0.1:${targetPort}`;
|
|
@@ -260,14 +263,16 @@ class ProxyServer {
|
|
|
260
263
|
* @param {object} [opts.logger]
|
|
261
264
|
* @param {{ key: string|Buffer, cert: string|Buffer }} [opts.tls] 若提供则启用 HTTPS(公网直连网关)
|
|
262
265
|
* @param {string} [opts.tag] 日志标签(如 '网关')
|
|
266
|
+
* @param {Array<{prefix:string,targetPort:number,stripPrefix?:boolean}>} [opts.extraTargets] 额外的本机插件 host 反代目标(如 points-checkin 的本地 bridge),鉴权通过后按前缀转发
|
|
263
267
|
*/
|
|
264
|
-
constructor({ localPort, targetPort, authManager, logger, tls, tag = '' }) {
|
|
268
|
+
constructor({ localPort, targetPort, authManager, logger, tls, tag = '', extraTargets = [] }) {
|
|
265
269
|
this.localPort = localPort;
|
|
266
270
|
this.targetPort = targetPort;
|
|
267
271
|
this.authManager = authManager;
|
|
268
272
|
this.logger = logger;
|
|
269
273
|
this.tls = tls ?? null;
|
|
270
274
|
this.tag = tag ? `[${tag}] ` : '';
|
|
275
|
+
this.extraTargets = Array.isArray(extraTargets) ? extraTargets : [];
|
|
271
276
|
this.server = null;
|
|
272
277
|
this.clientSockets = new Set();
|
|
273
278
|
this.activeConnections = 0;
|
|
@@ -417,6 +422,55 @@ class ProxyServer {
|
|
|
417
422
|
}
|
|
418
423
|
}
|
|
419
424
|
|
|
425
|
+
// 4.3 认证通过:可选插件 host 反向代理(如 points-checkin 本地 bridge,供远程 HTTPS 网关的页面同源调用)
|
|
426
|
+
if (this.extraTargets && this.extraTargets.length) {
|
|
427
|
+
const extra = this.extraTargets.find(
|
|
428
|
+
(t) => pathname === t.prefix || pathname.startsWith(`${t.prefix}/`)
|
|
429
|
+
);
|
|
430
|
+
if (extra) {
|
|
431
|
+
const qIndex = (req.url || '').indexOf('?');
|
|
432
|
+
const qs = qIndex >= 0 ? (req.url || '').slice(qIndex) : '';
|
|
433
|
+
const upstreamPath = extra.stripPrefix
|
|
434
|
+
? (pathname.slice(extra.prefix.length) || '/') + qs
|
|
435
|
+
: req.url;
|
|
436
|
+
const headers = loopbackHeaders(req.headers, extra.targetPort);
|
|
437
|
+
const extraReq = httpRequest(
|
|
438
|
+
{
|
|
439
|
+
host: '127.0.0.1',
|
|
440
|
+
port: extra.targetPort,
|
|
441
|
+
method: req.method,
|
|
442
|
+
path: upstreamPath,
|
|
443
|
+
headers,
|
|
444
|
+
agent: false,
|
|
445
|
+
},
|
|
446
|
+
(extraRes) => {
|
|
447
|
+
const out = { ...extraRes.headers };
|
|
448
|
+
delete out['transfer-encoding'];
|
|
449
|
+
delete out['content-length'];
|
|
450
|
+
res.writeHead(extraRes.statusCode ?? 502, out);
|
|
451
|
+
extraRes.pipe(res);
|
|
452
|
+
res.on('close', () => extraRes.destroy());
|
|
453
|
+
extraRes.on('error', () => {
|
|
454
|
+
if (!res.writableEnded) res.destroy();
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
extraReq.on('error', (err) => {
|
|
459
|
+
this.logger.error('插件 host 代理请求失败(%s): %s', extra.prefix, err.message);
|
|
460
|
+
if (!res.headersSent) {
|
|
461
|
+
res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });
|
|
462
|
+
res.end(
|
|
463
|
+
`dsh-bridge: 无法连接插件 host 服务 (127.0.0.1:${extra.targetPort}) — ${err.message}`
|
|
464
|
+
);
|
|
465
|
+
} else {
|
|
466
|
+
res.destroy();
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
req.pipe(extraReq);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
420
474
|
// 5. 认证通过:正常执行反向代理转发
|
|
421
475
|
const headers = loopbackHeaders(req.headers, this.targetPort);
|
|
422
476
|
const proxyReq = httpRequest(
|
|
@@ -577,14 +631,53 @@ class BridgeService {
|
|
|
577
631
|
return this.getStatus();
|
|
578
632
|
}
|
|
579
633
|
|
|
634
|
+
// 探测 points-checkin 本机 host bridge 端口(供网关反代 /points-checkin/*,实现远程 HTTPS 下积分签到可用)
|
|
635
|
+
async _resolvePointsCheckinPort() {
|
|
636
|
+
if (this._pcPortResolved) return this._pcPort ?? null;
|
|
637
|
+
this._pcPortResolved = true;
|
|
638
|
+
this._pcPort = null;
|
|
639
|
+
for (const port of POINTS_CHECKIN_PORTS) {
|
|
640
|
+
const ok = await new Promise((resolve) => {
|
|
641
|
+
const req = httpGet(
|
|
642
|
+
{ host: '127.0.0.1', port, path: '/ping', timeout: 500, headers: { 'User-Agent': 'dsh-bridge-gateway' } },
|
|
643
|
+
(res) => {
|
|
644
|
+
const chunks = [];
|
|
645
|
+
res.on('data', (c) => chunks.push(c));
|
|
646
|
+
res.on('end', () => {
|
|
647
|
+
try {
|
|
648
|
+
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
649
|
+
resolve(body?.ok === true && body.plugin === 'points-checkin');
|
|
650
|
+
} catch {
|
|
651
|
+
resolve(false);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
);
|
|
656
|
+
req.on('error', () => resolve(false));
|
|
657
|
+
req.on('timeout', () => {
|
|
658
|
+
req.destroy();
|
|
659
|
+
resolve(false);
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
if (ok) {
|
|
663
|
+
this._pcPort = port;
|
|
664
|
+
this.logger?.info?.('已发现 points-checkin host bridge: 127.0.0.1:%d', port);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return this._pcPort ?? null;
|
|
669
|
+
}
|
|
670
|
+
|
|
580
671
|
async startProxy() {
|
|
581
672
|
if (this.proxy) return this.proxy;
|
|
582
673
|
|
|
674
|
+
const pcPort = await this._resolvePointsCheckinPort();
|
|
583
675
|
this.proxy = new ProxyServer({
|
|
584
676
|
localPort: this.proxyPort,
|
|
585
677
|
targetPort: this.dshPort,
|
|
586
678
|
authManager: this.authManager,
|
|
587
679
|
logger: this.logger,
|
|
680
|
+
extraTargets: pcPort ? [{ prefix: '/points-checkin', targetPort: pcPort, stripPrefix: true }] : [],
|
|
588
681
|
});
|
|
589
682
|
|
|
590
683
|
await this.proxy.start();
|
|
@@ -886,6 +979,7 @@ class BridgeService {
|
|
|
886
979
|
}
|
|
887
980
|
|
|
888
981
|
const gatewayAuth = this._refreshGatewayAuth();
|
|
982
|
+
const pcPort = await this._resolvePointsCheckinPort();
|
|
889
983
|
this.gateway = new ProxyServer({
|
|
890
984
|
localPort: port,
|
|
891
985
|
targetPort: this.dshPort,
|
|
@@ -893,6 +987,7 @@ class BridgeService {
|
|
|
893
987
|
logger: this.logger,
|
|
894
988
|
tls: { key: tls.key, cert: tls.cert },
|
|
895
989
|
tag: '公网直连',
|
|
990
|
+
extraTargets: pcPort ? [{ prefix: '/points-checkin', targetPort: pcPort, stripPrefix: true }] : [],
|
|
896
991
|
});
|
|
897
992
|
|
|
898
993
|
await this.gateway.start();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-bridge-gateway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "以 dsh-bridge 为根基、融合 remote-gateway 能力的 DSH 远程访问网关:局域网 / Cloudflare 隧道 / 自建隧道之外,新增「公网直连网关」(0.0.0.0 监听 + 自带 HTTPS 自签证书 + 强制登录门禁),解决移动端外网直连操控电脑。",
|
|
5
5
|
"releaseNotes": "【dsh-bridge-gateway】以 dsh-bridge 为根基、融合 remote-gateway 能力的 DSH 远程访问网关\n• ➕ 公网访问区:隧道 / 直连网关 二选一,网关全部配置项可在界面配置\n• ➕ 直连网关:0.0.0.0:<端口> 自带 HTTPS(自签证书) + 强制 public_only 门禁,复用 AuthManager,链路打通到 DSH loopback\n• 📱 修复移动端选择模型时界面溢出屏幕的问题\n• 默认不启用,安装后原有局域网/隧道/IM 行为完全不变",
|
|
6
6
|
"type": "module",
|