dsh-pocket 2.1.1 → 2.1.3
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 +3 -1
- package/README.md +3 -1
- package/client/client.js +65 -8
- package/client/mobile/MobileNavOverlay.tsx +65 -14
- package/client/mobile/nav-targets.mjs +85 -0
- package/lib/index.js +12 -6
- package/lib/proxy.mjs +15 -7
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -191,9 +191,11 @@ Such tools take over all traffic and often cut cloudflared's tunnel-edge connect
|
|
|
191
191
|
```sh
|
|
192
192
|
npm install
|
|
193
193
|
node client/build.mjs # rebuild after editing client/
|
|
194
|
-
npm test # proxy / auth / compression / tunnel / service / RPC (
|
|
194
|
+
npm test # proxy / auth / compression / tunnel / service / RPC / settings (91 tests)
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
+
**Want to try your changes locally without publishing?** Point the installed plugin at your local checkout with a symlink and restart dsh web. Full steps (including switching back to the npm release) are in [LOCAL-DEV.md](./LOCAL-DEV.md).
|
|
198
|
+
|
|
197
199
|
## 🤝 Credits
|
|
198
200
|
|
|
199
201
|
- Mobile adaptation ported from [mexiaosqwq/dsh-web-mobile](https://github.com/mexiaosqwq/dsh-web-mobile) (MIT)
|
package/README.md
CHANGED
|
@@ -192,9 +192,11 @@ npx @deepseek-ai/dsh web
|
|
|
192
192
|
```sh
|
|
193
193
|
npm install
|
|
194
194
|
node client/build.mjs # 改 client/ 后重新打包
|
|
195
|
-
npm test # 代理 / 认证 / 压缩 / 隧道 / 服务 / RPC
|
|
195
|
+
npm test # 代理 / 认证 / 压缩 / 隧道 / 服务 / RPC / 设置(91 测试)
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
+
**改完想在本机先试?** 不用发版:把插件换成指向本地仓库的软链,重启 dsh web 就是本地代码。完整步骤(含怎么换回 npm 官方版本)见 [LOCAL-DEV.md](./LOCAL-DEV.md)。
|
|
199
|
+
|
|
198
200
|
## 🤝 致谢
|
|
199
201
|
|
|
200
202
|
- 移动端适配移植自 [mexiaosqwq/dsh-web-mobile](https://github.com/mexiaosqwq/dsh-web-mobile)(MIT)
|
package/client/client.js
CHANGED
|
@@ -140,6 +140,41 @@ function MobileNavToggle({ toggleSidebar, t }) {
|
|
|
140
140
|
// client/mobile/MobileNavOverlay.tsx
|
|
141
141
|
var import_react = require("react");
|
|
142
142
|
var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
143
|
+
|
|
144
|
+
// client/mobile/nav-targets.mjs
|
|
145
|
+
var DRAWER_SELECTOR = '[data-mobile-nav="frame"] > :first-child';
|
|
146
|
+
var TOGGLE_SELECTOR = '[data-mobile-nav="toggle"]';
|
|
147
|
+
var NAV_TARGETS = [
|
|
148
|
+
"button[data-dsh-taskboard-entry]",
|
|
149
|
+
"button[data-dsh-ssh-entry]",
|
|
150
|
+
// 抽屉底部的 "文件" 入口打开的是 dsh-web-ui 的 explorer 面板,它的 z-index
|
|
151
|
+
// (55) 低于展开的抽屉 (600),且在抽屉 DOM 之外:抽屉不关就会盖住面板,点
|
|
152
|
+
// 面板里的行又会被"点抽屉外就关"吃掉。所以按导航处理,一起关掉。
|
|
153
|
+
'[data-mobile-nav="files"]',
|
|
154
|
+
'[class*="sessionRow"]',
|
|
155
|
+
'[class*="newSession"]',
|
|
156
|
+
'[class*="searchResultWorkspace"]',
|
|
157
|
+
'[class*="searchResultRow"]'
|
|
158
|
+
].join(", ");
|
|
159
|
+
var NAV_EXCLUDE = '[class*="sessionRow"] button';
|
|
160
|
+
var OVERLAY_SELECTOR = [
|
|
161
|
+
'[role="menu"]',
|
|
162
|
+
'[role="listbox"]',
|
|
163
|
+
'[role="dialog"]',
|
|
164
|
+
'[role="tooltip"]',
|
|
165
|
+
"[data-radix-popper-content-wrapper]"
|
|
166
|
+
].join(", ");
|
|
167
|
+
function navTargetFor(target) {
|
|
168
|
+
if (target == null || typeof target.closest !== "function") return null;
|
|
169
|
+
if (target.closest(NAV_EXCLUDE) !== null) return null;
|
|
170
|
+
return target.closest(NAV_TARGETS);
|
|
171
|
+
}
|
|
172
|
+
function isOverlayTap(target) {
|
|
173
|
+
if (target == null || typeof target.closest !== "function") return false;
|
|
174
|
+
return target.closest(OVERLAY_SELECTOR) !== null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// client/mobile/MobileNavOverlay.tsx
|
|
143
178
|
var MOBILE_QUERY = "(max-width: 1023px)";
|
|
144
179
|
function useMobile() {
|
|
145
180
|
const [mobile, setMobile] = (0, import_react.useState)(() => window.matchMedia(MOBILE_QUERY).matches);
|
|
@@ -205,25 +240,47 @@ function MobileNavOverlay({ toggleSidebar, t }) {
|
|
|
205
240
|
if (document.querySelector('[aria-modal="true"]') !== null) return;
|
|
206
241
|
const target = event.target;
|
|
207
242
|
if (target === null) return;
|
|
208
|
-
const drawer = document.querySelector(
|
|
243
|
+
const drawer = document.querySelector(DRAWER_SELECTOR);
|
|
209
244
|
if (drawer === null || !drawer.contains(target)) return;
|
|
210
|
-
if (target
|
|
211
|
-
const navigates = target.closest(
|
|
212
|
-
'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [data-mobile-nav="files"]'
|
|
213
|
-
);
|
|
214
|
-
if (navigates !== null) toggleSidebar();
|
|
245
|
+
if (navTargetFor(target) !== null) toggleSidebar();
|
|
215
246
|
};
|
|
216
247
|
document.addEventListener("click", onDrawerClick, true);
|
|
217
248
|
return () => document.removeEventListener("click", onDrawerClick, true);
|
|
218
249
|
}, [mobile, open, toggleSidebar]);
|
|
250
|
+
(0, import_react.useEffect)(() => {
|
|
251
|
+
if (!mobile || !open) return;
|
|
252
|
+
let timer = null;
|
|
253
|
+
const onDrawerPointerUp = (event) => {
|
|
254
|
+
if (event.pointerType !== "touch" && event.pointerType !== "pen") return;
|
|
255
|
+
const target = event.target;
|
|
256
|
+
if (!(target instanceof Element)) return;
|
|
257
|
+
const drawer = document.querySelector(DRAWER_SELECTOR);
|
|
258
|
+
if (drawer === null || !drawer.contains(target)) return;
|
|
259
|
+
if (navTargetFor(target) === null) return;
|
|
260
|
+
if (timer !== null) return;
|
|
261
|
+
timer = window.setTimeout(() => {
|
|
262
|
+
timer = null;
|
|
263
|
+
const frame = document.querySelector('[data-mobile-nav="frame"]');
|
|
264
|
+
if (frame === null || frame.hasAttribute("data-sidebar-collapsed")) return;
|
|
265
|
+
const row = navTargetFor(target);
|
|
266
|
+
row?.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window }));
|
|
267
|
+
}, 0);
|
|
268
|
+
};
|
|
269
|
+
document.addEventListener("pointerup", onDrawerPointerUp, true);
|
|
270
|
+
return () => {
|
|
271
|
+
if (timer !== null) window.clearTimeout(timer);
|
|
272
|
+
document.removeEventListener("pointerup", onDrawerPointerUp, true);
|
|
273
|
+
};
|
|
274
|
+
}, [mobile, open]);
|
|
219
275
|
(0, import_react.useEffect)(() => {
|
|
220
276
|
if (!mobile || !open) return;
|
|
221
277
|
const onOutsideClick = (event) => {
|
|
222
278
|
if (document.querySelector('[aria-modal="true"]') !== null) return;
|
|
223
279
|
const target = event.target;
|
|
224
280
|
if (target === null) return;
|
|
225
|
-
if (target.closest(
|
|
226
|
-
|
|
281
|
+
if (target.closest(TOGGLE_SELECTOR) !== null) return;
|
|
282
|
+
if (isOverlayTap(target)) return;
|
|
283
|
+
const drawer = document.querySelector(DRAWER_SELECTOR);
|
|
227
284
|
if (drawer !== null && drawer.contains(target)) return;
|
|
228
285
|
toggleSidebar();
|
|
229
286
|
};
|
|
@@ -2,6 +2,12 @@ import { useEffect, useLayoutEffect, useState } from 'react'
|
|
|
2
2
|
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
3
3
|
import { IconPanelLeftOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
4
4
|
import { NS } from './locales.ts'
|
|
5
|
+
import {
|
|
6
|
+
DRAWER_SELECTOR,
|
|
7
|
+
TOGGLE_SELECTOR,
|
|
8
|
+
isOverlayTap,
|
|
9
|
+
navTargetFor,
|
|
10
|
+
} from './nav-targets.mjs'
|
|
5
11
|
|
|
6
12
|
/** Full props for the shell overlay entry. */
|
|
7
13
|
export interface MobileNavOverlayProps extends PropsRuntime<'shell.overlay'>, PropsLocale<typeof NS> {
|
|
@@ -104,7 +110,8 @@ export function MobileNavOverlay({ toggleSidebar, t }: MobileNavOverlayProps) {
|
|
|
104
110
|
// - Settings / Session log: their dialogs render INSIDE the drawer DOM
|
|
105
111
|
// (portaled into the sidebar); closing the drawer would slide the dialog
|
|
106
112
|
// off-screen with it.
|
|
107
|
-
// - Workspace folder
|
|
113
|
+
// - Workspace folder rows (YDXeBa_projectRow), the logo: pure UI toggles,
|
|
114
|
+
// not navigation — see NAV_TARGETS in nav-targets.mjs.
|
|
108
115
|
// - Anything while a modal dialog is open: the dialog owns the screen.
|
|
109
116
|
useEffect(() => {
|
|
110
117
|
if (!mobile || !open) return
|
|
@@ -112,27 +119,64 @@ export function MobileNavOverlay({ toggleSidebar, t }: MobileNavOverlayProps) {
|
|
|
112
119
|
if (document.querySelector('[aria-modal="true"]') !== null) return
|
|
113
120
|
const target = event.target as HTMLElement | null
|
|
114
121
|
if (target === null) return
|
|
115
|
-
const drawer = document.querySelector<HTMLElement>(
|
|
122
|
+
const drawer = document.querySelector<HTMLElement>(DRAWER_SELECTOR)
|
|
116
123
|
if (drawer === null || !drawer.contains(target)) return
|
|
117
124
|
// A session row's own action buttons — the "Session actions" kebab
|
|
118
125
|
// (delete / rename), revealed on hover / long-press — open an edit
|
|
119
126
|
// menu. Tapping one must NOT count as tapping the row, or the drawer
|
|
120
127
|
// would close and take the just-opened menu with it.
|
|
121
|
-
if (target
|
|
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.
|
|
127
|
-
const navigates = target.closest(
|
|
128
|
-
'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [data-mobile-nav="files"]',
|
|
129
|
-
)
|
|
130
|
-
if (navigates !== null) toggleSidebar()
|
|
128
|
+
if (navTargetFor(target) !== null) toggleSidebar()
|
|
131
129
|
}
|
|
132
130
|
document.addEventListener('click', onDrawerClick, true)
|
|
133
131
|
return () => document.removeEventListener('click', onDrawerClick, true)
|
|
134
132
|
}, [mobile, open, toggleSidebar])
|
|
135
133
|
|
|
134
|
+
// iOS Safari touch self-heal (issue #72).
|
|
135
|
+
//
|
|
136
|
+
// A tap on a drawer row is delivered to the page as touchstart/touchend
|
|
137
|
+
// plus a browser-synthesized click. On iOS that click is routinely
|
|
138
|
+
// suppressed: a few px of finger drift is classified as a pan, and any DOM
|
|
139
|
+
// shift under the finger before dispatch cancels it outright. When it does
|
|
140
|
+
// not arrive, neither the row's own onClick nor the capture handler above
|
|
141
|
+
// runs — the row looks completely dead ("抽屉点了没反应").
|
|
142
|
+
//
|
|
143
|
+
// So: on touch/pen pointerup inside the drawer that hits a navigation row,
|
|
144
|
+
// arm a one-macrotask timer. When it fires:
|
|
145
|
+
// - the drawer is already closed → the real click did arrive and handled
|
|
146
|
+
// everything → do nothing (zero interference with the normal path);
|
|
147
|
+
// - otherwise the click never came → re-dispatch a bubbling click on the
|
|
148
|
+
// row ourselves. React's delegated listener runs the row's onClick (the
|
|
149
|
+
// session opens / the workspace switches) and the same click bubbles
|
|
150
|
+
// through the capture handler above, which closes the drawer.
|
|
151
|
+
//
|
|
152
|
+
// Mouse/pen-on-desktop keeps the plain click path: pointerType is 'mouse'.
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (!mobile || !open) return
|
|
155
|
+
let timer: number | null = null
|
|
156
|
+
const onDrawerPointerUp = (event: PointerEvent): void => {
|
|
157
|
+
if (event.pointerType !== 'touch' && event.pointerType !== 'pen') return
|
|
158
|
+
const target = event.target
|
|
159
|
+
if (!(target instanceof Element)) return
|
|
160
|
+
const drawer = document.querySelector<HTMLElement>(DRAWER_SELECTOR)
|
|
161
|
+
if (drawer === null || !drawer.contains(target)) return
|
|
162
|
+
if (navTargetFor(target) === null) return
|
|
163
|
+
if (timer !== null) return
|
|
164
|
+
timer = window.setTimeout(() => {
|
|
165
|
+
timer = null
|
|
166
|
+
const frame = document.querySelector('[data-mobile-nav="frame"]')
|
|
167
|
+
// Real click already closed it — nothing to heal.
|
|
168
|
+
if (frame === null || frame.hasAttribute('data-sidebar-collapsed')) return
|
|
169
|
+
const row = navTargetFor(target)
|
|
170
|
+
row?.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }))
|
|
171
|
+
}, 0)
|
|
172
|
+
}
|
|
173
|
+
document.addEventListener('pointerup', onDrawerPointerUp, true)
|
|
174
|
+
return () => {
|
|
175
|
+
if (timer !== null) window.clearTimeout(timer)
|
|
176
|
+
document.removeEventListener('pointerup', onDrawerPointerUp, true)
|
|
177
|
+
}
|
|
178
|
+
}, [mobile, open])
|
|
179
|
+
|
|
136
180
|
// Tap-outside closes the drawer (issue #38). The backdrop is now
|
|
137
181
|
// pointer-events: none (pure dimming layer that never steals clicks), so
|
|
138
182
|
// "tap the dimmed area to close" moves here: any click outside the drawer
|
|
@@ -146,8 +190,15 @@ export function MobileNavOverlay({ toggleSidebar, t }: MobileNavOverlayProps) {
|
|
|
146
190
|
if (document.querySelector('[aria-modal="true"]') !== null) return
|
|
147
191
|
const target = event.target as HTMLElement | null
|
|
148
192
|
if (target === null) return
|
|
149
|
-
if (target.closest(
|
|
150
|
-
|
|
193
|
+
if (target.closest(TOGGLE_SELECTOR) !== null) return
|
|
194
|
+
// Portaled overlays (issue #72): the workspace section's "视图选项" /
|
|
195
|
+
// "添加工作区" and the session row kebab all open menus that live on
|
|
196
|
+
// document.body — outside the drawer DOM. Without this exemption the
|
|
197
|
+
// first tap on a menu item is eaten: the drawer slides away, the menu
|
|
198
|
+
// unmounts with the sidebar, and the item's onClick never runs, so
|
|
199
|
+
// every workspace control reads as "点了没反应" on a phone.
|
|
200
|
+
if (isOverlayTap(target)) return
|
|
201
|
+
const drawer = document.querySelector<HTMLElement>(DRAWER_SELECTOR)
|
|
151
202
|
if (drawer !== null && drawer.contains(target)) return
|
|
152
203
|
toggleSidebar()
|
|
153
204
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// 抽屉点击规则的判定常量与纯函数 —— 抽成独立 ESM 模块,好让 node 测试直接
|
|
2
|
+
// 引入(client 侧是 esbuild 打进 bundle 的,测试里没有 DOM 可用)。
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 抽屉本体:AppFrame 的第一个网格子元素,即被拉成抽屉的侧边栏列。
|
|
6
|
+
* @type {string}
|
|
7
|
+
*/
|
|
8
|
+
export const DRAWER_SELECTOR = '[data-mobile-nav="frame"] > :first-child'
|
|
9
|
+
|
|
10
|
+
/** 抽屉开关(会话头部那颗按钮)——点它不该被"点抽屉外就关"二次触发。 */
|
|
11
|
+
export const TOGGLE_SELECTOR = '[data-mobile-nav="toggle"]'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 命中即视为导航的行:点它们要把屏幕让给刚打开的内容,所以顺带关抽屉。
|
|
15
|
+
*
|
|
16
|
+
* 类名现状(2026-08,侧边栏 qDHVXG_* / YDXeBa_* 这一代):
|
|
17
|
+
* - `YDXeBa_sessionRow` 会话行 —— 命中 [class*="sessionRow"]
|
|
18
|
+
* - `YDXeBa_projectRow` 工作区行 —— **故意不收**:实测它是折叠/展开开关
|
|
19
|
+
* (aria-expanded 来回翻,会话列表显隐),不是导航。收进来会让每次展开
|
|
20
|
+
* 工作区都把抽屉关掉(issue #72)。
|
|
21
|
+
* 旧一代侧边栏的 `_searchResultWorkspace_` / `_searchResultRow_` 保留,兼容
|
|
22
|
+
* 尚未升级的宿主。
|
|
23
|
+
*
|
|
24
|
+
* @type {string}
|
|
25
|
+
*/
|
|
26
|
+
export const NAV_TARGETS = [
|
|
27
|
+
'button[data-dsh-taskboard-entry]',
|
|
28
|
+
'button[data-dsh-ssh-entry]',
|
|
29
|
+
// 抽屉底部的 "文件" 入口打开的是 dsh-web-ui 的 explorer 面板,它的 z-index
|
|
30
|
+
// (55) 低于展开的抽屉 (600),且在抽屉 DOM 之外:抽屉不关就会盖住面板,点
|
|
31
|
+
// 面板里的行又会被"点抽屉外就关"吃掉。所以按导航处理,一起关掉。
|
|
32
|
+
'[data-mobile-nav="files"]',
|
|
33
|
+
'[class*="sessionRow"]',
|
|
34
|
+
'[class*="newSession"]',
|
|
35
|
+
'[class*="searchResultWorkspace"]',
|
|
36
|
+
'[class*="searchResultRow"]',
|
|
37
|
+
].join(', ')
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 行内操作按钮(会话行的 kebab:重命名 / 删除)——点开的是菜单不是内容,
|
|
41
|
+
* 不能按导航处理,否则抽屉一关,刚弹出的菜单跟着没了。
|
|
42
|
+
* @type {string}
|
|
43
|
+
*/
|
|
44
|
+
export const NAV_EXCLUDE = '[class*="sessionRow"] button'
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 浮层(portal 到 body 的下拉菜单 / 弹窗)。
|
|
48
|
+
*
|
|
49
|
+
* 抽屉里的控件(工作区区块的"视图选项"、"添加工作区",会话行的 kebab…)
|
|
50
|
+
* 弹出的菜单在 DOM 上属于 body,不在抽屉里。"点抽屉外就关"因此会把菜单项
|
|
51
|
+
* 的第一下点击吃掉:抽屉先滑走 → 菜单随侧边栏卸载 → 菜单项的 onClick 永远
|
|
52
|
+
* 不执行。手机上表现就是工作区相关菜单"点了没反应"(issue #72)。
|
|
53
|
+
*
|
|
54
|
+
* 落在浮层内的点击一律豁免,交给浮层自己收尾。
|
|
55
|
+
*
|
|
56
|
+
* @type {string}
|
|
57
|
+
*/
|
|
58
|
+
export const OVERLAY_SELECTOR = [
|
|
59
|
+
'[role="menu"]',
|
|
60
|
+
'[role="listbox"]',
|
|
61
|
+
'[role="dialog"]',
|
|
62
|
+
'[role="tooltip"]',
|
|
63
|
+
'[data-radix-popper-content-wrapper]',
|
|
64
|
+
].join(', ')
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 抽屉内一次点击是否算导航(应关抽屉)。命中 NAV_EXCLUDE 的行内按钮不算。
|
|
68
|
+
* @param {Element | null | undefined} target - 点击目标。
|
|
69
|
+
* @returns {Element | null} 命中的导航行,未命中返回 null。
|
|
70
|
+
*/
|
|
71
|
+
export function navTargetFor(target) {
|
|
72
|
+
if (target == null || typeof target.closest !== 'function') return null
|
|
73
|
+
if (target.closest(NAV_EXCLUDE) !== null) return null
|
|
74
|
+
return target.closest(NAV_TARGETS)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 一次点击是否落在浮层内(浮层自己会关闭,不该由抽屉规则接管)。
|
|
79
|
+
* @param {Element | null | undefined} target - 点击目标。
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
*/
|
|
82
|
+
export function isOverlayTap(target) {
|
|
83
|
+
if (target == null || typeof target.closest !== 'function') return false
|
|
84
|
+
return target.closest(OVERLAY_SELECTOR) !== null
|
|
85
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
20
20
|
import { createPocketService } from './service.mjs';
|
|
21
21
|
import { installPocketRpc } from './web-rpc.js';
|
|
22
22
|
import { restartHost } from './restart.js';
|
|
23
|
-
import {
|
|
23
|
+
import { advancedNoticeScript, DEFAULT_INJECT, classifyHost } from './proxy.mjs';
|
|
24
24
|
import { lanEnabled, setLanEnabled, lanAuthEnabled, setLanAuthEnabled, lanIpOverride, setLanIpOverride, pinCustom, setPinCustom, tunnelMode, setTunnelMode, tunnelToken, setTunnelToken, tunnelHostname, setTunnelHostname, resetSettings } from './settings.mjs';
|
|
25
25
|
|
|
26
26
|
const name = 'dsh-pocket';
|
|
@@ -251,12 +251,18 @@ export function apply(ctx, config = {}, internals = {}) {
|
|
|
251
251
|
internals,
|
|
252
252
|
getLanIpOverride: () => lanIpOverride(),
|
|
253
253
|
getLanEnabled: () => lanEnabled(),
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
254
|
+
// 桌面端**不再**注入 dsh-desktop-* 标记(issue #76):
|
|
255
|
+
// - 旧版 dsh-plugin-desktop 缺 mode/platform 会抛错,当初正是为此加了补丁(issue #3/#4);
|
|
256
|
+
// - 但 2.0.3 起,mode 与 platform **同时缺失**时 parseDesktopClientEnvironment 直接返回
|
|
257
|
+
// undefined(视作非桌面外壳,跳过全部桌面逻辑),正是手机页需要的效果;
|
|
258
|
+
// - 反之只要 URL 上出现任一 dsh-desktop-* 标记,客户端就会强制校验整组
|
|
259
|
+
// (material + semver version + mica),只补两个必然抛 "invalid or missing
|
|
260
|
+
// dsh-desktop-material" → 插件树加载失败 → 页面变成「打开恢复模式」;
|
|
261
|
+
// - 更糟的是 decideDesktopBrowserAccess 见到 dsh-desktop-* 前缀就把没有渲染器 token 的
|
|
262
|
+
// 普通浏览器判为 denied(403),刷新后直接打不开。
|
|
263
|
+
// 结论:手机/浏览器访问一律不带这些标记,advanced 模式另加警告覆盖层(issue #19)。
|
|
258
264
|
injectHtml: isDesktop
|
|
259
|
-
? DEFAULT_INJECT +
|
|
265
|
+
? DEFAULT_INJECT + (desktopAdvanced ? advancedNoticeScript() : '')
|
|
260
266
|
: undefined,
|
|
261
267
|
// 访问密码(issue #13 + #18 + #24 + #33 + #66):公网永远要密码(默认每次开启变新,
|
|
262
268
|
// 用户自定义后不再轮换);局域网按开关(默认开启;关闭后局域网扫码直连)。
|
package/lib/proxy.mjs
CHANGED
|
@@ -38,15 +38,23 @@ export const RANDOM_UUID_POLYFILL = `<script data-dsh-pocket-polyfill="1">!funct
|
|
|
38
38
|
const INJECT_MARK = 'data-dsh-pocket-polyfill="1"';
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* DSH Desktop
|
|
41
|
+
* DSH Desktop(桌面版)渲染进程兼容补丁(issue #3/#4,已于 issue #76 停用)。
|
|
42
42
|
*
|
|
43
|
-
*
|
|
43
|
+
* 历史:旧版 dsh-plugin-desktop 的 client 在页面加载时从 URL query 读
|
|
44
44
|
* `dsh-desktop-mode` 与 `dsh-desktop-platform`,缺失即抛
|
|
45
|
-
* "invalid or missing dsh-desktop-mode null" →
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
45
|
+
* "invalid or missing dsh-desktop-mode null" → 页面崩(手机扫码访问桌面版时正是如此)。
|
|
46
|
+
* 本脚本用 history.replaceState 补上这两个参数(无跳转、不重载),取最轻的
|
|
47
|
+
* `compatibility` 模式——不激活桌面布局,避免与移动端适配叠加。
|
|
48
|
+
*
|
|
49
|
+
* @deprecated 不要再注入(issue #76,DSH Desktop 2.0.3 起):
|
|
50
|
+
* ① mode 与 platform **同时缺失**时,parseDesktopClientEnvironment 直接返回 undefined
|
|
51
|
+
* (视作非桌面外壳,跳过全部桌面逻辑),正是手机/浏览器页面需要的效果;
|
|
52
|
+
* ② 只要 URL 上出现任一 dsh-desktop-* 标记,客户端就强制校验整组(material +
|
|
53
|
+
* semver version + mica),只补两个必然抛 "invalid or missing
|
|
54
|
+
* dsh-desktop-material" → 插件树加载失败 → 页面变成「打开恢复模式」;
|
|
55
|
+
* ③ 更糟:decideDesktopBrowserAccess 见到 dsh-desktop-* 前缀就把没有渲染器 token 的
|
|
56
|
+
* 普通浏览器判为 denied(403),刷新后直接打不开。
|
|
57
|
+
* lib/index.js 已不再注入本脚本;保留导出仅为兼容旧版本桌面端与既有测试。
|
|
50
58
|
*/
|
|
51
59
|
export function desktopEnvPatchScript(platform) {
|
|
52
60
|
const p = ['darwin', 'win32', 'linux'].includes(platform) ? platform : 'linux';
|
package/package.json
CHANGED