dsh-mobile 0.6.16 → 0.6.18
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/CHANGELOG.md +34 -10
- package/CONTRIBUTORS.md +28 -10
- package/README.en.md +16 -10
- package/README.md +15 -10
- package/SECURITY.md +1 -1
- package/cordis.patch.yml +6 -17
- package/docs/ATTACH_EXISTING_FRPS.en.md +32 -2
- package/docs/ATTACH_EXISTING_FRPS.md +2 -2
- package/docs/CLIENT_MODULES.en.md +10 -2
- package/docs/CLIENT_MODULES.md +10 -2
- package/docs/MOBILE_PLUGIN_ADAPTATION.en.md +188 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.md +188 -0
- package/docs/README.md +8 -0
- package/docs/SELF_HOSTED_ORIGIN.en.md +1 -1
- package/docs/SELF_HOSTED_ORIGIN.md +2 -2
- package/docs/SLOW_CONNECTIONS.en.md +13 -3
- package/docs/SLOW_CONNECTIONS.md +13 -3
- package/docs/TRUSTED_NETWORKS.en.md +17 -0
- package/docs/TRUSTED_NETWORKS.md +17 -0
- package/lib/cli.js.map +1 -1
- package/lib/client.js +1310 -31
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +53 -1
- package/lib/index.mjs +686 -56
- package/lib/index.mjs.map +1 -1
- package/package.json +6 -1
- package/packages/question-fixes/package.json +1 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Mobile plugin adaptation guide
|
|
2
|
+
|
|
3
|
+
[中文](MOBILE_PLUGIN_ADAPTATION.md) · [Documentation index](README.md)
|
|
4
|
+
|
|
5
|
+
This guide is for DSH plugin authors, aiming for **usable small screens without desktop regressions**. DSH Mobile provides connectivity, page layout and shared entry points; it does not rearrange every third-party plugin's internal controls. Start in a browser, then add device acceptance; an Android device is not required to begin.
|
|
6
|
+
|
|
7
|
+
## Identify where the panel is mounted
|
|
8
|
+
|
|
9
|
+
| Current mobile-page behavior | What the plugin should do |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Opening a panel in the `main` slot adds a “Back to conversation” bar above it | Use the container's actual available height. The bar is at least 56px high; safe areas and content can increase it. Do not subtract a fixed 56px from screen height |
|
|
12
|
+
| The narrow sidebar is a drawer no wider than `min(88vw, 340px)`; a wide layout can keep it persistent | Do not assume a fixed 340px sidebar. Let entry labels and buttons shrink |
|
|
13
|
+
| The mobile layout has `.dshm-shell`, `[data-pane="conversation"]` and `[data-pane="sidebar"]` markers | Use these to locate regions while diagnosing. Scope internal styles to the plugin's own root rather than private host CSS class names |
|
|
14
|
+
| The mobile shell gives inputs and editable areas a 16px font floor while retaining larger inherited sizes | Leave room for editing, rather than counteracting user preferences with a smaller fixed font |
|
|
15
|
+
| Adapted shared entry points do not guarantee arbitrary third-party hit areas | Plugin authors must still check buttons, close actions, checkboxes and menus for actual hit testing |
|
|
16
|
+
|
|
17
|
+
These describe the current layout, not fixed dimensions a plugin should copy. Overlays, sidebars and main panels have different available space; verify the actual mounting location.
|
|
18
|
+
|
|
19
|
+
## Adapt to available space
|
|
20
|
+
|
|
21
|
+
Make the base layout shrink first: give flex/grid children `min-width: 0`, let toolbars wrap, and use content-driven height. Panel width is not always viewport width: a 400px panel in a desktop sidebar needs compact layout too.
|
|
22
|
+
|
|
23
|
+
For arrangement-only changes, CSS container queries are an option; full-page viewport adaptation can still use media queries. The `600px` below is an example breakpoint, to be chosen for the plugin's content:
|
|
24
|
+
|
|
25
|
+
```css
|
|
26
|
+
.plugin-panel-host { container-type: inline-size; min-width: 0; }
|
|
27
|
+
.plugin-panel { min-width: 0; }
|
|
28
|
+
.plugin-panel .toolbar { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
29
|
+
|
|
30
|
+
@container (max-width: 600px) {
|
|
31
|
+
.plugin-panel .toolbar { display: grid; grid-template-columns: minmax(0, 1fr); }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Wrap `.plugin-panel` in `.plugin-panel-host`. On engines without container queries, the base wrapping and fluid widths should remain usable; do not put an essential action solely in the query branch.
|
|
36
|
+
|
|
37
|
+
When a script branch is necessary, observe the component's root width instead of classifying a phone by user agent:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const rootRef = useRef(null)
|
|
41
|
+
const [panelWidth, setPanelWidth] = useState(null)
|
|
42
|
+
const isNarrow = panelWidth !== null && panelWidth <= 600
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const element = rootRef.current
|
|
46
|
+
if (element === null) return undefined
|
|
47
|
+
const measure = () => setPanelWidth(element.getBoundingClientRect().width)
|
|
48
|
+
measure()
|
|
49
|
+
window.addEventListener('resize', measure)
|
|
50
|
+
const observer = typeof window.ResizeObserver === 'function' ? new window.ResizeObserver(measure) : null
|
|
51
|
+
observer?.observe(element)
|
|
52
|
+
return () => {
|
|
53
|
+
window.removeEventListener('resize', measure)
|
|
54
|
+
observer?.disconnect()
|
|
55
|
+
}
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
58
|
+
// The component's returned root; keep a usable base layout before the first measurement.
|
|
59
|
+
h('section', { ref: rootRef, className: 'plugin-panel', 'data-narrow': isNarrow ? 'true' : 'false' }, contents)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Without ResizeObserver, this fallback only measures again on window resize. If a panel resizes independently, also use the plugin's existing layout notifications. Dispose observers and listeners when the component unmounts.
|
|
63
|
+
|
|
64
|
+
## Adaptation checklist
|
|
65
|
+
|
|
66
|
+
### Touch targets and spacing
|
|
67
|
+
|
|
68
|
+
This project recommends at least a **44×44 CSS px** hit area for primary Web controls, with 48px for primary actions where space permits; these are not conversions from Android dp or iOS pt. Leave space between adjacent actions rather than extending targets over their neighbors.
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
.plugin-panel :is(button, select, input:not([type="checkbox"]):not([type="radio"])) { min-height: 44px; }
|
|
72
|
+
.plugin-panel .icon-button { min-width: 44px; }
|
|
73
|
+
.plugin-panel .actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
74
|
+
.plugin-panel .check-label { box-sizing: border-box; display: inline-flex; align-items: center; gap: 8px; min-width: 44px; min-height: 44px; padding-inline: 8px; }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Put a small checkbox inside its associated `label` to enlarge the clickable label without enlarging the icon. Use actual taps and `elementFromPoint()` to check edge hits; a declared `min-height` can still be shrunk, clipped or obscured by layout.
|
|
78
|
+
|
|
79
|
+
### Filters, tabs and inline actions
|
|
80
|
+
|
|
81
|
+
Long filter strips can wrap or use their own horizontally scrollable container; do not make the whole page overflow horizontally. Retain visible focus and enough padding to bring the last item fully into view:
|
|
82
|
+
|
|
83
|
+
```css
|
|
84
|
+
.plugin-panel .chips { display: flex; gap: 8px; overflow-x: auto; padding: 4px; }
|
|
85
|
+
.plugin-panel .chip { flex: 0 0 auto; min-height: 44px; }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
An unambiguous inline action can become an icon on narrow panels, provided it keeps an accessible name and, where needed, a text explanation in a menu. Hover text cannot be the only explanation on a phone.
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
h('button', { className: 'icon-button', 'aria-label': label }, h(Icon, { 'aria-hidden': true, name: 'play' }))
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A narrow panel does not imply the absence of a keyboard. Decorative shortcut badges can collapse, but retain functioning shortcuts, help, Tab order and visible focus; do not remove keyboard-accessible behavior solely by width.
|
|
95
|
+
|
|
96
|
+
### Type, density and complete information
|
|
97
|
+
|
|
98
|
+
Inherit DSH's fonts and semantic colors. To follow the mobile page's text setting, a plugin can use `--dsh-content-font-size` on its content root and retain inheritance when the variable is absent:
|
|
99
|
+
|
|
100
|
+
```css
|
|
101
|
+
.plugin-panel { font-size: var(--dsh-content-font-size, inherit); line-height: 1.5; }
|
|
102
|
+
.plugin-panel .meta { color: var(--dsw-alias-label-secondary, currentColor); }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Compress decorative headings and whitespace, not touch targets. Let long titles wrap or offer an accessible way to view them in full. Errors, pending confirmations and essential action names must not exist only as truncated text. Check larger text, long paths and Chinese/English content.
|
|
106
|
+
|
|
107
|
+
### Overlays, forms and scrolling
|
|
108
|
+
|
|
109
|
+
Constrain overlays to their actual container and visible area, rather than using fixed dimensions that push actions off-screen. Reuse DSH's existing overlay components first; a custom overlay needs an accessible close action, focus management and Escape behavior.
|
|
110
|
+
|
|
111
|
+
```css
|
|
112
|
+
.plugin-panel .form-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; }
|
|
113
|
+
.plugin-panel .scroll-body { min-height: 0; overflow: auto; overscroll-behavior: contain; }
|
|
114
|
+
.plugin-panel textarea { box-sizing: border-box; width: 100%; min-height: 96px; }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Add `env(safe-area-inset-*)` only where content really reaches a screen edge and the host has not already handled it, avoiding duplicate space. Save, Cancel and errors must remain visible or reachable by scrolling with the keyboard open. An independent scroll area should not move the page underneath.
|
|
118
|
+
|
|
119
|
+
## Verification: what code checks, browsers and devices establish
|
|
120
|
+
|
|
121
|
+
### Structural checks
|
|
122
|
+
|
|
123
|
+
Unit tests can check narrow branches, accessible names, retained state and unmount cleanup. Server rendering does not run browser layout measurement; supplying a test width or layout stub only establishes the chosen branch, not actual dimensions, hit testing or pixel-identical desktop output. A `44px` string in CSS is not touch acceptance.
|
|
124
|
+
|
|
125
|
+
### Real browser checks
|
|
126
|
+
|
|
127
|
+
Mount the plugin in an actual DSH page and cover at least 375px and 390px narrow viewports, landscape, full-width desktop and a narrow desktop sidebar. If a static preview is maintained, load it in a browser too and describe its differences from the real host.
|
|
128
|
+
|
|
129
|
+
- Cover list, detail, settings, empty, long-content and error states;
|
|
130
|
+
- Inspect element bounds, clipping ancestors and actual hit targets, including the first and last actions;
|
|
131
|
+
- Use a keyboard to check Tab, Escape and focus restoration, with no accidental background action while an overlay is open;
|
|
132
|
+
- Change width and text size, checking that drafts, selections and expanded state survive;
|
|
133
|
+
- Check light/dark themes and desktop baselines. Explain screenshot changes rather than demanding pixel identity for intentional improvements.
|
|
134
|
+
|
|
135
|
+
For example, after opening the real panel through its existing entry point, Playwright can check a control's geometry and hit testing. Replace the following selector with the plugin's own stable marker:
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
const action = page.locator('[data-plugin-action="save"]')
|
|
139
|
+
await action.scrollIntoViewIfNeeded()
|
|
140
|
+
const box = await action.boundingBox()
|
|
141
|
+
if (box === null || box.width < 44 || box.height < 44) throw new Error('Save target is too small')
|
|
142
|
+
const hit = await action.evaluate(element => {
|
|
143
|
+
const box = element.getBoundingClientRect()
|
|
144
|
+
return element.contains(document.elementFromPoint(box.x + box.width / 2, box.y + box.height / 2))
|
|
145
|
+
})
|
|
146
|
+
if (!hit) throw new Error('Save target is obscured')
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
A center hit is a starting point, not a substitute for edge taps, scrolling, focus or state checks. See [CONTRIBUTING](../CONTRIBUTING.md#select-behavior-checks) for launch and verification procedures.
|
|
150
|
+
|
|
151
|
+
### Device follow-up
|
|
152
|
+
|
|
153
|
+
Browser emulation does not replace real WebView, keyboard, safe-area, momentum-scroll or external-keyboard acceptance. Record the device, App/WebView versions, connection and actual steps. State unverified parts directly instead of claiming all-platform success.
|
|
154
|
+
|
|
155
|
+
## Templates for plugin authors
|
|
156
|
+
|
|
157
|
+
### Issue
|
|
158
|
+
|
|
159
|
+
```markdown
|
|
160
|
+
## Plugin on a small screen
|
|
161
|
+
- Plugin name and version:
|
|
162
|
+
- DSH, DSH Mobile and App/browser versions:
|
|
163
|
+
- Panel location and available width (main / sidebar / overlay):
|
|
164
|
+
- Reproduction steps, expected and actual behavior:
|
|
165
|
+
- Screenshot and identifiable control (redact credentials and private content):
|
|
166
|
+
- Comparison with other optional frontend modules omitted, if tested:
|
|
167
|
+
|
|
168
|
+
## Acceptance goals
|
|
169
|
+
- [ ] Essential actions are visible and usable; long content is not clipped
|
|
170
|
+
- [ ] Actual touch areas, adjacent spacing and first/last items checked
|
|
171
|
+
- [ ] Keyboard focus, back/close behavior and drafts retained
|
|
172
|
+
- [ ] Overlays and forms work at narrow widths and larger text
|
|
173
|
+
- [ ] No desktop workflow regressions; unverified device behavior identified
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### PR
|
|
177
|
+
|
|
178
|
+
- [ ] Describe affected entry points and content, without hard-splitting phone/desktop by UA;
|
|
179
|
+
- [ ] Reuse components and state; avoid duplicating business logic when arrangements differ;
|
|
180
|
+
- [ ] Supply real-browser narrow and desktop comparisons, steps and relevant tests;
|
|
181
|
+
- [ ] Check focus, hit testing, long content, errors and cleanup, not only CSS text;
|
|
182
|
+
- [ ] Distinguish verified and unverified device behavior; do not copy another version's results.
|
|
183
|
+
|
|
184
|
+
## Scope
|
|
185
|
+
|
|
186
|
+
This guide draws on community plugin adaptation and [discussion #279](https://github.com/saya-ch/dsh-mobile/discussions/279). It offers reusable implementation and acceptance ideas, not an independent verification of a new third-party release. For theme-specific questions, see [Skins and mobile layout](SKIN_COMPATIBILITY.en.md).
|
|
187
|
+
|
|
188
|
+
DSH Mobile does not maintain a universal override table keyed by third-party private class names, or guarantee that plugins requiring native windows work directly on remote pages. The plugin owning the component should maintain its adaptation through DSH's public extension points. Report missing host capabilities separately rather than modifying installed DSH artifacts to work around them.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# 移动端插件适配指南
|
|
2
|
+
|
|
3
|
+
[English](MOBILE_PLUGIN_ADAPTATION.en.md) · [返回文档索引](README.md)
|
|
4
|
+
|
|
5
|
+
这份指南面向 DSH 插件作者,目标是**小屏可用,桌面功能不回退**。DSH Mobile 提供连接、页面布局和公共入口,不会逐个重排第三方插件的内部控件。可以先用浏览器完成适配,再补充真机验证;没有 Android 设备并不妨碍开始。
|
|
6
|
+
|
|
7
|
+
## 先确认面板所在位置
|
|
8
|
+
|
|
9
|
+
| 当前移动页面行为 | 插件应如何处理 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 打开 `main` 槽中的面板时,布局在面板上方提供「返回会话」栏 | 使用容器实际可用高度;该栏至少 56px,安全区和内容可能增加高度,不要手动从屏幕高度固定减去 56px |
|
|
12
|
+
| 窄屏侧栏以抽屉显示,展开宽度不超过 `min(88vw, 340px)`;宽屏可以常驻显示 | 不假设侧栏永远是 340px,入口文字和按钮应能收缩 |
|
|
13
|
+
| 移动布局带有 `.dshm-shell` 和 `[data-pane="conversation"]`、`[data-pane="sidebar"]` 标记 | 排障时可用这些标记定位区域;插件内部样式优先作用于自己的根节点,不依赖宿主私有 CSS 类名 |
|
|
14
|
+
| 移动外壳为输入框和可编辑区域设置至少 16px 的字体,同时保留更大的继承字号 | 为输入区域预留高度,不用更小的固定字体抵消用户设置 |
|
|
15
|
+
| 公共入口的适配不会保证任意第三方控件的可点击区域 | 插件作者仍需检查按钮、关闭键、复选框和菜单的实际命中区域 |
|
|
16
|
+
|
|
17
|
+
这些是当前布局行为,不是要求插件复制的固定尺寸。浮层、侧栏和主面板的可用空间不同,需在实际挂载位置验证。
|
|
18
|
+
|
|
19
|
+
## 按可用空间适配
|
|
20
|
+
|
|
21
|
+
先让基础布局能够收缩:给 flex/grid 子项设置 `min-width: 0`,允许工具栏换行,使用内容驱动的高度。面板宽度不一定等于视口宽度:桌面侧栏中的 400px 面板也需要紧凑布局。
|
|
22
|
+
|
|
23
|
+
只改变排列时,可用 CSS 容器查询;全屏页面的视口适配仍可使用媒体查询。下面的 `600px` 是示例断点,应按插件内容选择:
|
|
24
|
+
|
|
25
|
+
```css
|
|
26
|
+
.plugin-panel-host { container-type: inline-size; min-width: 0; }
|
|
27
|
+
.plugin-panel { min-width: 0; }
|
|
28
|
+
.plugin-panel .toolbar { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
29
|
+
|
|
30
|
+
@container (max-width: 600px) {
|
|
31
|
+
.plugin-panel .toolbar { display: grid; grid-template-columns: minmax(0, 1fr); }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`.plugin-panel-host` 应包住 `.plugin-panel`。对不支持容器查询的引擎,基础的换行和流式宽度仍应可用,不要把必需操作藏在唯一的查询分支里。
|
|
36
|
+
|
|
37
|
+
确实需要脚本分支时,可以在组件内部观察根节点宽度,而不是判断手机 UA:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const rootRef = useRef(null)
|
|
41
|
+
const [panelWidth, setPanelWidth] = useState(null)
|
|
42
|
+
const isNarrow = panelWidth !== null && panelWidth <= 600
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const element = rootRef.current
|
|
46
|
+
if (element === null) return undefined
|
|
47
|
+
const measure = () => setPanelWidth(element.getBoundingClientRect().width)
|
|
48
|
+
measure()
|
|
49
|
+
window.addEventListener('resize', measure)
|
|
50
|
+
const observer = typeof window.ResizeObserver === 'function' ? new window.ResizeObserver(measure) : null
|
|
51
|
+
observer?.observe(element)
|
|
52
|
+
return () => {
|
|
53
|
+
window.removeEventListener('resize', measure)
|
|
54
|
+
observer?.disconnect()
|
|
55
|
+
}
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
58
|
+
// 组件返回值中的根节点;初次测量前也应保留可用的基础布局。
|
|
59
|
+
h('section', { ref: rootRef, className: 'plugin-panel', 'data-narrow': isNarrow ? 'true' : 'false' }, contents)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
没有 ResizeObserver 的回退只能在窗口变化时重测;如果面板会独立调整宽度,还需接入插件已有的布局通知。观察器和监听器应随组件卸载清理。
|
|
63
|
+
|
|
64
|
+
## 适配清单
|
|
65
|
+
|
|
66
|
+
### 触控目标与间距
|
|
67
|
+
|
|
68
|
+
本项目建议主要 Web 操作提供至少 **44×44 CSS px** 的命中区域,主操作有空间时可用 48px;这不是 Android dp 或 iOS pt 的换算。相邻操作保留间距,不通过覆盖邻居来扩大点击区。
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
.plugin-panel :is(button, select, input:not([type="checkbox"]):not([type="radio"])) { min-height: 44px; }
|
|
72
|
+
.plugin-panel .icon-button { min-width: 44px; }
|
|
73
|
+
.plugin-panel .actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
74
|
+
.plugin-panel .check-label { box-sizing: border-box; display: inline-flex; align-items: center; gap: 8px; min-width: 44px; min-height: 44px; padding-inline: 8px; }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
小复选框可放在关联的 `label` 中,扩大标签的可点击区域,不必放大图标。用实际点按和 `elementFromPoint()` 检查边缘命中;声明了 `min-height` 仍可能被布局压缩、裁切或遮挡。
|
|
78
|
+
|
|
79
|
+
### 筛选条、标签与行内动作
|
|
80
|
+
|
|
81
|
+
过长的筛选条可以换行,或放入能横向滚动的独立容器;不要让页面整体横向溢出。保留焦点可见性和足够的内边距,使最后一项也能滚入视野:
|
|
82
|
+
|
|
83
|
+
```css
|
|
84
|
+
.plugin-panel .chips { display: flex; gap: 8px; overflow-x: auto; padding: 4px; }
|
|
85
|
+
.plugin-panel .chip { flex: 0 0 auto; min-height: 44px; }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
有明确含义的行内动作可以在窄屏收成图标,但要保留可访问名称,必要时在菜单中保留文字说明。不能靠悬停提示作为手机上唯一的操作说明。
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
h('button', { className: 'icon-button', 'aria-label': label }, h(Icon, { 'aria-hidden': true, name: 'play' }))
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
窄屏不代表没有键盘。可以收起装饰性的快捷键角标,但保留实际快捷键、帮助入口、Tab 顺序和可见焦点;不要只按宽度移除键盘可用功能。
|
|
95
|
+
|
|
96
|
+
### 字体、密度与完整信息
|
|
97
|
+
|
|
98
|
+
继承 DSH 的字体和语义颜色;需要跟随移动页面字号时,可在自己的内容根节点使用 `--dsh-content-font-size`,未提供该变量时保留继承值:
|
|
99
|
+
|
|
100
|
+
```css
|
|
101
|
+
.plugin-panel { font-size: var(--dsh-content-font-size, inherit); line-height: 1.5; }
|
|
102
|
+
.plugin-panel .meta { color: var(--dsw-alias-label-secondary, currentColor); }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
压缩装饰性标题和空白,不压缩触摸目标。长标题允许换行或提供可访问的完整查看入口;错误、待确认内容和关键操作名称不能只剩截断文本。大字号、长路径与中英文内容都要检查。
|
|
106
|
+
|
|
107
|
+
### 弹层、表单与滚动边界
|
|
108
|
+
|
|
109
|
+
浮层应受实际容器和可视区约束,避免固定宽高把按钮推到屏幕外。先使用 DSH 的现有浮层组件;自有弹层需保留可达的关闭入口、焦点管理和 Escape 行为。
|
|
110
|
+
|
|
111
|
+
```css
|
|
112
|
+
.plugin-panel .form-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; }
|
|
113
|
+
.plugin-panel .scroll-body { min-height: 0; overflow: auto; overscroll-behavior: contain; }
|
|
114
|
+
.plugin-panel textarea { box-sizing: border-box; width: 100%; min-height: 96px; }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
只有确实延伸到屏幕边缘且未被宿主处理的区域才追加 `env(safe-area-inset-*)`,避免重复留白。键盘打开后,保存、取消和错误提示仍应可见或可滚动到达;独立滚动区域不应拖动背后的页面。
|
|
118
|
+
|
|
119
|
+
## 验证:代码检查、浏览器与真机分别证明什么
|
|
120
|
+
|
|
121
|
+
### 结构检查
|
|
122
|
+
|
|
123
|
+
单测可验证窄屏分支、可访问名称、状态保留和卸载清理。服务端渲染不会执行浏览器布局测量;给测试提供宽度或布局桩,只能证明选中了哪个分支,不能证明真实尺寸、可点击性或桌面像素一致。不要把 CSS 中出现 `44px` 当成触摸验收。
|
|
124
|
+
|
|
125
|
+
### 真实浏览器检查
|
|
126
|
+
|
|
127
|
+
将插件挂到实际 DSH 页面后,至少检查 375px、390px 窄屏、横屏、桌面全宽和桌面窄侧栏。若维护静态预览,还要在浏览器中加载它,并明确它与真实宿主的差异。
|
|
128
|
+
|
|
129
|
+
- 覆盖列表、详情、设置、空状态、长内容和出错状态;
|
|
130
|
+
- 检查元素边界、裁切祖先和实际命中,确认首尾操作都能触达;
|
|
131
|
+
- 用键盘验证 Tab、Escape、焦点恢复,以及浮层打开时背景不会误操作;
|
|
132
|
+
- 改变宽度和字号,确认输入草稿、选择与展开状态保留;
|
|
133
|
+
- 检查浅色、深色和桌面基线;截图差异需解释,不要求有意改进也逐像素不变。
|
|
134
|
+
|
|
135
|
+
例如,在 Playwright 中对已有面板入口执行实际操作后,可检查控件几何和命中;以下选择器应替换为插件自己的稳定标记:
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
const action = page.locator('[data-plugin-action="save"]')
|
|
139
|
+
await action.scrollIntoViewIfNeeded()
|
|
140
|
+
const box = await action.boundingBox()
|
|
141
|
+
if (box === null || box.width < 44 || box.height < 44) throw new Error('Save target is too small')
|
|
142
|
+
const hit = await action.evaluate(element => {
|
|
143
|
+
const box = element.getBoundingClientRect()
|
|
144
|
+
return element.contains(document.elementFromPoint(box.x + box.width / 2, box.y + box.height / 2))
|
|
145
|
+
})
|
|
146
|
+
if (!hit) throw new Error('Save target is obscured')
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
中心点命中只是起点,不能替代边缘点按、滚动、焦点与状态检查。具体启动和验证流程见[贡献指南](../CONTRIBUTING.md#select-behavior-checks)。
|
|
150
|
+
|
|
151
|
+
### 真机补充
|
|
152
|
+
|
|
153
|
+
浏览器模拟不能代替真实 WebView、输入法、安全区、惯性滚动或外接键盘验收。记录设备、App/WebView 版本、连接方式和实际步骤;没有验证的部分直接说明,不宣称已全平台通过。
|
|
154
|
+
|
|
155
|
+
## 给插件作者的模板
|
|
156
|
+
|
|
157
|
+
### Issue
|
|
158
|
+
|
|
159
|
+
```markdown
|
|
160
|
+
## 插件与小屏
|
|
161
|
+
- 插件名和版本:
|
|
162
|
+
- DSH、DSH Mobile 与 App/浏览器版本:
|
|
163
|
+
- 面板位置与可用宽度(主面板 / 侧栏 / 浮层):
|
|
164
|
+
- 复现步骤、期望与实际表现:
|
|
165
|
+
- 截图及可定位的控件(隐藏凭据和私人内容):
|
|
166
|
+
- 关闭其他可选前端模块后的对照结果(如已测试):
|
|
167
|
+
|
|
168
|
+
## 验收目标
|
|
169
|
+
- [ ] 必需操作可见、可点,长内容不被截断
|
|
170
|
+
- [ ] 触控区域、相邻间距和首尾项经过实际检查
|
|
171
|
+
- [ ] 键盘焦点、返回/关闭和草稿状态无回退
|
|
172
|
+
- [ ] 弹层与表单在窄屏和大字号下可用
|
|
173
|
+
- [ ] 桌面主要流程无回退;注明尚未验证的设备行为
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### PR
|
|
177
|
+
|
|
178
|
+
- [ ] 说明受影响入口和内容,不按 UA 硬分手机与桌面;
|
|
179
|
+
- [ ] 优先复用组件与状态;确需不同排列时避免复制业务逻辑;
|
|
180
|
+
- [ ] 给出真实浏览器的窄屏和桌面对照、操作步骤及相关测试;
|
|
181
|
+
- [ ] 检查焦点、命中、长内容、错误和清理,不仅检查 CSS 文本;
|
|
182
|
+
- [ ] 说明真机已验证与未验证的范围,不复制其他版本的结果。
|
|
183
|
+
|
|
184
|
+
## 范围
|
|
185
|
+
|
|
186
|
+
本指南来自社区插件适配实践与 [#279 讨论](https://github.com/saya-ch/dsh-mobile/discussions/279),提供可复用的实现与验收思路,不代表这里已独立验证任何第三方插件的新版本。主题细节还可参考[皮肤与移动布局](SKIN_COMPATIBILITY.md)。
|
|
187
|
+
|
|
188
|
+
DSH Mobile 不按第三方私有类名维护通用覆盖表,也不保证依赖原生窗口的插件能在远程页面直接使用。适配应由拥有该组件的插件维护,并使用 DSH 的公开扩展接口;发现宿主缺失的能力时应单独报告,不修改已安装的 DSH 产物来绕过问题。
|
package/docs/README.md
CHANGED
|
@@ -29,6 +29,14 @@
|
|
|
29
29
|
| 为移动页面选择加载模块 | [模块设置](CLIENT_MODULES.md) | [Module settings](CLIENT_MODULES.en.md) | 电脑默认与设备覆盖分别保存,不自动刷新会话 |
|
|
30
30
|
| 允许受控路由网络访问 LAN Gateway | [额外可信网段](TRUSTED_NETWORKS.md) | [Trusted networks](TRUSTED_NETWORKS.en.md) | 不建立 VPN,不自动扩大防火墙规则 |
|
|
31
31
|
|
|
32
|
+
## 插件移动适配 / Plugin mobile adaptation
|
|
33
|
+
|
|
34
|
+
给插件作者的响应式布局、触控与验收建议:优先复用现有组件,兼顾小屏与桌面,并区分结构检查、真实浏览器和真机证据。
|
|
35
|
+
|
|
36
|
+
| 主题 | 中文 | English |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| 让第三方插件面板在手机上可用 | [移动端插件适配指南](MOBILE_PLUGIN_ADAPTATION.md) | [Mobile plugin adaptation](MOBILE_PLUGIN_ADAPTATION.en.md) |
|
|
39
|
+
|
|
32
40
|
## 开发与安全 / Development and security
|
|
33
41
|
|
|
34
42
|
0.6.3 的 [扩展 Worker 指南](EXTENSION_WORKERS.md)/[Worker guide](EXTENSION_WORKERS.en.md) 介绍按扩展启用、预算与显式恢复;此功能不迁移普通 Cordis 插件,也不是权限沙箱。
|
|
@@ -47,7 +47,7 @@ Select **Save and start backend**. An active backend reloads its settings. The p
|
|
|
47
47
|
2. Point the proxy at the displayed private HTTP backend, not the phone's public HTTPS address.
|
|
48
48
|
3. Preserve the external **Host including non-default port**: the example requires `phone.example.com:8815`, not the private target or a hostname without its port.
|
|
49
49
|
4. Preserve Origin, Cookie, Set-Cookie and authentication/CSRF headers. Do not rewrite Cookie domain/security attributes or cache authentication responses.
|
|
50
|
-
5. Forward HTTP/1.1 WebSocket upgrades and bidirectional data.
|
|
50
|
+
5. Forward HTTP/1.1 WebSocket upgrades and bidirectional data. The existing WebSocket path allowlist keeps applying; when a third-party plugin needs its own path, approve it separately in diagnostics.
|
|
51
51
|
|
|
52
52
|
[Lucky's official Web documentation](https://www.lucky666.cn/docs/modules/web/) describes WebSocket support. UI labels vary by version, but this backend requires the incoming external Host. Do not enable target-address Host; if a version offers custom Host, provide the full public hostname and port. Certificates belong on Lucky, not this HTTP backend.
|
|
53
53
|
|
|
@@ -48,7 +48,7 @@ CIDR 限制针对 TCP 连接的**直接来源地址**,不是手机 IP,也不
|
|
|
48
48
|
2. 代理目标使用面板给出的 **HTTP 后端地址**,而不是手机使用的 HTTPS 地址。
|
|
49
49
|
3. **保留请求的外部 Host,包含非默认端口。** 上例后端必须收到 `Host: phone.example.com:8815`,不能改成 `192.168.50.10:3444`,也不能漏掉 `:8815`。
|
|
50
50
|
4. 透传浏览器的 Origin、Cookie、Set-Cookie,以及认证/CSRF 相关头,不要改写 Cookie 的域或安全属性;不要缓存配对或认证响应。
|
|
51
|
-
5. 透传 WebSocket 升级(HTTP/1.1、Upgrade、Connection)以及双向数据。已有的 WebSocket
|
|
51
|
+
5. 透传 WebSocket 升级(HTTP/1.1、Upgrade、Connection)以及双向数据。已有的 WebSocket 路径白名单继续生效;第三方插件需要时仍在**连接诊断**面板中单独允许其路径。
|
|
52
52
|
|
|
53
53
|
[Lucky 官方 Web 文档](https://www.lucky666.cn/docs/modules/web/)说明其支持 WebSocket。配置界面随版本变化,但本插件要求不变:保留外部请求 Host,不要启用“使用目标地址Host”;若版本提供自定义 Host,请填写完整公网主机名及端口。TLS/证书配置在 Lucky,不在这个 HTTP 后端上配置。
|
|
54
54
|
|
|
@@ -86,7 +86,7 @@ location / {
|
|
|
86
86
|
|
|
87
87
|
**“后端已监听”只证明本地 HTTP 监听成功,不代表公网 HTTPS、证书或 WebSocket 已成功。** 自备代理模式的连接诊断也只报告本地状态,不自动向公网地址探测。
|
|
88
88
|
|
|
89
|
-
在电脑端点击 **生成远程配对二维码**,再在 Android App **0.4.0 或更高版本**的 **远程访问** 流程扫码(不要使用 LAN
|
|
89
|
+
在电脑端点击 **生成远程配对二维码**,再在 Android App **0.4.0 或更高版本**的 **远程访问** 流程扫码(不要使用 LAN 扫码流程)。使用受信任证书的自有域名也可在手机浏览器中完成配对;现有自有域名逻辑保留 HTTPS 自定义端口,无需为此功能修改 APK。
|
|
90
90
|
|
|
91
91
|
上线前,必须用手机从目标外网实际检查:
|
|
92
92
|
|
|
@@ -8,7 +8,7 @@ Start with the actual channel rather than treating every delay as a DSH heartbea
|
|
|
8
8
|
|
|
9
9
|
## Identify the stage that fails
|
|
10
10
|
|
|
11
|
-
First record the DSH, DSH Mobile plugin and Android app versions. On the computer, check the current channel under **Mobile Access →
|
|
11
|
+
First record the DSH, DSH Mobile plugin and Android app versions. On the computer, check the current channel under **Mobile Access → Diagnostics**, then inspect DSH startup logs and the browser's developer tools under Network → WS and update the component involved. The plugin and app release independently; their version numbers do not need to match. Remove Tokens, Cookies, and complete pairing URLs before sharing logs.
|
|
12
12
|
|
|
13
13
|
- **WebSocket never connects**: check for `101 Switching Protocols` and errors such as `401`, `403`, `404`, or `502`. Authentication, Host/Origin checks, proxy upgrade support, paths, and version differences can all cause this. Heartbeat adjustments do not resolve these failures. Allow blocked third-party plugin paths individually in Connection diagnostics; DSH's built-in paths do not need to be added again.
|
|
14
14
|
- **The request gets 101 but connections keep closing**: record the interval and compare the same page and Session through LAN and remote access. If a long Session repeatedly disconnects only through the remote channel while downloads saturate the link, investigate the heartbeat and initialization deadlines below. Browser close code `1006` means an abnormal end and does not identify which layer disconnected.
|
|
@@ -18,7 +18,7 @@ Both [#126](https://github.com/saya-ch/dsh-mobile/issues/126) and [#146](https:/
|
|
|
18
18
|
|
|
19
19
|
### HTTP rejection diagnostics (plugin 0.6.11+)
|
|
20
20
|
|
|
21
|
-
Plugin 0.6.11 and later write limited `auth failure` HTTP diagnostics to the plugin log (`$DSH_HOME/mobile-access/logs/dsh-mobile.log`): authorization versus CSRF rejection, status, method, a safe route or route category, related Cookie/header presence and counts, normalized Origin
|
|
21
|
+
Plugin 0.6.11 and later write limited `auth failure` HTTP diagnostics to the plugin log (`$DSH_HOME/mobile-access/logs/dsh-mobile.log`): authorization versus CSRF rejection, status, method, a safe route or route category, related Cookie/header presence and counts, normalized Origin, recognized fetch-site values and, when authorization is present, the first 8 characters of the device id. It omits tokens, Cookie values, query strings and arbitrary original paths. Each Gateway rate-limits its two categories separately: the first event is immediate and same-category rejections aggregate within a 30-second window; `suppressed` counts previously suppressed records of that category, not all requests or only the current path. This helps investigate missing credentials and CSRF failures; it does not automatically repair a 401/403. The absence of a record does not establish successful authentication: earlier Host/Origin rejections, WebSocket upgrades and native pairing/renewal are outside these two recording points. Review logs before sharing them.
|
|
22
22
|
|
|
23
23
|
### Internal authentication exchange diagnostics (plugin 0.6.15+)
|
|
24
24
|
|
|
@@ -32,6 +32,8 @@ Plugin 0.6.13 and later distinguish request receive timeouts as `408 request_tim
|
|
|
32
32
|
|
|
33
33
|
The `http failure` diagnostics distinguish three per-Gateway categories: `parser` for HTTP parsing/receive failures, `target` for invalid request targets, and `upstream` for an authenticated `POST /api/session/prompt` rejected upstream. The first event of each kind is immediate; repeats aggregate for 30 seconds, with `suppressed` counting earlier suppressed events of that kind. Logs exclude raw errors, request bodies, raw packets, Cookies, Tokens, queries and arbitrary URLs. No record does not establish success.
|
|
34
34
|
|
|
35
|
+
`slow api` samples authenticated, forwarded `/api/` requests taking at least 3 seconds: a bounded method, a fixed route category such as `/api/session` (unknown categories become `/api/other`), the status and `durationMs`. The duration spans entry into upstream forwarding until the response closes, including a client-abandoned response; it is a proxy wait, not a measurement of upstream processing time or proof of the slow layer. Logs exclude path suffixes such as Session IDs, query strings, request bodies and arbitrary raw paths. Each Gateway records its first event immediately and aggregates subsequent samples within 30 seconds. This changes no timeout, retry or caching policy.
|
|
36
|
+
|
|
35
37
|
`upstreamTimeoutMs` governs upstream transport waits and also configures the complete inbound-request receive deadline. A continuously active upload can still exceed that total deadline, and Node's periodic checks affect the observed firing time. Do not delete pairings, clear App data or automatically resend because of HTTP 400 alone. Identify the rejection layer using the actual response body, Content-Type, exact versions and redacted proxy upstream status. Check the Session result before retrying an operation whose execution outcome is unknown.
|
|
36
38
|
|
|
37
39
|
## Keep event streams distinct
|
|
@@ -108,7 +110,15 @@ Restart DSH and reopen the phone page after a change. Developer tools can show w
|
|
|
108
110
|
|
|
109
111
|
## Static-resource caching and repeated downloads
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
Owned static resources (`/mobile-access/compat.js`, `mobile-layout.js` and custom CSS/JS) use quoted content ETags and `Cache-Control: private, no-cache`. A browser may store the body but must revalidate before reuse. The gateway checks device authorization and the current resource before processing conditional GET/HEAD requests: unchanged content receives `304`, while a file edit returns the new body. Actual cache reuse still depends on browser behavior.
|
|
114
|
+
|
|
115
|
+
The Mobile start HTML includes a page identifier bound to the current authenticated Session and remains `no-store`; it does not reuse another page's or Session's start document. Pairing pages and API responses also remain `no-store`. When the gateway applies streaming gzip to an upstream static resource, a valid upstream ETag can remain usable as a weak validator with `Vary: Accept-Encoding`. Identity lengths and range metadata are not reused as compressed-representation metadata. Failed responses are not cached, and API requests receive no new caching policy.
|
|
116
|
+
|
|
117
|
+
Every compressible text response uses that streaming pipeline: static assets, HTML documents and `/api/` JSON are gzipped when the phone advertises `Accept-Encoding: gzip`, with `Vary` maintained. Static assets under `/plugins/` and `/assets/` **keep their existing decision unchanged**, including paths the upstream already compressed. Outside static assets, `text/event-stream` stays identity so gzip blocks cannot buffer event frames, and a response that declares fewer than 1KB or no length at all stays identity because compressing it costs more than it saves or may be consumed incrementally. Range requests, already encoded responses and 304s keep their previous behavior, and a client that sends no `Accept-Encoding` receives the same bytes as before.
|
|
118
|
+
|
|
119
|
+
The Mobile start document is assembled by the gateway rather than the proxy compression pipeline. Bodies of at least 1KiB are gzipped when the request accepts gzip; both identity and gzip responses maintain `Vary: Accept-Encoding`. The document retains `no-store` because it contains boot information for the current session and module selection.
|
|
120
|
+
|
|
121
|
+
The gateway groups upstream application modules into start batches (`/mobile-access/mobile-boot/<hash>.js`), targeting 3MiB of source per batch. It retains at most 16 merged batches, always including the Mobile layout replacement; overflow modules use their original individual resource URLs instead of rejecting the entire boot graph. A module larger than the target remains whole, and existing per-module and assembly limits still apply. Smaller responses and cache reuse reduce repeated transfers but do not guarantee total startup time or repair every cause of reconnection. Merged batches carry a quoted ETag and `private, max-age=30, must-revalidate`: a quick reload can reuse its local copy. After a layout-file edit, an already cached batch can remain fresh for up to 30 seconds before revalidation. The ETag identifies the body, so changed content produces a new body rather than a 304.
|
|
112
122
|
|
|
113
123
|
Resources without upstream validators do not automatically acquire a reliable one. Compare the same complete resource URL during ordinary navigation, reload and app cold start: inspect `200`/`304`, cache hits, `If-None-Match`, encoding and actual transfer volume. Do not clear site data first and treat the result as normal cache behavior. Improved HTTP validation does not guarantee cache reuse in every WebView, and these headers alone do not establish a complete fix for [#107](https://github.com/saya-ch/dsh-mobile/issues/107).
|
|
114
124
|
|
package/docs/SLOW_CONNECTIONS.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## 先判断重连发生在哪一步
|
|
10
10
|
|
|
11
|
-
先记录 DSH、DSH Mobile 插件和 Android App 的版本,再在电脑端 **移动访问 →
|
|
11
|
+
先记录 DSH、DSH Mobile 插件和 Android App 的版本,再在电脑端 **移动访问 → 诊断** 检查当前通道;查看 DSH 启动日志和浏览器开发者工具的 Network → WS,按问题涉及的组件更新。插件与 App 独立发版,版本号不必相同。分享日志时去掉 Token、Cookie 和完整配对地址。
|
|
12
12
|
|
|
13
13
|
- **WebSocket 没有连接成功**:检查请求是否获得 `101 Switching Protocols`,并留意 `401`、`403`、`404`、`502` 等错误。认证、Host/Origin、反向代理升级支持、连接路径或版本差异都可能造成这种情况。调整心跳不能解决这些问题;第三方插件被拦截的路径可在连接诊断中按需允许,不必把 DSH 内置路径全部重新添加。
|
|
14
14
|
- **已获得 101,但连接反复关闭**:记录关闭间隔,并对比同一页面、同一会话的局域网与远程访问。如果只在远程打开长会话时频繁断开,同时下载长时间占满链路,才进一步检查下面的心跳和初始化等待时间。浏览器显示 `1006` 表示连接异常结束,本身不能确定是哪一层断开。
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
### HTTP 拒绝诊断(插件 0.6.11 起)
|
|
20
20
|
|
|
21
|
-
插件 0.6.11 起会在插件日志(`$DSH_HOME/mobile-access/logs/dsh-mobile.log`)记录有限的 `auth failure` HTTP 诊断:区分授权与 CSRF 拒绝,包含状态、方法、安全路径或路径类别、相关 Cookie/请求头是否存在及计数、规范化 Origin
|
|
21
|
+
插件 0.6.11 起会在插件日志(`$DSH_HOME/mobile-access/logs/dsh-mobile.log`)记录有限的 `auth failure` HTTP 诊断:区分授权与 CSRF 拒绝,包含状态、方法、安全路径或路径类别、相关 Cookie/请求头是否存在及计数、规范化 Origin、受支持的 fetch-site 值,以及有授权时设备标识的前 8 位,不记录令牌、Cookie 值、查询参数或任意原始路径。每个网关的两类诊断分别限频,首条立即记录,30 秒窗口内同类拒绝聚合;`suppressed` 表示此前被抑制的同类记录数量,不是全部请求数,也不限定于当前路径。它帮助排查凭据未携带或 CSRF 校验失败,不会自动修复 401/403。没有日志也不代表认证成功:更早的 Host/Origin 拒绝、WebSocket 升级和原生配对/续期不在这两处记录范围内。分享前仍应检查日志。
|
|
22
22
|
|
|
23
23
|
### 内部认证交换诊断(插件 0.6.15 起)
|
|
24
24
|
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
|
|
33
33
|
`http failure` 日志按网关区分 `parser`、`target`、`upstream` 三类:分别表示 HTTP 解析/接收失败、非法请求目标、已认证的 `POST /api/session/prompt` 被上游拒绝。每类首条立即记录,后续 30 秒内聚合,下一条的 `suppressed` 是此前被抑制的同类数量。日志不保留原始报错、请求正文、原始包、Cookie、Token、查询参数或任意 URL;不记录某条也不能证明它成功。
|
|
34
34
|
|
|
35
|
+
`slow api` 日志采样已认证并转发、耗时至少 3 秒的 `/api/` 请求:固定方法、`/api/session` 等固定路由类别(未知类别归为 `/api/other`)、状态和 `durationMs`。耗时从进入上游代理转发算到响应关闭,包含客户端中途放弃的响应;它是代理等待时间,不是上游处理耗时,也不能单凭该数字确定哪一层慢。日志不保留会话 ID 等路径后缀、查询参数、请求正文或任意原始路径,每个网关首条立即记录、后续采样在 30 秒窗口内聚合。不改变超时、重试或缓存策略。
|
|
36
|
+
|
|
35
37
|
`upstreamTimeoutMs` 既用于上游传输等待,也配置了入站完整请求的接收期限;持续上传不保证避开这个总期限,Node 的定期检查会影响实际触发时刻。不要仅凭 HTTP 400 就删除配对、清空 App 数据或自动重发。先区分拒绝来源,并查看实际响应正文、Content-Type、准确版本和脱敏的代理上游状态;消息或工具是否已执行不明时,先核对会话结果。
|
|
36
38
|
|
|
37
39
|
## 不同事件流不能混为一谈
|
|
@@ -108,7 +110,15 @@ websocketCompression:
|
|
|
108
110
|
|
|
109
111
|
## 静态资源缓存与重复下载
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
网关自有静态资源(`/mobile-access/compat.js`、`mobile-layout.js`、自定义 CSS/JS)使用带引号的内容 ETag 和 `Cache-Control: private, no-cache`。浏览器可保存正文,但每次复用前必须重新校验;网关先检查设备授权和当前资源,再处理 GET/HEAD 条件请求,内容未变时返回 `304`,文件改动后返回新正文。实际缓存复用仍取决于浏览器行为。
|
|
114
|
+
|
|
115
|
+
移动启动 HTML 包含绑定当前认证 Session 的页面标识,保持 `no-store`,不复用其他页面或 Session 的启动文档;配对页和 API 响应也保持 `no-store`。网关对上游静态资源做流式 gzip 时,可保留合法上游 ETag 的弱校验形式,并维护 `Vary: Accept-Encoding`;不会把原始内容的长度或范围元信息当作压缩内容的元信息,也不会缓存失败响应或为 API 请求新增缓存。
|
|
116
|
+
|
|
117
|
+
可压缩的文本响应都走这条流式管道:静态资源、HTML 文档和 `/api/` 的 JSON 都会在手机声明 `Accept-Encoding: gzip` 时压缩,并维护 `Vary`。`/plugins/`、`/assets/` 下的静态资源**沿用原有判定,行为不变**(包括上游原本就会压缩的路径)。静态路径之外,`text/event-stream` 保持不压缩,否则逐帧事件会被 gzip 块缓冲;声明长度小于 1KB 或没有声明长度的响应也保持原样,压缩开销大于收益或可能被逐段消费。范围请求、已编码响应和 304 仍按原规则处理,不带 `Accept-Encoding` 的客户端收到的字节与以前一致。
|
|
118
|
+
|
|
119
|
+
移动端启动页由网关拼装,不经过上面的代理压缩流程;正文至少 1KiB 且请求接受 gzip 时会压缩,压缩和未压缩响应都维护 `Vary: Accept-Encoding`。页面仍使用 `no-store`,不复用旧启动文档;它包含当前会话与模块选择对应的启动信息。
|
|
120
|
+
|
|
121
|
+
网关把上游应用模块组合为启动批(`/mobile-access/mobile-boot/<hash>.js`),按 3MiB 源码目标大小切分,最多保留 16 个合并批,始终保留移动布局替换;超出数量的模块改用其原有独立资源地址,不因这项优化拒绝整个启动图。大于目标大小的单个模块不会拆开,原有单模块和组装大小限制不变。较小的响应和缓存复用可减少重复传输,但不能保证总启动时间或修复所有自动重连原因。合并批使用带引号 ETag 和 `private, max-age=30, must-revalidate`:短时间内重载可复用本地副本;布局文件改动后,已缓存的旧批最多可继续使用 30 秒,随后重新校验。ETag 与正文绑定,内容改变后返回新正文而不是 304。
|
|
112
122
|
|
|
113
123
|
上游未提供校验标识的资源不会因此自动得到一个可靠标识。检查重复下载时,比较同一完整资源 URL 在普通打开、重新加载和 App 冷启动中的 `200`/`304`、缓存命中、`If-None-Match`、编码与实际传输量;不要先清除站点数据再把结果当作正常缓存行为。HTTP 校验语义改善不保证每个 WebView 都会复用缓存,也不能仅凭这些响应头认定 [#107](https://github.com/saya-ch/dsh-mobile/issues/107) 已完全解决。
|
|
114
124
|
|
|
@@ -24,3 +24,20 @@ DSH Mobile's default Windows HTTPS / Discovery inbound rules allow only `LocalSu
|
|
|
24
24
|
A routed network may not support LAN broadcast discovery. If the computer is not discovered, use its displayed pairing link or QR code; routing, firewall access, and the HTTPS entry must still work. Adding an IPv6 CIDR does not create an IPv6 listener.
|
|
25
25
|
|
|
26
26
|
This setting controls source access to the LAN Gateway, not source CIDRs for an external reverse proxy. Configure that separately with the [own HTTPS reverse proxy guide](SELF_HOSTED_ORIGIN.en.md). Do not expose the LAN Gateway or private HTTP backend directly to the public internet.
|
|
27
|
+
|
|
28
|
+
## Accessing the NAS administration page
|
|
29
|
+
|
|
30
|
+
The administration page's `/api/mobile-access` and the phone Gateway are separate entries. The additional trusted networks above do not open the management API. By default, the management API still accepts only direct loopback peers.
|
|
31
|
+
|
|
32
|
+
Plugin 0.6.18 adds `adminAllowedCidrs` for NAS deployments already accessed through a controlled LAN. Add this field to the existing `mobile-access` entry's complete `config`, replacing the example with the administration computer's actual address:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
adminAllowedCidrs:
|
|
36
|
+
- 192.168.50.23/32
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This is only the additional field, **not a complete replacement `config`**. DSH entry overrides replace the whole configuration; retain the existing `setupFile`, `stateFile`, `controlFile`, TLS and other custom settings. Restart DSH after editing.
|
|
40
|
+
|
|
41
|
+
This option does not change DSH's listener, routing or firewall, or open a public management entry. It accepts only the listed direct TCP peers, never `X-Forwarded-For`, and also requires the current request to pass DSH session authentication. Sign in to DSH normally first. A version without usable authentication, a deployment with authentication disabled or a request without credentials cannot gain management access merely through a CIDR. Prefer a single administration computer's `/32`; the limit is 32 entries, with no all-address `/0` or duplicate networks.
|
|
42
|
+
|
|
43
|
+
Existing private-IP/loopback Host restrictions, exact Origin checks, cross-site rejection and denied CORS preflights remain. The management API does not accept arbitrary DNS names or public Hosts. A proxy's direct address may represent several visitors: the proxy must restrict access, retain DSH authentication and never add CORS permission for this path. Plain HTTP on a LAN does not prevent interception; use HTTPS or SSH forwarding on uncontrolled networks, and never publish `/api/mobile-access`. Remove the field or set an empty list to restore the default without deleting pairings.
|
package/docs/TRUSTED_NETWORKS.md
CHANGED
|
@@ -24,3 +24,20 @@ DSH Mobile 的默认 Windows HTTPS / Discovery 入站规则只允许 `LocalSubne
|
|
|
24
24
|
路由网络不一定支持局域网广播发现。发现列表没有电脑时,可使用电脑显示的配对链接或二维码,但路由、防火墙和 HTTPS 入口仍须可达。填写 IPv6 CIDR 也不会自动增加 IPv6 监听。
|
|
25
25
|
|
|
26
26
|
此设置用于局域网 Gateway 的来源访问范围,不改变外部反向代理的来源 CIDR。自有代理请按[自有 HTTPS 反向代理指南](SELF_HOSTED_ORIGIN.md)配置;不要把局域网 Gateway 或私有 HTTP 后端映射到公网。
|
|
27
|
+
|
|
28
|
+
## NAS 的管理网页访问
|
|
29
|
+
|
|
30
|
+
管理网页的 `/api/mobile-access` 与手机连接的 Gateway 是两个入口。上面的额外可信网段设置不会开放管理 API。默认情况下,管理 API 仍只允许直连来源为回环地址。
|
|
31
|
+
|
|
32
|
+
插件 0.6.18 起提供 `adminAllowedCidrs`,供已经通过受控局域网访问 DSH 的 NAS 部署使用。在现有 `mobile-access` 条目的完整 `config` 中追加以下字段,并将示例改成管理电脑的实际地址:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
adminAllowedCidrs:
|
|
36
|
+
- 192.168.50.23/32
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
这只是新增字段,**不是可直接替换的完整 `config`**。DSH 的条目覆盖会替换整个配置;请保留原有 `setupFile`、`stateFile`、`controlFile`、TLS 和其他自定义设置。修改后重启 DSH。
|
|
40
|
+
|
|
41
|
+
此选项不改变 DSH 的监听地址、路由或防火墙,也不会打开公网管理入口。仅允许列出的直接 TCP 来源,不读取 `X-Forwarded-For`;同时要求当前请求通过 DSH 的会话认证。浏览器须先正常登录 DSH;没有可用认证能力的版本、关闭认证的部署或无凭据请求不能靠填写 CIDR获得管理权限。尽量使用单台管理电脑的 `/32`,最多 32 项,不接受全地址 `/0` 或重复网段。
|
|
42
|
+
|
|
43
|
+
现有私有 IP/回环 Host 限制、精确 Origin、跨站检查和拒绝 CORS 预检的行为保持不变。管理 API 不接受任意域名或公网 Host。反向代理的直接地址可能代表多名访问者:代理必须限制访问、保留 DSH 认证、不能给此路径添加跨域许可。HTTP 局域网也不能防止链路窃听;不受控网络请使用 HTTPS 或 SSH 转发,不要公开 `/api/mobile-access`。恢复默认时移除该字段或设为空列表,不会删除配对数据。
|