dsh-remote-plugin 0.6.13 → 0.6.14
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/apk/dsh-remote.apk +0 -0
- package/gateway.cjs +59 -4
- package/package.json +1 -1
- package/public/announcements.json +25 -0
- package/public/app.js +313 -31
- package/public/desktop/desktop.css +20 -0
- package/public/desktop/desktop.html +8 -4
- package/public/desktop/desktop.js +124 -24
- package/public/index.html +32 -12
- package/public/md.js +71 -1
- package/public/styles.css +27 -0
- package/public/transcribe-core.js +74 -0
- package/public/update.json +8 -8
- package/public/version.json +1 -1
package/apk/dsh-remote.apk
CHANGED
|
Binary file
|
package/gateway.cjs
CHANGED
|
@@ -400,7 +400,8 @@ function issueWsTicket(auth) {
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
// ---------- 设备监控 ----------
|
|
403
|
-
const devices = new Map() // ip -> device
|
|
403
|
+
const devices = new Map() // ip[|clientId] -> device
|
|
404
|
+
const legacyDeviceAliases = new Map() // ip -> { clientId, ua, expiresAt }
|
|
404
405
|
// 设备 TTL 是“记录保留时间”,和下方 online 判断的 60s 活跃窗口是两回事:
|
|
405
406
|
// online 只看最近 60s 是否有请求;TTL 用于防止长期运行的网关内存/响应无限膨胀。
|
|
406
407
|
const DEVICE_TTL_MS = 24 * 60 * 60 * 1000
|
|
@@ -417,6 +418,9 @@ function pruneDevices(now = Date.now()) {
|
|
|
417
418
|
for (const [ip, d] of devices) {
|
|
418
419
|
if (now - d.lastSeen > DEVICE_TTL_MS) devices.delete(ip)
|
|
419
420
|
}
|
|
421
|
+
for (const [ip, alias] of legacyDeviceAliases) {
|
|
422
|
+
if (!alias || alias.expiresAt <= now) legacyDeviceAliases.delete(ip)
|
|
423
|
+
}
|
|
420
424
|
}
|
|
421
425
|
|
|
422
426
|
function loadNotes() {
|
|
@@ -444,10 +448,53 @@ function kindOf(req) {
|
|
|
444
448
|
return 'browser'
|
|
445
449
|
}
|
|
446
450
|
|
|
451
|
+
function mergeDeviceRecords(target, legacy) {
|
|
452
|
+
if (!target || !legacy || target === legacy) return
|
|
453
|
+
target.firstSeen = Math.min(target.firstSeen || Date.now(), legacy.firstSeen || Date.now())
|
|
454
|
+
target.lastSeen = Math.max(target.lastSeen || 0, legacy.lastSeen || 0)
|
|
455
|
+
target.requests += legacy.requests || 0
|
|
456
|
+
target.authFailures += legacy.authFailures || 0
|
|
457
|
+
target.credentialId ||= legacy.credentialId || ''
|
|
458
|
+
if (!target.ua || (legacy.ua && legacy.ua.length > target.ua.length)) target.ua = legacy.ua
|
|
459
|
+
for (const channel of new Set([...Object.keys(legacy.channelCounts || {}), ...Object.keys(target.channelCounts || {})])) {
|
|
460
|
+
target.channelCounts[channel] = (target.channelCounts[channel] || 0) + (legacy.channelCounts?.[channel] || 0)
|
|
461
|
+
target.channels[channel] = !!(target.channelCounts[channel] || target.channels[channel] || legacy.channels?.[channel])
|
|
462
|
+
}
|
|
463
|
+
for (const socket of legacy.sockets || []) target.sockets.add(socket)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function legacyDeviceFor(ip, clientId, req) {
|
|
467
|
+
if (!clientId) return null
|
|
468
|
+
const legacy = devices.get(ip)
|
|
469
|
+
if (!legacy || legacy.clientId) return null
|
|
470
|
+
const requestUa = String(req.headers['user-agent'] || '')
|
|
471
|
+
const sameUa = requestUa && legacy.ua && requestUa === legacy.ua
|
|
472
|
+
const legacyBackgroundPoll = /^Dalvik\/2\.1\.0/i.test(legacy.ua || '') && req.headers['x-dsh-remote-client'] === 'app'
|
|
473
|
+
if (!sameUa && !legacyBackgroundPoll) return null
|
|
474
|
+
return legacy
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function knownDeviceForLegacy(ip, req) {
|
|
478
|
+
const requestUa = String(req.headers['user-agent'] || '')
|
|
479
|
+
if (!requestUa) return null
|
|
480
|
+
const candidates = [...devices.values()].filter(d => {
|
|
481
|
+
if (d.ip !== ip || !d.clientId) return false
|
|
482
|
+
return (d.ua && d.ua === requestUa) || (d.kind === 'app' && /^Dalvik\/2\.1\.0/i.test(requestUa))
|
|
483
|
+
})
|
|
484
|
+
return candidates.length === 1 ? candidates[0] : null
|
|
485
|
+
}
|
|
486
|
+
|
|
447
487
|
function touchDevice(req, extra = {}) {
|
|
448
488
|
pruneDevices()
|
|
449
489
|
const ip = ipOf(req)
|
|
450
|
-
const
|
|
490
|
+
const headerClientId = req.headers['x-dsh-remote-client-id']
|
|
491
|
+
let clientId = String(extra.clientId || headerClientId || '').replace(/[^A-Za-z0-9._~-]/g, '').slice(0, 96)
|
|
492
|
+
const requestUa = String(req.headers['user-agent'] || '')
|
|
493
|
+
if (!clientId) {
|
|
494
|
+
const alias = legacyDeviceAliases.get(ip)
|
|
495
|
+
if (alias && alias.expiresAt > Date.now() && alias.ua && alias.ua === requestUa) clientId = alias.clientId
|
|
496
|
+
else clientId = knownDeviceForLegacy(ip, req)?.clientId || ''
|
|
497
|
+
}
|
|
451
498
|
const deviceKey = clientId ? `${ip}|${clientId}` : ip
|
|
452
499
|
totalRequests++
|
|
453
500
|
let d = devices.get(deviceKey)
|
|
@@ -458,6 +505,14 @@ function touchDevice(req, extra = {}) {
|
|
|
458
505
|
}
|
|
459
506
|
devices.set(deviceKey, d)
|
|
460
507
|
}
|
|
508
|
+
if (clientId && deviceKey !== ip) {
|
|
509
|
+
const legacy = legacyDeviceFor(ip, clientId, req)
|
|
510
|
+
if (legacy && legacy !== d) {
|
|
511
|
+
mergeDeviceRecords(d, legacy)
|
|
512
|
+
devices.delete(ip)
|
|
513
|
+
legacyDeviceAliases.set(ip, { clientId, ua: legacy.ua, expiresAt: Date.now() + DEVICE_TTL_MS })
|
|
514
|
+
}
|
|
515
|
+
}
|
|
461
516
|
d.lastSeen = Date.now()
|
|
462
517
|
d.requests++
|
|
463
518
|
if (extra.channel) {
|
|
@@ -473,7 +528,7 @@ function touchDevice(req, extra = {}) {
|
|
|
473
528
|
if (req.dshRemoteAuth?.type === 'device') d.credentialId = req.dshRemoteAuth.id
|
|
474
529
|
const marked = req.headers['x-dsh-remote-client']
|
|
475
530
|
if (marked) d.kind = marked
|
|
476
|
-
const ua =
|
|
531
|
+
const ua = requestUa
|
|
477
532
|
if (ua && ua.length > d.ua.length) d.ua = ua
|
|
478
533
|
return d
|
|
479
534
|
}
|
|
@@ -713,7 +768,7 @@ function cors(res, req = res.req) {
|
|
|
713
768
|
}
|
|
714
769
|
if (allowed) res.setHeader('access-control-allow-origin', origin || '*')
|
|
715
770
|
res.setHeader('vary', 'Origin')
|
|
716
|
-
res.setHeader('access-control-allow-headers', 'authorization, content-type, x-dsh-remote-client')
|
|
771
|
+
res.setHeader('access-control-allow-headers', 'authorization, content-type, x-dsh-remote-client, x-dsh-remote-client-id')
|
|
717
772
|
res.setHeader('access-control-allow-methods', 'GET, POST, OPTIONS')
|
|
718
773
|
res.setHeader('access-control-max-age', '600')
|
|
719
774
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.14",
|
|
4
4
|
"description": "DSH Remote 官方 bundle 插件:DSH 左侧原生边栏入口 + 右侧抽屉内嵌管理控制台;内置网关随 DSH 自动启停(systemd 独立单元),抽屉直显令牌与设备监控;网关提供 /fs/* 文件传输端点(列表/断点下载/上传),配合 Android App 远程操控会话/审批/提问/goal 与文件互传(多服务器测速切换、聊天记录离线缓存)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
@@ -50,6 +50,31 @@
|
|
|
50
50
|
{ "id": "not-used", "label": "尚未使用,暂时无法判断" }
|
|
51
51
|
]
|
|
52
52
|
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "2026-08-24-remote-plugin-demand-poll",
|
|
56
|
+
"title": "投票:你是否需要远程使用或管理插件?",
|
|
57
|
+
"content": "我们正在研究将插件管理和插件内具体功能接入 dsh-Remote 的可行性。后续可能由 DSH 端的插件管理工具负责实际处理,再通过远程接口提供给 App 使用。\n\n本投票用于了解大家的实际需求,结果仅作为后续版本规划参考,不代表功能已经确定或会自动执行插件操作。",
|
|
58
|
+
"minVersion": "0.6.11",
|
|
59
|
+
"maxVersion": "",
|
|
60
|
+
"publishedAt": "2026-08-24T23:28:14+08:00",
|
|
61
|
+
"poll": {
|
|
62
|
+
"id": "remote-plugin-demand-2026-08",
|
|
63
|
+
"question": "你更需要哪类远程插件能力?",
|
|
64
|
+
"options": [
|
|
65
|
+
{ "id": "use-plugin-features", "label": "远程使用插件内具体功能" },
|
|
66
|
+
{ "id": "manage-plugins-only", "label": "只需要远程安装、更新、卸载插件" },
|
|
67
|
+
{ "id": "no-remote-plugin", "label": "没有远程使用或管理插件的需求" }
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "2026-08-25-user-community-group",
|
|
73
|
+
"title": "dsh-Remote 用户交流群建立公告",
|
|
74
|
+
"content": "大家好,dsh-Remote 用户交流群已经建立,群号:1106138825。\n\n本群主要用于:\n\n- 用户需求和使用反馈\n- 后续版本更新说明\n- 使用过程中问题的交流与讨论\n\n欢迎有兴趣的用户加入。提交日志或截图前,请注意隐藏 Token、服务器地址等敏感信息。",
|
|
75
|
+
"minVersion": "",
|
|
76
|
+
"maxVersion": "",
|
|
77
|
+
"publishedAt": "2026-08-25T00:24:27+08:00"
|
|
53
78
|
}
|
|
54
79
|
]
|
|
55
80
|
}
|