@qzsy/dsh-bridge 3.0.1 → 3.0.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 +6 -0
- package/lib/config.js +1 -1
- package/lib/dsh-cookie.js +1 -1
- package/lib/index.js +6 -13
- package/lib/rpc-compat.js +46 -0
- package/lib/saas/tunnel-client.mjs +0 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/lib/config.js
CHANGED
|
@@ -35,7 +35,7 @@ export function normalizeConfig(raw = {}) {
|
|
|
35
35
|
const access = input.access && typeof input.access === 'object' ? input.access : {};
|
|
36
36
|
const port = Number(input.port);
|
|
37
37
|
return {
|
|
38
|
-
port: Number.isInteger(port) && port > 0 && port <
|
|
38
|
+
port: Number.isInteger(port) && port > 0 && port < 65_536 ? port : DEFAULT_PORT,
|
|
39
39
|
gatewayUrl: typeof input.gatewayUrl === 'string' && input.gatewayUrl.trim()
|
|
40
40
|
? input.gatewayUrl.trim()
|
|
41
41
|
: DEFAULT_GATEWAY_URL,
|
package/lib/dsh-cookie.js
CHANGED
|
@@ -15,7 +15,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { resolveDshHome } from './data-dir.js';
|
|
17
17
|
|
|
18
|
-
const COOKIE_SPEC = { prefix: 'dsh-auth-', version: 1, ttlMs:
|
|
18
|
+
const COOKIE_SPEC = { prefix: 'dsh-auth-', version: 1, ttlMs: 2_592_000_000 };
|
|
19
19
|
export const DSH_AUTH_COOKIE_PREFIX = COOKIE_SPEC.prefix;
|
|
20
20
|
export const DSH_AUTH_COOKIE_VERSION = COOKIE_SPEC.version;
|
|
21
21
|
export const DSH_AUTH_COOKIE_MAX_AGE_MS = COOKIE_SPEC.ttlMs;
|
package/lib/index.js
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// 职责(保持精简,全部为独立实现):
|
|
4
|
-
// 1. 本地门禁代理:监听本机端口 → 转发 DSH 本体,注入 DSH 原生 loopback cookie;
|
|
5
|
-
// 2. SaaS 隧道客户端:设备码绑定 → 短期令牌 → v2 控制通道(转发 HTTP / SSE / WS);
|
|
6
|
-
// 3. 设置页面的 RPC 控制面:状态、绑定、解绑、启停、访问链接。
|
|
7
|
-
import { randomBytes } from 'node:crypto';
|
|
1
|
+
|
|
8
2
|
import { readFileSync } from 'node:fs';
|
|
9
3
|
import { hostname, platform as osPlatform, arch as osArch } from 'node:os';
|
|
10
4
|
import { join, dirname } from 'node:path';
|
|
11
5
|
import { fileURLToPath } from 'node:url';
|
|
12
6
|
import QRCode from 'qrcode';
|
|
13
7
|
|
|
8
|
+
import { registerRpcChannel } from './rpc-compat.js';
|
|
14
9
|
import { PLUGIN_ID, PACKAGE_NAME, DEFAULT_PORT, RPC_CHANNEL, RPC_ENDPOINTS, GATEWAY_PATH } from './identity.js';
|
|
15
10
|
import { resolveConfigPath } from './data-dir.js';
|
|
16
11
|
import {
|
|
17
12
|
ensureConfigSecrets,
|
|
18
13
|
loadConfig,
|
|
19
14
|
maskSecret,
|
|
20
|
-
normalizeConfig,
|
|
21
15
|
saveConfig,
|
|
22
16
|
createSecret,
|
|
23
17
|
hashPassword,
|
|
@@ -55,21 +49,20 @@ function errorPayload(err) {
|
|
|
55
49
|
}
|
|
56
50
|
|
|
57
51
|
function registerRpc(ctx, handler, logger) {
|
|
58
|
-
|
|
59
|
-
if (!rpc?.handle) {
|
|
52
|
+
if (!ctx?.connection?.rpc?.handle) {
|
|
60
53
|
logger?.warn?.('connection RPC unavailable — panel will stay disabled');
|
|
61
54
|
return () => {};
|
|
62
55
|
}
|
|
63
56
|
try {
|
|
64
|
-
const dispose =
|
|
57
|
+
const dispose = registerRpcChannel(ctx, RPC_CHANNEL, handler, { authority: 'loopback' }, logger);
|
|
65
58
|
return typeof dispose === 'function' ? dispose : () => {};
|
|
66
59
|
} catch (err) {
|
|
67
60
|
logger?.warn?.(`RPC register with options failed, retrying without: ${err?.message ?? err}`);
|
|
68
61
|
try {
|
|
69
|
-
const dispose =
|
|
62
|
+
const dispose = registerRpcChannel(ctx, RPC_CHANNEL, handler, undefined, logger);
|
|
70
63
|
return typeof dispose === 'function' ? dispose : () => {};
|
|
71
64
|
} catch (retryErr) {
|
|
72
|
-
logger?.error?.(`RPC register failed: ${retryErr?.message ?? retryErr}`);
|
|
65
|
+
logger?.error?.(`RPC register failed, panel buttons will return 405: ${retryErr?.message ?? retryErr}`);
|
|
73
66
|
return () => {};
|
|
74
67
|
}
|
|
75
68
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// RPC 通道注册兼容层(自研)。
|
|
2
|
+
//
|
|
3
|
+
// 部分 DSH 版本的 connection 服务在注册 RPC 通道时,会在它自己的 ctx 上读取 webServer,
|
|
4
|
+
// 而该 ctx 没有声明 inject,cordis 抛出 `cannot get property "webServer" without inject`。
|
|
5
|
+
// 若不处理,通道注册失败,面板仍能渲染,但所有 RPC 调用在宿主侧得到 HTTP 405。
|
|
6
|
+
//
|
|
7
|
+
// 做法:先走公开的 rpc.handle;仅当抛出上述错误时,在 `internal/get` 事件上临时挂一个
|
|
8
|
+
// 监听器,默认解析失败才补上本插件已 inject 的 webServer 单例,调用结束立即摘除。
|
|
9
|
+
|
|
10
|
+
const MISSING_INJECT = /without inject/;
|
|
11
|
+
|
|
12
|
+
export function isMissingWebServerInject(err) {
|
|
13
|
+
return err instanceof Error && MISSING_INJECT.test(err.message) && /webServer/.test(err.message);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function installFallback(ctx) {
|
|
17
|
+
const webServer = ctx?.webServer;
|
|
18
|
+
if (webServer === undefined || typeof ctx.on !== 'function') return () => {};
|
|
19
|
+
const listener = (lookupCtx, prop, error, next) => {
|
|
20
|
+
if (prop !== 'webServer') return next();
|
|
21
|
+
try {
|
|
22
|
+
return next();
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (err !== error) throw err;
|
|
25
|
+
return webServer;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const off = ctx.on('internal/get', listener, { global: true });
|
|
29
|
+
return typeof off === 'function' ? off : () => {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registerRpcChannel(ctx, channel, handler, options, logger) {
|
|
33
|
+
const call = () => ctx.connection.rpc.handle(channel, handler, options);
|
|
34
|
+
try {
|
|
35
|
+
return call();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (!isMissingWebServerInject(err)) throw err;
|
|
38
|
+
logger?.warn?.('connection RPC hit webServer inject regression, retrying with fallback');
|
|
39
|
+
const off = installFallback(ctx);
|
|
40
|
+
try {
|
|
41
|
+
return call();
|
|
42
|
+
} finally {
|
|
43
|
+
off();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import { WebSocket } from 'ws';
|
|
8
8
|
import { request as httpRequest } from 'node:http';
|
|
9
9
|
import { connect as netConnect } from 'node:net';
|
|
10
|
-
import { randomBytes } from 'node:crypto';
|
|
11
10
|
|
|
12
11
|
export const SAAS_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
13
12
|
export const SAAS_HEARTBEAT_MAX_MISSED = 3;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qzsy/dsh-bridge",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "DSH 远程隧道云官方客户端插件:一条命令把本机 DeepSeek Harness 接入云端隧道(设备码绑定、短期令牌、本地门禁代理、HTTP/SSE/WebSocket 反向隧道)。",
|
|
5
5
|
"releaseNotes": "【v3.0.0】官方客户端插件首个独立实现版本\n• 全新自研代码:插件骨架、本地门禁代理、SaaS 隧道客户端与设置面板均独立实现\n• 设备码绑定 + 短期设备令牌 + v2 控制通道(心跳/路由热更新/drain/终态吊销)\n• 本地门禁:回环免认证、隧道来源用进程内随机密钥标记、局域网访问需本地口令\n• 支持 HTTP / SSE 流式 / WebSocket 转发,公网地址复制与二维码\n• 数据目录 ~/.dsh/qzsy-dsh-bridge,默认本地端口 3092(可配置)",
|
|
6
6
|
"type": "module",
|