dsh-remote-plugin 0.6.3 → 0.6.5

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/public/index.html CHANGED
@@ -250,7 +250,10 @@
250
250
  </div>
251
251
  <div class="setting-row">
252
252
  <div><div class="setting-name" data-i18n="settings.scanTitle">扫码连接</div><div class="setting-desc" data-i18n="settings.scanDesc">拍照/相册本地解码(无谷歌服务依赖);也可系统相机扫码或手动粘贴</div></div>
253
- <button id="btn-scan-pair" class="mini-btn" data-i18n="settings.scan">扫码</button>
253
+ <div class="setting-actions">
254
+ <button id="btn-scan-camera" class="mini-btn" data-i18n="scan.promptPhoto">拍照扫描</button>
255
+ <button id="btn-scan-gallery" class="mini-btn" data-i18n="scan.promptGallery">从相册选择</button>
256
+ </div>
254
257
  </div>
255
258
  </div>
256
259
  </div>
@@ -271,6 +274,10 @@
271
274
  <div><div class="setting-name" data-i18n="settings.peakRemindTitle">峰谷计费提醒</div><div class="setting-desc" data-i18n="settings.peakRemindDesc">每天 9:00 / 12:00 / 14:00 / 18:00 推送峰谷切换(App 内有效)</div></div>
272
275
  <label class="switch"><input type="checkbox" id="opt-peak-remind"><span class="slider"></span></label>
273
276
  </div>
277
+ <div class="setting-row">
278
+ <div><div class="setting-name" data-i18n="settings.testNotifyTitle">测试通知</div><div class="setting-desc" data-i18n="settings.testNotifyDesc">立即排一条 3 秒后的本地通知,用于排查系统通知限制</div></div>
279
+ <button id="btn-test-notify" class="mini-btn" data-i18n="settings.testNotify">发送测试通知</button>
280
+ </div>
274
281
  <div class="setting-row">
275
282
  <div><div class="setting-name" data-i18n="settings.bgTitle">后台轮询</div><div class="setting-desc" data-i18n="settings.bgDesc">退后台后由前台服务定时拉取事件,待办审批/提问不遗漏</div><div id="bg-auth-status" class="setting-desc hidden" data-i18n="settings.bgAuthFailed">登录失效:连续 3 次 401,后台轮询已停止</div></div>
276
283
  <label class="switch"><input type="checkbox" id="opt-bg-poll"><span class="slider"></span></label>
