@feiyang666/dsh-mobile-remote 1.7.0 → 1.7.1-1
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 +9 -0
- package/lib/client.js +54 -3
- package/lib/index.js +27 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# 更新日志 / Changelog
|
|
2
2
|
|
|
3
|
+
## v1.7.1-1 (2026-08-29) — 预发布(beta,供前端测试)
|
|
4
|
+
|
|
5
|
+
### 新增 / New
|
|
6
|
+
- **外网访问面板显示数据通道状态**(`lib/index.js` + `lib/client.js`):`/status` 新增 `external.ws` 字段(`available` = ws 依赖是否可用 / `connected` = WS 长连接是否建立 / `source` = 当前数据来源 `ws` | `http` | `none` / `lastPushAt` = 最后推送时刻),设置页「外网访问」卡片新增「数据通道」行徽标,让用户一眼看清当前是 **WS 实时推送**(绿色)、**HTTP 兜底(退让模式)**(未安装 ws 依赖)、**WS 已连接等待推送**、**WS 已断开 / 重连中**(含最后推送时间)中的哪一种。
|
|
7
|
+
- **运行时长本地每秒刷新**(`lib/client.js`):`runtimeInfo()` 增加采集时刻 `at`,前端新增 `LiveDuration` 组件——基于「采样秒数 + 采集时刻」在浏览器本地每秒累加,让「运行时长 / 开机时长 / 隧道已运行」等秒级数字每秒跳动(秒表效果),不再依赖 5s 的 `/status` 轮询刷新(此前数字每 5s 才跳一次且跳幅不连续)。
|
|
8
|
+
|
|
9
|
+
### 变更 / Changed
|
|
10
|
+
- 无 API 破坏;旧版 host / 新版 client 混用时,缺少 `at` / `ws` 字段会自动降级为原行为(数字按轮询刷新、不显示数据通道行)。
|
|
11
|
+
|
|
3
12
|
## v1.7.0 (2026-08-28)
|
|
4
13
|
|
|
5
14
|
### 新增 / New
|
package/lib/client.js
CHANGED
|
@@ -225,6 +225,23 @@ window.__ModuleLoader__.load({
|
|
|
225
225
|
return ss + ' 秒';
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// 实时时长组件:基于「一次采样的秒数 + 采集时刻」在浏览器本地每秒累加刷新,
|
|
229
|
+
// 让"运行时长 / 开机时长 / 隧道已运行"等秒级数字每秒跳动(秒表效果),
|
|
230
|
+
// 不再依赖 5s 的 /status HTTP 轮询(那会导致数字每 5s 才跳一次,且跳幅不连续)。
|
|
231
|
+
// 只要 host 在采集时带上 at 时间戳,两次 /status 之间也能精确走到下一秒。
|
|
232
|
+
function LiveDuration(props) {
|
|
233
|
+
var _n = useState(Date.now());
|
|
234
|
+
var now = _n[0];
|
|
235
|
+
var setNow = _n[1];
|
|
236
|
+
useEffect(function () {
|
|
237
|
+
var t = setInterval(function () { setNow(Date.now()); }, 1000);
|
|
238
|
+
return function () { clearInterval(t); };
|
|
239
|
+
}, []);
|
|
240
|
+
var at = props.at || now;
|
|
241
|
+
var sec = props.baseSec + Math.max(0, Math.floor((now - at) / 1000));
|
|
242
|
+
return fmtDuration(sec);
|
|
243
|
+
}
|
|
244
|
+
|
|
228
245
|
function fmtBytes(b) {
|
|
229
246
|
if (b === null || b === undefined) return '—';
|
|
230
247
|
var n = Number(b);
|
|
@@ -405,6 +422,32 @@ window.__ModuleLoader__.load({
|
|
|
405
422
|
// 后端探测到的错误(如鉴权失败 / token 不匹配),优先于请求时的临时错误
|
|
406
423
|
var extErrText = extErr || (extError && status && status.external && status.external.error ? status.external.error : null);
|
|
407
424
|
|
|
425
|
+
// ---- WS 状态推送通道(host 连接中转服务器的数据通道)----
|
|
426
|
+
// 让用户明确看到当前外网状态数据是经 WS 长连接实时推送,还是退让为 HTTP 低频轮询。
|
|
427
|
+
var wsInfo = status && status.external && status.external.ws ? status.external.ws : null;
|
|
428
|
+
var wsBadge = null;
|
|
429
|
+
var wsHint = null;
|
|
430
|
+
if (extStarted && wsInfo) {
|
|
431
|
+
if (wsInfo.connected && wsInfo.source === 'ws') {
|
|
432
|
+
wsBadge = el('span', { style: Object.assign({}, st.badge, st.badgeOn) }, 'WS 实时推送');
|
|
433
|
+
wsHint = '隧道状态经 WebSocket 长连接实时更新(零 HTTP 轮询)';
|
|
434
|
+
} else if (wsInfo.available === false) {
|
|
435
|
+
// 退让版本:ws 依赖缺失(如离线安装),WS 客户端 no-op,走 HTTP 低频兜底
|
|
436
|
+
wsBadge = el('span', { style: Object.assign({}, st.badge, st.badgeBusy) }, 'HTTP 兜底(退让模式)');
|
|
437
|
+
wsHint = '未安装 ws 依赖,已自动退让为 HTTP 低频查询(60s 节流),功能不受影响';
|
|
438
|
+
} else if (wsInfo.connected) {
|
|
439
|
+
wsBadge = el('span', { style: Object.assign({}, st.badge, st.badgeBusy) }, 'WS 已连接,等待推送');
|
|
440
|
+
wsHint = 'WS 长连接已建立,收到首条推送后自动切换为实时模式';
|
|
441
|
+
} else if (wsInfo.source === 'ws') {
|
|
442
|
+
wsBadge = el('span', { style: Object.assign({}, st.badge, st.badgeBusy) }, 'WS 已断开');
|
|
443
|
+
wsHint = 'WS 长连接已断开,正在自动重连;当前显示最近一次推送的缓存数据' +
|
|
444
|
+
(wsInfo.lastPushAt ? '(最后推送 ' + fmtAgo(wsInfo.lastPushAt) + ')' : '');
|
|
445
|
+
} else {
|
|
446
|
+
wsBadge = el('span', { style: Object.assign({}, st.badge, st.badgeBusy) }, 'WS 未连接');
|
|
447
|
+
wsHint = 'WS 长连接尚未建立(连接中 / 自动重连),期间数据走 HTTP 兜底';
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
408
451
|
function externalStart() {
|
|
409
452
|
if (extBusy || extConnecting) return;
|
|
410
453
|
if (!bindCode || !bindCode.trim()) {
|
|
@@ -730,8 +773,8 @@ window.__ModuleLoader__.load({
|
|
|
730
773
|
el('span', { style: Object.assign({}, st.badge, st.badgeOff) },
|
|
731
774
|
'PID ' + status.runtime.pid)),
|
|
732
775
|
el('div', { style: { display: 'flex', flexDirection: 'column', gap: 4 } }, kvList([
|
|
733
|
-
['运行时长',
|
|
734
|
-
['开机时长',
|
|
776
|
+
['运行时长', el(LiveDuration, { baseSec: status.runtime.uptime || 0, at: status.runtime.at })],
|
|
777
|
+
['开机时长', el(LiveDuration, { baseSec: status.runtime.sysUptime || 0, at: status.runtime.at })],
|
|
735
778
|
['Node.js', status.runtime.nodeVersion],
|
|
736
779
|
['主机名', status.runtime.hostname],
|
|
737
780
|
['系统', (status.runtime.platform || '—') + ' ' + (status.runtime.release || '') + ' (' + (status.runtime.arch || '—') + ')'],
|
|
@@ -857,8 +900,16 @@ window.__ModuleLoader__.load({
|
|
|
857
900
|
el('span', { style: st.meta },
|
|
858
901
|
'frpc 进程:' + (status.external.pid ? 'PID ' + status.external.pid : '—')),
|
|
859
902
|
status.external.startedAt
|
|
860
|
-
? el('span', { style: st.meta }, '已运行 '
|
|
903
|
+
? el('span', { style: st.meta }, '已运行 ',
|
|
904
|
+
el(LiveDuration, { baseSec: 0, at: status.external.startedAt }))
|
|
861
905
|
: null),
|
|
906
|
+
wsBadge
|
|
907
|
+
? el('div', { style: st.row },
|
|
908
|
+
el('span', { style: st.meta }, '数据通道'),
|
|
909
|
+
el('div', { style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 2 } },
|
|
910
|
+
wsBadge,
|
|
911
|
+
wsHint ? el('span', { style: st.hint }, wsHint) : null))
|
|
912
|
+
: null,
|
|
862
913
|
el('div', { style: st.row },
|
|
863
914
|
el('span', { style: st.meta },
|
|
864
915
|
'绑定码:' + (status.external.bindCodeShort || '—')),
|
package/lib/index.js
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
hasPassword, verifyPassword, setPassword,
|
|
44
44
|
isEnabled, login, verifyToken, revokeToken, clearSessions,
|
|
45
45
|
} from './password.js'
|
|
46
|
+
import { wsAvailable } from './ws-client.js'
|
|
46
47
|
|
|
47
48
|
/** 插件名(loader 诊断用)。 */
|
|
48
49
|
export const name = 'dsh-mobile-remote'
|
|
@@ -214,11 +215,14 @@ function getLanInterfaces() {
|
|
|
214
215
|
|
|
215
216
|
/** 当前进程运行时信息(诊断用,设置页展示)。 */
|
|
216
217
|
function runtimeInfo() {
|
|
218
|
+
// 采集时刻:前端可据此在两次 /status 轮询之间本地累加,让"运行时长"每秒跳动。
|
|
219
|
+
const at = Date.now()
|
|
217
220
|
const mem = process.memoryUsage()
|
|
218
221
|
const cpu = cpus()
|
|
219
222
|
const load = loadavg()
|
|
220
223
|
const total = totalmem()
|
|
221
224
|
return {
|
|
225
|
+
at,
|
|
222
226
|
pid: process.pid,
|
|
223
227
|
hostname: hostname(),
|
|
224
228
|
platform: platform(),
|
|
@@ -741,9 +745,18 @@ function handleStatus(ctx, devices, external, tracker, dshCache) {
|
|
|
741
745
|
// 外网隧道在线时,优先读 WS 推送缓存(经长连接实时更新,零中转服务器请求);
|
|
742
746
|
// 仅当从未收到推送(ws 依赖缺失 / WS 未建立)时才回退 HTTP 低频查询(60s 节流)。
|
|
743
747
|
let server = null
|
|
748
|
+
let serverSource = 'none'
|
|
744
749
|
if (extRunning && persisted) {
|
|
745
|
-
|
|
750
|
+
if (external.serverStatus) {
|
|
751
|
+
server = external.serverStatus
|
|
752
|
+
serverSource = 'ws'
|
|
753
|
+
} else {
|
|
754
|
+
server = await queryServerStatus(persisted.serverBase || undefined, persisted.bindCode)
|
|
755
|
+
serverSource = 'http'
|
|
756
|
+
}
|
|
746
757
|
}
|
|
758
|
+
// WS 长连接是否已建立(供前端判断当前处于"实时推送"还是"退让/重连")
|
|
759
|
+
const wsConnected = !!(external.wsClient && external.wsClient.isOpen())
|
|
747
760
|
json(res, 200, {
|
|
748
761
|
host: ctx.webServer.host,
|
|
749
762
|
port: ctx.webServer.port,
|
|
@@ -785,6 +798,19 @@ function handleStatus(ctx, devices, external, tracker, dshCache) {
|
|
|
785
798
|
: null,
|
|
786
799
|
// 中转服务器返回的成员状态(外网在线时才有)
|
|
787
800
|
server,
|
|
801
|
+
// WS 状态推送通道信息:让前端明确当前数据是 WS 实时推送还是 HTTP 兜底退让
|
|
802
|
+
ws: {
|
|
803
|
+
// ws 依赖是否可用(离线安装未带依赖时为 false,自动退让 HTTP)
|
|
804
|
+
available: wsAvailable(),
|
|
805
|
+
// WS 长连接是否已建立
|
|
806
|
+
connected: wsConnected,
|
|
807
|
+
// 当前 server 数据来源:'ws'(实时推送缓存)/ 'http'(兜底查询)/ 'none'
|
|
808
|
+
source: serverSource,
|
|
809
|
+
// 最近一次收到 WS 推送的时刻(未收到过为 null)
|
|
810
|
+
lastPushAt: external.serverStatus && external.serverStatus._at
|
|
811
|
+
? external.serverStatus._at
|
|
812
|
+
: null,
|
|
813
|
+
},
|
|
788
814
|
},
|
|
789
815
|
// 远程访问密码门禁状态(供设置面板展示)
|
|
790
816
|
auth: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feiyang666/dsh-mobile-remote",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.1-1",
|
|
4
4
|
"description": "DeepSeek Harness mobile remote-control plugin: LAN/external remote control (QR / on-off toggle / remote-access password gate), rich telemetry (expandable device details, system CPU/memory/load, DSH app state: version/sessions/workspaces/plugins/models), real external-tunnel status detection (frpc token / reconnect diagnostics), mobile-first UI polish (composer image upload entry via system picker, settings single-column layout) and faster remote loading.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek",
|