dsh-mobile-access 1.1.6 → 1.1.8
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.md +231 -51
- package/client.js +98 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/abbaababa/dsh-mobile-access/actions/workflows/verify.yml)
|
|
4
4
|
|
|
5
|
-
> 用 iPhone / iPad 自带的 Safari,安全打开 Mac 上的 DeepSeek Harness Web。
|
|
5
|
+
> 用 iPhone / iPad 自带的 Safari,安全打开 **Mac 或 Windows 10/11** 上的 DeepSeek Harness Web。
|
|
6
6
|
>
|
|
7
|
-
> **不用装 App,不用改 DSH
|
|
7
|
+
> **不用装 App,不用改 DSH 源码,不影响你原来在电脑上的使用。**
|
|
8
|
+
>
|
|
9
|
+
> **Windows 10/11 用户也能用**:同一份插件,不区分 Mac / Windows。
|
|
10
|
+
> Windows 只是多一个“防火墙允许 Node.js”的步骤;完整小白步骤见
|
|
11
|
+
> [Windows 10/11 快速上手](#windows-1011-快速上手小白版)。
|
|
8
12
|
|
|
9
|
-
这是一个独立的 TypeScript DSH 插件(bundle
|
|
13
|
+
这是一个独立的 TypeScript DSH 插件(bundle)。它主要在 DSH 里增加三样东西:
|
|
10
14
|
|
|
11
15
|
1. 左侧栏一个「手机访问」入口;
|
|
12
16
|
2. 一个给手机用的短地址和 8 位配对码;
|
|
13
|
-
3.
|
|
17
|
+
3. 一组只在 iPhone/iPad 上生效的输入与触摸小补丁(Return 换行、防自动放大、侧栏手势等)。
|
|
14
18
|
|
|
15
19
|
其他东西都不动:不改 DSH 核心、不改登录方式、不替换页面、不装后台服务、不拦截现有请求。
|
|
16
20
|
|
|
@@ -18,7 +22,7 @@
|
|
|
18
22
|
|
|
19
23
|
## 先看结论:它到底帮你做了什么
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
假设你的电脑(Mac 或 Windows 10/11)上正在运行 DSH Web,地址是:
|
|
22
26
|
|
|
23
27
|
```text
|
|
24
28
|
http://192.168.1.23:3080
|
|
@@ -30,12 +34,12 @@ http://192.168.1.23:3080
|
|
|
30
34
|
dsh web authentication required; reopen the URL printed by dsh web.
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
这不是网络坏了,而是 DSH
|
|
37
|
+
这不是网络坏了,而是 DSH 的安全保护:它只认电脑终端(Mac 终端 / Windows PowerShell)里打印出来的那条**带很长 token 的登录链接**。这条链接太长,手机不可能手输。
|
|
34
38
|
|
|
35
39
|
这个插件做的事,就是把这个过程变成:
|
|
36
40
|
|
|
37
41
|
```text
|
|
38
|
-
|
|
42
|
+
电脑面板显示 8 位配对码
|
|
39
43
|
↓
|
|
40
44
|
iPhone 打开短地址 /m
|
|
41
45
|
↓
|
|
@@ -49,37 +53,198 @@ iPhone 打开短地址 /m
|
|
|
49
53
|
- DSH 是一栋上锁的房子;
|
|
50
54
|
- 原来的长 token 是一把很长的钥匙,手机没法输;
|
|
51
55
|
- 插件在门口装了一个「临时门禁」;
|
|
52
|
-
-
|
|
56
|
+
- 电脑前的人看到 8 位门禁码,告诉手机;
|
|
53
57
|
- 手机输对后,屋里直接给它戴上 30 天有效的「手环」(HttpOnly 登录 Cookie)。
|
|
54
58
|
|
|
55
59
|
**长钥匙不会出现在手机地址栏、浏览器历史或网页来源信息里。**
|
|
56
60
|
|
|
57
61
|
---
|
|
58
62
|
|
|
63
|
+
## Windows 10/11 快速上手(小白版)
|
|
64
|
+
|
|
65
|
+
> 这一段是专门写给“不写代码、不懂网络”的 Windows 用户的。
|
|
66
|
+
> 如果你用的是 Mac,可以直接看下面的[安装](#安装mac-或-windows-操作复制即可)。
|
|
67
|
+
|
|
68
|
+
### 先说结论
|
|
69
|
+
|
|
70
|
+
**这个插件不是 Mac 专用的。**
|
|
71
|
+
|
|
72
|
+
它在 Windows 10 / Windows 11 上使用的是**同一个 npm 包、同一条安装命令、同一个「手机访问」面板**。
|
|
73
|
+
Windows 和 Mac 最需要额外注意的区别是:
|
|
74
|
+
|
|
75
|
+
> Windows 第一次运行时,系统防火墙会弹出一个“是否允许 Node.js 访问网络”的窗口,点一次“允许”即可。
|
|
76
|
+
|
|
77
|
+
不需要安装 iTunes,不需要买 Mac,不需要改 DSH 源码,也不需要在 iPhone 上装 App。
|
|
78
|
+
|
|
79
|
+
### 你需要准备什么
|
|
80
|
+
|
|
81
|
+
只需要 4 样东西:
|
|
82
|
+
|
|
83
|
+
1. 一台 **Windows 10 或 Windows 11** 电脑;
|
|
84
|
+
2. 电脑上的 **DSH Web 已经能正常打开**(平时启动 DSH 的那个窗口不要关);
|
|
85
|
+
3. iPhone 和 Windows 电脑连的是**同一个 Wi-Fi**;
|
|
86
|
+
4. Windows 上已经装好 DSH 需要的 **Node.js**(DSH 能启动,就说明已经装好)。
|
|
87
|
+
|
|
88
|
+
### 三步安装
|
|
89
|
+
|
|
90
|
+
#### 第 1 步:打开 PowerShell
|
|
91
|
+
|
|
92
|
+
在 Windows 上:
|
|
93
|
+
|
|
94
|
+
1. 按键盘上的 **`Win + X`**;
|
|
95
|
+
2. 在弹出的菜单里选 **“终端”** 或 **“Windows PowerShell”**;
|
|
96
|
+
3. 不需要“以管理员身份运行”;
|
|
97
|
+
4. 看到类似 `PS C:\Users\你的名字>` 的提示符,就说明打开了。
|
|
98
|
+
|
|
99
|
+
#### 第 2 步:复制下面这一条命令,粘贴,回车
|
|
100
|
+
|
|
101
|
+
```powershell
|
|
102
|
+
dsh plugin --profile web add dsh-mobile-access@1.1.8 --registry=https://registry.npmjs.org/
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
这条命令只做三件事:
|
|
106
|
+
|
|
107
|
+
1. 从 npm 下载插件;
|
|
108
|
+
2. 装进 DSH 的 `web` profile;
|
|
109
|
+
3. 自动把插件加入 DSH 的插件列表。
|
|
110
|
+
|
|
111
|
+
> 如果你的 DSH 平时用的不是 `web` 这个 profile,把命令里的 `web` 换成你实际使用的名字。
|
|
112
|
+
> 不确定时先照抄;如果提示 profile 不存在,再检查你平时启动 DSH 用的名称。
|
|
113
|
+
|
|
114
|
+
如果 PowerShell 提示“`dsh` 不是内部或外部命令”,说明 DSH 没有加入系统 PATH。
|
|
115
|
+
这不代表插件坏了:请先按你平时启动 DSH 的方式打开它的终端,再执行同一条命令;或者把 DSH 的 `bin` 目录加入 PATH。不要因此重装 Node.js 或 DSH。
|
|
116
|
+
|
|
117
|
+
#### 第 3 步:回到 DSH 页面,按 `Ctrl + R` 刷新
|
|
118
|
+
|
|
119
|
+
刷新后,DSH 左侧栏会出现一个**手机图标**,名字叫「手机访问」。
|
|
120
|
+
|
|
121
|
+
如果插件管理器提示“需要重启”,请**手动重启 DSH**;不要连续反复重装。
|
|
122
|
+
正常情况下不需要重启。
|
|
123
|
+
|
|
124
|
+
### 第一次打开:Windows 防火墙会问你
|
|
125
|
+
|
|
126
|
+
第一次让手机访问时,Windows 可能弹出:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
Windows Defender 防火墙已阻止此应用的部分功能
|
|
130
|
+
是否允许 Node.js 在专用网络上通信?
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
请这样选:
|
|
134
|
+
|
|
135
|
+
1. 勾选 **“专用网络”**(英文是 **Private networks**);
|
|
136
|
+
2. 点 **“允许访问”**(Allow access)。
|
|
137
|
+
|
|
138
|
+
不要勾“公用网络”(Public),除非你明确知道自己在做什么。
|
|
139
|
+
|
|
140
|
+
如果当时误点了“取消”,或者根本没弹窗,可以手动放行:
|
|
141
|
+
|
|
142
|
+
1. 打开 **Windows 安全中心**;
|
|
143
|
+
2. 点 **“防火墙和网络保护”**;
|
|
144
|
+
3. 点 **“允许应用通过防火墙”**;
|
|
145
|
+
4. 点 **“更改设置”**;
|
|
146
|
+
5. 在列表里找到 **Node.js**;
|
|
147
|
+
6. 勾选 **“专用”**;
|
|
148
|
+
7. 点“确定”。
|
|
149
|
+
|
|
150
|
+
还要确认当前 Wi-Fi 被 Windows 当作“专用网络”:
|
|
151
|
+
|
|
152
|
+
1. 打开 **设置 → 网络和 Internet → Wi-Fi**;
|
|
153
|
+
2. 点当前连接的 Wi-Fi 名称;
|
|
154
|
+
3. 把“网络配置文件类型”改成 **“专用网络”**。
|
|
155
|
+
|
|
156
|
+
> 如果 Wi-Fi 被设成“公用网络”,Windows 防火墙通常会更严格,iPhone 可能打不开页面。
|
|
157
|
+
|
|
158
|
+
> **如果 DSH 安装在 WSL(Windows 的 Linux 子系统)里**:WSL 默认使用虚拟网卡,局域网里的 iPhone 通常无法直接访问 WSL 内部的端口。这不是插件本身的问题,而是 WSL 的网络隔离。建议把官方 DSH 直接安装在 Windows 本机;如果必须用 WSL,需要额外配置 WSL 端口转发和 Windows 防火墙,超出本插件“零配置”的范围。
|
|
159
|
+
|
|
160
|
+
### 手机上输入哪个地址
|
|
161
|
+
|
|
162
|
+
在 Windows 的 DSH 左侧栏点「手机访问」,面板会显示一个地址。
|
|
163
|
+
直接把这个地址输入 iPhone 的 Safari 即可。
|
|
164
|
+
|
|
165
|
+
两种常见情况:
|
|
166
|
+
|
|
167
|
+
| Windows 上 DSH 的启动方式 | 手机应该输入的地址 |
|
|
168
|
+
|---|---|
|
|
169
|
+
| 只监听 `127.0.0.1`(默认) | 面板显示的 `http://<Windows 局域网 IP>:3081/m` |
|
|
170
|
+
| 已经监听局域网地址 | 面板显示的 `http://<Windows 局域网 IP>:3080/m` |
|
|
171
|
+
|
|
172
|
+
**不要输入 `127.0.0.1` 或 `localhost`。**
|
|
173
|
+
这两个地址在 iPhone 上代表“iPhone 自己”,不是你的 Windows 电脑。
|
|
174
|
+
|
|
175
|
+
如果你不知道 Windows 的局域网 IP:
|
|
176
|
+
|
|
177
|
+
1. 在 PowerShell 里输入:
|
|
178
|
+
```powershell
|
|
179
|
+
ipconfig
|
|
180
|
+
```
|
|
181
|
+
2. 找到当前 Wi-Fi 网卡下的 **“IPv4 地址”**;
|
|
182
|
+
3. 它通常长这样:`192.168.1.23`;
|
|
183
|
+
4. 面板一般会自动显示这个地址,不需要你手动拼。
|
|
184
|
+
|
|
185
|
+
### 配对(Windows + iPhone)
|
|
186
|
+
|
|
187
|
+
1. 在 Windows 的 DSH 左侧栏点「手机访问」;
|
|
188
|
+
2. 记下面板上的 **8 位配对码**;
|
|
189
|
+
3. 在 iPhone Safari 打开面板显示的 `/m` 地址;
|
|
190
|
+
4. 输入 8 位数字;
|
|
191
|
+
5. 点「连接 DSH」;
|
|
192
|
+
6. 进入后,建议点 Safari 的分享按钮 → **“添加到主屏幕”**。
|
|
193
|
+
|
|
194
|
+
### Windows 常见问题
|
|
195
|
+
|
|
196
|
+
| 现象 | 最可能的原因 | 怎么解决 |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| iPhone 打不开 `/m` | 防火墙没放行 Node.js | 按上面的“Windows 防火墙”步骤,允许“专用网络” |
|
|
199
|
+
| iPhone 打不开 `/m` | Windows 和 iPhone 不在同一个 Wi-Fi | 检查两边 Wi-Fi 名称,关掉手机蜂窝网络再试 |
|
|
200
|
+
| iPhone 打不开 `/m` | 地址里写了 `127.0.0.1` | 换成面板显示的 Windows 局域网 IP |
|
|
201
|
+
| 页面能开,但提示 401 | 打开的是 `/` 而不是 `/m` | 先打开 `/m` 配对,再进入 |
|
|
202
|
+
| 提示“配对码已过期” | 8 位码超过 10 分钟 | 在 Windows 面板点“刷新配对码”,用新码重试 |
|
|
203
|
+
| 提示“已锁定” | 连续输错次数太多 | 在 Windows 面板刷新配对码,不会自动解锁 |
|
|
204
|
+
| `3081` 被占用 | 其他软件占用了端口 | 插件会自动尝试后面 10 个端口,以面板显示为准 |
|
|
205
|
+
| 电脑睡眠后手机打不开 | DSH 随电脑睡眠暂停 | 唤醒 Windows,确认 DSH 窗口仍在运行 |
|
|
206
|
+
| 公司/学校 Wi-Fi 打不开 | 路由器开了“客户端隔离” | 换家庭 Wi-Fi,或用手机热点让两边连同一个热点 |
|
|
207
|
+
| 装了 VPN 后打不开 | VPN 把局域网路由改走 | 临时关闭 VPN,或让 VPN 放行局域网 |
|
|
208
|
+
|
|
209
|
+
### Windows 和 Mac 有什么区别
|
|
210
|
+
|
|
211
|
+
| 项目 | Mac | Windows 10/11 |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| 安装命令 | 同一条 | 同一条 |
|
|
214
|
+
| 终端 | “终端” App | PowerShell |
|
|
215
|
+
| 刷新 DSH | `Command + R` | `Ctrl + R` |
|
|
216
|
+
| 第一次防火墙 | 通常没有 | 允许 Node.js 访问“专用网络” |
|
|
217
|
+
| 回环网关 | 自动使用 `3081` | 自动使用 `3081` |
|
|
218
|
+
| iPhone 端操作 | 一样 | 一样 |
|
|
219
|
+
| Mac/Windows 原有使用 | 不变 | 不变 |
|
|
220
|
+
|
|
221
|
+
> Windows 的自动化构建和 33 项测试已在 GitHub 的 Windows CI 上通过。
|
|
222
|
+
> 真正挡住小白的通常不是代码,而是“防火墙点了拒绝”或“Wi-Fi 被设成公用网络”。
|
|
223
|
+
|
|
59
224
|
## 适合谁
|
|
60
225
|
|
|
61
|
-
- Mac
|
|
62
|
-
-
|
|
226
|
+
- **Mac 或 Windows 10/11** 电脑和 iPhone / iPad 在**同一个 Wi-Fi / 局域网**;
|
|
227
|
+
- 电脑上运行的是官方 DeepSeek Harness Web(Mac / Windows 均可);
|
|
63
228
|
- 希望手机用 Safari 完整使用 DSH,而不是只看一个 401 错误页。
|
|
64
229
|
|
|
65
230
|
## 不适合谁
|
|
66
231
|
|
|
67
|
-
- 想在**外网**(不在同一个 Wi-Fi
|
|
68
|
-
-
|
|
232
|
+
- 想在**外网**(不在同一个 Wi-Fi)访问家里的电脑:这不是 VPN / 内网穿透工具,请不要把 DSH 直接暴露到公网。
|
|
233
|
+
- 电脑上的 DSH 只监听 `127.0.0.1`:`1.1.0` 起插件会**自动**在 `3081` 起一个只做转发的局域网网关,电脑仍用 `127.0.0.1`,不需要改 Host 配置,也不需要把 DSH 暴露到公网。
|
|
69
234
|
- 使用 HTTPS 但证书不被 iPhone 信任:Safari 会先拦截证书,这和插件无关。
|
|
70
235
|
|
|
71
236
|
---
|
|
72
237
|
|
|
73
|
-
## 安装(Mac
|
|
238
|
+
## 安装(Mac 或 Windows 操作,复制即可)
|
|
74
239
|
|
|
75
|
-
### 第 1
|
|
240
|
+
### 第 1 步:确认电脑和 iPhone 连的是同一个 Wi-Fi
|
|
76
241
|
|
|
77
|
-
iPhone 打开「设置 →
|
|
242
|
+
iPhone 打开「设置 → 无线局域网」,确认和电脑连的是同一个网络。
|
|
78
243
|
|
|
79
|
-
### 第 2 步:在 Mac
|
|
244
|
+
### 第 2 步:在 Mac 终端或 Windows PowerShell 执行一条命令
|
|
80
245
|
|
|
81
246
|
```bash
|
|
82
|
-
dsh plugin --profile web add dsh-mobile-access@1.1.
|
|
247
|
+
dsh plugin --profile web add dsh-mobile-access@1.1.8 --registry=https://registry.npmjs.org/
|
|
83
248
|
```
|
|
84
249
|
|
|
85
250
|
这条命令做三件事:
|
|
@@ -90,15 +255,16 @@ dsh plugin --profile web add dsh-mobile-access@1.1.6 --registry=https://registry
|
|
|
90
255
|
|
|
91
256
|
> `--profile web` 里的 `web` 是本项目使用的 profile 名称。
|
|
92
257
|
> 如果你的 DSH 用的是其他 profile,把 `web` 换成实际名称即可。
|
|
93
|
-
> 不确定时,照抄这条命令;如果提示 profile 不存在,再检查你平时启动 DSH 用的名称。
|
|
258
|
+
> 不确定时,照抄这条命令;如果提示 profile 不存在,再检查你平时启动 DSH 用的名称。
|
|
259
|
+
> Windows PowerShell 里也是这一条命令,不需要改引号。
|
|
94
260
|
|
|
95
261
|
### 如果 DSH 只监听 `127.0.0.1`
|
|
96
262
|
|
|
97
263
|
从 `1.1.0` 开始不需要改 DSH 配置。插件检测到回环监听后,会自动在
|
|
98
264
|
`3081` 起一个只做转发的局域网网关:
|
|
99
265
|
|
|
100
|
-
-
|
|
101
|
-
- iPhone 使用面板上显示的 `http
|
|
266
|
+
- 电脑继续使用原来的 `http://127.0.0.1:3080`,行为不变;
|
|
267
|
+
- iPhone 使用面板上显示的 `http://<电脑局域网 IP>:3081/m`;
|
|
102
268
|
- 网关会同时转发 DSH 的 `/api/remote.mux` WebSocket,聊天流不受影响;
|
|
103
269
|
- 如果 `3081` 被占用,会自动尝试后 10 个端口,面板会显示实际地址。
|
|
104
270
|
|
|
@@ -108,10 +274,11 @@ Node.js 访问网络,请勾选“专用网络”并允许;如果误点了拒
|
|
|
108
274
|
|
|
109
275
|
### 第 3 步:刷新 DSH 页面
|
|
110
276
|
|
|
111
|
-
在
|
|
277
|
+
在 DSH 页面按:
|
|
112
278
|
|
|
113
279
|
```text
|
|
114
|
-
Command + R
|
|
280
|
+
Mac:Command + R
|
|
281
|
+
Windows:Ctrl + R
|
|
115
282
|
```
|
|
116
283
|
|
|
117
284
|
刷新后,左侧栏应该出现一个**手机图标**,名字叫「手机访问」。
|
|
@@ -129,9 +296,9 @@ dsh plugin --profile web why dsh-mobile-access
|
|
|
129
296
|
|
|
130
297
|
---
|
|
131
298
|
|
|
132
|
-
##
|
|
299
|
+
## 配对(电脑 + iPhone 一起操作)
|
|
133
300
|
|
|
134
|
-
### Mac
|
|
301
|
+
### 电脑端(Mac 或 Windows)
|
|
135
302
|
|
|
136
303
|
1. 打开 DSH Web;
|
|
137
304
|
2. 点左侧栏的「手机访问」;
|
|
@@ -144,7 +311,7 @@ dsh plugin --profile web why dsh-mobile-access
|
|
|
144
311
|
### iPhone 端
|
|
145
312
|
|
|
146
313
|
1. 打开 Safari;
|
|
147
|
-
2.
|
|
314
|
+
2. 输入电脑面板上显示的 `/m` 地址(不要输成 `127.0.0.1`,也不要用 `localhost`);
|
|
148
315
|
3. 输入 8 位配对码;
|
|
149
316
|
4. 点「连接 DSH」;
|
|
150
317
|
5. 成功后会直接进入完整 DSH Web。
|
|
@@ -167,11 +334,11 @@ Safari 分享按钮 → 添加到主屏幕
|
|
|
167
334
|
- 要发送时,点输入框右侧的**发送按钮**;
|
|
168
335
|
- 这样可以正常输入多行提示词。
|
|
169
336
|
|
|
170
|
-
###
|
|
337
|
+
### 电脑上有没有变化
|
|
171
338
|
|
|
172
339
|
没有。
|
|
173
340
|
|
|
174
|
-
- Mac 上仍然是:**Enter 发送,Shift+Enter 换行**;
|
|
341
|
+
- Mac 和 Windows 上仍然是:**Enter 发送,Shift+Enter 换行**;
|
|
175
342
|
- 插件只在 iPhone / iPad 上安装「Return 换行」监听,Mac 和非 Apple 平台不会安装;
|
|
176
343
|
- 左边栏只多一个「手机访问」入口,其他入口和面板保持原样。
|
|
177
344
|
|
|
@@ -190,18 +357,28 @@ iOS Safari 会把第一次触摸先用去聚焦,第二次才派发 `click`。
|
|
|
190
357
|
如果左侧栏仍然是窄窄的图标条,第一下是展开侧栏——这是官方布局,不是会话行的问题;
|
|
191
358
|
展开后每个会话只需单击一次。
|
|
192
359
|
|
|
193
|
-
###
|
|
360
|
+
### 切换会话后键盘不再自动弹出
|
|
361
|
+
|
|
362
|
+
从 `1.1.8` 起,iPhone / iPad 上切换会话时,插件会先收起当前输入框的键盘:
|
|
363
|
+
旧输入框如果还保持焦点,会立即失焦;会话切换后的 800 毫秒内,如果页面又尝试
|
|
364
|
+
用程序方式把焦点塞回输入框,插件也会把它收起。只有你亲手点一下输入框,键盘才会
|
|
365
|
+
再次出现。这样切换会话后不会莫名其妙弹出输入法,语音输入和正常打字都不受影响。
|
|
366
|
+
Mac 和 Windows 桌面不安装这条 iOS 专属逻辑,行为保持原样。
|
|
367
|
+
|
|
368
|
+
### iPhone 上展开 / 收起左侧栏
|
|
194
369
|
|
|
195
|
-
从 `1.1.
|
|
370
|
+
从 `1.1.7` 起,iPhone / iPad 上支持:
|
|
196
371
|
|
|
197
372
|
- 在对话内容区**向右滑**:展开左侧栏(仅在左侧栏已收起时生效);
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
|
|
373
|
+
- 在对话内容区**单击一下**:收起已经展开的左侧栏;
|
|
374
|
+
- 右滑需要水平距离至少 70px,且明显大于上下移动,避免和滚动、
|
|
375
|
+
横向代码块、输入框、模型菜单和右侧面板冲突;
|
|
376
|
+
- 单击收起只对“内容区”生效,点按钮、输入框、链接、代码块、会话行、
|
|
377
|
+
模型菜单或右侧面板不会误收起。
|
|
201
378
|
|
|
202
|
-
普通 Safari
|
|
379
|
+
普通 Safari 标签页里,右滑会避开屏幕左右各 24px 的边缘,以免和 Safari
|
|
203
380
|
自带的“后退 / 前进”边缘手势抢事件;添加到主屏幕的独立窗口里没有这个
|
|
204
|
-
系统手势,因此边缘也可以滑动。Mac
|
|
381
|
+
系统手势,因此边缘也可以滑动。Mac 和 Windows 桌面没有安装这些 iOS 专属触摸处理,行为保持原样。
|
|
205
382
|
|
|
206
383
|
`1.1.3` 的旧实现只移除 `tabindex`,在部分 iOS Safari 版本上仍可能被第一次触摸吃掉;
|
|
207
384
|
如果更新到 `1.1.4` 后仍是两次,请彻底关闭这个 Safari 标签页再重新打开(不要只刷新),
|
|
@@ -217,7 +394,7 @@ iOS Safari 会把第一次触摸先用去聚焦,第二次才派发 `click`。
|
|
|
217
394
|
- 打开右下角模型选择时,插件不会让模型搜索框自动抢焦点,所以键盘不会自动
|
|
218
395
|
弹出;需要搜索时,点一下搜索框再输入即可;
|
|
219
396
|
- 如果提示词键盘本来就是打开的,点模型入口时会先收起键盘;
|
|
220
|
-
- Mac
|
|
397
|
+
- 电脑端(Mac / Windows)和非 Apple 平台不安装这些 iOS 专属处理,行为保持原样。
|
|
221
398
|
|
|
222
399
|
如果页面已经处于放大状态,先点 Safari 地址栏刷新一次,或双击页面空白处
|
|
223
400
|
复位,再重新打开模型选择即可。
|
|
@@ -226,7 +403,7 @@ iOS Safari 会把第一次触摸先用去聚焦,第二次才派发 `click`。
|
|
|
226
403
|
|
|
227
404
|
配对码默认 **10 分钟**有效。过期后:
|
|
228
405
|
|
|
229
|
-
1.
|
|
406
|
+
1. 回到电脑的「手机访问」面板;
|
|
230
407
|
2. 点「刷新配对码」;
|
|
231
408
|
3. iPhone 重新打开 `/m`,输入新码。
|
|
232
409
|
|
|
@@ -267,14 +444,14 @@ iOS Safari 会把第一次触摸先用去聚焦,第二次才派发 `click`。
|
|
|
267
444
|
|
|
268
445
|
按顺序检查:
|
|
269
446
|
|
|
270
|
-
1. iPhone
|
|
271
|
-
2.
|
|
272
|
-
3.
|
|
447
|
+
1. iPhone 和电脑是不是同一个 Wi-Fi;
|
|
448
|
+
2. 地址是不是电脑面板上显示的**完整地址**(回环监听时通常是 `:3081`,局域网监听时可能是 `:3080`);
|
|
449
|
+
3. 电脑面板的「局域网网关」是否显示「已开启」;如果显示错误,按提示检查 Windows 防火墙或端口占用;
|
|
273
450
|
4. 路由器是否开启了「客户端隔离 / AP 隔离」(开启后同一 Wi-Fi 的设备也不能互访)。
|
|
274
451
|
|
|
275
452
|
### 2. 左边栏没有「手机访问」
|
|
276
453
|
|
|
277
|
-
- 先刷新 DSH
|
|
454
|
+
- 先刷新 DSH 页面(Mac 按 `Command + R`,Windows 按 `Ctrl + R`);
|
|
278
455
|
- 再确认安装命令没有报错;
|
|
279
456
|
- 检查 `dsh plugin --profile web why dsh-mobile-access`;
|
|
280
457
|
- 如果刚安装完仍没有,按插件管理器提示判断是否需要手动重启 DSH。
|
|
@@ -282,11 +459,11 @@ iOS Safari 会把第一次触摸先用去聚焦,第二次才派发 `click`。
|
|
|
282
459
|
### 3. 输入配对码后提示「已锁定」
|
|
283
460
|
|
|
284
461
|
这是安全设计,不会自动解锁。
|
|
285
|
-
|
|
462
|
+
在电脑面板点「刷新配对码」,然后用新码重试。
|
|
286
463
|
|
|
287
464
|
### 4. 提示「请求未通过 Host 或来源校验」
|
|
288
465
|
|
|
289
|
-
-
|
|
466
|
+
- 确认你访问的是电脑面板上显示的完整地址(包括实际端口,回环网关通常是 `:3081`);
|
|
290
467
|
- 不要用另一个域名 / IP 混着访问;
|
|
291
468
|
- 升级到 `1.1.0` 及以上版本。`1.1.0` 同时修复早期 iOS Safari 表单提交被误判的问题,并加入回环网关。
|
|
292
469
|
|
|
@@ -300,13 +477,14 @@ iOS Safari 会把第一次触摸先用去聚焦,第二次才派发 `click`。
|
|
|
300
477
|
|
|
301
478
|
- 插件在 iOS 的普通 HTTP 页面上提供剪贴板回退;
|
|
302
479
|
- 如果仍然失败,长按文字手动复制;
|
|
303
|
-
-
|
|
480
|
+
- 电脑端不受这个回退影响。
|
|
304
481
|
|
|
305
|
-
### 7.
|
|
482
|
+
### 7. 安卓手机 / Windows 电脑上的浏览器能用吗
|
|
306
483
|
|
|
307
|
-
-
|
|
484
|
+
- 配对页 `/m` 对现代浏览器通用;
|
|
308
485
|
- 左侧栏面板在所有 DSH Web 客户端显示;
|
|
309
|
-
-
|
|
486
|
+
- iOS 专属补丁(Return 换行、防自动放大、侧栏手势)只在 iPhone / iPad 上安装,安卓和 Windows 浏览器不会启用这些补丁;
|
|
487
|
+
- 在 Windows 电脑本机浏览器里打开 DSH,与在 Mac 本机浏览器里打开一样,原有行为不变。
|
|
310
488
|
|
|
311
489
|
---
|
|
312
490
|
|
|
@@ -314,13 +492,13 @@ iOS Safari 会把第一次触摸先用去聚焦,第二次才派发 `click`。
|
|
|
314
492
|
|
|
315
493
|
```text
|
|
316
494
|
┌─────────────────────────────┐
|
|
317
|
-
│
|
|
495
|
+
│ 电脑上的 DSH Web │
|
|
318
496
|
│ │
|
|
319
497
|
│ host 插件 │
|
|
320
498
|
│ ├─ GET /m │ 配对页:短地址 + 8 位码
|
|
321
499
|
│ ├─ POST /m │ 校验 nonce/码,签发 Cookie
|
|
322
|
-
│ ├─ GET /api/.../state │
|
|
323
|
-
│ └─ POST /api/.../rotate │
|
|
500
|
+
│ ├─ GET /api/.../state │ 仅电脑面板可读
|
|
501
|
+
│ └─ POST /api/.../rotate │ 仅电脑面板可刷新
|
|
324
502
|
│ │
|
|
325
503
|
│ client 插件 │
|
|
326
504
|
│ └─ 左侧栏「手机访问」 │
|
|
@@ -383,7 +561,7 @@ iPhone 软键盘没有可用的 Shift 键,因此插件在 **iOS / iPadOS** 上
|
|
|
383
561
|
### 推荐:npm 官方命令
|
|
384
562
|
|
|
385
563
|
```bash
|
|
386
|
-
dsh plugin --profile web add dsh-mobile-access@1.1.
|
|
564
|
+
dsh plugin --profile web add dsh-mobile-access@1.1.8 --registry=https://registry.npmjs.org/
|
|
387
565
|
```
|
|
388
566
|
|
|
389
567
|
### 使用 `@latest`
|
|
@@ -513,7 +691,9 @@ dsh plugin --profile web remove dsh-mobile-access
|
|
|
513
691
|
- `1.1.0` — 回环监听自动局域网网关(macOS / Windows,HTTP + `/api/remote.mux` WebSocket)。
|
|
514
692
|
- `1.1.1` — iPhone / iPad 上模型选择页 16px 缩放守卫。
|
|
515
693
|
- `1.1.2` — 打开模型选择时不自动弹键盘、不整体上移。
|
|
516
|
-
- `1.1.
|
|
694
|
+
- `1.1.8` — iPhone / iPad 上切换会话后不再自动弹出输入法键盘;当前 npm `latest`。
|
|
695
|
+
- `1.1.7` — iPhone / iPad 上右滑展开左侧栏,点击内容区收起。
|
|
696
|
+
- `1.1.6` — iPhone / iPad 上右滑展开、左滑收起左侧栏。
|
|
517
697
|
- `1.1.5` — Tailscale Serve 经 HTTPS 访问时,允许带 Tailscale 身份头的 `*.ts.net` Host(仅限本机回环转发)。
|
|
518
698
|
- `1.1.4` — iPhone / iPad 上第一次点击直接切换会话,并在切换后自动收起左侧栏。
|
|
519
699
|
- `1.1.3` — 首次尝试临时移除会话行 `tabindex`。
|
package/client.js
CHANGED
|
@@ -202,7 +202,39 @@ function installIOSSidebarTapGuard(layout) {
|
|
|
202
202
|
let pending = null;
|
|
203
203
|
let syntheticClick = false;
|
|
204
204
|
let suppressNativeClick = false;
|
|
205
|
+
let suppressComposerFocusUntil = 0;
|
|
206
|
+
let allowComposerFocus = false;
|
|
207
|
+
let composerFocusTimer;
|
|
205
208
|
const isAction = (target) => target.closest('button, a, input, textarea, select, [role="button"], [role="menuitem"], [role="menuitemradio"], [role="switch"], [data-row-actions]') !== null;
|
|
209
|
+
const isComposer = (target) => {
|
|
210
|
+
const element = target;
|
|
211
|
+
return element !== null
|
|
212
|
+
&& typeof element.closest === 'function'
|
|
213
|
+
&& element.closest('[data-composer-input], [data-composer-card] [contenteditable="true"]') !== null;
|
|
214
|
+
};
|
|
215
|
+
const blurActive = () => {
|
|
216
|
+
const active = doc.activeElement;
|
|
217
|
+
try {
|
|
218
|
+
active?.blur?.();
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const onFocusIn = (event) => {
|
|
224
|
+
if (Date.now() >= suppressComposerFocusUntil)
|
|
225
|
+
return;
|
|
226
|
+
if (!isComposer(event.target))
|
|
227
|
+
return;
|
|
228
|
+
if (allowComposerFocus) {
|
|
229
|
+
allowComposerFocus = false;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
event.target.blur();
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
}
|
|
237
|
+
};
|
|
206
238
|
const point = (event) => {
|
|
207
239
|
const touches = event.changedTouches ?? event.touches;
|
|
208
240
|
const touch = touches?.[0];
|
|
@@ -212,6 +244,15 @@ function installIOSSidebarTapGuard(layout) {
|
|
|
212
244
|
const target = event.target;
|
|
213
245
|
if (target === null || typeof target.closest !== 'function')
|
|
214
246
|
return;
|
|
247
|
+
if (isComposer(target)) {
|
|
248
|
+
allowComposerFocus = true;
|
|
249
|
+
if (composerFocusTimer !== undefined)
|
|
250
|
+
window.clearTimeout(composerFocusTimer);
|
|
251
|
+
composerFocusTimer = window.setTimeout(() => {
|
|
252
|
+
composerFocusTimer = undefined;
|
|
253
|
+
allowComposerFocus = false;
|
|
254
|
+
}, 500);
|
|
255
|
+
}
|
|
215
256
|
if (isAction(target)) {
|
|
216
257
|
pending = null;
|
|
217
258
|
return;
|
|
@@ -261,6 +302,9 @@ function installIOSSidebarTapGuard(layout) {
|
|
|
261
302
|
return;
|
|
262
303
|
if (Math.hypot(at.x - x, at.y - y) > 10 || Date.now() - startedAt > 600)
|
|
263
304
|
return;
|
|
305
|
+
suppressComposerFocusUntil = Date.now() + 800;
|
|
306
|
+
allowComposerFocus = false;
|
|
307
|
+
blurActive();
|
|
264
308
|
syntheticClick = true;
|
|
265
309
|
row.click();
|
|
266
310
|
const timer = window.setTimeout(() => {
|
|
@@ -282,22 +326,28 @@ function installIOSSidebarTapGuard(layout) {
|
|
|
282
326
|
doc.addEventListener('touchstart', onTouchStart, true);
|
|
283
327
|
doc.addEventListener('touchend', onTouchEnd, true);
|
|
284
328
|
doc.addEventListener('click', onClickCapture, true);
|
|
329
|
+
doc.addEventListener('focusin', onFocusIn, true);
|
|
285
330
|
return () => {
|
|
286
331
|
doc.removeEventListener('touchstart', onTouchStart, true);
|
|
287
332
|
doc.removeEventListener('touchend', onTouchEnd, true);
|
|
288
333
|
doc.removeEventListener('click', onClickCapture, true);
|
|
334
|
+
doc.removeEventListener('focusin', onFocusIn, true);
|
|
335
|
+
if (composerFocusTimer !== undefined)
|
|
336
|
+
window.clearTimeout(composerFocusTimer);
|
|
289
337
|
for (const timer of timers)
|
|
290
338
|
window.clearTimeout(timer);
|
|
291
339
|
timers.clear();
|
|
292
340
|
};
|
|
293
341
|
}
|
|
294
342
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
343
|
+
* Right swipe on iPhone opens the left sidebar. The closing gesture is a tap
|
|
344
|
+
* on the content area (not a left swipe): a normal tap is easier to discover
|
|
345
|
+
* and does not compete with horizontal scrolling. The swipe is scoped to the
|
|
346
|
+
* content area and ignores vertical movement, editable controls, menus, code
|
|
347
|
+
* blocks and the right panel. On a normal Safari tab we stay 24px away from the
|
|
348
|
+
* screen edges so the system back/forward gesture keeps priority; an installed
|
|
349
|
+
* PWA has no such gesture, so edge starts are allowed there. Mac never installs
|
|
350
|
+
* this listener.
|
|
301
351
|
*/
|
|
302
352
|
function installIOSSidebarSwipeGuard(layout) {
|
|
303
353
|
if (!isAppleMobile() || layout === undefined)
|
|
@@ -341,7 +391,7 @@ function installIOSSidebarSwipeGuard(layout) {
|
|
|
341
391
|
if (Math.abs(dx) < 70 || Math.abs(dx) <= Math.abs(dy) * 1.5)
|
|
342
392
|
return;
|
|
343
393
|
const collapsed = doc.querySelector('[data-sidebar-collapsed]') !== null;
|
|
344
|
-
if (
|
|
394
|
+
if (!collapsed)
|
|
345
395
|
return;
|
|
346
396
|
try {
|
|
347
397
|
layout.toggleSidebar();
|
|
@@ -356,6 +406,41 @@ function installIOSSidebarSwipeGuard(layout) {
|
|
|
356
406
|
doc.removeEventListener('touchend', onTouchEnd, true);
|
|
357
407
|
};
|
|
358
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* A tap on the conversation content closes an expanded narrow sidebar. It has
|
|
411
|
+
* to run in the bubble phase so message links and buttons still receive their
|
|
412
|
+
* click first, and it skips every interactive/row/right-panel target. The
|
|
413
|
+
* sidebar width comes from the frame's inline grid template, so a tap on the
|
|
414
|
+
* left column is not mistaken for content. Mac and wide layouts are untouched.
|
|
415
|
+
*/
|
|
416
|
+
function installIOSContentTapCollapse(layout) {
|
|
417
|
+
if (!isAppleMobile() || layout === undefined)
|
|
418
|
+
return () => undefined;
|
|
419
|
+
const doc = document;
|
|
420
|
+
const interactive = 'button, a, input, textarea, select, pre, code, [contenteditable="true"], [data-composer-card], [data-row-key], [role="treeitem"], [role="tree"], nav, [role="menu"], [role="listbox"], [role="button"], [role="menuitem"], [data-rightbar]';
|
|
421
|
+
const onClick = (event) => {
|
|
422
|
+
if (window.innerWidth >= 1024)
|
|
423
|
+
return;
|
|
424
|
+
if (doc.querySelector('[data-sidebar-collapsed]') !== null)
|
|
425
|
+
return;
|
|
426
|
+
const target = event.target;
|
|
427
|
+
if (target === null || typeof target.closest !== 'function' || target.closest(interactive) !== null)
|
|
428
|
+
return;
|
|
429
|
+
const frame = doc.querySelector('[style*="grid-template-columns"]');
|
|
430
|
+
const first = frame?.style.gridTemplateColumns.split(' ')[0] ?? '';
|
|
431
|
+
const width = Number.parseFloat(first);
|
|
432
|
+
const x = event.clientX;
|
|
433
|
+
if (Number.isFinite(width) && x < width)
|
|
434
|
+
return;
|
|
435
|
+
try {
|
|
436
|
+
layout.toggleSidebar();
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
doc.addEventListener('click', onClick, true);
|
|
442
|
+
return () => doc.removeEventListener('click', onClick, true);
|
|
443
|
+
}
|
|
359
444
|
/**
|
|
360
445
|
* The official model menu can move focus into its search field while it opens.
|
|
361
446
|
* On iPhone that opens the software keyboard and shifts the fixed-position menu
|
|
@@ -708,6 +793,12 @@ function factory(require) {
|
|
|
708
793
|
catch (error) {
|
|
709
794
|
ctx.logger?.warn?.(`[mobile-access] iOS sidebar swipe guard unavailable: ${String(error)}`);
|
|
710
795
|
}
|
|
796
|
+
try {
|
|
797
|
+
cleanups.push(installIOSContentTapCollapse(ctx.layout));
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
ctx.logger?.warn?.(`[mobile-access] iOS content tap collapse unavailable: ${String(error)}`);
|
|
801
|
+
}
|
|
711
802
|
return () => {
|
|
712
803
|
for (const cleanup of cleanups.reverse()) {
|
|
713
804
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mobile-access",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "Independent DSH bundle that pairs iPhones and iPads with the official DeepSeek Harness Web GUI over the local network.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "node scripts/build.mjs",
|
|
43
43
|
"typecheck": "node scripts/build.mjs --check",
|
|
44
|
-
"test": "node scripts/build.mjs && node
|
|
44
|
+
"test": "node scripts/build.mjs && node scripts/test.mjs",
|
|
45
45
|
"verify": "bash scripts/verify.sh"
|
|
46
46
|
},
|
|
47
47
|
"dsh": {
|