dsh-pocket 1.13.0 → 1.13.2
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/README.en.md +1 -0
- package/README.md +1 -0
- package/client/client.js +25 -1
- package/client/mobile/MobileNavOverlay.tsx +6 -1
- package/client/mobile/mobile-apply.tsx +25 -0
- package/client/mobile/mobile.css.ts +6 -0
- package/lib/proxy.mjs +3 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -48,6 +48,7 @@ What it looks like — the phone shows the exact same UI as your computer, live:
|
|
|
48
48
|
| 🧘 Session persistence | Enter the PIN once and you're set for a long time (login is tied to the computer's dsh web process: as long as it stays up, the phone won't ask again; **after a dsh web restart/update, enter it once more**) |
|
|
49
49
|
| ⚡ Real-time sync | Streaming output passes through WebSocket untouched — what the computer renders, the phone renders live; fully interactive both ways; built-in WS heartbeat keep-alive (defeats silent NAT/battery link drops with auto-reconnect) |
|
|
50
50
|
| 📱 Mobile-adaptive layout | Narrow screens get a drawer layout automatically (ported from dsh-web-mobile, MIT): sidebar drawer, full-width conversation, safe-area insets, touch optimizations |
|
|
51
|
+
| 📁 File browser | The mobile "Files" entries need a host-side explorer panel (a dsh-web-ui component); on stock DSH without it the entries are auto-hidden instead of doing nothing |
|
|
51
52
|
| 🗜️ Transfer compression | Large JSON responses are gzip/brotli'd on the fly (17MB session history → ~1MB; brotli quality 6: fast and bandwidth-friendly) — faster loads, less mobile data |
|
|
52
53
|
| 🔁 Tunnel auto-restore | After a DSH restart the previously-running public tunnel comes back automatically |
|
|
53
54
|
| 🧩 Zero-dependency install | One npm package, one settings tab — no core/adapter split, no account, no server |
|
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ DSH Pocket 就是干这个的:**装上它,手机扫个码,就能实时看
|
|
|
48
48
|
| 🧘 会话保持 | 手机输一次密码后**长期免输**(登录状态绑定电脑上的 dsh web 进程:只要它不重启,手机不用再输;**dsh web 重启/更新后需重新输入一次**) |
|
|
49
49
|
| ⚡ 实时同步 | 流式输出走 WebSocket 全透传——**电脑上在输出,手机上同步在滚**,可双向操作;内置心跳保活(防路由器 NAT/省电机制静默断链,断线自动重连) |
|
|
50
50
|
| 📱 移动端适配 | 窄屏自动变抽屉布局(移植 dsh-web-mobile,MIT):侧栏抽屉、会话全宽、状态栏安全区、触控优化 |
|
|
51
|
+
| 📁 文件浏览 | 移动端「文件浏览」入口需要宿主提供 explorer 面板(dsh-web-ui 组件);官方 DSH 未内置时入口自动隐藏,不会出现"点了没反应" |
|
|
51
52
|
| 🗜️ 传输压缩 | 大 JSON 响应自动 gzip/brotli(长会话 17MB → ~1MB,brotli 质量 6:快且省流量),手机加载更快、更省流量 |
|
|
52
53
|
| 🔁 隧道自动恢复 | DSH 重启后自动重新拉起之前开着的公网隧道,无需手动重开 |
|
|
53
54
|
| 🧩 零依赖安装 | 一个 npm 包、一个设置页,没有核心/适配器要分开装;无需账号、无需服务器 |
|
package/client/client.js
CHANGED
|
@@ -205,7 +205,7 @@ function MobileNavOverlay({ toggleSidebar, t }) {
|
|
|
205
205
|
if (drawer === null || !drawer.contains(target)) return;
|
|
206
206
|
if (target.closest('[class*="sessionRow"] button') !== null) return;
|
|
207
207
|
const navigates = target.closest(
|
|
208
|
-
'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"]'
|
|
208
|
+
'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [data-mobile-nav="files"]'
|
|
209
209
|
);
|
|
210
210
|
if (navigates !== null) toggleSidebar();
|
|
211
211
|
};
|
|
@@ -313,6 +313,12 @@ var MOBILE_CSS = `
|
|
|
313
313
|
align-items: center;
|
|
314
314
|
gap: 8px;
|
|
315
315
|
}
|
|
316
|
+
/* \u5BBF\u4E3B\u6CA1\u6709 aionui explorer \u5217\uFF08\u5B98\u65B9 DSH \u65E0 dsh-web-ui\uFF0Cissue #48\uFF09\u65F6\u9690\u85CF
|
|
317
|
+
\u79FB\u52A8\u7AEF\u300C\u6587\u4EF6\u6D4F\u89C8\u300D\u5165\u53E3\uFF08header \u56FE\u6807 + drawer footer \u9879\uFF09\u2014\u2014\u4E0D\u7136\u70B9\u4E86\u6CA1\u53CD\u5E94\u3002 */
|
|
318
|
+
[data-mobile-nav-explorer="0"] [data-mobile-nav="files"],
|
|
319
|
+
[data-mobile-nav-explorer="0"] [data-mobile-nav="explorer"] {
|
|
320
|
+
display: none !important;
|
|
321
|
+
}
|
|
316
322
|
[data-mobile-nav="session-log"],
|
|
317
323
|
[data-mobile-nav="explorer"] {
|
|
318
324
|
display: inline-flex;
|
|
@@ -1190,6 +1196,24 @@ function mobileApply(ctx) {
|
|
|
1190
1196
|
document.addEventListener("click", onChevronClick, true);
|
|
1191
1197
|
return () => document.removeEventListener("click", onChevronClick, true);
|
|
1192
1198
|
}, "dsh-mobile-nav: aionui explorer close marker");
|
|
1199
|
+
ctx.effect(() => {
|
|
1200
|
+
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1201
|
+
if (!narrow.matches) return () => {
|
|
1202
|
+
};
|
|
1203
|
+
const frame = () => document.querySelector('[data-mobile-nav="frame"]');
|
|
1204
|
+
const check = () => {
|
|
1205
|
+
const has = document.querySelector("[data-aionui-explorer-col]") !== null;
|
|
1206
|
+
frame()?.setAttribute("data-mobile-nav-explorer", has ? "1" : "0");
|
|
1207
|
+
};
|
|
1208
|
+
check();
|
|
1209
|
+
const timer = window.setTimeout(check, 1500);
|
|
1210
|
+
const observer = new MutationObserver(check);
|
|
1211
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1212
|
+
return () => {
|
|
1213
|
+
window.clearTimeout(timer);
|
|
1214
|
+
observer.disconnect();
|
|
1215
|
+
};
|
|
1216
|
+
}, "dsh-mobile-nav: explorer availability (issue #48)");
|
|
1193
1217
|
ctx.effect(() => {
|
|
1194
1218
|
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1195
1219
|
if (!narrow.matches) return () => {
|
|
@@ -119,8 +119,13 @@ export function MobileNavOverlay({ toggleSidebar, t }: MobileNavOverlayProps) {
|
|
|
119
119
|
// menu. Tapping one must NOT count as tapping the row, or the drawer
|
|
120
120
|
// would close and take the just-opened menu with it.
|
|
121
121
|
if (target.closest('[class*="sessionRow"] button') !== null) return
|
|
122
|
+
// The drawer footer "Files" action opens the dsh-web-ui explorer sheet,
|
|
123
|
+
// which sits at a LOWER z-index (55) than the open drawer (600) and
|
|
124
|
+
// outside the drawer DOM — leaving the drawer open would let it cover
|
|
125
|
+
// the sheet, and tapping a sheet row would be eaten by the tap-outside
|
|
126
|
+
// close handler. Close the drawer on Files, like navigation.
|
|
122
127
|
const navigates = target.closest(
|
|
123
|
-
'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"]',
|
|
128
|
+
'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [data-mobile-nav="files"]',
|
|
124
129
|
)
|
|
125
130
|
if (navigates !== null) toggleSidebar()
|
|
126
131
|
}
|
|
@@ -107,6 +107,31 @@ export function mobileApply(ctx): void {
|
|
|
107
107
|
return () => document.removeEventListener('click', onChevronClick, true)
|
|
108
108
|
}, 'dsh-mobile-nav: aionui explorer close marker')
|
|
109
109
|
|
|
110
|
+
// The mobile "Files" entries (header icon + drawer footer) open the
|
|
111
|
+
// dsh-web-ui **aionui explorer column** — a host-side component that plain
|
|
112
|
+
// DeepSeek Harness does NOT ship (issue #48). Without it the entries are
|
|
113
|
+
// dead buttons (they only toggle a frame attribute; nothing ever renders).
|
|
114
|
+
// Detect whether the host provides the column and mark the frame with
|
|
115
|
+
// `data-mobile-nav-explorer="1|0"` so the stylesheet can hide the entries
|
|
116
|
+
// on hosts without it (dsh-web-ui installs keep the feature).
|
|
117
|
+
ctx.effect(() => {
|
|
118
|
+
const narrow = window.matchMedia('(max-width: 1023px)')
|
|
119
|
+
if (!narrow.matches) return () => {}
|
|
120
|
+
const frame = (): HTMLElement | null => document.querySelector('[data-mobile-nav="frame"]')
|
|
121
|
+
const check = () => {
|
|
122
|
+
const has = document.querySelector('[data-aionui-explorer-col]') !== null
|
|
123
|
+
frame()?.setAttribute('data-mobile-nav-explorer', has ? '1' : '0')
|
|
124
|
+
}
|
|
125
|
+
check()
|
|
126
|
+
const timer = window.setTimeout(check, 1500) // 宿主懒渲染:稍后再查一次
|
|
127
|
+
const observer = new MutationObserver(check)
|
|
128
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
129
|
+
return () => {
|
|
130
|
+
window.clearTimeout(timer)
|
|
131
|
+
observer.disconnect()
|
|
132
|
+
}
|
|
133
|
+
}, 'dsh-mobile-nav: explorer availability (issue #48)')
|
|
134
|
+
|
|
110
135
|
// dsh-web-ui compatibility: the aionui preview column persists its open
|
|
111
136
|
// tabs in localStorage and restores them on load, which would pop the
|
|
112
137
|
// preview sheet over the fresh UI after a reload. Gate it like the
|
|
@@ -49,6 +49,12 @@ export const MOBILE_CSS = `
|
|
|
49
49
|
align-items: center;
|
|
50
50
|
gap: 8px;
|
|
51
51
|
}
|
|
52
|
+
/* 宿主没有 aionui explorer 列(官方 DSH 无 dsh-web-ui,issue #48)时隐藏
|
|
53
|
+
移动端「文件浏览」入口(header 图标 + drawer footer 项)——不然点了没反应。 */
|
|
54
|
+
[data-mobile-nav-explorer="0"] [data-mobile-nav="files"],
|
|
55
|
+
[data-mobile-nav-explorer="0"] [data-mobile-nav="explorer"] {
|
|
56
|
+
display: none !important;
|
|
57
|
+
}
|
|
52
58
|
[data-mobile-nav="session-log"],
|
|
53
59
|
[data-mobile-nav="explorer"] {
|
|
54
60
|
display: inline-flex;
|
package/lib/proxy.mjs
CHANGED
|
@@ -445,6 +445,9 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
|
|
|
445
445
|
if (heartbeat !== false) attachWebSocketHeartbeat(socket, heartbeat ?? {});
|
|
446
446
|
// 任一端断开都要清理另一端(避免上游残留僵尸连接占用 dsh 连接槽)
|
|
447
447
|
const teardown = () => { try { proxySocket.destroy(); } catch {} try { socket.destroy(); } catch {} };
|
|
448
|
+
// 上游侧透传 socket 的读错误(如 dsh web 重启/断开时的 ECONNRESET)必须
|
|
449
|
+
// 吞掉并清理对端,否则未处理的 'error' 事件会让整个 dsh web 进程崩溃退出。
|
|
450
|
+
proxySocket.on('error', () => { try { socket.destroy(); } catch {} });
|
|
448
451
|
proxySocket.on('close', teardown);
|
|
449
452
|
socket.on('close', teardown);
|
|
450
453
|
});
|
package/package.json
CHANGED