dsh-mobile-plugin 0.1.57 → 0.1.58
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 +33 -2
- package/lib/routes/control-plane.js +73 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,12 +79,43 @@ DSH_HOME=/opt/dsh-mobile/.dsh DSH_MOBILE_PROFILE=mobile node host.js
|
|
|
79
79
|
市场代劳重启 DSH。
|
|
80
80
|
- **装完/更新完要完整重启 DSH**:插件是 profile 层的 cordis bundle,进程起来时依赖树就定死了。
|
|
81
81
|
市场会把这件事报成 `restartRequired`,页面上有对应提示与按钮。
|
|
82
|
-
-
|
|
83
|
-
|
|
82
|
+
- 如果「设置 → 移动端 → 打开配置页面」打不开、页面吐一段 `panel_missing` JSON:那段文案会
|
|
83
|
+
写清是**哪一种**读不到 —— 「装完没重启」(旧实例还挂在这次安装删掉的那个目录上,最常见)、
|
|
84
|
+
「装了老版本」(0.1.47 及更早还没有这一页)还是「真没构建」(源码树里才会遇到)。三种解法
|
|
85
|
+
不同,别照着「没重启」的现场去跑 React 构建。同一句话也会进宿主 stderr。
|
|
84
86
|
- 从前那套的教训记在这里:热更新只换宿主侧的实现代,**换不掉设置菜单那一半**(渲染器直接从
|
|
85
87
|
安装目录读它),于是出现过「更新报成功、界面一动不动」,重启 DSH、重启 sidecar 都没用。
|
|
86
88
|
现在只有一条更新路径,这类「哪一半是旧的」不再存在。
|
|
87
89
|
|
|
90
|
+
### 发版流程(自己发一版更新)
|
|
91
|
+
|
|
92
|
+
1. **抬版本号**:`node scripts/bump-version.mjs`(三段各 `0..999`)。
|
|
93
|
+
2. **改过 `React/` 就先重建面板**:`cd React && pnpm run build`。产物 `lib/panel/index.html`
|
|
94
|
+
是**提交物**,要和源码一起提交 —— 忘了重建,发出去的就是旧页面,而仓库里没有任何东西能
|
|
95
|
+
替你发现(守卫只比字节形状,不比新旧)。
|
|
96
|
+
3. **自检**:`npm test`(其中 `panel-artifact` 管产物形状与「读不到时报什么」,
|
|
97
|
+
`publish-payload` 管发布清单收得住每个会被加载的文件);`npm pack --dry-run` 扫一眼发了什么。
|
|
98
|
+
4. **发布**:`npm publish`。
|
|
99
|
+
5. **核对**:`npm view dsh-mobile-plugin version`。
|
|
100
|
+
6. **到机器上更新**:DSH → 设置 → 插件 里更新,**然后完整重启 DSH**。
|
|
101
|
+
|
|
102
|
+
**发布这一步现在要你现场过二次验证**(2026-10 起,别以为是配错了):
|
|
103
|
+
|
|
104
|
+
- `npm publish` 会打印一条 `https://www.npmjs.com/auth/cli/…` 链接(或直接开浏览器),用账号
|
|
105
|
+
绑定的**安全密钥**确认一下,它自己接着把包发出去。
|
|
106
|
+
- 所以这一步**必须能打开 www.npmjs.com**:机房/代理出口 IP 会被它的反机器人拦成 403
|
|
107
|
+
(页面写「访问暂时受限」并带上你的 IP)。换个节点或直连再发。
|
|
108
|
+
- npm 已经关掉了「命令行建号」和「验证器 App(TOTP)」两条路:建号只能在网页上做,2FA 只能挂
|
|
109
|
+
安全密钥。这两件事上试命令是白费时间,认它就行。
|
|
110
|
+
- 想少点一次确认,可以建一把「允许绕过 2FA」的细粒度令牌少输几次验证:
|
|
111
|
+
`npm token create --bypass-2fa --packages dsh-mobile-plugin --packages-and-scopes-permission read-write --expires 90`
|
|
112
|
+
(**最长 90 天**)。代价:这类令牌 **2027-01 起不能再直接发布**,到期或到点都得回到上面
|
|
113
|
+
那条交互式路径。
|
|
114
|
+
- 还有个死结记在这里:令牌不能指向**还不存在**的包(会报
|
|
115
|
+
`You do not have access to the requested package(s)`),所以**第一版只能用浏览器验证发**,
|
|
116
|
+
令牌是「包已经存在之后」才有的便利。
|
|
117
|
+
|
|
118
|
+
|
|
88
119
|
插件没有 GUI 外壳,配置入口是插件注册在 `webServer` 上的**仅限本机访问**(`localOnly` 守卫)
|
|
89
120
|
的 HTTP 端点。`host.js` 启动后,其 `webServer.port` 就是下面 URL 里的 `<port>`
|
|
90
121
|
(无头部署下固定 `3080`):
|
|
@@ -35,13 +35,19 @@ const MAX_DESKTOP_READ_IDS = 200;
|
|
|
35
35
|
/**
|
|
36
36
|
* 面板页(`React/` 那份 React 源码构建成的单文件 HTML)在包里的位置。
|
|
37
37
|
*
|
|
38
|
-
* 用 `__dirname`
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* 用 `__dirname` 而不是包根,是为了「谁跟着这份路由一起走」:HTML 是**提交物**,和
|
|
39
|
+
* `lib/` 在同一份安装里,装哪个版本就带哪个版本的页面。
|
|
40
|
+
*
|
|
41
|
+
* 这段注释过去写的是「控制面路由住在实现代 `lib/.live/<releaseId>/` 里,改版式不用重装」——
|
|
42
|
+
* 那套「稳定外壳 + 可热替换的代」随插件 OTA 在 2026-10 一起删了(A5-02)。现在改版式只有
|
|
43
|
+
* 一条路:`React/` 里重建 → 提交产物 → 发新版本 → 市场里更新 → 完整重启 DSH。
|
|
41
44
|
*/
|
|
42
45
|
const PANEL_FILE = path.join(__dirname, '..', 'panel', 'index.html');
|
|
43
46
|
|
|
44
|
-
/**
|
|
47
|
+
/** 插件包根(`<pkg>/lib/routes` 往上两级):诊断时读它的 `package.json` 与 `React/`。 */
|
|
48
|
+
const PACKAGE_DIR = path.join(__dirname, '..', '..');
|
|
49
|
+
|
|
50
|
+
/** 按 mtime 缓存:文件被换掉之后内容变了,缓存自然失效,不必手工清。 */
|
|
45
51
|
let panelCache = { file: '', mtime: -1, html: '' };
|
|
46
52
|
|
|
47
53
|
function panelHtml() {
|
|
@@ -56,6 +62,59 @@ function panelHtml() {
|
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
/**
|
|
66
|
+
* 面板读不到时,说清楚**是哪一种读不到**。
|
|
67
|
+
*
|
|
68
|
+
* 这条是给「装了但打不开」的现场用的。过去这里只有一句「去 `React/` 里跑一次
|
|
69
|
+
* pnpm run build」,那是**开发树**才遇得到的情况;装到别人机器上时它几乎总是另外两件事:
|
|
70
|
+
*
|
|
71
|
+
* 1. **安装目录整个不见了**(2026-10-10 实测现场):在插件市场里装/更新过、DSH 还没完整
|
|
72
|
+
* 重启,跑着的仍是旧实例,而旧实例所在的 pnpm 目录已经被这次安装删掉了 —— 代码在内存里,
|
|
73
|
+
* 所以路由照常响应,`fs` 一读就 ENOENT,面板页于是吐一段 JSON。解法是**重启 DSH**,
|
|
74
|
+
* 不是去跑构建。
|
|
75
|
+
* 2. **这份安装里根本没有产物**:0.1.47 及更早的版本还没有这一页(`files` 白名单当时
|
|
76
|
+
* 没收 `lib/` 下的 html),老包里点开就是这条。解法是**更新到当前版本**。
|
|
77
|
+
*
|
|
78
|
+
* 两件事的解法完全不同,所以分开报,并把路径与版本一起带上 —— 下次看到这段报错,一眼就知道
|
|
79
|
+
* 该重启还是该升级。三种情形由 `test/panel-artifact.test.cjs` 钉住。
|
|
80
|
+
*
|
|
81
|
+
* @param {{panelFile?: string, packageDir?: string}} [paths] 只给测试喂临时目录用;运行时走默认路径
|
|
82
|
+
* @returns {{reason: 'install-gone'|'artifact-missing'|'unreadable', message: string}}
|
|
83
|
+
*/
|
|
84
|
+
function panelDiagnosis(paths) {
|
|
85
|
+
const panelFile = paths?.panelFile ?? PANEL_FILE;
|
|
86
|
+
const packageDir = paths?.packageDir ?? PACKAGE_DIR;
|
|
87
|
+
let version = '';
|
|
88
|
+
try {
|
|
89
|
+
version = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')).version || '';
|
|
90
|
+
} catch {}
|
|
91
|
+
const versionOf = version ? `v${version}` : '版本读不到';
|
|
92
|
+
|
|
93
|
+
if (!fs.existsSync(packageDir)) {
|
|
94
|
+
return {
|
|
95
|
+
reason: 'install-gone',
|
|
96
|
+
message: `插件的安装目录已经不在了(${packageDir}):多半是刚在插件市场里装/更新过、但 DSH 还没完整重启 —— 跑着的旧实例还挂在这次安装删掉的那个目录上。完整重启 DSH 即可恢复。`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (!fs.existsSync(panelFile)) {
|
|
100
|
+
// 有没有 `React/src` 是「源码树 vs 装进来的包」唯一可靠的判据:`files` 不收 React/。
|
|
101
|
+
const sourceTree = fs.existsSync(path.join(packageDir, 'React', 'src'));
|
|
102
|
+
return {
|
|
103
|
+
reason: 'artifact-missing',
|
|
104
|
+
message: sourceTree
|
|
105
|
+
? `面板产物还没构建(${panelFile}):在 dsh-mobile-plugin/React 里跑一次 pnpm run build(产物是提交物,构建完要跟源码一起提交)。`
|
|
106
|
+
: `这份安装(${versionOf})里没有面板产物(${panelFile}):0.1.47 及更早的版本还没有这一页。在 DSH 的插件市场里更新到当前版本,再完整重启 DSH。`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
let cause = '';
|
|
110
|
+
try {
|
|
111
|
+
fs.readFileSync(panelFile);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
cause = `:${error && error.message ? error.message : error}`;
|
|
114
|
+
}
|
|
115
|
+
return { reason: 'unreadable', message: `面板产物读不出来(${panelFile},${versionOf})${cause}` };
|
|
116
|
+
}
|
|
117
|
+
|
|
59
118
|
/**
|
|
60
119
|
* 面板页的绝对地址,给客户端半边那个「打开配置页面」的链接用。
|
|
61
120
|
*
|
|
@@ -130,8 +189,13 @@ function registerControlPlaneRoutes(deps) {
|
|
|
130
189
|
// 而局域网与中转那两条链路本来就不该看到它(它们带 dsh_pair/dsh_lan 凭据,会被挡掉)。
|
|
131
190
|
const servePanel = (res) => {
|
|
132
191
|
const html = panelHtml();
|
|
133
|
-
if (
|
|
134
|
-
|
|
192
|
+
if (html) return sendHtml(res, 200, html);
|
|
193
|
+
// 读不到就别只说「还没构建」:装进来的包几乎从不是这个原因,而是「装完没重启」或
|
|
194
|
+
// 「装了老版本」(见 panelDiagnosis 的注释)。原因也写进宿主 stderr —— 页面上的
|
|
195
|
+
// JSON 只有点开那个人看得见,而这类故障通常是在别人的机器上被报出来的。
|
|
196
|
+
const diagnosis = panelDiagnosis();
|
|
197
|
+
process.stderr.write(`[dsh-mobile-plugin] panel unavailable (${diagnosis.reason}) ${PANEL_FILE}: ${diagnosis.message}\n`);
|
|
198
|
+
return sendJson(res, 500, { ok: false, error: { code: 'panel_missing', reason: diagnosis.reason, message: diagnosis.message, path: PANEL_FILE } });
|
|
135
199
|
};
|
|
136
200
|
registerRoute({ kind: 'exact', path: '/api/relay/panel', handler: localOnly('GET', async (_req, res) => servePanel(res)) })
|
|
137
201
|
registerRoute({ kind: 'exact', path: '/api/relay/panel/setup', handler: localOnly('GET', async (_req, res) => servePanel(res)) })
|
|
@@ -308,4 +372,6 @@ function registerControlPlaneRoutes(deps) {
|
|
|
308
372
|
}) })
|
|
309
373
|
}
|
|
310
374
|
|
|
311
|
-
|
|
375
|
+
// `panelDiagnosis` 一并导出,只为让 test/panel-artifact.test.cjs 能拿临时目录跑那三条分支:
|
|
376
|
+
// 三种「读不到」的文案分别对应「重启 / 升级 / 构建」,它们混起来就是这个 bug 的原样。
|
|
377
|
+
module.exports = { registerControlPlaneRoutes, panelDiagnosis };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mobile-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.58",
|
|
4
4
|
"description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (0.2.0-rc.2 compatible, peers resolved by the host)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|