dsh-remote-mobile 1.1.0

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.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * 远程移动端接入底座兼容性与上下文虚拟化桥接层 (Bridge & Compatibility Virtualizer)
3
+ *
4
+ * 【设计职责】
5
+ * 本模块集中封装所有针对 DSH 底座及第三方生态插件(如 @linxin666/dsh-ssh, @linxin666/dsh-pet, @linxin666/dsh-client-ui-task-board 等)
6
+ * 的兼容性支持逻辑与网络上下文虚拟化代理,主要包含三大核心能力:
7
+ *
8
+ * 1. 【前端安全上下文 Polyfill 物理注入 (Crypto Polyfill Injection)】:
9
+ * - 解决现代浏览器在非安全上下文(HTTP + 外部 IP,如 Tailscale/局域网)下禁用 window.crypto.randomUUID() 的规范限制;
10
+ * - 在服务端通过 HTML <head> 顶部首行注入 Polyfill 脚本,确保在所有业务与核心 RPC 客户端执行前 100% 就绪。
11
+ *
12
+ * 2. 【底层 HTTP 门禁劫持与回环上下文虚拟化 (Loopback Context Virtualizer)】:
13
+ * - 挂载全链路 HTTP 门禁中间件,对未授权请求实施 302 重定向到 /auth 或 401 拦截;
14
+ * - 对通过认证(已扫码配对 / 长期密码 / 免密直连)的合法外部请求,在进入下游各业务插件前,
15
+ * 统一将 Host、Origin、Socket RemoteAddress 及 Sec-Fetch-Site 规范化虚拟为 127.0.0.1 回环特征;
16
+ * - 彻底解除底层 WebServer 的 DNS-Rebinding 阻断,以及第三方插件写死的 loopback-only 403 限制。
17
+ *
18
+ * 3. 【Cordis 服务桥接 (remoteWebUiPairing Bridge)】:
19
+ * - 在用户关闭旧版 @linxin666/dsh-remote-web-ui 的情况下,以标准 Cordis Service 模式向全局暴露 remoteWebUiPairing 服务;
20
+ * - 实现宠物插件 (dsh-pet)、任务看板 (task-board) 等第三方生态组件与本插件授权状态的 100% 互通。
21
+ */
22
+ import type { IncomingMessage, ServerResponse } from 'node:http';
23
+ import type { SessionStore } from '../auth/token.js';
24
+ /**
25
+ * 注入到 index.html <head> 最前列的 Polyfill 脚本代码片段
26
+ * 针对非安全上下文(HTTP + 外部 IP)提供轻量高精度的 UUID v4 生成能力
27
+ */
28
+ export declare const CRYPTO_POLYFILL_SNIPPET = "\n(function() {\n if (typeof window !== \"undefined\") {\n if (!window.crypto) window.crypto = {};\n if (typeof window.crypto.randomUUID !== \"function\") {\n window.crypto.randomUUID = function() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0;\n var v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n };\n }\n }\n})();\n";
29
+ /**
30
+ * 挂载底层 http.Server 门禁劫持与回环上下文虚拟化代理
31
+ *
32
+ * @param server - 底层 Node.js http.Server 实例
33
+ * @param gateMiddleware - 全局安全门禁拦截器函数
34
+ * @param store - 会话与配置管理器实例
35
+ */
36
+ export declare function patchHttpServerWithVirtualizer(server: any, gateMiddleware: (req: IncomingMessage, res: ServerResponse, next?: () => void) => boolean, _store: SessionStore): void;
37
+ /**
38
+ * 注册 WebServer HTML 模板层的 Polyfill 注入钩子
39
+ *
40
+ * @param ctx - Cordis 上下文对象
41
+ */
42
+ export declare function mountIndexInjections(ctx: any): void;
43
+ /**
44
+ * 向 Cordis 容器桥接注册 remoteWebUiPairing 服务
45
+ * 完美兼容 @linxin666 生态插件(如 dsh-pet 宠物、面板插件等),实现免登信任自动共享
46
+ *
47
+ * @param ctx - Cordis 上下文对象
48
+ * @param store - 会话管理器实例
49
+ */
50
+ export declare function registerRemoteWebUiPairingBridge(ctx: any, store: SessionStore): void;
@@ -0,0 +1,191 @@
1
+ /**
2
+ * 远程移动端接入底座兼容性与上下文虚拟化桥接层 (Bridge & Compatibility Virtualizer)
3
+ *
4
+ * 【设计职责】
5
+ * 本模块集中封装所有针对 DSH 底座及第三方生态插件(如 @linxin666/dsh-ssh, @linxin666/dsh-pet, @linxin666/dsh-client-ui-task-board 等)
6
+ * 的兼容性支持逻辑与网络上下文虚拟化代理,主要包含三大核心能力:
7
+ *
8
+ * 1. 【前端安全上下文 Polyfill 物理注入 (Crypto Polyfill Injection)】:
9
+ * - 解决现代浏览器在非安全上下文(HTTP + 外部 IP,如 Tailscale/局域网)下禁用 window.crypto.randomUUID() 的规范限制;
10
+ * - 在服务端通过 HTML <head> 顶部首行注入 Polyfill 脚本,确保在所有业务与核心 RPC 客户端执行前 100% 就绪。
11
+ *
12
+ * 2. 【底层 HTTP 门禁劫持与回环上下文虚拟化 (Loopback Context Virtualizer)】:
13
+ * - 挂载全链路 HTTP 门禁中间件,对未授权请求实施 302 重定向到 /auth 或 401 拦截;
14
+ * - 对通过认证(已扫码配对 / 长期密码 / 免密直连)的合法外部请求,在进入下游各业务插件前,
15
+ * 统一将 Host、Origin、Socket RemoteAddress 及 Sec-Fetch-Site 规范化虚拟为 127.0.0.1 回环特征;
16
+ * - 彻底解除底层 WebServer 的 DNS-Rebinding 阻断,以及第三方插件写死的 loopback-only 403 限制。
17
+ *
18
+ * 3. 【Cordis 服务桥接 (remoteWebUiPairing Bridge)】:
19
+ * - 在用户关闭旧版 @linxin666/dsh-remote-web-ui 的情况下,以标准 Cordis Service 模式向全局暴露 remoteWebUiPairing 服务;
20
+ * - 实现宠物插件 (dsh-pet)、任务看板 (task-board) 等第三方生态组件与本插件授权状态的 100% 互通。
21
+ */
22
+ import { getClientIp, isTailscaleIp, isLanIp } from '../auth/tailscale.js';
23
+ /**
24
+ * 注入到 index.html <head> 最前列的 Polyfill 脚本代码片段
25
+ * 针对非安全上下文(HTTP + 外部 IP)提供轻量高精度的 UUID v4 生成能力
26
+ */
27
+ export const CRYPTO_POLYFILL_SNIPPET = `
28
+ (function() {
29
+ if (typeof window !== "undefined") {
30
+ if (!window.crypto) window.crypto = {};
31
+ if (typeof window.crypto.randomUUID !== "function") {
32
+ window.crypto.randomUUID = function() {
33
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
34
+ var r = Math.random() * 16 | 0;
35
+ var v = c === 'x' ? r : (r & 0x3 | 0x8);
36
+ return v.toString(16);
37
+ });
38
+ };
39
+ }
40
+ }
41
+ })();
42
+ `;
43
+ /**
44
+ * 挂载底层 http.Server 门禁劫持与回环上下文虚拟化代理
45
+ *
46
+ * @param server - 底层 Node.js http.Server 实例
47
+ * @param gateMiddleware - 全局安全门禁拦截器函数
48
+ * @param store - 会话与配置管理器实例
49
+ */
50
+ export function patchHttpServerWithVirtualizer(server, gateMiddleware, _store) {
51
+ if (!server || server.__dsh_tailscale_gate_patched__)
52
+ return;
53
+ server.__dsh_tailscale_gate_patched__ = true;
54
+ // 复制并清空原有 request 事件监听器列表,确保门禁成为全链路首道关卡
55
+ const originalListeners = server.listeners('request').slice(0);
56
+ server.removeAllListeners('request');
57
+ server.on('request', (req, res) => {
58
+ // 0. 优先在 Socket 首道关卡记录未经虚拟化篡改的真实客户端物理 IP (防止 Keep-Alive 连接复用污染)
59
+ if (req.socket && !req.socket.__dsh_real_remote_address__) {
60
+ req.socket.__dsh_real_remote_address__ = req.socket.remoteAddress;
61
+ }
62
+ // 1. 响应层拦截:在主文档 index.html 响应流中的 <head> 首行注入 Crypto Polyfill
63
+ const originalEnd = res.end;
64
+ let isHtml = false;
65
+ res.writeHead = ((origWriteHead) => {
66
+ return function (statusCode, ...args) {
67
+ const headers = args.find((a) => typeof a === 'object' && a !== null) || {};
68
+ const contentType = this.getHeader('content-type') || headers['content-type'] || headers['Content-Type'] || '';
69
+ if (String(contentType).includes('text/html')) {
70
+ isHtml = true;
71
+ }
72
+ return origWriteHead.apply(this, [statusCode, ...args]);
73
+ };
74
+ })(res.writeHead);
75
+ res.end = function (chunk, ...args) {
76
+ const currentCt = this.getHeader?.('content-type') || this.getHeader?.('Content-Type') || '';
77
+ const shouldInject = isHtml || String(currentCt).includes('text/html');
78
+ if (shouldInject && chunk) {
79
+ if (typeof chunk === 'string' && chunk.includes('<head>')) {
80
+ chunk = chunk.replace('<head>', `<head><script id="dsh-crypto-polyfill">${CRYPTO_POLYFILL_SNIPPET}</script>`);
81
+ }
82
+ else if (Buffer.isBuffer(chunk)) {
83
+ const str = chunk.toString('utf8');
84
+ if (str.includes('<head>')) {
85
+ chunk = Buffer.from(str.replace('<head>', `<head><script id="dsh-crypto-polyfill">${CRYPTO_POLYFILL_SNIPPET}</script>`), 'utf8');
86
+ }
87
+ }
88
+ }
89
+ return originalEnd.apply(this, [chunk, ...args]);
90
+ };
91
+ // 2. 请求层门禁判定与上下文虚拟化
92
+ const allowed = gateMiddleware(req, res, () => {
93
+ // 记录真实客户端来源信息(内部专用头,无条件覆盖避免伪造)
94
+ req.headers['x-dsh-real-ip'] = getClientIp(req);
95
+ req.headers['x-real-ip'] = req.headers['x-dsh-real-ip'];
96
+ if (!req.headers['x-forwarded-host'] && req.headers.host) {
97
+ req.headers['x-forwarded-host'] = req.headers.host;
98
+ }
99
+ // 虚拟化 Host 与 Origin(解决 DNS-Rebinding 400 阻断与同源 host === origin 严格检查)
100
+ const hostHeader = req.headers.host || '';
101
+ const port = hostHeader.includes(':') ? hostHeader.split(':')[1] : '3080';
102
+ req.headers.host = `127.0.0.1:${port}`;
103
+ if (req.headers.origin) {
104
+ req.headers.origin = `http://127.0.0.1:${port}`;
105
+ }
106
+ // 补齐浏览器同源探针标记(解决 task-board 等插件对 sec-fetch-site 的检查)
107
+ if (!req.headers['sec-fetch-site'] || req.headers['sec-fetch-site'] === 'cross-site') {
108
+ req.headers['sec-fetch-site'] = 'same-origin';
109
+ }
110
+ // 统一虚拟化 socket.remoteAddress 为 127.0.0.1(解除 dsh-ssh / plugin-manager 等插件写死的 loopback-only 限制)
111
+ if (req.socket && req.socket.remoteAddress !== '127.0.0.1') {
112
+ try {
113
+ Object.defineProperty(req.socket, 'remoteAddress', {
114
+ get: () => '127.0.0.1',
115
+ configurable: true,
116
+ });
117
+ }
118
+ catch { }
119
+ }
120
+ // 放行至下游业务插件与路由分发器
121
+ for (const listener of originalListeners) {
122
+ listener.call(server, req, res);
123
+ }
124
+ });
125
+ if (allowed === false) {
126
+ // 请求未通过安全门禁,已在门禁层执行重定向或返回 401 拦截
127
+ }
128
+ });
129
+ }
130
+ /**
131
+ * 注册 WebServer HTML 模板层的 Polyfill 注入钩子
132
+ *
133
+ * @param ctx - Cordis 上下文对象
134
+ */
135
+ export function mountIndexInjections(ctx) {
136
+ if (ctx.webServer && typeof ctx.webServer.tapIndex === 'function') {
137
+ ctx.webServer.tapIndex((html) => {
138
+ if (html.includes('<head>')) {
139
+ return html.replace('<head>', `<head><script id="dsh-crypto-polyfill">${CRYPTO_POLYFILL_SNIPPET}</script>`);
140
+ }
141
+ return html;
142
+ });
143
+ }
144
+ ctx.on?.('webserver/index-inject', (table) => {
145
+ if (Array.isArray(table)) {
146
+ table.push({
147
+ kind: 'script',
148
+ placement: 'head',
149
+ text: CRYPTO_POLYFILL_SNIPPET,
150
+ });
151
+ }
152
+ });
153
+ }
154
+ /**
155
+ * 向 Cordis 容器桥接注册 remoteWebUiPairing 服务
156
+ * 完美兼容 @linxin666 生态插件(如 dsh-pet 宠物、面板插件等),实现免登信任自动共享
157
+ *
158
+ * @param ctx - Cordis 上下文对象
159
+ * @param store - 会话管理器实例
160
+ */
161
+ export function registerRemoteWebUiPairingBridge(ctx, store) {
162
+ try {
163
+ const importCordis = new Function('return import("@deepseek-ai/cordis")');
164
+ importCordis().then((m) => {
165
+ const ServiceClass = m?.Service || ctx?.constructor?.Service;
166
+ if (ServiceClass) {
167
+ class RemoteWebUiPairingBridge extends ServiceClass {
168
+ constructor(c) {
169
+ super(c, 'remoteWebUiPairing');
170
+ }
171
+ isPairedDevice(req) {
172
+ const clientIp = getClientIp(req);
173
+ const options = store.getOptions();
174
+ // 1. 免密直连设备自动放行
175
+ if (options.allowTailscale && isTailscaleIp(clientIp))
176
+ return true;
177
+ if (options.allowLan && isLanIp(clientIp))
178
+ return true;
179
+ // 2. 已通过配对码/长期密码认证的设备放行
180
+ const token = store.extractTokenFromRequest(req);
181
+ if (token && store.validateToken(token, clientIp))
182
+ return true;
183
+ return false;
184
+ }
185
+ }
186
+ new RemoteWebUiPairingBridge(ctx);
187
+ }
188
+ }).catch(() => { });
189
+ }
190
+ catch { }
191
+ }
@@ -0,0 +1,5 @@
1
+ declare const _default: (ctx: any) => void;
2
+ export default _default;
3
+ export declare const inject: string[];
4
+ export declare function apply(ctx: any): void;
5
+ export declare function TailscaleMobileSection(props: any): any;