@mrrisega/dsh-remote 0.6.4-beta.1 → 0.6.4-beta.10
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/README.md +55 -0
- package/clients/dsh-remote/dsh-bridge.mjs +32 -1
- package/clients/dsh-remote/test/harness-cookie-selfheal.test.mjs +118 -0
- package/docs/telemetry.md +145 -0
- package/dsh-setup.mjs +533 -96
- package/package.json +4 -3
- package/packages/dsh-remote-web/lib/client.js +110 -6
- package/packages/dsh-remote-web/lib/index.js +637 -25
- package/packages/dsh-remote-web/package.json +1 -1
- package/packages/dsh-remote-web/test/auto-restart.test.mjs +303 -0
- package/packages/dsh-remote-web/test/harness-restart.test.mjs +106 -40
- package/packages/dsh-remote-web/test/launchd-domain.test.mjs +78 -0
- package/packages/dsh-remote-web/test/patch-activation.test.mjs +183 -0
- package/packages/dsh-remote-web/test/setup-output.test.mjs +111 -0
- package/packages/dsh-remote-web/test/telemetry.test.mjs +607 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrisega/dsh-remote",
|
|
3
|
-
"version": "0.6.4-beta.
|
|
3
|
+
"version": "0.6.4-beta.10",
|
|
4
4
|
"description": "手机远程控制 DeepSeek Harness · Remote control DeepSeek Harness (dsh web) from any phone browser — 100% 全功能 App 级体验:发消息、看工具执行、审批权限、改设置、管凭据,含特权操作,免内网穿透。一条命令安装 npx @mrrisega/dsh-remote。Mobile remote control for DSH, self-host or SaaS, no server needed on LAN.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"dsh-setup.mjs",
|
|
39
39
|
"clients/dsh-remote",
|
|
40
40
|
"packages/dsh-remote-web",
|
|
41
|
-
"docs/self-hosting.md"
|
|
41
|
+
"docs/self-hosting.md",
|
|
42
|
+
"docs/telemetry.md"
|
|
42
43
|
],
|
|
43
44
|
"workspaces": [
|
|
44
45
|
"packages/*",
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
"test": "npm run test:router && npm run test:plugin && npm run test:bridge",
|
|
52
53
|
"test:router": "node --test packages/relay-router/test/native-device-selection.test.mjs packages/relay-router/test/quotas.test.mjs packages/relay-router/test/presence-report.test.mjs",
|
|
53
54
|
"_comment:test:plugin": "DSH_RELAY_SKIP_SERVICE=1 = 测试隔离:插件加载时不做真实系统操作(npx 补装 / launchctl / 重启 harness);需要真实分支的用例自行 unset(见 account-switch / runtime-provision / harness-restart 用例注释)",
|
|
54
|
-
"test:plugin": "DSH_RELAY_SKIP_SERVICE=1 node --test --test-concurrency=1 packages/dsh-remote-web/test/*.test.mjs",
|
|
55
|
+
"test:plugin": "DSH_REMOTE_TELEMETRY=0 DSH_RELAY_SKIP_SERVICE=1 node --test --test-concurrency=1 packages/dsh-remote-web/test/*.test.mjs",
|
|
55
56
|
"test:bridge": "node --test clients/dsh-remote/test/*.test.mjs",
|
|
56
57
|
"check": "node --check clients/dsh-remote/dsh-bridge.mjs && node --check clients/dsh-remote/e2ee-shim.mjs && node --check dsh-setup.mjs && node --check packages/dsh-remote-web/lib/index.js && node --check packages/dsh-remote-web/lib/client.js && bash -n deploy/install-open.sh && npm run check:alias",
|
|
57
58
|
"sync:alias": "node scripts/sync-legacy-alias.mjs",
|
|
@@ -142,6 +142,8 @@ window.__ModuleLoader__.load({
|
|
|
142
142
|
".dru-popup-foot button:hover{color:#0969da}",
|
|
143
143
|
".dru-popup .dru-msg{text-align:left}",
|
|
144
144
|
".dru-community-qr{display:block;width:220px;max-width:62vw;margin:0 auto;background:#ffffff;padding:10px;border-radius:10px;border:1px solid #d0d7de;box-sizing:content-box}",
|
|
145
|
+
".dru-restart-auto{display:flex;align-items:center;gap:8px;margin-top:10px;font-size:12.5px;color:#1a7f37;background:#dafbe1;border-radius:8px;padding:8px 10px}",
|
|
146
|
+
".dru-restart-auto.muted{color:#57606a;background:#f6f8fa}",
|
|
145
147
|
".dru-fb-community{margin-top:16px;padding-top:14px;border-top:1px dashed #d0d7de;text-align:center}",
|
|
146
148
|
".dru-fb-community .dru-community-qr{width:180px;max-width:56vw}",
|
|
147
149
|
// ── 自管理:版本与更新(插件面板内提供在线更新/彻底卸载,市场无更新按钮) ──
|
|
@@ -1129,6 +1131,8 @@ window.__ModuleLoader__.load({
|
|
|
1129
1131
|
}
|
|
1130
1132
|
|
|
1131
1133
|
// ── 面板主体(渲染于设置页 settings.section 栏目内) ─────────────────────
|
|
1134
|
+
// 匿名装机统计的「面板打开」只上报一次/每页(node 半还会每进程去重)。
|
|
1135
|
+
var panelOpenedSent = false;
|
|
1132
1136
|
function RemoteControlSection(props) {
|
|
1133
1137
|
|
|
1134
1138
|
var statusArr = useState(null); var st = statusArr[0]; var setSt = statusArr[1];
|
|
@@ -1189,6 +1193,10 @@ window.__ModuleLoader__.load({
|
|
|
1189
1193
|
// ── 💬 交流群(「加入交流群」按钮 + 弹窗):同样放在全部既有字段之后 ──
|
|
1190
1194
|
var communityArr = useState(null); var community = communityArr[0]; var setCommunity = communityArr[1]; // {qrcode,wechat};null=未加载
|
|
1191
1195
|
var commOpenArr = useState(false); var commOpen = commOpenArr[0]; var setCommOpen = commOpenArr[1];
|
|
1196
|
+
// 【自动化】重启倒计时 / 是否已被用户取消(初值按本次 pending 事件的持久化标记,跨刷新生效)
|
|
1197
|
+
var restartCancelArr = useState(function () { return autoRestartCancelled((st && st.restart && st.restart.at) || 0); });
|
|
1198
|
+
var restartCancelled = restartCancelArr[0]; var setRestartCancelled = restartCancelArr[1];
|
|
1199
|
+
var restartCountArr = useState(null); var restartCountdown = restartCountArr[0]; var setRestartCountdown = restartCountArr[1];
|
|
1192
1200
|
// ── 🔗 登录后自动闭环:连接阶段(运行环境 → bridge 进程 → 设备已在中继注册=online) ──
|
|
1193
1201
|
// conn 来自 GET /dsh-remote/bridge-status(node 半在返回前会自动补装/拉起/退避重试),
|
|
1194
1202
|
// 面板据它显示「正在准备运行环境…/正在启动 Bridge…/正在连接中继…/已连接 ✅」,
|
|
@@ -1235,6 +1243,9 @@ window.__ModuleLoader__.load({
|
|
|
1235
1243
|
// 是否需要重启 DeepSeek harness(首次安装/在线更新后置顶提醒 + 底部常驻按钮)
|
|
1236
1244
|
var restartInfo = (st && st.restart) || {};
|
|
1237
1245
|
var restartPending = !!restartInfo.pending;
|
|
1246
|
+
// kind === "refresh" = 插件文件在运行中被改写(装完/更新完),只需刷新页面;
|
|
1247
|
+
// 其余情况(极为罕见)才提重启 dsh web。
|
|
1248
|
+
var isRefreshOnly = restartPending && restartInfo.kind === "refresh";
|
|
1238
1249
|
// 连接阶段(node 半下发):轮询拿到的新鲜结果优先,其次用 /status 里带的那一份;
|
|
1239
1250
|
// 旧版 host(响应里没有 connect 字段)→ 回退到原来的 service.running 文案(行为不变)。
|
|
1240
1251
|
var connInfo = conn || (st && st.connect) || null;
|
|
@@ -1551,6 +1562,16 @@ window.__ModuleLoader__.load({
|
|
|
1551
1562
|
};
|
|
1552
1563
|
}, [view, mode, loggedIn, phoneKey, serviceRunning]);
|
|
1553
1564
|
|
|
1565
|
+
// ── 匿名装机统计(面板打开) ──────────────────────────────────────────────
|
|
1566
|
+
// 只上报「面板被打开过」这一个事件名(node 半每进程去重、DSH_REMOTE_TELEMETRY=0 直接丢弃);
|
|
1567
|
+
// 不含任何账号、手机号、设备指纹、会话内容或文件内容——隐私边界见 docs/telemetry.md。
|
|
1568
|
+
useEffect(function () {
|
|
1569
|
+
if (panelOpenedSent) return undefined;
|
|
1570
|
+
panelOpenedSent = true;
|
|
1571
|
+
post("/dsh-remote/telemetry/panel-opened", {}).catch(function () { /* 静默:统计失败绝不影响面板 */ });
|
|
1572
|
+
return undefined;
|
|
1573
|
+
}, []);
|
|
1574
|
+
|
|
1554
1575
|
// ── 登录后自动闭环(0.6.4):连接阶段短轮询 ──────────────────────────────
|
|
1555
1576
|
// 登录成功(含从别处已登录、被面板读到)后立刻开始推进连接,之后自调度:
|
|
1556
1577
|
// 非 online → 2.5s 一次(阶段自动前进,用户零操作);online → 15s 一次(退避,只做保活/新设备发现)。
|
|
@@ -1861,6 +1882,25 @@ window.__ModuleLoader__.load({
|
|
|
1861
1882
|
});
|
|
1862
1883
|
};
|
|
1863
1884
|
|
|
1885
|
+
/**
|
|
1886
|
+
* 【自动化】首次安装/在线更新后自动重启 DeepSeek harness —— 用户不需要点任何按钮。
|
|
1887
|
+
*
|
|
1888
|
+
* 为什么能自动:① 重启只是重启 dsh web(bridge 与手机端连接不受影响);② 重启后
|
|
1889
|
+
* waitHarnessBack 会轮询到它回来并**自动刷新页面**,面板与「连接中→已连接」流程继续跑完。
|
|
1890
|
+
* 安全阀:① 面板不可见(用户没在看)→ 不计时,回来再继续;② 15 秒倒计时内可一键取消,
|
|
1891
|
+
* 取消标记按本次 pending 事件(at 时间戳)持久化,同一事件不再自动重启(下次安装/更新会重新触发);
|
|
1892
|
+
* ③ 面板上有其他操作在跑(busy≠"")→ 暂停计时,避免打断用户正在做的事。
|
|
1893
|
+
*/
|
|
1894
|
+
var AUTO_RESTART_DELAY_MS = 15000;
|
|
1895
|
+
var RESTART_CANCEL_KEY = "dsh-remote-auto-restart-cancelled";
|
|
1896
|
+
|
|
1897
|
+
function autoRestartCancelled(at) {
|
|
1898
|
+
try { return localStorage.getItem(RESTART_CANCEL_KEY) === String(at || ""); } catch (e) { return false; }
|
|
1899
|
+
}
|
|
1900
|
+
function markAutoRestartCancelled(at) {
|
|
1901
|
+
try { localStorage.setItem(RESTART_CANCEL_KEY, String(at || "")); } catch (e) { /* 忽略 */ }
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1864
1904
|
/** 轮询等待 harness 回来;观察到「断过又恢复」或等待足够久后自动刷新页面。 */
|
|
1865
1905
|
var waitHarnessBack = function (tries, sawDown) {
|
|
1866
1906
|
if (tries > 40) { setMsg("err", "重启后仍未就绪,请手动刷新页面或重启 DeepSeek harness。"); setBusy(""); return; }
|
|
@@ -2385,6 +2425,13 @@ window.__ModuleLoader__.load({
|
|
|
2385
2425
|
h("div", { className: "dru-hint", style: { marginBottom: 6 } }, "🛠 电脑端一键安装:bridge 与「远程访问」面板一次到位——云端/自建切换、账号登录、bridge 启停、一次性扫码访问、已授权设备管理、意见反馈都在这里。"),
|
|
2386
2426
|
h("div", { className: "dru-hint", style: { marginBottom: 6 } }, "🔒 安全与通道:HTTP / WebSocket 全量透传,一次性访问密钥认证,面板实时显示设备与已授权设备列表;服务端可配置流量配额。"),
|
|
2387
2427
|
h("div", { className: "dru-hint" }, "🛡 端到端加密:手机↔电脑之间的消息内容用「你的账号密码派生密钥」端到端加密——密钥与密码不落服务端(仅存校验值),中继只可见路径/大小/时间(详见 README「安全与隐私」)。"),
|
|
2428
|
+
// 匿名装机统计(隐私披露):开源项目必须把「采什么 / 不采什么 / 怎么关」写在用户看得到的地方。
|
|
2429
|
+
// 只保留结论式的说明句:不再单列「隐私说明: README… · docs/telemetry.md」那一行文档链接
|
|
2430
|
+
// (面板里堆文档链接既占地方又不像产品文案;完整字段清单在仓库 docs/telemetry.md)。
|
|
2431
|
+
h("div", { className: "dru-hint", style: { marginTop: 6 } },
|
|
2432
|
+
"📊 匿名装机统计:只上报「装机/连接是否成功」这类事件(安装开始与失败原因、bridge 是否注册成功、是否首次远程打通)——"
|
|
2433
|
+
+ "不含任何账号、手机号、会话或文件内容、主机名、路径、设备指纹与 IP;标识是本机随机 ID(重装即变)。"
|
|
2434
|
+
+ "用环境变量 DSH_REMOTE_TELEMETRY=0 可完全关闭(不生成 ID、不发任何请求)。"),
|
|
2388
2435
|
// 加入交流群(后台上传二维码后出现;点击弹出二维码大图便于扫码)
|
|
2389
2436
|
community && community.qrcode
|
|
2390
2437
|
? h("div", { className: "dru-actions", style: { marginTop: 10 } },
|
|
@@ -2402,6 +2449,33 @@ window.__ModuleLoader__.load({
|
|
|
2402
2449
|
);
|
|
2403
2450
|
}
|
|
2404
2451
|
|
|
2452
|
+
// 【自动化】待重启时自动倒计时重启(见 autoRestartCancelled 说明);倒计时只在面板可见且无其他操作时推进。
|
|
2453
|
+
// ⚠️ isRefreshOnly(插件更新后只需刷新)时**不**自动重启:刷新是零风险动作,重启 dsh web 会打断
|
|
2454
|
+
// 用户正在进行的会话,不能替他做这个决定 —— 让他点「刷新页面」即可。
|
|
2455
|
+
useEffect(function () {
|
|
2456
|
+
if (!restartPending || isRefreshOnly || busy !== "" || restartCancelled) { setRestartCountdown(null); return undefined; }
|
|
2457
|
+
var at = restartInfo.at || 0;
|
|
2458
|
+
if (autoRestartCancelled(at)) { setRestartCountdown(null); return undefined; }
|
|
2459
|
+
var left = Math.round(AUTO_RESTART_DELAY_MS / 1000);
|
|
2460
|
+
setRestartCountdown(left);
|
|
2461
|
+
var iv = setInterval(function () {
|
|
2462
|
+
if (document.hidden) return; // 用户没在看 → 暂停计时
|
|
2463
|
+
left -= 1;
|
|
2464
|
+
if (left > 0) { setRestartCountdown(left); return; }
|
|
2465
|
+
clearInterval(iv);
|
|
2466
|
+
setRestartCountdown(0);
|
|
2467
|
+
restartHarness(); // 已有实现:重启 + 轮询等它回来 + 自动刷新页面
|
|
2468
|
+
}, 1000);
|
|
2469
|
+
return function () { clearInterval(iv); };
|
|
2470
|
+
}, [restartPending, isRefreshOnly, restartInfo.at, busy, restartCancelled]);
|
|
2471
|
+
|
|
2472
|
+
/** 取消本次自动重启(按事件持久化:同一 pending 事件不再自动重启)。 */
|
|
2473
|
+
var cancelAutoRestart = function () {
|
|
2474
|
+
markAutoRestartCancelled(restartInfo.at || 0);
|
|
2475
|
+
setRestartCancelled(true);
|
|
2476
|
+
setRestartCountdown(null);
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2405
2479
|
/** 交流群弹窗(复用满意度弹窗的 dru-popup 视觉;点遮罩/关闭即收起)。 */
|
|
2406
2480
|
function renderCommunityModal() {
|
|
2407
2481
|
if (!commOpen || !(community && community.qrcode)) return null;
|
|
@@ -2439,16 +2513,46 @@ window.__ModuleLoader__.load({
|
|
|
2439
2513
|
h("span", { className: "dru-restart-alert-icon" }, "🔔"),
|
|
2440
2514
|
h("div", { style: { flex: "1" } },
|
|
2441
2515
|
h("div", { className: "dru-restart-alert-title" },
|
|
2442
|
-
|
|
2516
|
+
// 插件已改为 patch 热加载:装插件/在线更新都不再需要重启 harness,
|
|
2517
|
+
// 唯一要用户动一下的情形是"插件文件在运行中被改写"→ 刷新页面即可。
|
|
2518
|
+
isRefreshOnly ? "插件已更新,刷新页面即可生效" : "需要重启 dsh web 才能载入新插件"),
|
|
2443
2519
|
h("div", { className: "dru-restart-alert-sub" },
|
|
2444
|
-
|
|
2520
|
+
isRefreshOnly
|
|
2521
|
+
? "刚装好/刚更新的插件已在磁盘上,浏览器里的面板还是旧版本。刷新一下这个页面即可(手机端连接与已登录状态都不受影响)。"
|
|
2522
|
+
: "当前运行中的 dsh web 里还没有载入这个插件。先刷新页面;如果刷新后仍然看不到面板,再重启一次 dsh web。"),
|
|
2523
|
+
// 【自动化】待重启时自动倒计时重启(用户零操作);倒计时期间可一键取消
|
|
2524
|
+
restartCountdown !== null && restartCountdown > 0
|
|
2525
|
+
? h("div", { className: "dru-restart-auto", role: "status" },
|
|
2526
|
+
h("span", { className: "loading" }),
|
|
2527
|
+
h("span", null, "安装已完成,将在 " + restartCountdown + " 秒后自动重启 DeepSeek harness(页面会自动恢复,无需操作)"))
|
|
2528
|
+
: null,
|
|
2529
|
+
restartCountdown === 0
|
|
2530
|
+
? h("div", { className: "dru-restart-auto", role: "status" }, h("span", { className: "loading" }), h("span", null, "正在自动重启…"))
|
|
2531
|
+
: null,
|
|
2532
|
+
restartCancelled && restartCountdown === null
|
|
2533
|
+
? h("div", { className: "dru-restart-auto muted" }, "已取消自动重启 —— 你也可以随时点下面的按钮完成安装")
|
|
2534
|
+
: null,
|
|
2445
2535
|
h("div", { className: "dru-actions", style: { marginTop: 10 } },
|
|
2536
|
+
isRefreshOnly
|
|
2537
|
+
? h("button", {
|
|
2538
|
+
type: "button",
|
|
2539
|
+
className: "dru-btn dru-btn-primary",
|
|
2540
|
+
onClick: function () { try { location.reload(); } catch (e) { /* 忽略 */ } }
|
|
2541
|
+
}, "刷新页面")
|
|
2542
|
+
: null,
|
|
2446
2543
|
h("button", {
|
|
2447
2544
|
type: "button",
|
|
2448
|
-
className: "dru-btn dru-btn-primary",
|
|
2545
|
+
className: "dru-btn " + (isRefreshOnly ? "dru-btn-ghost" : "dru-btn-primary"),
|
|
2449
2546
|
disabled: busy !== "",
|
|
2450
2547
|
onClick: function () { restartHarness(); }
|
|
2451
|
-
}, busy === "restart" ? "重启中…" : "重启
|
|
2548
|
+
}, busy === "restart" ? "重启中…" : (restartCountdown !== null && restartCountdown > 0 ? "立即重启(不用等)" : "重启 dsh web")),
|
|
2549
|
+
restartCountdown !== null && restartCountdown > 0
|
|
2550
|
+
? h("button", {
|
|
2551
|
+
type: "button",
|
|
2552
|
+
className: "dru-btn dru-btn-ghost",
|
|
2553
|
+
onClick: cancelAutoRestart
|
|
2554
|
+
}, "取消自动重启")
|
|
2555
|
+
: null
|
|
2452
2556
|
)
|
|
2453
2557
|
)
|
|
2454
2558
|
)
|
|
@@ -2457,13 +2561,13 @@ window.__ModuleLoader__.load({
|
|
|
2457
2561
|
),
|
|
2458
2562
|
// 常驻入口最底部:「重启 DeepSeek harness」按钮始终可达(首次安装/更新/排查都用它)
|
|
2459
2563
|
h("div", { className: "dru-restart-foot" },
|
|
2460
|
-
h("div", { className: "dru-hint" }, "重启
|
|
2564
|
+
h("div", { className: "dru-hint" }, "重启 dsh web:仅在「刷新页面后仍看不到面板」时才需要。日常安装/更新插件已自动热加载,装完刷新页面即可;重启只影响 dsh web 本身,不影响 bridge 与手机端连接。"),
|
|
2461
2565
|
h("button", {
|
|
2462
2566
|
type: "button",
|
|
2463
2567
|
className: "dru-btn " + (restartPending ? "dru-btn-primary" : "dru-btn-ghost"),
|
|
2464
2568
|
disabled: busy !== "",
|
|
2465
2569
|
onClick: function () { restartHarness(); }
|
|
2466
|
-
}, busy === "restart" ? "重启中…" : "🔄 重启
|
|
2570
|
+
}, busy === "restart" ? "重启中…" : "🔄 重启 dsh web")
|
|
2467
2571
|
),
|
|
2468
2572
|
renderCommunityModal()
|
|
2469
2573
|
);
|