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.
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 clientId = String(extra.clientId || '').replace(/[^A-Za-z0-9._~-]/g, '').slice(0, 96)
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 = String(req.headers['user-agent'] || '')
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.13",
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
  }