@feiyang666/dsh-mobile-remote 1.6.1 → 1.7.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.
- package/CHANGELOG.md +13 -0
- package/RELEASE_NOTES.md +12 -14
- package/lib/external.js +31 -7
- package/lib/index.js +14 -5
- package/lib/ws-client.js +152 -0
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# 更新日志 / Changelog
|
|
2
2
|
|
|
3
|
+
## v1.7.0 (2026-08-28)
|
|
4
|
+
|
|
5
|
+
### 新增 / New
|
|
6
|
+
- **中转服务器 WS 状态推送客户端**(`lib/ws-client.js` + `lib/external.js`):外网隧道开启后,插件端改为通过 **WebSocket 长连接**(`/api/tunnel/ws?bindCode=…`)接收中转服务器的隧道状态实时推送,**替代原来每 5s 的 HTTP 轮询** `/api/tunnel/stats`,中转服务器请求压力降为 0(服务器资源集中用于 frp 隧道中转)。
|
|
7
|
+
- 指数退避自动重连(2s → 4s → … → 60s 封顶),断线自动恢复;
|
|
8
|
+
- `ws` 依赖缺失(如离线安装未带依赖)时自动降级:WS 客户端 no-op,心跳与状态查询回退 HTTP 低频兜底,**功能不中断**;
|
|
9
|
+
- 停止外网 / 插件卸载时主动断开,不再自动重连。
|
|
10
|
+
- **心跳优先经 WS 长连接发送**(`lib/external.js` `sendHeartbeat`):WS 已连上时心跳直接经长连接送达(零 HTTP 请求),未连上才回退 HTTP `/api/tunnel/heartbeat`。
|
|
11
|
+
- **状态查询 60s 节流缓存**(`lib/index.js` `queryServerStatus`):即使移动端前端每 5s 轮询本机 `/status`,也不会高频打中转服务器;前端 `/status` 优先读 WS 推送缓存,仅在从未收到推送(ws 缺失 / WS 未建立)时才回退 HTTP 低频查询。
|
|
12
|
+
|
|
13
|
+
### 变更 / Changed
|
|
14
|
+
- 新增运行时依赖 `ws`(`^8.18.0`);`npm run check` / `prepublishOnly` 增加 `lib/ws-client.js` 语法检查。
|
|
15
|
+
|
|
3
16
|
## v1.6.1 (2026-08-23)
|
|
4
17
|
|
|
5
18
|
### 修复 / Fixed
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-mobile-remote v1.
|
|
1
|
+
# dsh-mobile-remote v1.7.0
|
|
2
2
|
|
|
3
3
|
**DeepSeek Harness 移动端远程控制插件** —— 在 Web UI 设置页内置「远程控制」:连接二维码、一键开关、在线设备数,让手机通过局域网访问并操控电脑上的 DeepSeek Harness。移动端提供上传图片入口、底部抽屉式模型选择、发送状态提示等移动优先体验。
|
|
4
4
|
|
|
@@ -10,25 +10,23 @@ dsh plugin --profile web add @feiyang666/dsh-mobile-remote
|
|
|
10
10
|
|
|
11
11
|
安装完成后**重启 dsh web 服务**,打开 `http://127.0.0.1:3080` → **设置 → 远程控制** 即可使用。
|
|
12
12
|
|
|
13
|
-
## 本版更新(v1.
|
|
13
|
+
## 本版更新(v1.7.0)
|
|
14
14
|
|
|
15
|
-
###
|
|
15
|
+
### 🆕 中转服务器 WS 状态推送(长连接替代轮询)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
外网隧道开启后,插件端改为通过 **WebSocket 长连接**(`/api/tunnel/ws?bindCode=…`)接收中转服务器的隧道状态实时推送,**替代原来每 5s 的 HTTP 轮询** `/api/tunnel/stats`,中转服务器请求压力降为 0(服务器资源集中用于 frp 隧道中转)。
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
- **指数退避自动重连**:断线后 2s → 4s → … → 60s 封顶自动恢复;
|
|
20
|
+
- **心跳优先走 WS**:WS 已连上时心跳经长连接送达(零 HTTP 请求),未连上才回退 HTTP 兜底;
|
|
21
|
+
- **状态查询 60s 节流**:即使移动端每 5s 轮询本机 `/status`,也不会高频打中转服务器;前端优先读 WS 推送缓存;
|
|
22
|
+
- **ws 依赖缺失自动降级**:`ws` 未安装时 WS 客户端 no-op,心跳与状态查询回退 HTTP 低频兜底,**功能不中断**。
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
- ≤540px 时对带 `collapse-mobile` 的宽表隐藏第 4 列至倒数第 2 列,只保留前 3 个主标识列与最后合计列,关键数据一屏看全;
|
|
23
|
-
- 价格表等窄表(无 `collapse-mobile`)不折叠,保留横向滚动。
|
|
24
|
+
> ⚠️ 本版新增运行时依赖 `ws`(`^8.18.0`)。通过 `dsh plugin --profile web add` 安装会自动带上;若使用离线 tgz 手动安装,请确保 `ws` 已安装(缺失时插件自动降级,不会崩溃)。
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
## 历史更新(v1.6.x)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
### 🆕 SSE 实时推送端点
|
|
30
|
-
|
|
31
|
-
新增 `GET /__dsh_remote/events` 事件流端点(受远程密码门禁保护),设备心跳上报 / 远程开关切换 / 外网隧道启动停止等状态变化时向订阅者推送 `data: {"type":"changed","at":<时间戳>}`。桌面端数据中心等客户端订阅该流即可**即时**感知远程设备与隧道状态变化,无需轮询。无订阅者时零开销,插件独立使用不受影响。
|
|
28
|
+
- **v1.6.1**:用量插件等宽表移动端兜底(强制 720px + ≤540px 折叠中间列)
|
|
29
|
+
- **v1.6.0**:新增 `GET /__dsh_remote/events` SSE 实时推送端点(设备心跳 / 远程开关 / 外网隧道状态变化即时推送)
|
|
32
30
|
|
|
33
31
|
## 已知事项
|
|
34
32
|
|
package/lib/external.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// - 写 profile 的 connection trustedHosts,放行外网域名(通过 /api 信任围栏)
|
|
7
7
|
// - 心跳上报、状态查询、停止
|
|
8
8
|
//
|
|
9
|
-
//
|
|
9
|
+
// 运行时依赖:Node 内置模块 + `ws`(WebSocket 长连接接收中转服务状态推送,
|
|
10
|
+
// 替代高频轮询;ws 依赖缺失时自动回退 HTTP 低频查询)。frpc 二进制由中转服务提供或用户本地放置。
|
|
10
11
|
//
|
|
11
12
|
// 稳健性说明(针对"开启难/开了又关"问题):
|
|
12
13
|
// - 强制给 frpc.toml 追加 loginFailExit=false:连不上 frps 时持续重试,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
existsSync, readFileSync, writeFileSync, mkdirSync, openSync, closeSync, rmSync,
|
|
23
24
|
} from 'node:fs'
|
|
24
25
|
import { spawn } from 'node:child_process'
|
|
26
|
+
import { createWsStatusClient } from './ws-client.js'
|
|
25
27
|
|
|
26
28
|
/** 默认中转服务地址(自建 dsh-update-server,Nginx 反代 443→3200,对外无端口)。
|
|
27
29
|
* 可用 DSH_REMOTE_SERVER 覆盖。 */
|
|
@@ -480,6 +482,19 @@ async function startExternalInner(state, body, patchFile, defaultLocalPort) {
|
|
|
480
482
|
state.serverBase = base
|
|
481
483
|
state.localPort = Number((body && body.localPort) || defaultLocalPort)
|
|
482
484
|
savePersistedState(state)
|
|
485
|
+
// 建立 WS 长连接接收隧道状态推送(替代每 5s HTTP 轮询中转服务器);
|
|
486
|
+
// ws 依赖缺失(离线安装)时 wsClient.connect() 自动 no-op,心跳走 HTTP 兜底
|
|
487
|
+
state.serverStatus = null
|
|
488
|
+
if (state.wsClient) {
|
|
489
|
+
try { state.wsClient.disconnect() } catch { /* ignore */ }
|
|
490
|
+
}
|
|
491
|
+
state.wsClient = createWsStatusClient({
|
|
492
|
+
serverBase: base,
|
|
493
|
+
bindCode: state.bindCode,
|
|
494
|
+
onStatus: (data) => { state.serverStatus = data },
|
|
495
|
+
logger: null,
|
|
496
|
+
})
|
|
497
|
+
state.wsClient.connect()
|
|
483
498
|
// 隧道确认建立后启动持续心跳,让后端 last_seen 保持新鲜(管理端在线判定依赖它)
|
|
484
499
|
startKeepAlive(state)
|
|
485
500
|
}
|
|
@@ -568,6 +583,12 @@ async function startExternalInner(state, body, patchFile, defaultLocalPort) {
|
|
|
568
583
|
*/
|
|
569
584
|
function stopExternal(state, opts = {}) {
|
|
570
585
|
stopKeepAlive(state)
|
|
586
|
+
// 断开 WS 长连接(中转服务端据此判定离线)
|
|
587
|
+
if (state.wsClient) {
|
|
588
|
+
try { state.wsClient.disconnect() } catch { /* ignore */ }
|
|
589
|
+
state.wsClient = null
|
|
590
|
+
}
|
|
591
|
+
state.serverStatus = null
|
|
571
592
|
// 停止前上报离线,让管理端/成员端立即反映(后端支持 status 字段才生效)
|
|
572
593
|
if (!opts.keepState) {
|
|
573
594
|
state.status = 'offline'
|
|
@@ -612,20 +633,23 @@ function frpcLogTail(bytes = 6000) {
|
|
|
612
633
|
/** 持续心跳间隔(毫秒):后端据此维持 last_seen,并做离线超时判定。 */
|
|
613
634
|
const HEARTBEAT_INTERVAL = 60_000
|
|
614
635
|
|
|
615
|
-
/** 上报一次在线/离线心跳(尽力而为,失败静默)。
|
|
636
|
+
/** 上报一次在线/离线心跳(尽力而为,失败静默)。
|
|
637
|
+
* 优先经 WS 长连接发送(零 HTTP 请求);WS 不可用/未连上时回退 HTTP。 */
|
|
616
638
|
async function sendHeartbeat(state) {
|
|
617
639
|
const base = state.serverBase || process.env.DSH_REMOTE_SERVER || DEFAULT_SERVER_BASE
|
|
618
640
|
const bindCode = state.bindCode
|
|
619
641
|
if (!bindCode) return
|
|
642
|
+
const status = state.status === 'online' ? 'online' : 'offline'
|
|
643
|
+
// WS 已连接 → 直接经长连接发送,减轻中转服务器 HTTP 压力
|
|
644
|
+
if (state.wsClient && state.wsClient.isOpen() && state.wsClient.sendHeartbeat(status)) {
|
|
645
|
+
return
|
|
646
|
+
}
|
|
647
|
+
// WS 不可用 / 未连上 → HTTP 心跳兜底(旧后端 / ws 依赖缺失场景)
|
|
620
648
|
try {
|
|
621
649
|
await fetch(`${base.replace(/\/$/, '')}/api/tunnel/heartbeat`, {
|
|
622
650
|
method: 'POST',
|
|
623
651
|
headers: { 'Content-Type': 'application/json' },
|
|
624
|
-
body: JSON.stringify({
|
|
625
|
-
bindCode,
|
|
626
|
-
// offline 表示隧道已停止,让管理端立即标记离线(后端支持时才生效)
|
|
627
|
-
status: state.status === 'online' ? 'online' : 'offline',
|
|
628
|
-
}),
|
|
652
|
+
body: JSON.stringify({ bindCode, status }),
|
|
629
653
|
})
|
|
630
654
|
} catch { /* ignore */ }
|
|
631
655
|
}
|
package/lib/index.js
CHANGED
|
@@ -602,11 +602,18 @@ self.addEventListener('fetch', function (event) {
|
|
|
602
602
|
|
|
603
603
|
/**
|
|
604
604
|
* 从中转服务器查询本成员隧道状态与统计;失败时返回 null(不影响整体 status)。
|
|
605
|
+
* 仅作 WS 推送不可用(ws 依赖缺失 / 旧后端)时的 HTTP 兜底。
|
|
605
606
|
* 优先调用 /api/tunnel/stats(含 frps 实时流量/连接数等),接口不可用则回退到
|
|
606
|
-
* /api/tunnel/status
|
|
607
|
+
* /api/tunnel/status(旧版后端)。
|
|
608
|
+
* 注意:带 60s 节流缓存——即使前端每 5s 轮询本机 /status,也不会高频打中转服务器。
|
|
607
609
|
*/
|
|
610
|
+
let serverStatusCache = { at: 0, value: null }
|
|
608
611
|
async function queryServerStatus(base, bindCode) {
|
|
609
612
|
if (!bindCode) return null
|
|
613
|
+
const now = Date.now()
|
|
614
|
+
if (serverStatusCache.value && now - serverStatusCache.at < 60000) {
|
|
615
|
+
return serverStatusCache.value
|
|
616
|
+
}
|
|
610
617
|
const server = String(base || DEFAULT_SERVER_BASE).replace(/\/$/, '')
|
|
611
618
|
const call = (path) => fetch(`${server}${path}`, {
|
|
612
619
|
headers: { Accept: 'application/json' },
|
|
@@ -619,8 +626,9 @@ async function queryServerStatus(base, bindCode) {
|
|
|
619
626
|
}).catch(() => null)
|
|
620
627
|
const q = `?bindCode=${encodeURIComponent(bindCode)}`
|
|
621
628
|
const stats = await call(`/api/tunnel/stats${q}`)
|
|
622
|
-
|
|
623
|
-
|
|
629
|
+
const data = stats || await call(`/api/tunnel/status${q}`)
|
|
630
|
+
serverStatusCache = { at: now, value: data }
|
|
631
|
+
return data
|
|
624
632
|
}
|
|
625
633
|
|
|
626
634
|
/** 当前 profile 的 cordis.patch.yml 绝对路径。 */
|
|
@@ -730,10 +738,11 @@ function handleStatus(ctx, devices, external, tracker, dshCache) {
|
|
|
730
738
|
})
|
|
731
739
|
}
|
|
732
740
|
deviceList.sort((a, b) => b.lastSeen - a.lastSeen)
|
|
733
|
-
//
|
|
741
|
+
// 外网隧道在线时,优先读 WS 推送缓存(经长连接实时更新,零中转服务器请求);
|
|
742
|
+
// 仅当从未收到推送(ws 依赖缺失 / WS 未建立)时才回退 HTTP 低频查询(60s 节流)。
|
|
734
743
|
let server = null
|
|
735
744
|
if (extRunning && persisted) {
|
|
736
|
-
server = await queryServerStatus(persisted.serverBase || undefined, persisted.bindCode)
|
|
745
|
+
server = external.serverStatus || await queryServerStatus(persisted.serverBase || undefined, persisted.bindCode)
|
|
737
746
|
}
|
|
738
747
|
json(res, 200, {
|
|
739
748
|
host: ctx.webServer.host,
|
package/lib/ws-client.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// dsh-mobile-remote — 中转服务器 WS 状态推送客户端(host 侧)。
|
|
2
|
+
//
|
|
3
|
+
// 目标:用 WebSocket 长连接接收中转服务器的隧道状态推送,替代插件端
|
|
4
|
+
// 每 5s 轮询 /api/tunnel/stats,把中转服务器的请求压力降为 0。
|
|
5
|
+
//
|
|
6
|
+
// 说明:
|
|
7
|
+
// - 本插件运行在 Electron 内置 Node(当前 20.x,无原生全局 WebSocket),
|
|
8
|
+
// 因此需要 `ws` 第三方库。若依赖缺失(如离线 tgz 安装未装依赖),
|
|
9
|
+
// wsAvailable() 返回 false,调用方自动回退 HTTP 低频查询,功能不中断。
|
|
10
|
+
// - 连接管理:指数退避自动重连(2s→4s→…→60s);心跳由 external.js 的
|
|
11
|
+
// keepAliveTimer 统一驱动(每 60s),WS 已连上时走 WS、否则走 HTTP。
|
|
12
|
+
// - 收到 tunnel:status 后更新本地缓存并回调 onStatus,前端 /status 直接读缓存。
|
|
13
|
+
|
|
14
|
+
import { createRequire } from 'node:module'
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url)
|
|
17
|
+
|
|
18
|
+
/** 尝试加载 ws 库(Node 20 无原生 WebSocket,需要它)。 */
|
|
19
|
+
let WsCtor = null
|
|
20
|
+
try {
|
|
21
|
+
WsCtor = require('ws')
|
|
22
|
+
} catch {
|
|
23
|
+
WsCtor = null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 当前环境是否可用 WebSocket(ws 依赖是否加载成功)。 */
|
|
27
|
+
export function wsAvailable() {
|
|
28
|
+
return !!WsCtor
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 创建成员端 WS 状态推送客户端。
|
|
33
|
+
* @param {object} opts
|
|
34
|
+
* @param {string} opts.serverBase 中转服务基地址(https://… 或 http://…)
|
|
35
|
+
* @param {string} opts.bindCode 绑定码
|
|
36
|
+
* @param {(data: object) => void} [opts.onStatus] 收到 tunnel:status 时的回调
|
|
37
|
+
* @param {(msg: string, ...args: unknown[]) => void} [opts.logger] 日志(默认静默)
|
|
38
|
+
*/
|
|
39
|
+
export function createWsStatusClient({ serverBase, bindCode, onStatus, logger }) {
|
|
40
|
+
let ws = null
|
|
41
|
+
let manualClose = false
|
|
42
|
+
let retryTimer = null
|
|
43
|
+
let retryDelay = 2000
|
|
44
|
+
let lastStatus = null
|
|
45
|
+
const log = logger || (() => {})
|
|
46
|
+
|
|
47
|
+
function wsUrl() {
|
|
48
|
+
// http:// → ws://,https:// → wss://
|
|
49
|
+
const base = String(serverBase || '').replace(/^http/, 'ws').replace(/\/+$/, '')
|
|
50
|
+
return `${base}/api/tunnel/ws?bindCode=${encodeURIComponent(String(bindCode || ''))}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isOpen() {
|
|
54
|
+
return !!(ws && ws.readyState === ws.OPEN)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function scheduleReconnect() {
|
|
58
|
+
if (manualClose || retryTimer) return
|
|
59
|
+
retryTimer = setTimeout(() => {
|
|
60
|
+
retryTimer = null
|
|
61
|
+
connect()
|
|
62
|
+
}, retryDelay)
|
|
63
|
+
if (retryTimer.unref) retryTimer.unref()
|
|
64
|
+
retryDelay = Math.min(retryDelay * 2, 60000)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function connect() {
|
|
68
|
+
if (!WsCtor) return
|
|
69
|
+
if (manualClose) return
|
|
70
|
+
if (ws && (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING)) return
|
|
71
|
+
let client
|
|
72
|
+
try {
|
|
73
|
+
client = new WsCtor(wsUrl())
|
|
74
|
+
} catch (e) {
|
|
75
|
+
log('[ws-client] 连接失败:', String((e && e.message) || e))
|
|
76
|
+
scheduleReconnect()
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
ws = client
|
|
80
|
+
|
|
81
|
+
client.on('open', () => {
|
|
82
|
+
retryDelay = 2000
|
|
83
|
+
log('[ws-client] 已连接(bindCode=****)')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
client.on('message', (buf) => {
|
|
87
|
+
try {
|
|
88
|
+
const msg = JSON.parse(buf.toString())
|
|
89
|
+
if (msg && msg.type === 'tunnel:status' && msg.data) {
|
|
90
|
+
msg.data._at = Date.now() // 缓存新鲜度标记
|
|
91
|
+
lastStatus = msg.data
|
|
92
|
+
try { onStatus && onStatus(msg.data) } catch (e) { /* ignore */ }
|
|
93
|
+
}
|
|
94
|
+
} catch (e) { /* ignore */ }
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
client.on('close', (code, reason) => {
|
|
98
|
+
log('[ws-client] 连接关闭 code=' + code, String(reason || ''))
|
|
99
|
+
ws = null
|
|
100
|
+
if (!manualClose) scheduleReconnect()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
client.on('error', (err) => {
|
|
104
|
+
log('[ws-client] 错误:', String((err && err.message) || err))
|
|
105
|
+
try { client.terminate() } catch (e) { /* ignore */ }
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 主动断开(停止外网 / 插件卸载时调用),之后不再自动重连。 */
|
|
110
|
+
function disconnect() {
|
|
111
|
+
manualClose = true
|
|
112
|
+
if (retryTimer) { clearTimeout(retryTimer); retryTimer = null }
|
|
113
|
+
if (ws) {
|
|
114
|
+
try { ws.close() } catch (e) { /* ignore */ }
|
|
115
|
+
try { ws.terminate() } catch (e) { /* ignore */ }
|
|
116
|
+
ws = null
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 重置并重连(绑定码 / 中转地址变化后使用)。 */
|
|
121
|
+
function reconnect() {
|
|
122
|
+
manualClose = false
|
|
123
|
+
if (retryTimer) { clearTimeout(retryTimer); retryTimer = null }
|
|
124
|
+
retryDelay = 2000
|
|
125
|
+
if (ws) {
|
|
126
|
+
try { ws.terminate() } catch (e) { /* ignore */ }
|
|
127
|
+
ws = null
|
|
128
|
+
}
|
|
129
|
+
connect()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 经 WS 发送心跳(成功返回 true;未连接返回 false,调用方回退 HTTP)。
|
|
134
|
+
* @param {'online'|'offline'} [status]
|
|
135
|
+
*/
|
|
136
|
+
function sendHeartbeat(status) {
|
|
137
|
+
if (!isOpen()) return false
|
|
138
|
+
try {
|
|
139
|
+
ws.send(JSON.stringify({ type: 'heartbeat', status: status || 'online' }))
|
|
140
|
+
return true
|
|
141
|
+
} catch (e) {
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 最近一次收到的隧道状态(未收到过为 null)。 */
|
|
147
|
+
function getLastStatus() {
|
|
148
|
+
return lastStatus
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { connect, disconnect, reconnect, isOpen, sendHeartbeat, getLastStatus }
|
|
152
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feiyang666/dsh-mobile-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
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",
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=18"
|
|
42
42
|
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"ws": "^8.18.0"
|
|
45
|
+
},
|
|
43
46
|
"dsh": {
|
|
44
47
|
"bundle": {
|
|
45
48
|
"patch": "./cordis.patch.yml"
|
|
@@ -62,8 +65,8 @@
|
|
|
62
65
|
],
|
|
63
66
|
"scripts": {
|
|
64
67
|
"pack:list": "npm pack --dry-run",
|
|
65
|
-
"check": "node --check lib/index.js && node --check lib/client.js && node --check lib/external.js && node --check lib/password.js && node --check lib/qrcode.js",
|
|
66
|
-
"prepublishOnly": "node --check lib/index.js && node --check lib/client.js && node --check lib/external.js && node --check lib/password.js && node --check lib/qrcode.js",
|
|
68
|
+
"check": "node --check lib/index.js && node --check lib/client.js && node --check lib/external.js && node --check lib/ws-client.js && node --check lib/password.js && node --check lib/qrcode.js",
|
|
69
|
+
"prepublishOnly": "node --check lib/index.js && node --check lib/client.js && node --check lib/external.js && node --check lib/ws-client.js && node --check lib/password.js && node --check lib/qrcode.js",
|
|
67
70
|
"pack": "npm pack"
|
|
68
71
|
},
|
|
69
72
|
"peerDependencies": {
|