dsh-auto-open-web 0.1.4 → 0.1.9
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 +40 -30
- package/README.md +34 -24
- package/cordis.patch.yml +12 -0
- package/host-publish/DshAppWindow.deps.json +2 -2
- package/host-publish/DshAppWindow.dll +0 -0
- package/host-publish/DshAppWindow.exe +0 -0
- package/host-publish/DshAppWindow.pdb +0 -0
- package/lib/client.js +558 -267
- package/lib/index.js +120 -109
- package/lib/platform.js +2 -0
- package/lib/posix.js +24 -1
- package/lib/win32.js +25 -0
- package/package.json +13 -6
package/lib/index.js
CHANGED
|
@@ -7,28 +7,58 @@
|
|
|
7
7
|
* - Windows: lib/win32.js(全量实现)
|
|
8
8
|
* - 其他平台: lib/posix.js(降级实现,记日志不打开)
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* 行为(逐级降级,前一级失败即进入下一级):
|
|
11
11
|
* 1. 默认打开 **WebView2 宿主窗口**(随包分发的 DshAppWindow.exe,独立进程,
|
|
12
12
|
* 无标签栏/地址栏,直接加载 GUI 根地址):窗口/任务栏图标 = DSH(Form.Icon);
|
|
13
13
|
* 宿主监视父进程 PID,随 DSH 退出而关闭。
|
|
14
14
|
* 2. 宿主缺失/启动失败 → `--app` **专用浏览器实例**(--user-data-dir 隔离,
|
|
15
|
-
* 不与正常浏览器页面共用进程)
|
|
15
|
+
* 不与正常浏览器页面共用进程)。
|
|
16
|
+
* 3. 再失败 → **默认浏览器打开**(官方 dsh web 同款 open 交接,普通标签页,
|
|
17
|
+
* 作为最后的兜底;open 包不可用时平台原生 cmd start / open / xdg-open)。
|
|
16
18
|
*
|
|
17
19
|
* 配置来源:行配置(cordis.patch.yml)作为启动种子;设置页卡片(客户端 bundle,
|
|
18
|
-
* settings.plugin.item 插槽)
|
|
19
|
-
* settings
|
|
20
|
+
* settings.plugin.item 插槽)经官方 settings 域(settingsScope)读写本命名
|
|
21
|
+
* 空间,持久化到官方 settings 文档(首次保存后设置值优先于行配置)。
|
|
22
|
+
* 宿主保留的辅助路由:/auto-open-web/pick-browser(原生浏览对话框)、
|
|
23
|
+
* /auto-open-web/test-browser(浏览器拉起测试)。
|
|
20
24
|
*
|
|
21
25
|
* 端口来自 webServer 服务的真实监听值(--port 自定义、--port 0 均正确)。
|
|
22
26
|
* 打开窗口的子进程 detached + unref,不阻塞 DSH、不随 DSH 退出被终止
|
|
23
27
|
* (WebView2 宿主例外:随 DSH 退出而关闭)。
|
|
24
28
|
*/
|
|
25
29
|
import { spawn } from 'node:child_process';
|
|
26
|
-
import { existsSync,
|
|
30
|
+
import { existsSync, rmSync, writeFileSync } from 'node:fs';
|
|
31
|
+
import { createRequire } from 'node:module';
|
|
27
32
|
import { join } from 'node:path';
|
|
28
|
-
import
|
|
33
|
+
import { pathToFileURL } from 'node:url';
|
|
29
34
|
import { dshHome, appProfileDir, testProfileDir, instanceStateFile } from './paths.js';
|
|
30
35
|
import * as platform from './platform.js';
|
|
31
36
|
|
|
37
|
+
/**
|
|
38
|
+
* schemastery 依赖策略(与 win32.js 的 koffi 同一模式):本包零 dependencies,
|
|
39
|
+
* schemastery 由 DSH 部署自带(官方核心依赖),运行时解析:
|
|
40
|
+
* 常规 import(profile 已装/手动安装)优先 → Windows 全局 npm 布局的
|
|
41
|
+
* DSH 部署副本;两者都不可用时模块加载失败(插件依赖 Config,无法降级)。
|
|
42
|
+
*/
|
|
43
|
+
let z = null;
|
|
44
|
+
try {
|
|
45
|
+
z = (await import('@deepseek-ai/schemastery')).default;
|
|
46
|
+
} catch {
|
|
47
|
+
try {
|
|
48
|
+
const globalRoot = process.env.APPDATA ? join(process.env.APPDATA, 'npm', 'node_modules') : '';
|
|
49
|
+
const dshEntry = join(globalRoot, '@deepseek-ai', 'dsh', 'package.json');
|
|
50
|
+
if (existsSync(dshEntry)) z = createRequire(dshEntry)('@deepseek-ai/schemastery');
|
|
51
|
+
} catch {
|
|
52
|
+
/* 两次解析均失败 → 下方抛错 */
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (z === null || z === undefined) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
'[auto-open-web] schemastery not found (neither local nor DSH deployment); ' +
|
|
58
|
+
'this plugin cannot build its config schema. Check the DSH deployment.',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
32
62
|
export const name = 'auto-open-web';
|
|
33
63
|
|
|
34
64
|
/**
|
|
@@ -45,7 +75,7 @@ export const Config = z.object({
|
|
|
45
75
|
appWindow: z.boolean().default(true),
|
|
46
76
|
/** 窗口类型:webview2 = WebView2 宿主(独立进程,任务栏 DSH 图标,随 DSH 退出);
|
|
47
77
|
* browser = 浏览器 --app 专用实例(--user-data-dir 隔离)。所选类型不可用时
|
|
48
|
-
*
|
|
78
|
+
* 降级为最后的兜底:默认浏览器打开(官方 dsh web 同款,普通标签页)。 */
|
|
49
79
|
windowKind: z.union([z.const('webview2'), z.const('browser')]).default('webview2'),
|
|
50
80
|
/** 手动维护的浏览器可执行文件路径(单条;优先于内置候选 Edge → Chrome;仅 browser 模式使用)。 */
|
|
51
81
|
browserPath: z.string().default(''),
|
|
@@ -53,7 +83,6 @@ export const Config = z.object({
|
|
|
53
83
|
exitOnWindowClose: z.boolean().default(false),
|
|
54
84
|
});
|
|
55
85
|
|
|
56
|
-
const CONFIG_API_PATH = '/auto-open-web/config';
|
|
57
86
|
const PICK_API_PATH = '/auto-open-web/pick-browser';
|
|
58
87
|
const TEST_API_PATH = '/auto-open-web/test-browser';
|
|
59
88
|
const ICON_SIZES = [16, 32, 48, 64, 128, 256];
|
|
@@ -205,6 +234,66 @@ function spawnHostWindow(exe, url, iconPath) {
|
|
|
205
234
|
}
|
|
206
235
|
}
|
|
207
236
|
|
|
237
|
+
// ── 最后的兜底:默认浏览器打开(官方 dsh web 同款) ──────────────────────────
|
|
238
|
+
//
|
|
239
|
+
// 官方(web-app bundle)启动后把 URL 交给操作系统默认浏览器,方式是:在独立
|
|
240
|
+
// node 子进程里用 `open` npm 包打开(win32 = PowerShell Start,darwin = open,
|
|
241
|
+
// linux = xdg-open),win32 下还等待启动器退出码确认交接成功。本插件把这一
|
|
242
|
+
// 方式作为 WebView2 宿主与 --app 专用实例都失败后的最后兜底——至少用户能
|
|
243
|
+
// 打开 GUI(普通标签页)。
|
|
244
|
+
//
|
|
245
|
+
// open 包解析策略(与 schemastery/koffi 同一模式):常规 import(profile 已装)
|
|
246
|
+
// 优先,其次 DSH 部署副本(官方 web-app 的依赖,open@11,纯 ESM,须先 resolve
|
|
247
|
+
// 出文件路径再动态 import)。open 包不可用时退回平台适配器原生拉起
|
|
248
|
+
// (win32: cmd start;darwin: open;linux: xdg-open)。
|
|
249
|
+
let openPkgPromise = null;
|
|
250
|
+
|
|
251
|
+
/** 解析 open 包(惰性 + 缓存);两次解析均失败返回 null。 */
|
|
252
|
+
function getOpenPackage() {
|
|
253
|
+
if (openPkgPromise === null) {
|
|
254
|
+
openPkgPromise = (async () => {
|
|
255
|
+
try {
|
|
256
|
+
const mod = await import('open');
|
|
257
|
+
if (mod !== null && mod !== undefined && mod.default !== undefined) return mod.default;
|
|
258
|
+
} catch {
|
|
259
|
+
/* 常规解析失败,尝试部署副本 */
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const globalRoot = process.env.APPDATA ? join(process.env.APPDATA, 'npm', 'node_modules') : '';
|
|
263
|
+
const dshEntry = join(globalRoot, '@deepseek-ai', 'dsh', 'package.json');
|
|
264
|
+
if (existsSync(dshEntry)) {
|
|
265
|
+
const resolved = createRequire(dshEntry).resolve('open');
|
|
266
|
+
const mod = await import(pathToFileURL(resolved).href);
|
|
267
|
+
if (mod !== null && mod !== undefined && mod.default !== undefined) return mod.default;
|
|
268
|
+
}
|
|
269
|
+
} catch {
|
|
270
|
+
/* 部署解析失败 */
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
})();
|
|
274
|
+
}
|
|
275
|
+
return openPkgPromise;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 最后兜底:把 URL 交给操作系统默认浏览器(普通标签页)。
|
|
280
|
+
* 优先 open 包(与官方同一路径);不可用/启动失败时退回平台原生拉起。
|
|
281
|
+
* 返回 true = 已交给默认浏览器;false = 全部失败(调用方仅记录日志,
|
|
282
|
+
* 提示手动访问 URL)。
|
|
283
|
+
*/
|
|
284
|
+
async function openDefaultBrowser(url) {
|
|
285
|
+
const openPkg = await getOpenPackage();
|
|
286
|
+
if (openPkg !== null && openPkg !== undefined) {
|
|
287
|
+
try {
|
|
288
|
+
await openPkg(url); // wait:false → spawn 后即 resolve,子进程已 unref
|
|
289
|
+
return true;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error(`[auto-open-web] open package launch failed: ${error.message}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return platform.openDefaultBrowser(url);
|
|
295
|
+
}
|
|
296
|
+
|
|
208
297
|
// ── 配置与历史迁移(共通) ─────────────────────────────────────────────────
|
|
209
298
|
|
|
210
299
|
/** 删除 v0.3 遗留的 state 文件(一次性清理,防孤儿数据)。 */
|
|
@@ -311,48 +400,6 @@ async function ensureIconFile(server) {
|
|
|
311
400
|
|
|
312
401
|
// ── 插件主体 ─────────────────────────────────────────────────────────────
|
|
313
402
|
|
|
314
|
-
/**
|
|
315
|
-
* dsh 宿主版本兼容范围(与 package.json 的 peerDependencies 一致):
|
|
316
|
-
* settings.plugin.item 自 dsh 0.1.0-rc.7 起为 keyed 插槽(注册需 options.key);
|
|
317
|
-
* 0.1.4 的卡片注册同时提供 id 与 key,新旧两代 dsh 均可挂载,但低于
|
|
318
|
-
* 0.1.0-rc.7 时官方 UI 未定义该插槽的 keyed 语义,超出支持范围。
|
|
319
|
-
*/
|
|
320
|
-
const DSH_MIN_VERSION = '0.1.0-rc.7';
|
|
321
|
-
const DSH_MAX_VERSION = '0.2.0';
|
|
322
|
-
|
|
323
|
-
/** 解析 "x.y.z[-rc.N]" 为数值数组;rc 缺失视为 Infinity(正式版 > 同号 rc)。失败返回 null。 */
|
|
324
|
-
function parseVersion(text) {
|
|
325
|
-
const m = /^(\d+)\.(\d+)\.(\d+)(?:-rc\.(\d+))?/.exec(String(text).trim());
|
|
326
|
-
if (m === null) return null;
|
|
327
|
-
return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] !== undefined ? Number(m[4]) : Infinity];
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function compareVersions(a, b) {
|
|
331
|
-
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2] || a[3] - b[3];
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/** dsh 版本是否在支持范围内;版本未知(读不到/解析失败)返回 true(不误伤)。 */
|
|
335
|
-
export function isDshVersionSupported(versionText) {
|
|
336
|
-
const v = parseVersion(versionText);
|
|
337
|
-
const min = parseVersion(DSH_MIN_VERSION);
|
|
338
|
-
const max = parseVersion(DSH_MAX_VERSION);
|
|
339
|
-
if (v === null || min === null || max === null) return true;
|
|
340
|
-
return compareVersions(v, min) >= 0 && compareVersions(v, max) < 0;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** 尽力读取部署的 dsh 版本(Windows 全局 npm 布局;其余环境返回 null)。 */
|
|
344
|
-
export function detectDshVersion() {
|
|
345
|
-
try {
|
|
346
|
-
const globalRoot = process.env.APPDATA ? join(process.env.APPDATA, 'npm', 'node_modules') : '';
|
|
347
|
-
const entry = join(globalRoot, '@deepseek-ai', 'dsh', 'package.json');
|
|
348
|
-
if (!existsSync(entry)) return null;
|
|
349
|
-
const version = JSON.parse(readFileSync(entry, 'utf8')).version;
|
|
350
|
-
return typeof version === 'string' && version !== '' ? version : null;
|
|
351
|
-
} catch {
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
403
|
/**
|
|
357
404
|
* 窗口退出监听共享标志:"窗口关闭时退出 DSH"当前是否生效。
|
|
358
405
|
* 监听始终注册(见 openAppWindow/spawnHostWindow),行为由本标志实时决定:
|
|
@@ -370,28 +417,17 @@ export const internals = {
|
|
|
370
417
|
buildIco,
|
|
371
418
|
resolveHostExe: platform.resolveHostExe,
|
|
372
419
|
spawnHostWindow,
|
|
420
|
+
getOpenPackage,
|
|
421
|
+
openDefaultBrowser,
|
|
373
422
|
/** 测试钩子:同步"窗口关闭时退出 DSH"生效标志(模拟设置保存)。 */
|
|
374
423
|
setExitWatcherEnabled(value) {
|
|
375
424
|
exitWatcherEnabled = value === true;
|
|
376
425
|
},
|
|
377
|
-
isDshVersionSupported,
|
|
378
|
-
detectDshVersion,
|
|
379
426
|
};
|
|
380
427
|
|
|
381
428
|
export function apply(ctx, config) {
|
|
382
429
|
removeLegacyState();
|
|
383
430
|
|
|
384
|
-
// dsh 宿主版本约束(声明性):低于支持范围时明确提示(宿主能力兼容,
|
|
385
|
-
// 卡片注册做了新旧双兼容,不硬停;日志指导用户升级 dsh)。
|
|
386
|
-
const dshVersion = detectDshVersion();
|
|
387
|
-
if (dshVersion !== null && !isDshVersionSupported(dshVersion)) {
|
|
388
|
-
console.error(
|
|
389
|
-
`[auto-open-web] DSH version ${dshVersion} is outside the supported range ` +
|
|
390
|
-
`(${DSH_MIN_VERSION} <= dsh < ${DSH_MAX_VERSION}); the settings card may not mount. ` +
|
|
391
|
-
'Please upgrade dsh to continue using this plugin.',
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
431
|
// browser 模式的专用实例随 DSH 退出:正常退出时结束其实例进程树
|
|
396
432
|
// (被强杀时由下次启动前的预清理兜底)。
|
|
397
433
|
process.on('exit', () => {
|
|
@@ -434,7 +470,9 @@ export function apply(ctx, config) {
|
|
|
434
470
|
}
|
|
435
471
|
syncExitWatcher(); // 启动时的最终生效值(settings 已合并或行配置)
|
|
436
472
|
|
|
437
|
-
// ----
|
|
473
|
+
// ---- 卡片辅助 API(浏览/测试;设置读写走官方 settings 域) ----
|
|
474
|
+
// 设置卡片经客户端 settingsScope(settings 域)读写本命名空间,不再自建
|
|
475
|
+
// config 路由;这里只保留官方通道无法覆盖的能力。
|
|
438
476
|
function sendJson(res, status, value) {
|
|
439
477
|
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
440
478
|
res.end(JSON.stringify(value));
|
|
@@ -445,44 +483,6 @@ export function apply(ctx, config) {
|
|
|
445
483
|
return body;
|
|
446
484
|
}
|
|
447
485
|
if (typeof server.register === 'function') {
|
|
448
|
-
server.register({
|
|
449
|
-
kind: 'exact',
|
|
450
|
-
path: CONFIG_API_PATH,
|
|
451
|
-
handler: async (req, res) => {
|
|
452
|
-
if (req.method === 'GET') {
|
|
453
|
-
sendJson(res, 200, { ok: true, ...state, writable });
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
if (req.method === 'POST') {
|
|
457
|
-
if (!writable) {
|
|
458
|
-
sendJson(res, 200, { ok: false, error: '当前配置只读(settings 不可写)' });
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
let parsed;
|
|
462
|
-
try {
|
|
463
|
-
parsed = JSON.parse(await readBody(req));
|
|
464
|
-
} catch {
|
|
465
|
-
sendJson(res, 400, { ok: false, error: 'invalid JSON body' });
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
try {
|
|
469
|
-
const next = normalizeState(parsed);
|
|
470
|
-
if (scope !== null) await scope.update(next);
|
|
471
|
-
state = next;
|
|
472
|
-
syncExitWatcher(); // 保存后即时生效,当前会话无需重启
|
|
473
|
-
sendJson(res, 200, { ok: true, ...state });
|
|
474
|
-
} catch (error) {
|
|
475
|
-
sendJson(res, 200, {
|
|
476
|
-
ok: false,
|
|
477
|
-
error: String(error && error.message !== undefined ? error.message : error),
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
res.writeHead(405);
|
|
483
|
-
res.end();
|
|
484
|
-
},
|
|
485
|
-
});
|
|
486
486
|
// 原生"浏览"对话框:选择浏览器可执行文件(平台能力;非 Windows 提示手动输入)。
|
|
487
487
|
server.register({
|
|
488
488
|
kind: 'exact',
|
|
@@ -560,7 +560,18 @@ export function apply(ctx, config) {
|
|
|
560
560
|
const url = `http://127.0.0.1:${String(server.port)}`;
|
|
561
561
|
|
|
562
562
|
// 独立应用窗口:按 windowKind 显式选择(webview2 | browser),直接加载 GUI 根地址。
|
|
563
|
-
//
|
|
563
|
+
// 所选类型不可用时降级到最后兜底:默认浏览器打开(官方 dsh web 同款,
|
|
564
|
+
// 普通标签页),保证用户至少能打开 GUI。
|
|
565
|
+
const fallbackOpen = async (reason) => {
|
|
566
|
+
console.log(`[auto-open-web] ${reason}; falling back to default browser (official open method)`);
|
|
567
|
+
const ok = await openDefaultBrowser(url);
|
|
568
|
+
if (ok) {
|
|
569
|
+
console.log(`[auto-open-web] opened default browser at ${url}`);
|
|
570
|
+
} else {
|
|
571
|
+
console.error('[auto-open-web] default browser open failed; visit the URL manually');
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
564
575
|
if (state.appWindow) {
|
|
565
576
|
if (state.windowKind === 'browser') {
|
|
566
577
|
const resolved = platform.resolveBrowserExe(state.browserPath);
|
|
@@ -574,21 +585,21 @@ export function apply(ctx, config) {
|
|
|
574
585
|
console.log(`[auto-open-web] opened browser app window (${resolved.browser} --app) at ${url}`);
|
|
575
586
|
return;
|
|
576
587
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
console.log('[auto-open-web] no Edge/Chrome executable found; not opening anything');
|
|
588
|
+
await fallbackOpen('browser app window launch failed');
|
|
589
|
+
return;
|
|
580
590
|
}
|
|
591
|
+
await fallbackOpen('no Edge/Chrome executable found');
|
|
581
592
|
return;
|
|
582
593
|
}
|
|
583
594
|
|
|
584
595
|
// webview2 模式
|
|
585
596
|
if (!platform.isWindows) {
|
|
586
|
-
|
|
597
|
+
await fallbackOpen('webview2 mode requires Windows');
|
|
587
598
|
return;
|
|
588
599
|
}
|
|
589
600
|
const hostExe = platform.resolveHostExe();
|
|
590
601
|
if (hostExe === null) {
|
|
591
|
-
|
|
602
|
+
await fallbackOpen('host exe not found');
|
|
592
603
|
return;
|
|
593
604
|
}
|
|
594
605
|
const iconPath = await ensureIconFile(server);
|
|
@@ -597,7 +608,7 @@ export function apply(ctx, config) {
|
|
|
597
608
|
console.log(`[auto-open-web] opened host window (DshAppWindow) at ${url}`);
|
|
598
609
|
return;
|
|
599
610
|
}
|
|
600
|
-
|
|
611
|
+
await fallbackOpen('host window launch failed');
|
|
601
612
|
return;
|
|
602
613
|
}
|
|
603
614
|
|
package/lib/platform.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// killProcessTree(pid)
|
|
10
10
|
// pickBrowserExe() → Promise<string | null>
|
|
11
11
|
// resolveHostExe() → string | null
|
|
12
|
+
// openDefaultBrowser(url) → Promise<boolean>(原生拉起默认浏览器,兜底)
|
|
12
13
|
//
|
|
13
14
|
// 按平台**条件加载**适配器(ESM 顶层 await):
|
|
14
15
|
// - Windows:加载 win32.js(koffi 驱动 Job Object / 进程校验 / 原生对话框)
|
|
@@ -25,3 +26,4 @@ export const killDedicatedBrowserInstances = impl.killDedicatedBrowserInstances;
|
|
|
25
26
|
export const killProcessTree = impl.killProcessTree;
|
|
26
27
|
export const pickBrowserExe = impl.pickBrowserExe;
|
|
27
28
|
export const resolveHostExe = impl.resolveHostExe;
|
|
29
|
+
export const openDefaultBrowser = impl.openDefaultBrowser;
|
package/lib/posix.js
CHANGED
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
// - 专用实例清理:无操作(Unix 下浏览器实例由 Job-less 方式管理,暂无实现)
|
|
7
7
|
// - 进程树终止:尽力而为(SIGKILL 单进程,后代进程不保证)
|
|
8
8
|
// - 原生文件对话框:不支持(返回 null,卡片提示手动输入)
|
|
9
|
-
// - WebView2 宿主:无(返回 null,webview2
|
|
9
|
+
// - WebView2 宿主:无(返回 null,webview2 模式降级到最后兜底)
|
|
10
|
+
// - 默认浏览器打开:原生拉起(darwin: open;linux: xdg-open)
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
|
|
10
13
|
export const isWindows = false;
|
|
11
14
|
|
|
12
15
|
export function resolveBrowserExe() {
|
|
@@ -40,3 +43,23 @@ export function pickBrowserExe() {
|
|
|
40
43
|
export function resolveHostExe() {
|
|
41
44
|
return null;
|
|
42
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 原生默认浏览器打开(最后兜底,open 包不可用时):darwin 用 open,其余
|
|
49
|
+
* 平台用 xdg-open。detached + stdio ignore,不阻塞 DSH;启动失败返回 false。
|
|
50
|
+
*/
|
|
51
|
+
export function openDefaultBrowser(url) {
|
|
52
|
+
const command = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
let child;
|
|
55
|
+
try {
|
|
56
|
+
child = spawn(command, [url], { detached: true, stdio: 'ignore' });
|
|
57
|
+
} catch {
|
|
58
|
+
resolve(false);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
child.on('error', () => resolve(false));
|
|
62
|
+
child.on('exit', (code) => resolve(code === 0));
|
|
63
|
+
child.unref();
|
|
64
|
+
});
|
|
65
|
+
}
|
package/lib/win32.js
CHANGED
|
@@ -381,3 +381,28 @@ export function resolveHostExe() {
|
|
|
381
381
|
const exe = fileURLToPath(new URL('../host-publish/DshAppWindow.exe', import.meta.url));
|
|
382
382
|
return existsSync(exe) ? exe : null;
|
|
383
383
|
}
|
|
384
|
+
|
|
385
|
+
// ── 原生默认浏览器打开(最后兜底,open 包不可用时) ─────────────────────────
|
|
386
|
+
//
|
|
387
|
+
// 与官方 web-app 同效但零依赖:cmd /c start 把 URL 交给系统默认浏览器
|
|
388
|
+
// (普通标签页)。detached + stdio ignore + windowsHide,不闪控制台窗口、
|
|
389
|
+
// 不阻塞 DSH;启动失败(找不到 cmd)返回 false。exit code 仅作参考——start
|
|
390
|
+
// 交出 URL 后 cmd 即退出,交接是否成功由操作系统决定。
|
|
391
|
+
export function openDefaultBrowser(url) {
|
|
392
|
+
return new Promise((resolve) => {
|
|
393
|
+
let child;
|
|
394
|
+
try {
|
|
395
|
+
child = spawn('cmd.exe', ['/d', '/c', 'start', '', url], {
|
|
396
|
+
detached: true,
|
|
397
|
+
stdio: 'ignore',
|
|
398
|
+
windowsHide: true,
|
|
399
|
+
});
|
|
400
|
+
} catch {
|
|
401
|
+
resolve(false);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
child.on('error', () => resolve(false));
|
|
405
|
+
child.on('exit', (code) => resolve(code === 0));
|
|
406
|
+
child.unref();
|
|
407
|
+
});
|
|
408
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-auto-open-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Open the DSH Web GUI in an app-style WebView2 window on profile start, with a settings card for browser paths",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/jinsiyu/dsh-auto-open-web.git"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"main": "lib/index.js",
|
|
7
11
|
"scripts": {
|
|
@@ -22,7 +26,9 @@
|
|
|
22
26
|
"inject": [
|
|
23
27
|
"@deepseek-ai/dsh-client-connection",
|
|
24
28
|
"@deepseek-ai/dsh-api-remotes",
|
|
25
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
29
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
30
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
31
|
+
"@deepseek-ai/dsh-client-locale"
|
|
26
32
|
]
|
|
27
33
|
}
|
|
28
34
|
},
|
|
@@ -39,15 +45,16 @@
|
|
|
39
45
|
"README.md",
|
|
40
46
|
"README.en.md"
|
|
41
47
|
],
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@deepseek-ai/schemastery": "^3.18.1"
|
|
44
|
-
},
|
|
45
48
|
"peerDependencies": {
|
|
46
|
-
"@deepseek-ai/dsh": ">=0.1.0-rc.7 <0.2.0"
|
|
49
|
+
"@deepseek-ai/dsh": ">=0.1.0-rc.7 <0.2.0",
|
|
50
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
47
51
|
},
|
|
48
52
|
"peerDependenciesMeta": {
|
|
49
53
|
"@deepseek-ai/dsh": {
|
|
50
54
|
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"@deepseek-ai/schemastery": {
|
|
57
|
+
"optional": true
|
|
51
58
|
}
|
|
52
59
|
},
|
|
53
60
|
"license": "MIT"
|