dsh-auto-open-web 0.1.5 → 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 +16 -4
- package/README.md +15 -4
- package/cordis.patch.yml +12 -0
- 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 +31 -6
- package/lib/index.js +86 -10
- package/lib/platform.js +2 -0
- package/lib/posix.js +24 -1
- package/lib/win32.js +25 -0
- package/package.json +5 -1
package/README.en.md
CHANGED
|
@@ -31,7 +31,12 @@ selected by `windowKind`:
|
|
|
31
31
|
kills the dedicated instance process tree (matching only our own user-data-dir, never the normal
|
|
32
32
|
browser); instances left behind by a force-kill are cleaned up before the next launch.
|
|
33
33
|
3. If the selected type is unavailable (host missing / browser not found / non-Windows, etc.) →
|
|
34
|
-
**
|
|
34
|
+
**falls back to the official default-browser handoff** — the URL is given to the OS default
|
|
35
|
+
browser (plain tab), so the user can always reach the GUI. The implementation mirrors exactly
|
|
36
|
+
how the DSH core (web-app bundle) opens the page at startup: it prefers the `open` package
|
|
37
|
+
shipped with the DSH deployment (win32 = PowerShell Start, darwin = `open`, linux = `xdg-open`);
|
|
38
|
+
when the package is unavailable it falls back to a native platform launch
|
|
39
|
+
(win32 = `cmd /c start`, darwin = `open`, linux = `xdg-open`).
|
|
35
40
|
`appWindow: false` opens nothing automatically.
|
|
36
41
|
|
|
37
42
|
The port comes from the real listening value of the webServer service (`--port` overrides and
|
|
@@ -42,6 +47,13 @@ Both modes close with DSH: the webview2 host watches the parent process; the
|
|
|
42
47
|
browser dedicated instance is ended by the Job Object (also effective on force-kill) plus exit
|
|
43
48
|
cleanup.
|
|
44
49
|
|
|
50
|
+
> **Relationship with DSH's core browser handoff**: the DSH core (web-app bundle) opens the GUI
|
|
51
|
+
> in the system default browser at startup by default (`openBrowser: true` — a plain tab/window,
|
|
52
|
+
> not an app window). Installing this plugin flips `web-runtime.openBrowser` to `false` via the
|
|
53
|
+
> plugin's bundle patch, so only the plugin's app-style window opens and no ordinary browser page
|
|
54
|
+
> pops up; uninstalling the plugin restores the default behavior (or use `dsh web --no-open`
|
|
55
|
+
> to disable it temporarily).
|
|
56
|
+
|
|
45
57
|
### WebView2 host requirements (webview2 mode only)
|
|
46
58
|
|
|
47
59
|
- Windows 10 1803+ / Windows 11 / Windows Server 2016+
|
|
@@ -69,7 +81,7 @@ Two equivalent ways:
|
|
|
69
81
|
| Field | Default | Description |
|
|
70
82
|
| --- | --- | --- |
|
|
71
83
|
| `appWindow` | `true` | Automatically open the independent app window on start; `false` opens nothing |
|
|
72
|
-
| `windowKind` | `webview2` | `webview2` = WebView2 host (own process, DSH taskbar icon, exits with DSH); `browser` = dedicated `--app` browser instance. If the selected type is unavailable,
|
|
84
|
+
| `windowKind` | `webview2` | `webview2` = WebView2 host (own process, DSH taskbar icon, exits with DSH); `browser` = dedicated `--app` browser instance. If the selected type is unavailable, it falls back to the default-browser handoff (official open method, plain tab) |
|
|
73
85
|
| `exitOnWindowClose` | `false` | **(Experimental)** Exit DSH when the auto-opened window closes (off by default; only effective while `appWindow` is on). Triggered only when the window process exits **normally** (user closes the window) → `process.exit(0)`; startup failures/crashes/force-kills (non-zero exit code) do not trigger, preventing accidental exits. **Takes effect immediately in the current session after saving** (the exit listener is always registered; behavior is driven by a live flag), no restart needed |
|
|
74
86
|
| `browserPath` | `''` | Manual browser executable path (single entry, e.g. `C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`; used only in browser mode), preferred over the built-in candidates Edge → Chrome; a non-existent path is skipped with a warning. The "Browse" button on the card opens a **native file dialog**: same mechanism as the official workspace directory picker (child process + koffi-driven `IFileOpenDialog`; the dialog is the child's first window and is automatically brought to front; no PowerShell). The "Test" button **actually launches** a dedicated `--app` test instance (separate user-data-dir `~/.dsh/<browser>-test-profile`, never pollutes the real instance): after confirming the browser main process stays alive it reports success, then automatically ends that test process tree after a few seconds of display (exact pid, never touches the real instance; the test instance is also placed in the Job Object when available as an exit safety net); the test uses the currently typed path (works even when unsaved), and failures show the reason |
|
|
75
87
|
|
|
@@ -185,8 +197,8 @@ without touching the package.
|
|
|
185
197
|
## Platform support
|
|
186
198
|
|
|
187
199
|
- Windows: `windowKind: webview2` (default, DSH taskbar icon) or `windowKind: browser` (dedicated
|
|
188
|
-
`--app` instance);
|
|
189
|
-
- macOS/Linux: `webview2` mode is unavailable (
|
|
200
|
+
`--app` instance); falls back to the default-browser handoff (official open method, plain tab) if the selected type is unavailable
|
|
201
|
+
- macOS/Linux: `webview2` mode is unavailable (falls back to the default-browser handoff); `browser` mode is untested
|
|
190
202
|
(dedicated `--app` instance)
|
|
191
203
|
|
|
192
204
|
## Edge cases
|
package/README.md
CHANGED
|
@@ -27,8 +27,13 @@ dsh web profile 启动后自动打开独立应用窗口(或网页标签页)
|
|
|
27
27
|
依赖任何退出事件。另有两层兜底:DSH 正常退出时 `process 'exit'` 结束该
|
|
28
28
|
专用实例的整个进程树(仅匹配我们自己的 user-data-dir,不影响正常浏览器);
|
|
29
29
|
DSH 被强杀后的残留实例由下次启动前预清理。
|
|
30
|
-
3.
|
|
31
|
-
|
|
30
|
+
3. **默认浏览器打开(最后兜底)**:所选类型不可用时(宿主缺失/浏览器找不到/
|
|
31
|
+
非 Windows 等)→ **降级为官方同款的默认浏览器交接**——把 URL 交给操作系统
|
|
32
|
+
默认浏览器(普通标签页),保证用户至少能打开 GUI。实现与 DSH 核心
|
|
33
|
+
(web-app bundle)启动时打开网页的方式一致:优先用 DSH 部署自带的
|
|
34
|
+
`open` 包(win32 = PowerShell Start,darwin = `open`,linux = `xdg-open`),
|
|
35
|
+
`open` 包不可用时退回平台原生拉起(win32 = `cmd /c start`,darwin = `open`,
|
|
36
|
+
linux = `xdg-open`)。
|
|
32
37
|
`appWindow: false` 时不自动打开任何窗口。
|
|
33
38
|
|
|
34
39
|
端口取自 webServer 服务的真实监听值(`--port` 自定义、`--port 0` 均正确)。
|
|
@@ -38,6 +43,12 @@ webServer 插件 `Service.init()` 完成(HTTP socket 已绑定、端口已写
|
|
|
38
43
|
两种模式都随 DSH 退出而关闭:webview2 宿主监视父进程;browser 专用实例由
|
|
39
44
|
Job Object(强杀也生效)+ 退出清理结束进程树。
|
|
40
45
|
|
|
46
|
+
> **与 DSH 核心浏览器交接的关系**:DSH 核心(web-app bundle)默认会在启动后
|
|
47
|
+
> 用系统默认浏览器打开 GUI(`openBrowser: true`,普通标签页/窗口,非独立窗口)。
|
|
48
|
+
> 安装本插件后,插件的 bundle 补丁会把 `web-runtime.openBrowser` 置为 `false`,
|
|
49
|
+
> 只保留插件的独立应用窗口,不再弹出普通浏览器页面;卸载插件后恢复默认行为
|
|
50
|
+
> (临时关闭也可用 `dsh web --no-open`)。
|
|
51
|
+
|
|
41
52
|
### WebView2 宿主要求(仅 webview2 模式)
|
|
42
53
|
|
|
43
54
|
- Windows 10 1803+ / Windows 11 / Windows Server 2016+
|
|
@@ -169,8 +180,8 @@ dsh plugin --profile web remove dsh-auto-open-web # 同时移除依赖与对
|
|
|
169
180
|
|
|
170
181
|
## 平台支持
|
|
171
182
|
|
|
172
|
-
- Windows:`windowKind: webview2`(默认,任务栏图标 DSH)或 `windowKind: browser`(--app
|
|
173
|
-
- macOS/Linux:`webview2`
|
|
183
|
+
- Windows:`windowKind: webview2`(默认,任务栏图标 DSH)或 `windowKind: browser`(--app 专用实例);所选类型不可用时降级为默认浏览器打开(官方同款,普通标签页)
|
|
184
|
+
- macOS/Linux:`webview2` 模式不可用(降级为默认浏览器打开);`browser` 模式未测试(--app 专用实例)
|
|
174
185
|
|
|
175
186
|
## 边界情况
|
|
176
187
|
|
package/cordis.patch.yml
CHANGED
|
@@ -3,3 +3,15 @@
|
|
|
3
3
|
- insert:
|
|
4
4
|
- id: auto-open-web
|
|
5
5
|
name: dsh-auto-open-web
|
|
6
|
+
|
|
7
|
+
# 本插件用独立应用窗口打开 GUI,替代 DSH 核心(web-app bundle)启动后的
|
|
8
|
+
# 默认浏览器交接,避免同时弹出普通浏览器页面(核心的 openBrowser 默认 true,
|
|
9
|
+
# 会以普通标签页/窗口打开 GUI;插件窗口才是"独立应用窗口")。
|
|
10
|
+
# 补丁按 id 整体替换 config,故逐键重述 web-runtime 原配置,仅把
|
|
11
|
+
# openBrowser 置为 false(trustedHosts 保留 webStartup 提供的原表达式)。
|
|
12
|
+
- id: web-runtime
|
|
13
|
+
config:
|
|
14
|
+
openBrowser: false
|
|
15
|
+
printUrl: true
|
|
16
|
+
surfaceContext: true
|
|
17
|
+
trustedHosts: !!js ctx.webStartup.trustedHosts
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// dsh-auto-open-web — Client bundle(设置卡片,与官方 Shell/Agent loop 卡片同构)
|
|
2
2
|
// 格式:window.__ModuleLoader__.load({ id, factory });factory(require) 的
|
|
3
3
|
// require 解析浏览器端冻结模块表(react、@deepseek-ai/dsh-client-ui-primitives、
|
|
4
|
-
// @deepseek-ai/dsh-client-
|
|
4
|
+
// @deepseek-ai/dsh-client-runtime)。
|
|
5
5
|
//
|
|
6
6
|
// 架构完全对齐官方 @deepseek-ai/dsh-client-ui-settings-plugins:
|
|
7
7
|
// - 数据通道:settingsScope.bind({namespace})(官方 settings 域,dsh ≥0.1.0-rc.7
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
// 附带"浏览/测试"按钮槽);checkbox/radio 值型字段自绘但带同款徽章
|
|
13
13
|
// - 外壳:PluginCard 移植(折叠/未保存标记/只读提示/放弃/保存/保存失败)
|
|
14
14
|
// - 文案:locale 注册(zh/en),与官方同机制
|
|
15
|
-
// - 依赖:
|
|
15
|
+
// - 依赖:react/primitives(壳层 seed 词)+ dsh-client-runtime(动态包)
|
|
16
|
+
// + 代码级 5 服务,与官方 settings-plugins 一致
|
|
16
17
|
//
|
|
17
18
|
// 宿主保留的辅助路由(官方通道无法覆盖):
|
|
18
19
|
// POST /auto-open-web/pick-browser → 原生"浏览"对话框
|
|
@@ -55,20 +56,44 @@ window.__ModuleLoader__.load({
|
|
|
55
56
|
injectCss(EXTRA_TAG, EXTRA_CSS)
|
|
56
57
|
|
|
57
58
|
// ---- 官方共享模块(冻结表) ----
|
|
59
|
+
// 与官方 settings-plugins 同源:react/primitives 是壳层 seed 词,
|
|
60
|
+
// dsh-client-runtime 是动态包(经 /plugins/<pkg>/client.js 提供)。
|
|
61
|
+
// 各 require 独立容错:任一可选模块缺失不得拖垮其余模块(尤其 runtime)。
|
|
58
62
|
var prim = null
|
|
59
|
-
var form = null
|
|
60
63
|
var runtime = null
|
|
61
64
|
try {
|
|
62
65
|
prim = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
63
|
-
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.error('[auto-open-web] primitives require failed:', e !== null && e !== undefined && e.message !== undefined ? e.message : String(e))
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
64
70
|
runtime = require('@deepseek-ai/dsh-client-runtime')
|
|
65
71
|
} catch (e) {
|
|
66
|
-
console.error('[auto-open-web]
|
|
72
|
+
console.error('[auto-open-web] runtime require failed:', e !== null && e !== undefined && e.message !== undefined ? e.message : String(e))
|
|
67
73
|
}
|
|
68
74
|
var Button = prim !== null && prim.Button !== undefined ? prim.Button : null
|
|
69
75
|
var Input = prim !== null && prim.Input !== undefined ? prim.Input : null
|
|
70
76
|
var ChevronIcon = prim !== null && prim.IconChevronDownOutline14 !== undefined ? prim.IconChevronDownOutline14 : null
|
|
71
|
-
|
|
77
|
+
// 快照 store 契约与官方一致:{ getSnapshot, subscribe, set }。runtime 缺失
|
|
78
|
+
// 或版本不含 createSnapshotStore 时退化为内置最小实现(卡片仍可用)。
|
|
79
|
+
var createSnapshotStore = runtime !== null && runtime.createSnapshotStore !== undefined ? runtime.createSnapshotStore : fallbackSnapshotStore
|
|
80
|
+
function fallbackSnapshotStore(init) {
|
|
81
|
+
let value = init
|
|
82
|
+
const listeners = new Set()
|
|
83
|
+
return {
|
|
84
|
+
getSnapshot: () => value,
|
|
85
|
+
subscribe: (listener) => {
|
|
86
|
+
listeners.add(listener)
|
|
87
|
+
return () => {
|
|
88
|
+
listeners.delete(listener)
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
set: (next) => {
|
|
92
|
+
value = next
|
|
93
|
+
for (const listener of [...listeners]) listener(value)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
72
97
|
|
|
73
98
|
function ProxyInput(props) {
|
|
74
99
|
if (Input !== null) return React.createElement(Input, props)
|
package/lib/index.js
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
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
20
|
* settings.plugin.item 插槽)经官方 settings 域(settingsScope)读写本命名
|
|
@@ -28,6 +30,7 @@ import { spawn } from 'node:child_process';
|
|
|
28
30
|
import { existsSync, rmSync, writeFileSync } from 'node:fs';
|
|
29
31
|
import { createRequire } from 'node:module';
|
|
30
32
|
import { join } from 'node:path';
|
|
33
|
+
import { pathToFileURL } from 'node:url';
|
|
31
34
|
import { dshHome, appProfileDir, testProfileDir, instanceStateFile } from './paths.js';
|
|
32
35
|
import * as platform from './platform.js';
|
|
33
36
|
|
|
@@ -72,7 +75,7 @@ export const Config = z.object({
|
|
|
72
75
|
appWindow: z.boolean().default(true),
|
|
73
76
|
/** 窗口类型:webview2 = WebView2 宿主(独立进程,任务栏 DSH 图标,随 DSH 退出);
|
|
74
77
|
* browser = 浏览器 --app 专用实例(--user-data-dir 隔离)。所选类型不可用时
|
|
75
|
-
*
|
|
78
|
+
* 降级为最后的兜底:默认浏览器打开(官方 dsh web 同款,普通标签页)。 */
|
|
76
79
|
windowKind: z.union([z.const('webview2'), z.const('browser')]).default('webview2'),
|
|
77
80
|
/** 手动维护的浏览器可执行文件路径(单条;优先于内置候选 Edge → Chrome;仅 browser 模式使用)。 */
|
|
78
81
|
browserPath: z.string().default(''),
|
|
@@ -231,6 +234,66 @@ function spawnHostWindow(exe, url, iconPath) {
|
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
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
|
+
|
|
234
297
|
// ── 配置与历史迁移(共通) ─────────────────────────────────────────────────
|
|
235
298
|
|
|
236
299
|
/** 删除 v0.3 遗留的 state 文件(一次性清理,防孤儿数据)。 */
|
|
@@ -354,6 +417,8 @@ export const internals = {
|
|
|
354
417
|
buildIco,
|
|
355
418
|
resolveHostExe: platform.resolveHostExe,
|
|
356
419
|
spawnHostWindow,
|
|
420
|
+
getOpenPackage,
|
|
421
|
+
openDefaultBrowser,
|
|
357
422
|
/** 测试钩子:同步"窗口关闭时退出 DSH"生效标志(模拟设置保存)。 */
|
|
358
423
|
setExitWatcherEnabled(value) {
|
|
359
424
|
exitWatcherEnabled = value === true;
|
|
@@ -495,7 +560,18 @@ export function apply(ctx, config) {
|
|
|
495
560
|
const url = `http://127.0.0.1:${String(server.port)}`;
|
|
496
561
|
|
|
497
562
|
// 独立应用窗口:按 windowKind 显式选择(webview2 | browser),直接加载 GUI 根地址。
|
|
498
|
-
//
|
|
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
|
+
|
|
499
575
|
if (state.appWindow) {
|
|
500
576
|
if (state.windowKind === 'browser') {
|
|
501
577
|
const resolved = platform.resolveBrowserExe(state.browserPath);
|
|
@@ -509,21 +585,21 @@ export function apply(ctx, config) {
|
|
|
509
585
|
console.log(`[auto-open-web] opened browser app window (${resolved.browser} --app) at ${url}`);
|
|
510
586
|
return;
|
|
511
587
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
console.log('[auto-open-web] no Edge/Chrome executable found; not opening anything');
|
|
588
|
+
await fallbackOpen('browser app window launch failed');
|
|
589
|
+
return;
|
|
515
590
|
}
|
|
591
|
+
await fallbackOpen('no Edge/Chrome executable found');
|
|
516
592
|
return;
|
|
517
593
|
}
|
|
518
594
|
|
|
519
595
|
// webview2 模式
|
|
520
596
|
if (!platform.isWindows) {
|
|
521
|
-
|
|
597
|
+
await fallbackOpen('webview2 mode requires Windows');
|
|
522
598
|
return;
|
|
523
599
|
}
|
|
524
600
|
const hostExe = platform.resolveHostExe();
|
|
525
601
|
if (hostExe === null) {
|
|
526
|
-
|
|
602
|
+
await fallbackOpen('host exe not found');
|
|
527
603
|
return;
|
|
528
604
|
}
|
|
529
605
|
const iconPath = await ensureIconFile(server);
|
|
@@ -532,7 +608,7 @@ export function apply(ctx, config) {
|
|
|
532
608
|
console.log(`[auto-open-web] opened host window (DshAppWindow) at ${url}`);
|
|
533
609
|
return;
|
|
534
610
|
}
|
|
535
|
-
|
|
611
|
+
await fallbackOpen('host window launch failed');
|
|
536
612
|
return;
|
|
537
613
|
}
|
|
538
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": {
|