dsh-dock 0.12.0 → 0.13.0
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 +51 -6
- package/client.js +633 -234
- package/features/mobile/view.js +305 -0
- package/features/mobile-relay/gateway.js +187 -11
- package/features/mobile-relay/host.js +175 -50
- package/features/mobile-relay/view.jsx +217 -86
- package/index.js +5 -1
- package/package.json +2 -2
- package/src/client.jsx +3 -2
- package/src/host-core.js +129 -0
package/src/host-core.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { readFileSync, existsSync } from 'node:fs'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { homedir } from 'node:os'
|
|
6
|
+
import yaml from 'js-yaml'
|
|
6
7
|
import z from '@deepseek-ai/schemastery'
|
|
7
8
|
|
|
8
9
|
/** dsh-dock 自有 settings 命名空间 = profile 条目 id(cordis.patch.yml 的 id)。 */
|
|
@@ -26,6 +27,15 @@ export const DockConfig = z.object({
|
|
|
26
27
|
passwordHash: z.string().default(''),
|
|
27
28
|
salt: z.string().default(''),
|
|
28
29
|
}).default({}).volatile(),
|
|
30
|
+
/** 远程访问网关的持久化状态:enabled=上次是否在跑;port=自选端口(0=主端口+1)。
|
|
31
|
+
* dsh web 重启后按此自动拉起网关(账号存在才拉),免得每次重启都要进面板点一次。 */
|
|
32
|
+
remoteGateway: z.object({
|
|
33
|
+
enabled: z.boolean().default(false),
|
|
34
|
+
/** 网关端口(0 = 跟随默认的主端口+1);面板「开启远程访问」时回写。 */
|
|
35
|
+
port: z.natural().default(0),
|
|
36
|
+
}).default({}).volatile(),
|
|
37
|
+
/** settings.yaml.imported 全量恢复(migrateImportedSections)是否已执行过的一次性标记。 */
|
|
38
|
+
importedRestored: z.boolean().default(false).volatile(),
|
|
29
39
|
visionProxy: z.object({
|
|
30
40
|
enabled: z.boolean().default(false),
|
|
31
41
|
provider: z.string().default(''),
|
|
@@ -296,6 +306,125 @@ export async function migrateImportedFeatures(ctx) {
|
|
|
296
306
|
}
|
|
297
307
|
}
|
|
298
308
|
|
|
309
|
+
/** 深比较(JSON 化配置快照之间的相等判断)。 */
|
|
310
|
+
function sameValue(a, b) {
|
|
311
|
+
if (a === b) return true
|
|
312
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
313
|
+
return a.length === b.length && a.every((v, i) => sameValue(v, b[i]))
|
|
314
|
+
}
|
|
315
|
+
if (a && b && typeof a === 'object' && typeof b === 'object' && !Array.isArray(a) && !Array.isArray(b)) {
|
|
316
|
+
const ka = Object.keys(a)
|
|
317
|
+
const kb = Object.keys(b)
|
|
318
|
+
return ka.length === kb.length && ka.every((k) => Object.hasOwn(b, k) && sameValue(a[k], b[k]))
|
|
319
|
+
}
|
|
320
|
+
return false
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** schema 缺省值的普通对象快照(用来判断「现网这一段用户从没写过」)。
|
|
324
|
+
* schemastery 没有 parse():走 Standard Schema validate 取值,volatile 读写器交给 plainify 解开。 */
|
|
325
|
+
function dockDefaults() {
|
|
326
|
+
try {
|
|
327
|
+
const standard = DockConfig['~standard'] || DockConfig[Symbol.for('standard.schema')]
|
|
328
|
+
if (!standard || typeof standard.validate !== 'function') return null
|
|
329
|
+
const result = standard.validate({})
|
|
330
|
+
if (!result || result.issues || !result.value) return null
|
|
331
|
+
return plainifyDockConfig(result.value)
|
|
332
|
+
} catch {
|
|
333
|
+
return null
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** 只保留 schema 认识的顶层键:imported 旧文件可能带已废弃字段(如 animation 段里的
|
|
338
|
+
* 旧通知字段),整段原样写会被 mutate 校验拒绝——丢字段会让整次恢复失败。
|
|
339
|
+
* 值原样透传(嵌套结构交 schema 处理),这里只做顶层白名单。 */
|
|
340
|
+
function sanitizeSection(value, template) {
|
|
341
|
+
if (!template || typeof template !== 'object' || Array.isArray(template)) return value
|
|
342
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value
|
|
343
|
+
const out = {}
|
|
344
|
+
for (const key of Object.keys(template)) {
|
|
345
|
+
if (Object.hasOwn(value, key)) out[key] = value[key]
|
|
346
|
+
}
|
|
347
|
+
return out
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 一次性全量恢复:`~/.dsh/settings.yaml` 被 dsh 升级改名为 `settings.yaml.imported`
|
|
352
|
+
* 并尝试导入 profile 时,dsh-dock 当时还没导出 Config → 整段导入失败,账号
|
|
353
|
+
* (remoteAuth)、通知、动画、图片代理等配置全部停在 imported 文件里;后来只补迁了
|
|
354
|
+
* features(见 migrateImportedFeatures),于是「远程访问账号丢失、网关开不起来」。
|
|
355
|
+
*
|
|
356
|
+
* 这里把整段补回来,规则保守:
|
|
357
|
+
* - `features`:逐键补缺(现网已有的键绝不覆盖);
|
|
358
|
+
* - 其余 schema 认识的段:仅当现网该段仍等于 schema 缺省值(= 用户从未在此写过)
|
|
359
|
+
* 才整段恢复,现网有任何改动一律以现网为准(不覆盖新配置);
|
|
360
|
+
* - 执行过写入或确认无 imported 文档后置 `importedRestored` 标记,仅跑一次。
|
|
361
|
+
*/
|
|
362
|
+
export async function migrateImportedSections(ctx) {
|
|
363
|
+
try {
|
|
364
|
+
const root = readDockRoot(ctx)
|
|
365
|
+
if (root && root.importedRestored === true) return
|
|
366
|
+
const home = process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
367
|
+
const file = join(home, 'settings.yaml.imported')
|
|
368
|
+
if (!existsSync(file)) return
|
|
369
|
+
let data = null
|
|
370
|
+
try {
|
|
371
|
+
data = yaml.load(readFileSync(file, 'utf8'))
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.warn('[dsh-dock] settings.yaml.imported 不是合法 YAML,跳过恢复:', (error && error.message) || String(error))
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
const section = data && typeof data === 'object' && !Array.isArray(data) ? data[DOCK_NS] : null
|
|
377
|
+
if (!section || typeof section !== 'object' || Array.isArray(section)) {
|
|
378
|
+
await mutateDockSection(ctx, ['importedRestored'], true)
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
const defaults = dockDefaults()
|
|
382
|
+
const live = root && typeof root === 'object' ? root : {}
|
|
383
|
+
const writes = []
|
|
384
|
+
const importedFeatures = section.features && typeof section.features === 'object' ? section.features : null
|
|
385
|
+
if (importedFeatures) {
|
|
386
|
+
const merged = live.features && typeof live.features === 'object' ? { ...live.features } : {}
|
|
387
|
+
let added = 0
|
|
388
|
+
for (const [key, value] of Object.entries(importedFeatures)) {
|
|
389
|
+
if (typeof value === 'boolean' && typeof merged[key] !== 'boolean') {
|
|
390
|
+
merged[key] = value
|
|
391
|
+
added++
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (added > 0) writes.push(['features', merged])
|
|
395
|
+
}
|
|
396
|
+
if (defaults) {
|
|
397
|
+
for (const key of Object.keys(section)) {
|
|
398
|
+
if (key === 'features') continue
|
|
399
|
+
if (!Object.hasOwn(defaults, key)) continue
|
|
400
|
+
// 现网缺该段(= 从没写过)或仍等于 schema 缺省 → 恢复;现网有用户改动一律不覆盖。
|
|
401
|
+
if (live[key] !== undefined && !sameValue(live[key], defaults[key])) continue
|
|
402
|
+
writes.push([key, sanitizeSection(section[key], defaults[key])])
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// 逐段独立落盘:一个段被拒(schema 不认/写盘瞬时失败)不拖死其余段——
|
|
406
|
+
// 尤其 remoteAuth 必须尽量恢复,否则远程访问账号一直起不来。
|
|
407
|
+
const restored = []
|
|
408
|
+
const failed = []
|
|
409
|
+
for (const [path, value] of writes) {
|
|
410
|
+
try {
|
|
411
|
+
await mutateDockSection(ctx, [path], value)
|
|
412
|
+
restored.push(path)
|
|
413
|
+
} catch (error) {
|
|
414
|
+
failed.push(path)
|
|
415
|
+
console.warn(`[dsh-dock] 恢复 ${path} 段失败:`, (error && error.message) || String(error))
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// 全部落盘(或本来就无事可做)才置一次性标记;有失败则下次启动重试。
|
|
419
|
+
if (failed.length === 0) await mutateDockSection(ctx, ['importedRestored'], true)
|
|
420
|
+
if (restored.length > 0) {
|
|
421
|
+
console.log('[dsh-dock] restored from settings.yaml.imported:', restored.join(', '))
|
|
422
|
+
}
|
|
423
|
+
} catch (e) {
|
|
424
|
+
console.warn('[dsh-dock] imported section restore skipped:', (e && e.message) || String(e))
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
299
428
|
/** 任务动画 effectMode 合法值(客户端动画模式)。 */
|
|
300
429
|
export const ANIMATION_MODES = [
|
|
301
430
|
'flow', 'breathe', 'ring', 'orbit', 'robot', 'matrix', 'stars', 'aurora', 'space',
|