@@ -484,6 +491,7 @@
484
491
  zh: {
485
492
  'a11y.hostAdmin': '主机管理', 'a11y.refresh': '刷新', 'a11y.more': '更多操作', 'a11y.moreTitle': '指令/权限/模型',
486
493
  'conn.on': '已连接', 'conn.off': '未连接', 'conn.reconnecting': '连接中断,正在重连…',
494
+ 'conn.reconnectIn': '重连中 {n}s', 'conn.offline': '离线', 'conn.failed': '连接失败',
487
495
  'conn.poll': '轮询', 'conn.pollTitle': '当前网络不支持实时推送,已降级为轮询(延迟数秒)',
488
496
  'conn.titleGroup': '{group} · {url}({ms})',
489
497
  'common.refreshing': '刷新中…',
@@ -530,7 +538,7 @@
530
538
  'session.confirmStop': '停止当前会话正在运行的任务?', 'session.stopFailed': '停止失败', 'session.stopRequested': '已请求停止',
531
539
  'notify.approvalTitle': '工具审批', 'notify.approvalBody': '{tool} 需要批准', 'notify.questionTitle': 'DSH 提问', 'notify.questionBody': '需要你回答',
532
540
  'notify.permissionFailed': '通知权限申请失败:{msg}',
533
- 'history.loading': '加载历史…', 'history.cacheFallback': '网络不可用:显示本地缓存的历史', 'history.loadFailed': '加载历史失败:{msg}',
541
+ 'history.loading': '加载历史…', 'history.cacheFallback': '网络不可用:显示本地缓存的历史', 'history.loadFailed': '加载历史失败:{msg}', 'history.retry': '重试',
534
542
  'history.offlineCache': '离线缓存 {n} 条', 'history.count': '{n} 条', 'history.empty': '还没有消息',
535
543
  'history.queueAndCount': '队列 {q} · 历史 {n}', 'history.countOnly': '历史 {n}',
536
544
  'role.me': '我', 'role.dsh': 'DSH',
@@ -610,6 +618,8 @@
610
618
  'settings.servers': '服务器地址(可多个)', 'settings.speedTest': '测速', 'settings.add': '添加',
611
619
  'settings.scanTitle': '扫码连接', 'settings.scanDesc': '拍照/相册本地解码(无谷歌服务依赖);也可系统相机扫码或手动粘贴', 'settings.scan': '扫码',
612
620
  'settings.notifyTitle': '通知', 'settings.notifyDesc': '收到审批/提问时推送',
621
+ 'settings.testNotifyTitle': '测试通知', 'settings.testNotifyDesc': '立即排一条 3 秒后的本地通知,用于排查系统通知限制', 'settings.testNotify': '发送测试通知',
622
+ 'settings.testNotifySent': '测试通知已排定,3 秒后到达', 'settings.testNotifyFailed': '发送测试通知失败:{msg}', 'settings.testNotifyUnavailable': '测试通知仅在 App 内可用',
613
623
  'settings.peakRemindTitle': '峰谷计费提醒', 'settings.peakRemindDesc': '每天 9:00 / 12:00 / 14:00 / 18:00 推送峰谷切换(App 内有效)',
614
624
  'peakRemind.enterPeak': '已进入高峰计费时段', 'peakRemind.enterOff': '已进入空闲计费时段',
615
625
  'peakRemind.peak0912': '高峰 9:00-12:00', 'peakRemind.off1214': '空闲 12:00-14:00',
@@ -663,6 +673,7 @@
663
673
  en: {
664
674
  'a11y.hostAdmin': 'Host admin', 'a11y.refresh': 'Refresh', 'a11y.more': 'More actions', 'a11y.moreTitle': 'Commands / Permissions / Models',
665
675
  'conn.on': 'Connected', 'conn.off': 'Offline', 'conn.reconnecting': 'Connection lost, reconnecting…',
676
+ 'conn.reconnectIn': 'Reconnecting {n}s', 'conn.offline': 'Offline', 'conn.failed': 'Connection failed',
666
677
  'conn.poll': 'Polling', 'conn.pollTitle': 'Realtime push is unavailable on this network; degraded to polling (a few seconds delay)',
667
678
  'conn.titleGroup': '{group} · {url} ({ms})',
668
679
  'common.refreshing': 'Refreshing…',
@@ -709,7 +720,7 @@
709
720
  'session.confirmStop': 'Stop the currently running task in this session?', 'session.stopFailed': 'Stop failed', 'session.stopRequested': 'Stop requested',
710
721
  'notify.approvalTitle': 'Tool approval', 'notify.approvalBody': '{tool} needs approval', 'notify.questionTitle': 'DSH question', 'notify.questionBody': 'Needs your answer',
711
722
  'notify.permissionFailed': 'Notification permission failed: {msg}',
712
- 'history.loading': 'Loading history…', 'history.cacheFallback': 'Network unavailable: showing cached history', 'history.loadFailed': 'Failed to load history: {msg}',
723
+ 'history.loading': 'Loading history…', 'history.cacheFallback': 'Network unavailable: showing cached history', 'history.loadFailed': 'Failed to load history: {msg}', 'history.retry': 'Retry',
713
724
  'history.offlineCache': 'Offline cache {n} items', 'history.count': '{n} items', 'history.empty': 'No messages yet',
714
725
  'history.queueAndCount': 'Queue {q} · History {n}', 'history.countOnly': 'History {n}',
715
726
  'role.me': 'Me', 'role.dsh': 'DSH',
@@ -789,6 +800,8 @@
789
800
  'settings.servers': 'Servers (one or more)', 'settings.speedTest': 'Test', 'settings.add': 'Add',
790
801
  'settings.scanTitle': 'Scan to pair', 'settings.scanDesc': 'Photo/gallery QR decoding on device (no Google services); system camera or manual paste also work', 'settings.scan': 'Scan',
791
802
  'settings.notifyTitle': 'Notifications', 'settings.notifyDesc': 'Push on approvals / questions',
803
+ 'settings.testNotifyTitle': 'Test notification', 'settings.testNotifyDesc': 'Schedule a local notification in 3 seconds to verify the notification pipeline', 'settings.testNotify': 'Send test notification',
804
+ 'settings.testNotifySent': 'Test notification scheduled, arriving in 3s', 'settings.testNotifyFailed': 'Failed to send test notification: {msg}', 'settings.testNotifyUnavailable': 'Test notification is only available in the app',
792
805
  'settings.peakRemindTitle': 'Peak/off-peak reminders', 'settings.peakRemindDesc': 'Push at 9:00 / 12:00 / 14:00 / 18:00 daily (in-app only)',
793
806
  'peakRemind.enterPeak': 'Peak pricing period started', 'peakRemind.enterOff': 'Off-peak pricing period started',
794
807
  'peakRemind.peak0912': 'Peak 9:00-12:00', 'peakRemind.off1214': 'Off-peak 12:00-14:00',
@@ -845,6 +858,7 @@
845
858
  <script src="jsqr.min.js"></script>
846
859
  <script src="i18n.js"></script>
847
860
  <script src="theme.js"></script>
861
+ <script src="md.js"></script>
848
862
  <script src="app.js"></script>
849
863
  </body>
850
864
  </html>
package/public/md.js ADDED
@@ -0,0 +1,115 @@
1
+ /* DSH Remote 轻量 Markdown 渲染器 · 零依赖
2
+ * 用法: mdToHtml(text) -> HTML 字符串
3
+ * 安全: 先 HTML 转义再转标记; 链接仅允许 http/https, 其余保留为纯文本。
4
+ * 支持: 代码块 / 行内代码 / #~### 标题 / **粗体** / *斜体* / - 无序列表 /
5
+ * 1. 有序列表 / > 引用 / [text](url) 链接 / 换行。
6
+ * 同时支持浏览器全局 window.mdToHtml 与 Node CommonJS module.exports。
7
+ */
8
+ (function (root, factory) {
9
+ if (typeof module === 'object' && module.exports) module.exports = factory()
10
+ else root.mdToHtml = factory()
11
+ })(typeof self !== 'undefined' ? self : this, function () {
12
+ 'use strict'
13
+
14
+ function escapeHtml(s) {
15
+ return String(s).replace(/[&<>"']/g, function (c) {
16
+ return {
17
+ '&': '&amp;',
18
+ '<': '&lt;',
19
+ '>': '&gt;',
20
+ '"': '&quot;',
21
+ "'": '&#39;'
22
+ }[c]
23
+ })
24
+ }
25
+
26
+ function inline(s) {
27
+ s = s.replace(/`([^`]+)`/g, '<code>$1</code>')
28
+ s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
29
+ s = s.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1<em>$2</em>')
30
+ s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, function (m, text, url) {
31
+ url = url.trim()
32
+ if (!/^https?:\/\//i.test(url)) return m
33
+ return '<a href="' + url + '" target="_blank" rel="noopener noreferrer">' + text + '</a>'
34
+ })
35
+ return s
36
+ }
37
+
38
+ function renderLines(lines) {
39
+ var html = ''
40
+ var i = 0
41
+ while (i < lines.length) {
42
+ var line = lines[i]
43
+ var t = line.trim()
44
+ if (!t) { i++; continue }
45
+ var h = line.match(/^(#{1,3})\s+(.*)$/)
46
+ if (h) {
47
+ var level = h[1].length
48
+ html += '<h' + level + '>' + inline(h[2]) + '</h' + level + '>'
49
+ i++
50
+ continue
51
+ }
52
+ var q = line.match(/^&gt;\s?(.*)$/)
53
+ if (q) {
54
+ html += '<blockquote>' + inline(q[1]) + '</blockquote>'
55
+ i++
56
+ continue
57
+ }
58
+ var ul = line.match(/^[-*]\s+(.*)$/)
59
+ if (ul) {
60
+ var items = []
61
+ while (i < lines.length) {
62
+ var um = lines[i].match(/^[-*]\s+(.*)$/)
63
+ if (!um) break
64
+ items.push(inline(um[1]))
65
+ i++
66
+ }
67
+ html += '<ul>' + items.map(function (x) { return '<li>' + x + '</li>' }).join('') + '</ul>'
68
+ continue
69
+ }
70
+ var ol = line.match(/^\d+[.)]\s+(.*)$/)
71
+ if (ol) {
72
+ var oitems = []
73
+ while (i < lines.length) {
74
+ var om = lines[i].match(/^\d+[.)]\s+(.*)$/)
75
+ if (!om) break
76
+ oitems.push(inline(om[1]))
77
+ i++
78
+ }
79
+ html += '<ol>' + oitems.map(function (x) { return '<li>' + x + '</li>' }).join('') + '</ol>'
80
+ continue
81
+ }
82
+ var para = []
83
+ while (i < lines.length) {
84
+ var cur = lines[i]
85
+ var ct = cur.trim()
86
+ if (!ct) break
87
+ if (/^(#{1,3})\s+/.test(cur) || /^&gt;\s?/.test(cur) || /^[-*]\s+/.test(cur) || /^\d+[.)]\s+/.test(cur)) break
88
+ para.push(inline(cur))
89
+ i++
90
+ }
91
+ if (para.length) html += '<p>' + para.join('<br>') + '</p>'
92
+ }
93
+ return html
94
+ }
95
+
96
+ function mdToHtml(text) {
97
+ var raw = String(text == null ? '' : text)
98
+ var parts = raw.split(/```/)
99
+ var out = ''
100
+ for (var i = 0; i < parts.length; i++) {
101
+ if (i % 2 === 1) {
102
+ var code = parts[i]
103
+ code = code.replace(/^\n/, '')
104
+ if (code.slice(-1) === '\n') code = code.slice(0, -1)
105
+ out += '<pre><code>' + escapeHtml(code) + '</code></pre>'
106
+ } else {
107
+ var escaped = escapeHtml(parts[i])
108
+ out += renderLines(escaped.split('\n'))
109
+ }
110
+ }
111
+ return out
112
+ }
113
+
114
+ return mdToHtml
115
+ })
package/public/styles.css CHANGED
@@ -298,6 +298,12 @@ body.in-session .view {
298
298
  .msg p:first-child { margin-top: 0; } .msg p:last-child { margin-bottom: 0; }
299
299
  .msg ul, .msg ol { margin: 4px 0; padding-left: 20px; }
300
300
  .msg code { font-family: ui-monospace, monospace; font-size: .9em; background: var(--dsr-accent-soft); padding: 0 4px; border-radius: 4px; }
301
+ .msg h1, .msg h2, .msg h3 { margin: 8px 0 4px; line-height: 1.3; font-weight: 700; }
302
+ .msg h1 { font-size: 1.3em; } .msg h2 { font-size: 1.18em; } .msg h3 { font-size: 1.05em; }
303
+ .msg blockquote { margin: 6px 0; padding: 2px 10px; border-left: 3px solid var(--dsr-accent-2); color: var(--dsr-muted); }
304
+ .msg a { color: var(--dsr-accent-strong); text-decoration: underline; word-break: break-all; }
305
+ .msg.user a { color: var(--dsr-on-accent); }
306
+ .msg li { margin: 2px 0; }
301
307
 
302
308
  .event {
303
309
  align-self: center; font-size: 11px; color: var(--dsr-muted);
@@ -454,6 +460,7 @@ body.in-session .main { padding-bottom: 84px; }
454
460
  .setting-name { font-size: 14.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
455
461
  .setting-desc { font-size: 12px; color: var(--dsr-muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
456
462
  .setting-desc.expanded { white-space: normal; overflow: visible; text-overflow: clip; word-break: break-word; }
463
+ .setting-actions { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
457
464
  .about { text-align: center; color: var(--dsr-muted); font-size: 12px; margin-top: 16px; line-height: 1.8; }
458
465
 
459
466
  /* 多服务器列表(分组) */
@@ -1,10 +1,18 @@
1
1
  {
2
- "version": "0.6.3",
2
+ "version": "0.6.5",
3
3
  "apkUrl": "dsh-remote.apk",
4
- "sha256": "e658387db7cee1e0b5392fad3719056b463d40ce55b5aac0d313d797c07438f2",
5
- "releasedAt": "2026-08-19T03:20:39.386Z",
6
- "notes": "修复 DSH 重启后网关 upstream 端口不刷新(Issue #1):/health 增加 upstream/upstreamOk/pid 探测;插件 ensureGateway 检测上游变化或不可达时自动杀旧网关并按新 DSH_UPSTREAM 重启;启动/刷新写插件日志与 PID 文件。",
4
+ "sha256": "132da02e3b5cb2b348f71cdc88590430fdc88abe6ee764788f658372c239e317",
5
+ "releasedAt": "2026-08-19T12:12:05.411Z",
6
+ "notes": "连接稳定性全面优化:25 秒心跳保活(WiFi 切换 / NAT 超时不再假活);60 秒无消息自动重连,指数退避重连(1s 30s 上限 + 抖动);网关死连接自动清理(60 秒无数据双向销毁);断网自动检测与恢复(离线 / 重连中倒计时 / 连接失败状态可见);降级轮询恢复优化。",
7
7
  "history": [
8
+ {
9
+ "version": "0.6.5",
10
+ "notes": "连接稳定性全面优化:25 秒心跳保活(WiFi 切换 / NAT 超时不再假活);60 秒无消息自动重连,指数退避重连(1s → 30s 上限 + 抖动);网关死连接自动清理(60 秒无数据双向销毁);断网自动检测与恢复(离线 / 重连中倒计时 / 连接失败状态可见);降级轮询恢复优化。"
11
+ },
12
+ {
13
+ "version": "0.6.4",
14
+ "notes": "扫码配对改为显式双按钮(拍照/相册),修复小米等 ROM 选择器错乱;会话历史加载超时放宽至 45 秒,失败可重试;网关端口自定义(插件管理页可改,环境变量仍优先)+ 启动前端口占用检测;峰谷计费提醒改前台服务驱动,后台按时必达;消息 Markdown 渲染(标题/代码块/列表/链接);修复 DSH 重启后网关上游端口不刷新(Issue #1);新增测试通知按钮。"
15
+ },
8
16
  {
9
17
  "version": "0.6.3",
10
18
  "notes": "修复 DSH 重启后网关 upstream 端口不刷新(Issue #1):/health 增加 upstream/upstreamOk/pid 探测;插件 ensureGateway 检测上游变化或不可达时自动杀旧网关并按新 DSH_UPSTREAM 重启;启动/刷新写插件日志与 PID 文件。"
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.6.3"
2
+ "version": "0.6.5"
3
3
  }