dsh-remote-ui 0.6.0 → 0.6.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/lib/client.js +311 -18
- package/lib/index.js +655 -52
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -91,6 +91,13 @@ window.__ModuleLoader__.load({
|
|
|
91
91
|
".dru-msg-err{color:#cf222e}",
|
|
92
92
|
".dru-msg-warn{color:#9a6700}",
|
|
93
93
|
".dru-hint{font-size:12px;color:#57606a;line-height:1.6}",
|
|
94
|
+
// ── 重启 DeepSeek harness(首次安装/更新后置顶提醒 + 底部常驻按钮) ──
|
|
95
|
+
".dru-restart-alert{display:flex;gap:10px;align-items:flex-start;background:#fff8c5;border:1px solid #d4a72c;border-radius:10px;padding:12px 14px;margin-bottom:12px}",
|
|
96
|
+
".dru-restart-alert-icon{font-size:18px;line-height:1.2}",
|
|
97
|
+
".dru-restart-alert-title{font-size:13px;font-weight:700;color:#7d4e00}",
|
|
98
|
+
".dru-restart-alert-sub{font-size:12px;color:#6b5900;margin-top:4px;line-height:1.6}",
|
|
99
|
+
".dru-restart-foot{margin-top:14px;padding-top:12px;border-top:1px solid #eaeef2;display:flex;gap:10px;align-items:center;flex-wrap:wrap}",
|
|
100
|
+
".dru-restart-foot .dru-hint{flex:1;min-width:180px;margin:0}",
|
|
94
101
|
// ── 用户反馈模块 ──
|
|
95
102
|
".dru-fb-tabs{display:flex;gap:8px;margin-bottom:10px}",
|
|
96
103
|
".dru-fb-tab{flex:1;padding:6px 0;text-align:center;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;color:#57606a;background:#eaeef2;border:1px solid #d0d7de;user-select:none}",
|
|
@@ -134,6 +141,9 @@ window.__ModuleLoader__.load({
|
|
|
134
141
|
".dru-popup-foot button{border:none;background:none;color:#57606a;cursor:pointer;font-size:12px;font-family:inherit;padding:4px 6px}",
|
|
135
142
|
".dru-popup-foot button:hover{color:#0969da}",
|
|
136
143
|
".dru-popup .dru-msg{text-align:left}",
|
|
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-fb-community{margin-top:16px;padding-top:14px;border-top:1px dashed #d0d7de;text-align:center}",
|
|
146
|
+
".dru-fb-community .dru-community-qr{width:180px;max-width:56vw}",
|
|
137
147
|
// ── 自管理:版本与更新(插件面板内提供在线更新/彻底卸载,市场无更新按钮) ──
|
|
138
148
|
".dru-ver-badge{display:inline-block;font-size:11px;border-radius:999px;padding:1px 8px;margin-left:6px;vertical-align:1px}",
|
|
139
149
|
".dru-ver-badge-new{color:#9a6700;background:#fff8c5;border:1px solid #eed888}",
|
|
@@ -347,6 +357,60 @@ window.__ModuleLoader__.load({
|
|
|
347
357
|
return api(path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(data || {}) });
|
|
348
358
|
};
|
|
349
359
|
|
|
360
|
+
// ── 交流群(运营二维码;管理后台统一上传,node 半经公开配置下发) ─────────
|
|
361
|
+
// 设置面板「加入交流群」按钮与用户反馈页都展示同一张码;未配置(qrcode 为空)→ 不展示任何入口。
|
|
362
|
+
var COMMUNITY_TTL_MS = 5 * 60 * 1000; // 面板/反馈卡共用缓存:5 分钟内不重复请求
|
|
363
|
+
var communityCache = { at: 0, data: null };
|
|
364
|
+
/** 取交流群信息(模块级缓存;失败视为未配置,不打扰用户)。 */
|
|
365
|
+
function loadCommunity(force) {
|
|
366
|
+
if (!force && communityCache.data && Date.now() - communityCache.at < COMMUNITY_TTL_MS) {
|
|
367
|
+
return Promise.resolve(communityCache.data);
|
|
368
|
+
}
|
|
369
|
+
return api("/dsh-remote/community").then(function (b) {
|
|
370
|
+
communityCache = { at: Date.now(), data: { qrcode: (b && b.qrcode) || "", wechat: (b && b.wechat) || "" } };
|
|
371
|
+
return communityCache.data;
|
|
372
|
+
}).catch(function () {
|
|
373
|
+
communityCache = { at: Date.now(), data: { qrcode: "", wechat: "" } };
|
|
374
|
+
return communityCache.data;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── 登录后首次加载的自愈(0.6.1) ───────────────────────────────
|
|
379
|
+
// 现象(首次安装后立即登录):面板在 loggedIn 翻真的瞬间就请求 /dsh-remote/access-key 与
|
|
380
|
+
// /dsh-remote/mobile-sessions;此时中继握手可能尚未就绪(bridge 刚被重启、device-login 共享密钥
|
|
381
|
+
// 刚补齐或正在轮换、中继冷启动/网络抖动),旧版只报一次红字且不重试——必须手动刷新页面才行。
|
|
382
|
+
// 现在:可重试类失败按退避自动重试(成功即自动清除红字),并给红字配「重试」按钮。
|
|
383
|
+
var LOGIN_LOAD_RETRY_MS = [1200, 3000, 6000];
|
|
384
|
+
|
|
385
|
+
/** node 半标记 retryable(中继未就绪)或网络/5xx → 值得自动重试;未登录/密码失效不重试。 */
|
|
386
|
+
function retryableFail(e) {
|
|
387
|
+
var body = e && e.body;
|
|
388
|
+
if (body && body.retryable === true) return true;
|
|
389
|
+
if (body && typeof body.hint === "string" && /^relay_/.test(body.hint)) return true;
|
|
390
|
+
var st = e && e.status;
|
|
391
|
+
if (typeof st !== "number") return true; // fetch 本身失败(网络中断/被中止)
|
|
392
|
+
return st === 0 || st === 408 || st === 429 || st >= 500;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** 定时器节流:tests/SSR 环境没有 setTimeout 时静默降级(不抛错、不阻塞面板)。 */
|
|
396
|
+
function later(fn, ms) {
|
|
397
|
+
try { return setTimeout(fn, ms); } catch (e) { return null; }
|
|
398
|
+
}
|
|
399
|
+
function clearLater(t) {
|
|
400
|
+
if (t == null) return;
|
|
401
|
+
try { clearTimeout(t); } catch (e) { /* 忽略 */ }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** 一次性访问密钥 / 已授权设备列表的自动重试状态(放在组件外:不随渲染重建)。 */
|
|
405
|
+
var akeyRetry = { timer: null, attempt: 0 };
|
|
406
|
+
var devRetry = { timer: null, attempt: 0 };
|
|
407
|
+
// 请求在途标记同样放组件外:组件内 state 闭包会在定时器/轮询回调里过期,
|
|
408
|
+
// 导致 busy 护栏失效(重复请求)或误拦(该重试却跳过)。
|
|
409
|
+
var akeyInFlight = { v: false };
|
|
410
|
+
var devInFlight = { v: false };
|
|
411
|
+
function cancelAkeyRetry() { clearLater(akeyRetry.timer); akeyRetry.timer = null; }
|
|
412
|
+
function cancelDevRetry() { clearLater(devRetry.timer); devRetry.timer = null; }
|
|
413
|
+
|
|
350
414
|
/** 一次性访问 url → 升级/续费页 url(带同一 auth,进入后即登录态到 /app/promo)。 */
|
|
351
415
|
function promoUrlOf(u) {
|
|
352
416
|
var s = String(u || "");
|
|
@@ -442,6 +506,8 @@ window.__ModuleLoader__.load({
|
|
|
442
506
|
var mineListArr = useState(null); var mineList = mineListArr[0]; var setMineList = mineListArr[1];
|
|
443
507
|
var mineErrArr = useState(false); var mineErr = mineErrArr[0]; var setMineErr = mineErrArr[1];
|
|
444
508
|
var mineBusyArr = useState(false); var mineBusy = mineBusyArr[0]; var setMineBusy = mineBusyArr[1];
|
|
509
|
+
// 💬 交流群:反馈页底部展示「加入交流群」二维码(与设置面板共用同一份缓存)
|
|
510
|
+
var commArr = useState(communityCache.data); var comm = commArr[0]; var setComm = commArr[1];
|
|
445
511
|
|
|
446
512
|
function setMsg(kind, text) { setFbMsg({ kind: kind, text: text }); }
|
|
447
513
|
|
|
@@ -449,6 +515,12 @@ window.__ModuleLoader__.load({
|
|
|
449
515
|
// 自建/未登录(cfg.phone 为空)→ 仍走本地 thread_token,不发 /mine。
|
|
450
516
|
var fbAccount = !!(cfg && cfg.phone && fbAuth === "account");
|
|
451
517
|
|
|
518
|
+
useEffect(function () {
|
|
519
|
+
var alive = true;
|
|
520
|
+
loadCommunity().then(function (d) { if (alive) setComm(d); });
|
|
521
|
+
return function () { alive = false; };
|
|
522
|
+
}, []);
|
|
523
|
+
|
|
452
524
|
var loadCfg = useCallback(function () {
|
|
453
525
|
api("/dsh-remote/feedback-config").then(function (b) {
|
|
454
526
|
setCfg(b);
|
|
@@ -688,7 +760,15 @@ window.__ModuleLoader__.load({
|
|
|
688
760
|
h("button", { type: "button", className: "dru-btn dru-btn-primary", style: { width: "100%" }, disabled: busy !== "", onClick: doSubmit }, busy === "submit" ? "提交中…" : "提交反馈"),
|
|
689
761
|
fbMsg && h("div", { className: "dru-msg dru-msg-" + fbMsg.kind }, fbMsg.text)
|
|
690
762
|
)
|
|
691
|
-
: renderMine()
|
|
763
|
+
: renderMine(),
|
|
764
|
+
// 反馈页底部:企微交流群二维码(后台上传后出现;未配置不展示)
|
|
765
|
+
comm && comm.qrcode
|
|
766
|
+
? h("div", { className: "dru-fb-community" },
|
|
767
|
+
h("div", { className: "dru-fb-community-title" }, "💬 加入企微交流群"),
|
|
768
|
+
h("div", { className: "dru-hint", style: { marginBottom: 8 } }, "扫码入群:安装答疑 / 使用技巧 / 版本更新 / 问题反馈"),
|
|
769
|
+
h("img", { className: "dru-community-qr", src: comm.qrcode, alt: "企微交流群二维码" }),
|
|
770
|
+
comm.wechat ? h("div", { className: "dru-hint", style: { marginTop: 8 } }, "二维码失效或群满,可加客服微信:" + comm.wechat) : null)
|
|
771
|
+
: null
|
|
692
772
|
);
|
|
693
773
|
}
|
|
694
774
|
|
|
@@ -1099,6 +1179,9 @@ window.__ModuleLoader__.load({
|
|
|
1099
1179
|
var pwdSmsArr = useState(""); var pwdSms = pwdSmsArr[0]; var setPwdSms = pwdSmsArr[1];
|
|
1100
1180
|
var pwdSmsBtnArr = useState("获取验证码"); var pwdSmsBtn = pwdSmsBtnArr[0]; var setPwdSmsBtn = pwdSmsBtnArr[1];
|
|
1101
1181
|
var pwdNewArr = useState(""); var pwdNew = pwdNewArr[0]; var setPwdNew = pwdNewArr[1]; // 新密码(≥8)
|
|
1182
|
+
// ── 💬 交流群(「加入交流群」按钮 + 弹窗):同样放在全部既有字段之后 ──
|
|
1183
|
+
var communityArr = useState(null); var community = communityArr[0]; var setCommunity = communityArr[1]; // {qrcode,wechat};null=未加载
|
|
1184
|
+
var commOpenArr = useState(false); var commOpen = commOpenArr[0]; var setCommOpen = commOpenArr[1];
|
|
1102
1185
|
|
|
1103
1186
|
var refresh = useCallback(function () {
|
|
1104
1187
|
setBusy("status");
|
|
@@ -1126,6 +1209,21 @@ window.__ModuleLoader__.load({
|
|
|
1126
1209
|
|
|
1127
1210
|
var loggedIn = !!(st && st.config && (st.config.phone || st.config.hasLocalKey));
|
|
1128
1211
|
var serviceRunning = !!(st && st.service && st.service.running);
|
|
1212
|
+
// 0.6.2:运行环境是否就绪 + launchd 是否处于崩溃循环(入口脚本缺失导致的 KeepAlive 重拉)。
|
|
1213
|
+
// 旧版把「作业在 launchd 里但进程秒退」显示成「运行中」,用户完全看不出问题在哪。
|
|
1214
|
+
var serviceRuntimeReady = !(st && st.service && st.service.runtimeReady === false);
|
|
1215
|
+
var serviceLaunchd = (st && st.service && st.service.launchd) || {};
|
|
1216
|
+
var serviceCrashing = !!serviceLaunchd.crashing;
|
|
1217
|
+
var serviceStateText = !st ? "查询中…"
|
|
1218
|
+
: serviceRunning ? "运行中"
|
|
1219
|
+
: serviceCrashing ? "启动失败(已自动转入修复)"
|
|
1220
|
+
: !serviceRuntimeReady ? "运行环境安装中…"
|
|
1221
|
+
: "已停止";
|
|
1222
|
+
// 是否需要重启 DeepSeek harness(首次安装/在线更新后置顶提醒 + 底部常驻按钮)
|
|
1223
|
+
var restartInfo = (st && st.restart) || {};
|
|
1224
|
+
var restartPending = !!restartInfo.pending;
|
|
1225
|
+
// 账号标识(掩码手机号):登录成功/换账号时变化 → 触发上方的重取 effect(比布尔 loggedIn 更敏感)
|
|
1226
|
+
var phoneKey = (st && st.config && st.config.phone) || "";
|
|
1129
1227
|
var launchdPid = st && st.service && st.service.launchd && st.service.launchd.pid;
|
|
1130
1228
|
|
|
1131
1229
|
// 已登录(SaaS)→ 拉我的信息/配额/公共配置
|
|
@@ -1137,6 +1235,13 @@ window.__ModuleLoader__.load({
|
|
|
1137
1235
|
}, [st && st.config && st.config.phone]);
|
|
1138
1236
|
|
|
1139
1237
|
useEffect(function () { refresh(); }, [refresh]);
|
|
1238
|
+
|
|
1239
|
+
// 交流群二维码(后台上传即可展示;未配置 → community.qrcode 为空,入口不出现)
|
|
1240
|
+
useEffect(function () {
|
|
1241
|
+
var alive = true;
|
|
1242
|
+
loadCommunity().then(function (d) { if (alive) setCommunity(d); });
|
|
1243
|
+
return function () { alive = false; };
|
|
1244
|
+
}, []);
|
|
1140
1245
|
useEffect(function () {
|
|
1141
1246
|
// 展开「忘记密码」重置表单时不用预载登录表单验证码(收起的 reset 表单用独立 pwdCap)
|
|
1142
1247
|
if (st !== null && !loggedIn && mode === "saas" && authTab === "login" && !pwdOpen && !lcap) loadCaptcha("login");
|
|
@@ -1184,20 +1289,26 @@ window.__ModuleLoader__.load({
|
|
|
1184
1289
|
return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate()) + " " + pad2(d.getHours()) + ":" + pad2(d.getMinutes());
|
|
1185
1290
|
}
|
|
1186
1291
|
|
|
1187
|
-
/**
|
|
1292
|
+
/**
|
|
1293
|
+
* 创建(或刷新)一次性访问密钥:GET /dsh-remote/access-key(node 半转发企业端 /api/auth-key)。
|
|
1294
|
+
* 失败自愈:中继未就绪类失败(node 半 retryable / 网络 / 5xx)按退避自动重试,
|
|
1295
|
+
* 见「登录后首次加载自愈」——首次安装立即登录时不再需要手动刷新页面。
|
|
1296
|
+
*/
|
|
1188
1297
|
function loadAccessKey() {
|
|
1189
|
-
if (
|
|
1298
|
+
if (akeyInFlight.v) return;
|
|
1190
1299
|
// 登录前不请求企业端(避免 401/404 噪音);由账号登录成功后触发
|
|
1191
1300
|
if (!(st && st.config && st.config.phone)) {
|
|
1192
1301
|
setAkeyMsg(null);
|
|
1302
|
+
cancelAkeyRetry();
|
|
1193
1303
|
return;
|
|
1194
1304
|
}
|
|
1305
|
+
akeyInFlight.v = true;
|
|
1195
1306
|
setAkeyBusy(true);
|
|
1196
1307
|
api("/dsh-remote/access-key").then(function (b) {
|
|
1197
1308
|
if (!b || !b.ok) {
|
|
1198
1309
|
var why = (b && (b.error || (b.body && b.body.error))) || "未知错误";
|
|
1199
|
-
|
|
1200
|
-
|
|
1310
|
+
var e = new Error(why); e.body = b || null;
|
|
1311
|
+
throw e;
|
|
1201
1312
|
}
|
|
1202
1313
|
// 契约容错:url 必须有;qr_data_url 取不到时仍展示链接/复制/直接打开
|
|
1203
1314
|
if (!b.url) {
|
|
@@ -1212,9 +1323,16 @@ window.__ModuleLoader__.load({
|
|
|
1212
1323
|
qr_data_url: b.qr_data_url != null ? b.qr_data_url : null
|
|
1213
1324
|
});
|
|
1214
1325
|
setAkeyMsg(null);
|
|
1326
|
+
akeyRetry.attempt = 0;
|
|
1215
1327
|
}).catch(function (e) {
|
|
1216
|
-
|
|
1217
|
-
|
|
1328
|
+
var text = "获取一次性访问地址失败:" + e.message;
|
|
1329
|
+
if (retryableFail(e) && akeyRetry.attempt < LOGIN_LOAD_RETRY_MS.length) {
|
|
1330
|
+
scheduleAkeyRetry(text);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
// 自动重试额度用尽(或不该重试)→ 红字 + 「立即重试」入口,不让用户只能刷新页面
|
|
1334
|
+
setAkeyMsg({ kind: "err", text: text, retry: retryAkeyManually });
|
|
1335
|
+
}).finally(function () { akeyInFlight.v = false; setAkeyBusy(false); });
|
|
1218
1336
|
}
|
|
1219
1337
|
|
|
1220
1338
|
var copyKeyUrl = function () {
|
|
@@ -1232,17 +1350,67 @@ window.__ModuleLoader__.load({
|
|
|
1232
1350
|
try { window.open(akey.url, "_blank", "noopener"); } catch (e) {}
|
|
1233
1351
|
};
|
|
1234
1352
|
|
|
1235
|
-
/** 加载已授权设备列表:GET /dsh-remote/mobile-sessions
|
|
1353
|
+
/** 加载已授权设备列表:GET /dsh-remote/mobile-sessions(失败同上:可重试类自动重试)。 */
|
|
1236
1354
|
function loadDevices() {
|
|
1237
|
-
if (devBusy !== "") return;
|
|
1355
|
+
if (devInFlight.v || devBusy !== "") return;
|
|
1356
|
+
devInFlight.v = true;
|
|
1238
1357
|
setDevBusy("list");
|
|
1239
1358
|
api("/dsh-remote/mobile-sessions").then(function (b) {
|
|
1240
1359
|
if (!b || !b.ok) throw new Error((b && b.error) || "加载已授权设备失败");
|
|
1241
1360
|
setDevSessions(Array.isArray(b.sessions) ? b.sessions : []);
|
|
1242
1361
|
setDevMsg(null);
|
|
1362
|
+
devRetry.attempt = 0;
|
|
1243
1363
|
}).catch(function (e) {
|
|
1244
|
-
|
|
1245
|
-
|
|
1364
|
+
var text = "加载已授权设备失败:" + e.message;
|
|
1365
|
+
if (retryableFail(e) && devRetry.attempt < LOGIN_LOAD_RETRY_MS.length) {
|
|
1366
|
+
scheduleDevRetry(text);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
setDevMsg({ kind: "err", text: text, retry: retryDevicesManually });
|
|
1370
|
+
}).finally(function () { devInFlight.v = false; setDevBusy(""); });
|
|
1371
|
+
}
|
|
1372
|
+
/** 手动重试(红字旁的「重试」按钮):重置退避计数后立即重新拉取。 */
|
|
1373
|
+
var retryAkeyManually = function () {
|
|
1374
|
+
cancelAkeyRetry();
|
|
1375
|
+
akeyRetry.attempt = 0;
|
|
1376
|
+
setAkeyMsg(null);
|
|
1377
|
+
loadAccessKey();
|
|
1378
|
+
};
|
|
1379
|
+
var retryDevicesManually = function () {
|
|
1380
|
+
cancelDevRetry();
|
|
1381
|
+
devRetry.attempt = 0;
|
|
1382
|
+
setDevMsg(null);
|
|
1383
|
+
loadDevices();
|
|
1384
|
+
};
|
|
1385
|
+
/** 定时重试一次性访问密钥(黄字提示 + 退避;成功时由 loadAccessKey 清掉提示)。 */
|
|
1386
|
+
function scheduleAkeyRetry(why) {
|
|
1387
|
+
cancelAkeyRetry();
|
|
1388
|
+
var wait = LOGIN_LOAD_RETRY_MS[akeyRetry.attempt] || LOGIN_LOAD_RETRY_MS[LOGIN_LOAD_RETRY_MS.length - 1];
|
|
1389
|
+
akeyRetry.attempt += 1;
|
|
1390
|
+
setAkeyMsg({
|
|
1391
|
+
kind: "warn",
|
|
1392
|
+
text: "中继连接尚未就绪," + Math.round(wait / 1000) + " 秒后自动重试(第 " + akeyRetry.attempt + "/" + LOGIN_LOAD_RETRY_MS.length + " 次):" + why,
|
|
1393
|
+
retry: retryAkeyManually
|
|
1394
|
+
});
|
|
1395
|
+
akeyRetry.timer = later(function () {
|
|
1396
|
+
akeyRetry.timer = null;
|
|
1397
|
+
loadAccessKey(); // 在途标记由上一个请求的 finally 释放,退避窗口内用户也可手动刷新
|
|
1398
|
+
}, wait);
|
|
1399
|
+
}
|
|
1400
|
+
/** 定时重试已授权设备列表(同上)。 */
|
|
1401
|
+
function scheduleDevRetry(why) {
|
|
1402
|
+
cancelDevRetry();
|
|
1403
|
+
var wait = LOGIN_LOAD_RETRY_MS[devRetry.attempt] || LOGIN_LOAD_RETRY_MS[LOGIN_LOAD_RETRY_MS.length - 1];
|
|
1404
|
+
devRetry.attempt += 1;
|
|
1405
|
+
setDevMsg({
|
|
1406
|
+
kind: "warn",
|
|
1407
|
+
text: "中继连接尚未就绪," + Math.round(wait / 1000) + " 秒后自动重试(第 " + devRetry.attempt + "/" + LOGIN_LOAD_RETRY_MS.length + " 次):" + why,
|
|
1408
|
+
retry: retryDevicesManually
|
|
1409
|
+
});
|
|
1410
|
+
devRetry.timer = later(function () {
|
|
1411
|
+
devRetry.timer = null;
|
|
1412
|
+
loadDevices();
|
|
1413
|
+
}, wait);
|
|
1246
1414
|
}
|
|
1247
1415
|
/** 操作(取消配对/删除记录/清理已解绑)成功后静默重拉列表,覆盖行内状态。 */
|
|
1248
1416
|
function refreshDeviceList() {
|
|
@@ -1346,13 +1514,22 @@ window.__ModuleLoader__.load({
|
|
|
1346
1514
|
|
|
1347
1515
|
// 已登录云端主视图停留期间:打开即取一把新 key + 已授权设备;之后每 ~25s 自动轮换
|
|
1348
1516
|
// (未登录不轮询,避免 401 空转;离开栏目/切视图/退出登录即清理定时器)
|
|
1517
|
+
//
|
|
1518
|
+
// 依赖里带上「账号」与「bridge 运行态」:登录成功(含换账号)与 bridge 拉起/重启后立即重取,
|
|
1519
|
+
// 首次安装后立即登录不再需要手动刷新页面;失败重试由 loadAccessKey/loadDevices 内部退避处理。
|
|
1349
1520
|
useEffect(function () {
|
|
1350
1521
|
if (view !== "home" || mode !== "saas" || !loggedIn) return undefined;
|
|
1522
|
+
cancelAkeyRetry(); cancelDevRetry();
|
|
1523
|
+
akeyRetry.attempt = 0; devRetry.attempt = 0;
|
|
1351
1524
|
loadAccessKey();
|
|
1352
1525
|
loadDevices();
|
|
1353
1526
|
var rotateIv = setInterval(function () { loadAccessKey(); }, KEY_AUTO_REFRESH_MS);
|
|
1354
|
-
return function () {
|
|
1355
|
-
|
|
1527
|
+
return function () {
|
|
1528
|
+
clearInterval(rotateIv);
|
|
1529
|
+
cancelAkeyRetry();
|
|
1530
|
+
cancelDevRetry();
|
|
1531
|
+
};
|
|
1532
|
+
}, [view, mode, loggedIn, phoneKey, serviceRunning]);
|
|
1356
1533
|
|
|
1357
1534
|
function setMsg(kind, text) { setMessage({ kind: kind, text: text }); }
|
|
1358
1535
|
|
|
@@ -1561,6 +1738,39 @@ window.__ModuleLoader__.load({
|
|
|
1561
1738
|
.finally(function () { setBusy(""); });
|
|
1562
1739
|
};
|
|
1563
1740
|
|
|
1741
|
+
/**
|
|
1742
|
+
* 重启 DeepSeek harness:插件本体与浏览器半在进程启动时装载,首次安装/在线更新后
|
|
1743
|
+
* 必须重启才生效。重启会短暂断开本页面 —— 这里轮询等它回来,然后自动刷新。
|
|
1744
|
+
*/
|
|
1745
|
+
var restartHarness = function () {
|
|
1746
|
+
setBusy("restart");
|
|
1747
|
+
post("/dsh-remote/harness/restart")
|
|
1748
|
+
.then(function (body) {
|
|
1749
|
+
setSt(body);
|
|
1750
|
+
setMsg("ok", "🔄 正在重启 DeepSeek harness" + (body && body.mode ? "(" + body.mode + ")" : "") +
|
|
1751
|
+
"…约 2~10 秒,页面会自动刷新,无需手动操作。");
|
|
1752
|
+
waitHarnessBack(0, false);
|
|
1753
|
+
})
|
|
1754
|
+
.catch(function (e) {
|
|
1755
|
+
setMsg("err", "重启失败: " + e.message + " —— 请手动重启 DeepSeek harness(退出 dsh web 后重新启动)。");
|
|
1756
|
+
setBusy("");
|
|
1757
|
+
});
|
|
1758
|
+
};
|
|
1759
|
+
|
|
1760
|
+
/** 轮询等待 harness 回来;观察到「断过又恢复」或等待足够久后自动刷新页面。 */
|
|
1761
|
+
var waitHarnessBack = function (tries, sawDown) {
|
|
1762
|
+
if (tries > 40) { setMsg("err", "重启后仍未就绪,请手动刷新页面或重启 DeepSeek harness。"); setBusy(""); return; }
|
|
1763
|
+
setTimeout(function () {
|
|
1764
|
+
fetch("/dsh-remote/status", { cache: "no-store" })
|
|
1765
|
+
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
|
1766
|
+
.then(function () {
|
|
1767
|
+
if (sawDown || tries >= 8) { location.reload(); return; }
|
|
1768
|
+
waitHarnessBack(tries + 1, sawDown);
|
|
1769
|
+
})
|
|
1770
|
+
.catch(function () { waitHarnessBack(tries + 1, true); });
|
|
1771
|
+
}, 1500);
|
|
1772
|
+
};
|
|
1773
|
+
|
|
1564
1774
|
var saveLocal = function () {
|
|
1565
1775
|
if (!selfHost.trim() || !localKey.trim()) { setMsg("err", "请填写服务器地址与访问密钥"); return; }
|
|
1566
1776
|
setBusy("local");
|
|
@@ -1760,7 +1970,13 @@ window.__ModuleLoader__.load({
|
|
|
1760
1970
|
h("span", null, statusTxt)
|
|
1761
1971
|
),
|
|
1762
1972
|
renderE2eeBadge(),
|
|
1763
|
-
akeyMsg
|
|
1973
|
+
akeyMsg
|
|
1974
|
+
? h("div", { className: "dru-msg dru-msg-" + akeyMsg.kind, style: { marginTop: 8 } },
|
|
1975
|
+
akeyMsg.text,
|
|
1976
|
+
akeyMsg.retry
|
|
1977
|
+
? h("button", { type: "button", className: "dru-linkbtn", style: { marginLeft: 8 }, disabled: akeyBusy, onClick: akeyMsg.retry }, akeyBusy ? "重试中…" : "立即重试")
|
|
1978
|
+
: null)
|
|
1979
|
+
: null,
|
|
1764
1980
|
hasKey ? h("div", null, [
|
|
1765
1981
|
h("div", { className: "dru-url big", style: { marginTop: 8 } },
|
|
1766
1982
|
h("span", null, akey.url),
|
|
@@ -1883,7 +2099,13 @@ window.__ModuleLoader__.load({
|
|
|
1883
2099
|
}, devBusy === "list" ? "加载中…" : btnLabel)
|
|
1884
2100
|
),
|
|
1885
2101
|
devOpen ? renderDeviceList() : null,
|
|
1886
|
-
devMsg
|
|
2102
|
+
devMsg
|
|
2103
|
+
? h("div", { className: "dru-msg dru-msg-" + devMsg.kind, style: { marginTop: 6 } },
|
|
2104
|
+
devMsg.text,
|
|
2105
|
+
devMsg.retry
|
|
2106
|
+
? h("button", { type: "button", className: "dru-linkbtn", style: { marginLeft: 8 }, disabled: devBusy !== "", onClick: devMsg.retry }, devBusy === "list" ? "重试中…" : "立即重试")
|
|
2107
|
+
: null)
|
|
2108
|
+
: null
|
|
1887
2109
|
]);
|
|
1888
2110
|
}
|
|
1889
2111
|
|
|
@@ -1970,7 +2192,7 @@ window.__ModuleLoader__.load({
|
|
|
1970
2192
|
card("🖥 Bridge 服务",
|
|
1971
2193
|
h("div", { className: "dru-status-line" },
|
|
1972
2194
|
h("span", { className: "dru-dot " + (serviceRunning ? "dru-dot-on" : "dru-dot-off") }),
|
|
1973
|
-
h("span", null, st ?
|
|
2195
|
+
h("span", null, st ? serviceStateText : "查询中…"),
|
|
1974
2196
|
launchdPid ? h("span", { className: "dru-meta", style: { marginTop: 0 } }, "(pid=" + launchdPid + ")") : null
|
|
1975
2197
|
),
|
|
1976
2198
|
h("div", { className: "dru-actions", style: { marginTop: 10 } },
|
|
@@ -1980,6 +2202,14 @@ window.__ModuleLoader__.load({
|
|
|
1980
2202
|
),
|
|
1981
2203
|
h("div", { className: "dru-meta" }, st && st.config && st.config.deviceId ? "设备 ID:" + st.config.deviceId : "设备 ID:生成中"),
|
|
1982
2204
|
h("div", { className: "dru-meta" }, st ? (st.service && st.service.plistExists ? "自启动服务已安装" : "自启动服务未安装(启动时自动创建)") : ""),
|
|
2205
|
+
// 0.6.2:把「运行环境缺失 / launchd 崩溃循环」如实告诉用户,而不是显示「运行中」
|
|
2206
|
+
st && !serviceRuntimeReady
|
|
2207
|
+
? h("div", { className: "dru-hint", style: { marginTop: 8 } },
|
|
2208
|
+
"🛠 插件市场只装「面板插件」,桌面运行环境(bridge)需要额外补装——已自动在后台安装,装完会自动启动 bridge,不用做任何操作。若超过几分钟仍未变成「运行中」,点上方版本卡的「一键更新」手动补全。")
|
|
2209
|
+
: serviceCrashing
|
|
2210
|
+
? h("div", { className: "dru-hint", style: { marginTop: 8 } },
|
|
2211
|
+
"⚠️ 上一次启动失败(自启动入口失效),已自动清理失效自启动项并重新补装运行环境。稍候会自动恢复;仍失败请点「一键更新」。")
|
|
2212
|
+
: null,
|
|
1983
2213
|
st && st.service && st.service.bindError
|
|
1984
2214
|
? h("div", { className: "dru-msg dru-msg-err", style: { marginTop: 8 } },
|
|
1985
2215
|
"⚠️ 设备注册失败:" + (st.service.bindError.message || "未说明原因"),
|
|
@@ -1995,7 +2225,17 @@ window.__ModuleLoader__.load({
|
|
|
1995
2225
|
h("div", { className: "dru-hint", style: { marginBottom: 6 } }, "📱 远程访问:用手机或另一台电脑的浏览器,随时随地使用同一份 dsh web——人在哪都能用(免公网 IP、免内网穿透);官方托管中继,4G/5G 即用,也可自建服务。"),
|
|
1996
2226
|
h("div", { className: "dru-hint", style: { marginBottom: 6 } }, "🛠 电脑端一键安装:bridge 与「远程访问」面板一次到位——云端/自建切换、账号登录、bridge 启停、一次性扫码访问、已授权设备管理、意见反馈都在这里。"),
|
|
1997
2227
|
h("div", { className: "dru-hint", style: { marginBottom: 6 } }, "🔒 安全与通道:HTTP / WebSocket 全量透传,一次性访问密钥认证,面板实时显示设备与已授权设备列表;服务端可配置流量配额。"),
|
|
1998
|
-
h("div", { className: "dru-hint" }, "🛡 端到端加密:手机↔电脑之间的消息内容用「你的账号密码派生密钥」端到端加密——密钥与密码不落服务端(仅存校验值),中继只可见路径/大小/时间(详见 README「安全与隐私」)。")
|
|
2228
|
+
h("div", { className: "dru-hint" }, "🛡 端到端加密:手机↔电脑之间的消息内容用「你的账号密码派生密钥」端到端加密——密钥与密码不落服务端(仅存校验值),中继只可见路径/大小/时间(详见 README「安全与隐私」)。"),
|
|
2229
|
+
// 加入交流群(后台上传二维码后出现;点击弹出二维码大图便于扫码)
|
|
2230
|
+
community && community.qrcode
|
|
2231
|
+
? h("div", { className: "dru-actions", style: { marginTop: 10 } },
|
|
2232
|
+
h("button", {
|
|
2233
|
+
type: "button",
|
|
2234
|
+
className: "dru-btn dru-btn-ghost",
|
|
2235
|
+
style: { width: "100%" },
|
|
2236
|
+
onClick: function () { setCommOpen(true); }
|
|
2237
|
+
}, "💬 加入交流群"))
|
|
2238
|
+
: null
|
|
1999
2239
|
]),
|
|
2000
2240
|
// 版本与更新(自管理:检测新版 / 一键在线更新 / 彻底卸载)
|
|
2001
2241
|
h(SelfManageCard, null),
|
|
@@ -2003,6 +2243,29 @@ window.__ModuleLoader__.load({
|
|
|
2003
2243
|
);
|
|
2004
2244
|
}
|
|
2005
2245
|
|
|
2246
|
+
/** 交流群弹窗(复用满意度弹窗的 dru-popup 视觉;点遮罩/关闭即收起)。 */
|
|
2247
|
+
function renderCommunityModal() {
|
|
2248
|
+
if (!commOpen || !(community && community.qrcode)) return null;
|
|
2249
|
+
var close = function () { setCommOpen(false); };
|
|
2250
|
+
return h("div", { className: "dru-popup", onMouseDown: function (e) { if (e.target === e.currentTarget) close(); } },
|
|
2251
|
+
h("div", { className: "dru-popup-card", role: "dialog", "aria-label": "加入企微交流群" },
|
|
2252
|
+
h("div", { className: "dru-popup-body" },
|
|
2253
|
+
h("div", { className: "dru-popup-icon" }, "💬"),
|
|
2254
|
+
h("div", { className: "dru-popup-title" }, "加入企微交流群"),
|
|
2255
|
+
h("div", { className: "dru-popup-sub" }, "扫码入群:安装答疑 / 使用技巧 / 版本更新 / 问题反馈"),
|
|
2256
|
+
h("img", { className: "dru-community-qr", src: community.qrcode, alt: "企微交流群二维码" }),
|
|
2257
|
+
community.wechat
|
|
2258
|
+
? h("div", { className: "dru-hint", style: { marginTop: 10 } }, "二维码失效或群满,可加客服微信:" + community.wechat)
|
|
2259
|
+
: h("div", { className: "dru-hint", style: { marginTop: 10 } }, "二维码失效或群满,可到 GitHub 提 Issue 联系作者")
|
|
2260
|
+
),
|
|
2261
|
+
h("div", { className: "dru-popup-foot" },
|
|
2262
|
+
h("span", null, "手机微信/企业微信扫码即可入群"),
|
|
2263
|
+
h("button", { type: "button", onClick: close }, "关闭")
|
|
2264
|
+
)
|
|
2265
|
+
)
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2006
2269
|
return h("div", { className: "dru-settings-section", role: "region", "aria-label": "远程访问" },
|
|
2007
2270
|
h("div", { className: "dru-settings-head" },
|
|
2008
2271
|
h("span", { className: "dru-settings-icon" }, "📱"),
|
|
@@ -2012,8 +2275,38 @@ window.__ModuleLoader__.load({
|
|
|
2012
2275
|
)
|
|
2013
2276
|
),
|
|
2014
2277
|
h("div", { className: "dru-settings-body" },
|
|
2278
|
+
restartPending
|
|
2279
|
+
? h("div", { className: "dru-restart-alert", role: "status" },
|
|
2280
|
+
h("span", { className: "dru-restart-alert-icon" }, "🔔"),
|
|
2281
|
+
h("div", { style: { flex: "1" } },
|
|
2282
|
+
h("div", { className: "dru-restart-alert-title" },
|
|
2283
|
+
restartInfo.kind === "update" ? "在线更新已完成,需要重启 DeepSeek harness" : "首次安装需要重启 DeepSeek harness"),
|
|
2284
|
+
h("div", { className: "dru-restart-alert-sub" },
|
|
2285
|
+
"插件本体与「远程访问」面板是在 DeepSeek harness 启动时装载的,重启之后即可正常使用(账号、二维码、bridge 全部生效)。"),
|
|
2286
|
+
h("div", { className: "dru-actions", style: { marginTop: 10 } },
|
|
2287
|
+
h("button", {
|
|
2288
|
+
type: "button",
|
|
2289
|
+
className: "dru-btn dru-btn-primary",
|
|
2290
|
+
disabled: busy !== "",
|
|
2291
|
+
onClick: function () { restartHarness(); }
|
|
2292
|
+
}, busy === "restart" ? "重启中…" : "重启 DeepSeek harness")
|
|
2293
|
+
)
|
|
2294
|
+
)
|
|
2295
|
+
)
|
|
2296
|
+
: null,
|
|
2015
2297
|
view === "feedback" ? renderFeedback() : view === "invite" ? renderInvite() : renderHome()
|
|
2016
|
-
)
|
|
2298
|
+
),
|
|
2299
|
+
// 常驻入口最底部:「重启 DeepSeek harness」按钮始终可达(首次安装/更新/排查都用它)
|
|
2300
|
+
h("div", { className: "dru-restart-foot" },
|
|
2301
|
+
h("div", { className: "dru-hint" }, "重启 DeepSeek harness:插件与面板在 harness 启动时装载,首次安装或在线更新后重启即可生效;仅重启 dsh web,不影响 bridge 与手机端连接。"),
|
|
2302
|
+
h("button", {
|
|
2303
|
+
type: "button",
|
|
2304
|
+
className: "dru-btn " + (restartPending ? "dru-btn-primary" : "dru-btn-ghost"),
|
|
2305
|
+
disabled: busy !== "",
|
|
2306
|
+
onClick: function () { restartHarness(); }
|
|
2307
|
+
}, busy === "restart" ? "重启中…" : "🔄 重启 DeepSeek harness")
|
|
2308
|
+
),
|
|
2309
|
+
renderCommunityModal()
|
|
2017
2310
|
);
|
|
2018
2311
|
}
|
|
2019
2312
|
|