@mrrisega/dsh-remote 0.6.10 → 0.6.12
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/clients/dsh-remote/dsh-events.mjs +11 -1
- package/clients/dsh-remote/test/wechat-runtime.test.mjs +79 -0
- package/clients/dsh-remote/wechat-runtime.mjs +72 -4
- package/dsh-setup.mjs +55 -15
- package/package.json +3 -3
- package/packages/dsh-remote-web/lib/client.js +137 -0
- package/packages/dsh-remote-web/lib/fix-prompt.js +160 -0
- package/packages/dsh-remote-web/lib/index.js +748 -18
- package/packages/dsh-remote-web/lib/offline-update.mjs +66 -0
- package/packages/dsh-remote-web/lib/tarball-update.js +442 -0
- package/packages/dsh-remote-web/package.json +5 -1
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/dsh-bridge.mjs +1451 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/dsh-events.mjs +1991 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/e2ee-client.mjs +728 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/e2ee-shim-script.js +767 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/e2ee-shim.mjs +163 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/mobile-adapter.mjs +1575 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/package.json +11 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/src/lifecycle.mjs +45 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/wechat-channel.mjs +2825 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/wechat-runtime.mjs +1984 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-web/native.html +2526 -0
- package/packages/dsh-remote-web/runtime/dsh-setup.mjs +2390 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/LICENSE +20 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/README.md +548 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/browser.js +8 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/index.js +22 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/buffer-util.js +131 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/constants.js +19 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/event-target.js +292 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/extension.js +203 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/limiter.js +55 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/permessage-deflate.js +530 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/receiver.js +743 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/sender.js +607 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/stream.js +161 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/subprotocol.js +62 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/validation.js +152 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/websocket-server.js +562 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/lib/websocket.js +1407 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/package.json +74 -0
- package/packages/dsh-remote-web/runtime/node_modules/ws/wrapper.mjs +21 -0
- package/packages/dsh-remote-web/test/connect-stuck-visibility.test.mjs +396 -0
- package/packages/dsh-remote-web/test/diag-report.test.mjs +236 -0
- package/packages/dsh-remote-web/test/fix-prompt.test.mjs +170 -0
- package/packages/dsh-remote-web/test/runtime-bundle.test.mjs +198 -0
- package/packages/dsh-remote-web/test/tarball-update.test.mjs +460 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* e2ee-shim.mjs — bridge 侧注入的「镜像页 E2EE 加密 shim」(Phase-4)。
|
|
3
|
+
*
|
|
4
|
+
* 注入目标(仅经隧道回传的官方 dsh web 文档;同 mobile-adapter 的注入管线与宿主特征门):
|
|
5
|
+
* - 官方 dsh web index.html 的 </head> 前,由 dsh-bridge.mjs 在 text/html 响应上,
|
|
6
|
+
* 与 mobile-adapter 同一位置注入 <style id="dsh-e2ee-shim-css"> +
|
|
7
|
+
* <script id="dsh-e2ee-shim-js">(源码见同目录 e2ee-shim-script.js)。
|
|
8
|
+
*
|
|
9
|
+
* 职责(运行时,全部在镜像页 document-start 完成):
|
|
10
|
+
* 1. 读 native.html 解锁后写入的**一次性交接单**(sessionStorage 键 dsh-e2ee-handover),
|
|
11
|
+
* 读到后**立即删除**,用内存重建同一 E2EE 会话(与桥端已验证会话 sessId 一一对应);
|
|
12
|
+
* 无交接单(明文跳过 / 刷新丢密钥)→ 数据面零行为 + 状态徽标明确提示「⚠ 未加密」。
|
|
13
|
+
* 2. 有会话时 patch window.fetch / window.WebSocket:
|
|
14
|
+
* - HTTP:命中 §4.5 策略表(/api 排除 SSE、/sidebar、/git、/pet,含 channel/device
|
|
15
|
+
* 两种 /remote 形态)→ 原请求封装 v2 信封 POST → 响应信封解密(gzip)重建 Response;
|
|
16
|
+
* 静态壳/manifest/SSE/router 错误页 保持明文(信封缺失即原样透传);
|
|
17
|
+
* - WS:URL 追加 &e2ee=<sessId>&w=<8B hex>,逐消息封/解(k:"w"),AAD/子密钥/
|
|
18
|
+
* nonce/方向计数/wsLabel 与 e2ee-client.mjs / Phase-3 手机端完全一致;
|
|
19
|
+
* - 解封/会话失败显式「⚠ 无法解密/未加密」,绝不静默降级。
|
|
20
|
+
* 3. 状态徽标(#dsh-e2ee-badge,与 Phase-3 命名一致):🔒 已加密 / ⚠ 未加密(原因)+ 指引。
|
|
21
|
+
*
|
|
22
|
+
* 注入开关:
|
|
23
|
+
* - DSH_E2EE_SHIM=0 关闭注入(默认开启);
|
|
24
|
+
* - 开关:bridge 端 e2ee.enabled 为 false 时 dsh-bridge 不调用本模块 → 零注入、零行为
|
|
25
|
+
* (默认关 = 本 shim 默认不出现,镜像页保持既有明文路径)。
|
|
26
|
+
* - 「桌面宽屏/明文零行为」:未交接时数据面不拦截;桌面直连 dsh web 不经 bridge → 无注入。
|
|
27
|
+
*
|
|
28
|
+
* 测试:本模块的纯函数契约 + e2ee-shim-script.js 抽取的
|
|
29
|
+
* ==DSH-E2EE-SHIM-CORE== 区间(见 clients/dsh-remote/test/lib-e2ee-shim.mjs)
|
|
30
|
+
* 在 mock 与真实本地 router+bridge 上对拍(与 e2ee-client.mjs 字节一致)。
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import fs from "node:fs";
|
|
34
|
+
import path from "node:path";
|
|
35
|
+
import { fileURLToPath } from "node:url";
|
|
36
|
+
|
|
37
|
+
const THIS_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
export const SHIM_ID = "dsh-e2ee-shim";
|
|
39
|
+
export const SHIM_SCRIPT_PATH = path.join(THIS_DIR, "e2ee-shim-script.js");
|
|
40
|
+
|
|
41
|
+
/* 注入前 raw HTML 层官方特征(与 mobile-adapter 同源;仅注入官方 dsh web 文档)。 */
|
|
42
|
+
const HOST_FEATURE_RE =
|
|
43
|
+
/(__ModuleLoader__|@deepseek-ai\/dsh-client-modules|@deepseek-ai\/dsh-client-connection|<title>\s*DeepSeek Harness)/i;
|
|
44
|
+
|
|
45
|
+
const STYLE = `
|
|
46
|
+
/* ===== dsh-e2ee-shim(Phase-4 镜像页加密状态徽标;固定右上角,不影响官方布局) ===== */
|
|
47
|
+
#dsh-e2ee-badge {
|
|
48
|
+
position: fixed;
|
|
49
|
+
top: max(10px, env(safe-area-inset-top));
|
|
50
|
+
right: max(10px, env(safe-area-inset-right));
|
|
51
|
+
z-index: 2147483000;
|
|
52
|
+
display: flex; align-items: center; gap: 7px;
|
|
53
|
+
max-width: min(86vw, 380px);
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
padding: 6px 12px;
|
|
56
|
+
border-radius: 999px;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
font: 12px/1.5 -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Segoe UI", Roboto, sans-serif;
|
|
59
|
+
-webkit-user-select: none; user-select: none;
|
|
60
|
+
background: rgba(255, 255, 255, .96);
|
|
61
|
+
border: 1px solid rgba(127, 127, 127, .35);
|
|
62
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, .16);
|
|
63
|
+
color: #222;
|
|
64
|
+
}
|
|
65
|
+
#dsh-e2ee-badge .dsh-e2ee-badge-ico { flex: none; font-size: 13px; }
|
|
66
|
+
#dsh-e2ee-badge .dsh-e2ee-badge-txt { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
67
|
+
#dsh-e2ee-badge.ok { color: #14532d; border-color: rgba(26, 127, 55, .5); background: rgba(225, 244, 232, .97); }
|
|
68
|
+
#dsh-e2ee-badge.warn{ color: #7a4a00; border-color: rgba(181, 121, 0, .5); background: rgba(255, 247, 224, .97); }
|
|
69
|
+
#dsh-e2ee-badge.err { color: #8f1d26; border-color: rgba(207, 34, 46, .55); background: rgba(255, 236, 237, .97); }
|
|
70
|
+
#dsh-e2ee-badge .dsh-e2ee-badge-note {
|
|
71
|
+
display: none;
|
|
72
|
+
position: absolute;
|
|
73
|
+
top: calc(100% + 6px);
|
|
74
|
+
right: 0;
|
|
75
|
+
width: max-content;
|
|
76
|
+
max-width: min(84vw, 320px);
|
|
77
|
+
padding: 10px 12px;
|
|
78
|
+
border-radius: 12px;
|
|
79
|
+
font-weight: 400;
|
|
80
|
+
white-space: normal; word-break: break-word;
|
|
81
|
+
background: #fff;
|
|
82
|
+
border: 1px solid rgba(127, 127, 127, .3);
|
|
83
|
+
box-shadow: 0 4px 18px rgba(0, 0, 0, .2);
|
|
84
|
+
}
|
|
85
|
+
#dsh-e2ee-badge.expanded .dsh-e2ee-badge-note { display: block; }
|
|
86
|
+
#dsh-e2ee-badge .dsh-e2ee-badge-note a { color: #0b57d0; text-decoration: underline; }
|
|
87
|
+
/* 自动收缩态(2026-09):加密态展示数秒后缩成仅 🔒 芯片,不挡右上界面;悬停/点击即展开 */
|
|
88
|
+
#dsh-e2ee-badge.collapsed { padding: 5px 7px; gap: 0; max-width: 40px; }
|
|
89
|
+
#dsh-e2ee-badge.collapsed .dsh-e2ee-badge-txt,
|
|
90
|
+
#dsh-e2ee-badge.collapsed .dsh-e2ee-badge-note { display: none; }
|
|
91
|
+
#dsh-e2ee-badge { transition: padding .25s ease, max-width .25s ease; }
|
|
92
|
+
@media (max-width: 480px) {
|
|
93
|
+
#dsh-e2ee-badge { padding: 5px 10px; font-size: 11px; }
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
/** 读镜像 shim 脚本源码(缓存;文件缺失/读取失败 → 空串 + 警告,注入自然跳过)。 */
|
|
98
|
+
let cachedScript = null;
|
|
99
|
+
export function shimScriptText() {
|
|
100
|
+
if (cachedScript === null) {
|
|
101
|
+
try {
|
|
102
|
+
cachedScript = fs.readFileSync(SHIM_SCRIPT_PATH, "utf8");
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.warn(`[bridge] e2ee-shim 脚本缺失(${SHIM_SCRIPT_PATH}): ${e.message}(镜像页不回注 shim)`);
|
|
105
|
+
cachedScript = "";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return cachedScript;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 环境开关:DSH_E2EE_SHIM=0 关闭注入(默认开启;叠加桥端 e2ee.enabled 开关)。 */
|
|
112
|
+
export function e2eeShimEnabled() {
|
|
113
|
+
return process.env.DSH_E2EE_SHIM !== "0";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 纯函数:把 shim 注入到上游 HTML 的 </head> 之前(与 injectMobileAdapter 同 splice 位置)。
|
|
118
|
+
* @param {string} html
|
|
119
|
+
* @returns {{html:string, injected:boolean}}
|
|
120
|
+
*/
|
|
121
|
+
export function injectE2eeShim(html) {
|
|
122
|
+
const src = String(html);
|
|
123
|
+
const headIdx = src.toLowerCase().lastIndexOf("</head>");
|
|
124
|
+
if (headIdx === -1) return { html: src, injected: false };
|
|
125
|
+
if (src.includes(`data-${SHIM_ID}`)) return { html: src, injected: false }; // 防重复注入
|
|
126
|
+
const script = shimScriptText();
|
|
127
|
+
if (!script) return { html: src, injected: false };
|
|
128
|
+
const block =
|
|
129
|
+
`\n<style id="${SHIM_ID}-css" data-${SHIM_ID}>${STYLE}</style>` +
|
|
130
|
+
`\n<script id="${SHIM_ID}-js" data-${SHIM_ID}>${script}</script>\n`;
|
|
131
|
+
return { html: src.slice(0, headIdx) + block + src.slice(headIdx), injected: true };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 判断上游响应是否应注入 shim(与 mobile-adapter.shouldInjectHtml 同 gate:
|
|
136
|
+
* 仅官方 dsh web 的 text/html,含 </head>、无替换字符)。e2ee.enabled 由调用方(dsh-bridge)
|
|
137
|
+
* 决定,本函数只做类型/特征门 —— 「开关关 = 不调用 = 零注入」。
|
|
138
|
+
*/
|
|
139
|
+
export function shouldInjectE2eeShim({ contentType, html }) {
|
|
140
|
+
const ct = String(contentType || "");
|
|
141
|
+
if (!/text\/html/i.test(ct)) return false;
|
|
142
|
+
const src = String(html || "");
|
|
143
|
+
if (!/<\/head>/i.test(src)) return false;
|
|
144
|
+
if (/\uFFFD/.test(src)) return false; // 非 UTF-8,不冒险改写
|
|
145
|
+
return HOST_FEATURE_RE.test(src);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* bridge 接线主入口:类型/特征 gate + 注入,一步完成。
|
|
150
|
+
* @param {{buf:Buffer, contentType?:string}} args
|
|
151
|
+
* @returns {{buf:Buffer, injected:boolean}}
|
|
152
|
+
*/
|
|
153
|
+
export function maybeInjectE2eeShim({ buf, contentType }) {
|
|
154
|
+
if (!e2eeShimEnabled() || !Buffer.isBuffer(buf) || buf.length === 0) {
|
|
155
|
+
return { buf, injected: false };
|
|
156
|
+
}
|
|
157
|
+
const text = buf.toString("utf8");
|
|
158
|
+
if (!shouldInjectE2eeShim({ contentType, html: text })) return { buf, injected: false };
|
|
159
|
+
const out = injectE2eeShim(text);
|
|
160
|
+
return { buf: Buffer.from(out.html, "utf8"), injected: out.injected };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export default { injectE2eeShim, maybeInjectE2eeShim, shouldInjectE2eeShim, e2eeShimEnabled, shimScriptText };
|