@dsh-sup/dsh-core-linux-x64 0.1.6-BETA.11 → 0.1.6-BETA.13

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/core.cjs CHANGED
@@ -947,7 +947,7 @@ var require_version = __commonJS({
947
947
  var fs2 = require("node:fs");
948
948
  var path2 = require("node:path");
949
949
  function guardVersion() {
950
- if (true) return String("0.1.6-BETA.11");
950
+ if (true) return String("0.1.6-BETA.13");
951
951
  try {
952
952
  return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
953
953
  } catch {
@@ -4639,23 +4639,66 @@ var require_desktop = __commonJS({
4639
4639
  "src/platform/os/desktop.js"(exports2, module2) {
4640
4640
  "use strict";
4641
4641
  var fs2 = require("node:fs");
4642
+ var os2 = require("node:os");
4643
+ var path2 = require("node:path");
4642
4644
  var PLATFORM = process.platform;
4643
- function hasX11Socket() {
4645
+ var X11_DIR = "/tmp/.X11-unix";
4646
+ function deps(o) {
4647
+ const ov = o || {};
4648
+ return {
4649
+ env: ov.env || process.env,
4650
+ readdir: ov.readdir || ((p) => fs2.readdirSync(p)),
4651
+ exists: ov.exists || ((p) => fs2.existsSync(p)),
4652
+ home: ov.home || os2.homedir(),
4653
+ uid: typeof process.getuid === "function" ? process.getuid() : 0
4654
+ };
4655
+ }
4656
+ function runtimeDir(env, uid) {
4657
+ return env.XDG_RUNTIME_DIR || "/run/user/" + uid;
4658
+ }
4659
+ function x11Displays(o) {
4660
+ const d = deps(o);
4644
4661
  try {
4645
- return fs2.readdirSync("/tmp/.X11-unix").some((f) => /^X\d+$/.test(f));
4662
+ return d.readdir(X11_DIR).filter((f) => /^X\d+$/.test(f)).map((f) => Number(f.slice(1))).sort((a, b) => a - b);
4646
4663
  } catch {
4647
- return false;
4664
+ return [];
4648
4665
  }
4649
4666
  }
4650
- function hasWaylandSocket() {
4651
- const uid = typeof process.getuid === "function" ? process.getuid() : 0;
4652
- const rt = process.env.XDG_RUNTIME_DIR || "/run/user/" + uid;
4667
+ function waylandSockets(o) {
4668
+ const d = deps(o);
4653
4669
  try {
4654
- return fs2.readdirSync(rt).some((f) => /^wayland-\d+$/.test(f));
4670
+ return d.readdir(runtimeDir(d.env, d.uid)).filter((f) => /^wayland-\d+$/.test(f)).sort();
4655
4671
  } catch {
4656
- return false;
4672
+ return [];
4657
4673
  }
4658
4674
  }
4675
+ function hasX11Socket(o) {
4676
+ return x11Displays(o).length > 0;
4677
+ }
4678
+ function hasWaylandSocket(o) {
4679
+ return waylandSockets(o).length > 0;
4680
+ }
4681
+ function sessionEnv(o) {
4682
+ const d = deps(o);
4683
+ const out = {};
4684
+ if (PLATFORM !== "linux") return out;
4685
+ if (!d.env.DISPLAY && !d.env.WAYLAND_DISPLAY) {
4686
+ const xs = x11Displays(d);
4687
+ if (xs.length) {
4688
+ out.DISPLAY = ":" + xs[0];
4689
+ const xauth = path2.join(d.home, ".Xauthority");
4690
+ if (d.exists(xauth)) out.XAUTHORITY = xauth;
4691
+ } else {
4692
+ const wl = waylandSockets(d);
4693
+ if (wl.length) out.WAYLAND_DISPLAY = wl[0];
4694
+ }
4695
+ }
4696
+ if (!d.env.DBUS_SESSION_BUS_ADDRESS) {
4697
+ const bus = path2.join(runtimeDir(d.env, d.uid), "bus");
4698
+ if (d.exists(bus)) out.DBUS_SESSION_BUS_ADDRESS = "unix:path=" + bus;
4699
+ }
4700
+ return out;
4701
+ }
4659
4702
  function sessionAvailable() {
4660
4703
  if (PLATFORM === "linux") {
4661
4704
  if (process.env.DISPLAY || process.env.WAYLAND_DISPLAY) return true;
@@ -4682,7 +4725,7 @@ var require_desktop = __commonJS({
4682
4725
  reason: "session-scoped-by-launcher"
4683
4726
  };
4684
4727
  }
4685
- module2.exports = { sessionAvailable, describe, PLATFORM };
4728
+ module2.exports = { sessionAvailable, sessionEnv, describe, x11Displays, waylandSockets, PLATFORM };
4686
4729
  }
4687
4730
  });
4688
4731
 
@@ -4755,8 +4798,9 @@ var require_capability_profile = __commonJS({
4755
4798
  autostart: true,
4756
4799
  // 期望 schtasks(实测覆写)
4757
4800
  frpExpose: true,
4758
- // 可打开,但只有注册表解析到默认浏览器时才有退出证据;退到 explorer.exe 调度器即恒返 0,
4759
- // 每次调用的证据档位由 openBrowser 的 confirmed/handedOff 如实回报,不在此处宣称。
4801
+ // 可打开,但 Windows 没有可信调度器(系统调度器只有 darwin 的 open 与 linux 的 xdg-open),
4802
+ // 只能直启探测层解析出的浏览器本体;浏览器已在运行时本次进程只转交地址,退出码与窗口出现与否无关,
4803
+ // 故该平台每次调用最多到 handedOff(判据见 browser.js#ownsItsWindow)。能力位只声明「能交出去」。
4760
4804
  openBrowser: true,
4761
4805
  hostService: "windows-service",
4762
4806
  guardAutostart: true,
@@ -5452,18 +5496,567 @@ var require_notify = __commonJS({
5452
5496
  }
5453
5497
  });
5454
5498
 
5499
+ // src/platform/os/browser-inventory.js
5500
+ var require_browser_inventory = __commonJS({
5501
+ "src/platform/os/browser-inventory.js"(exports2, module2) {
5502
+ "use strict";
5503
+ var fs2 = require("node:fs");
5504
+ var path2 = require("node:path");
5505
+ var os2 = require("node:os");
5506
+ var exec = require_exec();
5507
+ var { isExecutableFile } = require_exec_path();
5508
+ var PROBE_TIMEOUT_MS = 1500;
5509
+ function engineOf(bin) {
5510
+ const base = String(bin || "").toLowerCase().split(/[\\/]/).pop();
5511
+ if (/(chrome|chromium|msedge|edge|brave|vivaldi|opera|thorium)/.test(base)) return "chromium";
5512
+ if (/(firefox|librewolf|waterfox)/.test(base)) return "firefox";
5513
+ return "other";
5514
+ }
5515
+ function tokenizeExec(line) {
5516
+ const toks = [];
5517
+ let cur = "";
5518
+ let q = null;
5519
+ let esc = false;
5520
+ let has = false;
5521
+ for (const ch of String(line)) {
5522
+ if (esc) {
5523
+ cur += ch;
5524
+ esc = false;
5525
+ continue;
5526
+ }
5527
+ if (ch === "\\") {
5528
+ esc = true;
5529
+ has = true;
5530
+ continue;
5531
+ }
5532
+ if (q) {
5533
+ if (ch === q) q = null;
5534
+ else cur += ch;
5535
+ continue;
5536
+ }
5537
+ if (ch === "'" || ch === '"') {
5538
+ q = ch;
5539
+ has = true;
5540
+ continue;
5541
+ }
5542
+ if (/\s/.test(ch)) {
5543
+ if (has) {
5544
+ toks.push(cur);
5545
+ cur = "";
5546
+ has = false;
5547
+ }
5548
+ continue;
5549
+ }
5550
+ cur += ch;
5551
+ has = true;
5552
+ }
5553
+ if (has) toks.push(cur);
5554
+ return toks;
5555
+ }
5556
+ function parseExecLine(line) {
5557
+ let toks = tokenizeExec(line).filter((t) => t !== "%%" && !/^%[a-zA-Z]$/.test(t));
5558
+ if (toks[0] === "env") {
5559
+ let i = 1;
5560
+ while (i < toks.length && /=/.test(toks[i])) i++;
5561
+ toks = toks.slice(i);
5562
+ }
5563
+ if (!toks.length) return null;
5564
+ return { bin: toks[0], baseArgs: toks.slice(1) };
5565
+ }
5566
+ function regValueOf(out) {
5567
+ const m = String(out || "").match(/REG_(?:EXPAND_SZ|SZ)\s+(.*)/);
5568
+ if (!m) return null;
5569
+ return m[1].trim() || null;
5570
+ }
5571
+ function expandEnvVars(value, env) {
5572
+ const e = env || process.env;
5573
+ return String(value).replace(/%([^%]+)%/g, (all, name) => e[name] === void 0 ? all : e[name]);
5574
+ }
5575
+ function exeFromCmdLine(cmdLine) {
5576
+ const s = String(cmdLine || "");
5577
+ let m = s.match(/^\s*"([^"]+\.exe)"/i);
5578
+ if (m) return m[1];
5579
+ m = s.match(/^\s*(.*?\.exe)/i);
5580
+ return m ? m[1] : null;
5581
+ }
5582
+ function safeRegKeyPart(s) {
5583
+ return typeof s === "string" && s.length > 0 && s.length <= 200 && /^[\x20-\x7e]+$/.test(s) && !/[;|&`$()<>^"'\r\n]/.test(s);
5584
+ }
5585
+ function regValue(runner, note, key, name) {
5586
+ const args = name ? ["query", key, "/v", name] : ["query", key, "/ve"];
5587
+ const out = runner("reg.exe", args);
5588
+ const v = regValueOf(out);
5589
+ note(name ? key + " /v " + name : key, v ? "ok" : "empty");
5590
+ return v;
5591
+ }
5592
+ var REG_HIVE_ALIAS = { HKLM: "HKEY_LOCAL_MACHINE", HKCU: "HKEY_CURRENT_USER", HKCR: "HKEY_CLASSES_ROOT", HKU: "HKEY_USERS", HKCC: "HKEY_CURRENT_CONFIG" };
5593
+ function regKeyFull(key) {
5594
+ const s = String(key || "");
5595
+ const i = s.indexOf("\\");
5596
+ const root = i < 0 ? s : s.slice(0, i);
5597
+ return (REG_HIVE_ALIAS[root.toUpperCase()] || root) + (i < 0 ? "" : s.slice(i));
5598
+ }
5599
+ function regSubkeys(runner, note, key) {
5600
+ const out = runner("reg.exe", ["query", key]);
5601
+ if (!out) {
5602
+ note(key, "unreadable");
5603
+ return [];
5604
+ }
5605
+ const prefix = regKeyFull(key) + "\\";
5606
+ const names = [];
5607
+ for (const line of String(out).split(/\r?\n/)) {
5608
+ const l = line.trim();
5609
+ if (!/^HKEY_/i.test(l) || l.length <= prefix.length) continue;
5610
+ if (l.slice(0, prefix.length).toUpperCase() !== prefix.toUpperCase()) continue;
5611
+ const rest = l.slice(prefix.length);
5612
+ if (rest && !rest.includes("\\") && safeRegKeyPart(rest)) names.push(rest);
5613
+ }
5614
+ note(key, names.length ? names.length + " \u9879" : "\u65E0\u5B50\u952E");
5615
+ return names;
5616
+ }
5617
+ var WIN_APP_PATHS = ["msedge.exe", "chrome.exe", "firefox.exe", "brave.exe", "opera.exe", "vivaldi.exe", "chromium.exe", "thorium.exe", "librewolf.exe"];
5618
+ function winExeOfProgId(runner, note, progId, env) {
5619
+ if (!safeRegKeyPart(progId)) return null;
5620
+ for (const root of ["HKCU\\Software\\Classes", "HKLM\\Software\\Classes"]) {
5621
+ const cmd2 = regValue(runner, note, root + "\\" + progId + "\\shell\\open\\command");
5622
+ const exe = cmd2 ? exeFromCmdLine(expandEnvVars(cmd2, env)) : null;
5623
+ if (exe) return exe;
5624
+ }
5625
+ return null;
5626
+ }
5627
+ function probeWin(d) {
5628
+ const { runOut, env, canExec } = d;
5629
+ const notes = [];
5630
+ const note = (source, detail) => {
5631
+ notes.push({ source, detail });
5632
+ };
5633
+ const found = /* @__PURE__ */ new Map();
5634
+ const keyOf = (exe) => String(exe || "").toLowerCase().replace(/[\\/]/g, "\\");
5635
+ const push = (exe, how) => {
5636
+ const full = expandEnvVars(exe, env).trim();
5637
+ if (!/\.exe$/i.test(full)) {
5638
+ note(how, "\u6765\u6E90\u7ED9\u51FA\u7684\u4E0D\u662F exe \u8DEF\u5F84: " + full);
5639
+ return null;
5640
+ }
5641
+ const k = keyOf(full);
5642
+ const hit = found.get(k);
5643
+ if (hit) {
5644
+ if (!hit.sources.includes(how)) hit.sources.push(how);
5645
+ return hit;
5646
+ }
5647
+ if (!canExec(full)) {
5648
+ note(how, "\u6307\u5411\u7684\u6587\u4EF6\u4E0D\u53EF\u6267\u884C: " + full);
5649
+ return null;
5650
+ }
5651
+ const item = { id: k, name: path2.basename(full).replace(/\.exe$/i, ""), engine: engineOf(full), bin: full, source: how, sources: [how] };
5652
+ found.set(k, item);
5653
+ note(how, item.name);
5654
+ return item;
5655
+ };
5656
+ const progIds = /* @__PURE__ */ new Map();
5657
+ const addProgId = (pid, how) => {
5658
+ const s = String(pid || "").trim();
5659
+ if (!safeRegKeyPart(s)) return;
5660
+ if (progIds.get(s) === "userchoice") return;
5661
+ progIds.set(s, how);
5662
+ };
5663
+ let defaultId = null;
5664
+ let defaultSource = null;
5665
+ const DEF_RANK = { userchoice: 1, "scheme-association": 2, "only-installed": 9 };
5666
+ let defRank = 99;
5667
+ const setDefault = (item, how) => {
5668
+ const r = DEF_RANK[how] === void 0 ? 99 : DEF_RANK[how];
5669
+ if (item && r < defRank) {
5670
+ defRank = r;
5671
+ defaultId = item.id;
5672
+ defaultSource = how;
5673
+ }
5674
+ };
5675
+ const cmdToExe = (raw) => raw ? exeFromCmdLine(expandEnvVars(raw, env)) : null;
5676
+ const uc = regValue(runOut, note, "HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice", "ProgId");
5677
+ if (uc) addProgId(uc, "userchoice");
5678
+ for (const root of ["HKCU\\Software\\Classes", "HKLM\\Software\\Classes"]) {
5679
+ const pid = regValue(runOut, note, root + "\\https");
5680
+ if (pid) addProgId(pid, "scheme-association");
5681
+ const exe = cmdToExe(regValue(runOut, note, root + "\\https\\shell\\open\\command"));
5682
+ if (exe) setDefault(push(exe, "scheme-association"), "scheme-association");
5683
+ }
5684
+ for (const name of regSubkeys(runOut, note, "HKLM\\SOFTWARE\\Clients\\StartMenuInternet")) {
5685
+ addProgId(name, "startmenu-catalog");
5686
+ const exe = cmdToExe(regValue(runOut, note, "HKLM\\SOFTWARE\\Clients\\StartMenuInternet\\" + name + "\\shell\\open\\command")) || winExeOfProgId(runOut, note, name, env);
5687
+ if (exe) push(exe, "startmenu-catalog");
5688
+ }
5689
+ for (const root of ["HKLM\\SOFTWARE", "HKCU\\SOFTWARE"]) {
5690
+ for (const capPath of regValueTargets(runOut, note, root + "\\RegisteredApplications")) {
5691
+ const https = regValue(runOut, note, capPath + "\\UrlAssociations\\https");
5692
+ const sid = regValue(runOut, note, capPath, "StartMenuInternet");
5693
+ const pid = https || sid;
5694
+ if (pid) addProgId(pid, "registered-apps");
5695
+ }
5696
+ }
5697
+ for (const exe of WIN_APP_PATHS) {
5698
+ for (const root of ["HKCU", "HKLM"]) {
5699
+ const v = regValue(runOut, note, root + "\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + exe);
5700
+ if (v && push(v, "app-paths")) break;
5701
+ }
5702
+ }
5703
+ for (const [pid, how] of progIds) {
5704
+ const exe = winExeOfProgId(runOut, note, pid, env);
5705
+ if (!exe) continue;
5706
+ const item = push(exe, how);
5707
+ if (how === "userchoice") setDefault(item, "userchoice");
5708
+ }
5709
+ if (found.size === 1) setDefault([...found.values()][0], "only-installed");
5710
+ return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5711
+ }
5712
+ function regValueTargets(runOut, note, key) {
5713
+ const out = runOut("reg.exe", ["query", key]);
5714
+ if (!out) {
5715
+ note(key, "unreadable");
5716
+ return [];
5717
+ }
5718
+ const paths = [];
5719
+ for (const line of String(out).split(/\r?\n/)) {
5720
+ const m = line.trim().match(/REG_SZ\s+(.*)/);
5721
+ if (!m) continue;
5722
+ const rel = m[1].trim();
5723
+ if (!safeRegKeyPart(rel)) continue;
5724
+ const root = key.slice(0, key.indexOf("\\"));
5725
+ paths.push(root + "\\" + rel);
5726
+ }
5727
+ note(key, paths.length ? paths.length + " \u4E2A\u80FD\u529B\u8DEF\u5F84" : "\u65E0\u503C");
5728
+ return paths;
5729
+ }
5730
+ var MAC_JXA_LIST = [
5731
+ "ObjC.import('Foundation');",
5732
+ "var ws=$.NSWorkspace.sharedWorkspace;",
5733
+ "var u=$.NSURL.URLWithString('https://dsh.local/probe');",
5734
+ "var out=[];var def='';",
5735
+ "try{var d=ws.URLForApplicationToOpenURL(u); if(!d.isNil()) def=ObjC.unwrap(d.path)||'';}catch(e){}",
5736
+ "var list=null;",
5737
+ "try{list=ws.urlsForApplicationsToOpenURL(u);}catch(e){}",
5738
+ "if(list===null||typeof list.count!=='number'){if(def){out.push(def+'\\t\\t1');}}else{",
5739
+ "var n=Number(list.count);",
5740
+ "for(var i=0;i<n;i++){",
5741
+ "var p=ObjC.unwrap(list.objectAtIndex(i).path)||''; if(!p) continue;",
5742
+ "var exe='';var bid='';",
5743
+ "var b=$.NSBundle.bundleWithPath(p);",
5744
+ "if(!b.isNil()){exe=ObjC.unwrap(b.executablePath)||'';bid=ObjC.unwrap(b.bundleIdentifier)||'';}",
5745
+ "out.push((exe||p)+'\\t'+bid+'\\t'+(p&&def&&p===def?1:0));}",
5746
+ "}",
5747
+ // 末句必须是裸表达式:osascript 取的是最后一条**语句**的值,if/else 语句不产结果,
5748
+ // 写在分支里会让整段查询输出空字符串(表现为「系统未报任何可用应用」)。
5749
+ "out.join('\\n');"
5750
+ ].join("");
5751
+ function probeMac(d) {
5752
+ const { runOut, canExec } = d;
5753
+ const notes = [];
5754
+ const note = (source, detail) => {
5755
+ notes.push({ source, detail });
5756
+ };
5757
+ const out = runOut("osascript", ["-l", "JavaScript", "-e", MAC_JXA_LIST]);
5758
+ if (!out || !out.trim()) {
5759
+ note("launchservices", "\u67E5\u8BE2\u65E0\u8F93\u51FA\uFF08\u65E7\u7CFB\u7EDF\u6216\u88AB\u6743\u9650\u62E6\u622A\uFF09");
5760
+ return { browsers: [], defaultId: null, defaultSource: null, probed: notes };
5761
+ }
5762
+ const keyOf = (exe) => String(exe || "").toLowerCase();
5763
+ const found = /* @__PURE__ */ new Map();
5764
+ let defaultId = null;
5765
+ let defaultSource = null;
5766
+ for (const line of out.trim().split("\n")) {
5767
+ const parts = line.split(" ");
5768
+ const bin = (parts[0] || "").trim();
5769
+ if (!bin) continue;
5770
+ if (!canExec(bin)) {
5771
+ note("launchservices", "\u4E0D\u53EF\u6267\u884C: " + bin);
5772
+ continue;
5773
+ }
5774
+ const k = keyOf(bin);
5775
+ if (!found.has(k)) found.set(k, { id: k, name: path2.basename(bin), engine: engineOf(bin), bin, source: "launchservices", sources: ["launchservices"], isDefault: false });
5776
+ if (parts[2] === "1") {
5777
+ found.get(k).isDefault = true;
5778
+ if (!defaultId) {
5779
+ defaultId = k;
5780
+ defaultSource = "launchservices";
5781
+ }
5782
+ }
5783
+ }
5784
+ note("launchservices", found.size ? found.size + " \u4E2A\u53EF\u6253\u5F00 https \u7684\u5E94\u7528" : "\u7CFB\u7EDF\u672A\u62A5\u4EFB\u4F55\u53EF\u7528\u5E94\u7528");
5785
+ if (!defaultId && found.size === 1) {
5786
+ defaultId = [...found.keys()][0];
5787
+ defaultSource = "only-installed";
5788
+ }
5789
+ return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5790
+ }
5791
+ var PJ = path2.posix.join;
5792
+ function desktopDirs(env, home) {
5793
+ const dataHome = env.XDG_DATA_HOME || PJ(home, ".local", "share");
5794
+ const dataDirs = String(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":").filter(Boolean);
5795
+ const dirs = [[dataHome, "xdg"], ...dataDirs.map((p) => [p, "xdg"])];
5796
+ dirs.push([PJ(home, ".local", "share"), "xdg"]);
5797
+ dirs.push(["/var/lib/flatpak/exports/share", "flatpak"]);
5798
+ dirs.push([PJ(home, ".local", "share", "flatpak", "exports", "share"), "flatpak"]);
5799
+ dirs.push(["/var/lib/snapd/desktop", "snap"]);
5800
+ const seen = [];
5801
+ return dirs.map(([root, kind]) => ({ dir: PJ(root, "applications"), kind })).filter((x) => {
5802
+ if (seen.includes(x.dir)) return false;
5803
+ seen.push(x.dir);
5804
+ return true;
5805
+ });
5806
+ }
5807
+ function browserFromDesktop(text, kind, file) {
5808
+ let inMain = false;
5809
+ const got = {};
5810
+ for (const line of String(text).split(/\r?\n/)) {
5811
+ if (/^\[Desktop Entry\]\s*$/.test(line)) {
5812
+ inMain = true;
5813
+ continue;
5814
+ }
5815
+ if (/^\[/.test(line)) break;
5816
+ if (!inMain) continue;
5817
+ const m = line.match(/^(Exec|Name|TryExec|Categories)=(.*)$/);
5818
+ if (!m) continue;
5819
+ if (m[1] === "Name" && got.Name) continue;
5820
+ got[m[1]] = m[2].trim();
5821
+ }
5822
+ if (!got.Exec) return null;
5823
+ const categories = String(got.Categories || "");
5824
+ const parsed = parseExecLine(got.Exec);
5825
+ if (!parsed) return null;
5826
+ const engine = engineOf(parsed.bin);
5827
+ const claimsBrowser = /(^|;)WebBrowser(;|$)/.test(categories);
5828
+ if (!claimsBrowser && engine === "other") return null;
5829
+ if (engine === "other") return null;
5830
+ return {
5831
+ // id 恒为归一后的可执行文件路径:defaultId 靠它在清单里定位条目,三平台同一契约
5832
+ id: parsed.bin.toLowerCase(),
5833
+ name: got.Name || path2.basename(file, ".desktop"),
5834
+ engine,
5835
+ bin: parsed.bin,
5836
+ baseArgs: parsed.baseArgs,
5837
+ source: kind,
5838
+ desktopFile: file
5839
+ };
5840
+ }
5841
+ function resolveLinuxBin(bin, canExec, env) {
5842
+ const b = String(bin || "").trim();
5843
+ if (!b) return null;
5844
+ if (b.startsWith("/")) return canExec(b) ? b : null;
5845
+ if (b.includes("/")) return null;
5846
+ for (const dir of String(env.PATH || "").split(":")) {
5847
+ if (!dir) continue;
5848
+ const p = PJ(dir.replace(/\/+$/, ""), b);
5849
+ if (canExec(p)) return p;
5850
+ }
5851
+ return null;
5852
+ }
5853
+ function probeLinux(d) {
5854
+ const { runOut, readFile, exists, listDir, canExec, env, home } = d;
5855
+ const notes = [];
5856
+ const note = (source, detail) => {
5857
+ notes.push({ source, detail });
5858
+ };
5859
+ const found = /* @__PURE__ */ new Map();
5860
+ const usable = (item) => {
5861
+ const resolved = resolveLinuxBin(item.bin, canExec, env);
5862
+ if (!resolved) {
5863
+ note(item.source, "\u4E0D\u53EF\u6267\u884C: " + item.bin);
5864
+ return null;
5865
+ }
5866
+ const k = resolved.toLowerCase();
5867
+ const hit = found.get(k);
5868
+ if (hit) {
5869
+ if (!hit.sources.includes(item.source)) hit.sources.push(item.source);
5870
+ return hit;
5871
+ }
5872
+ const v = Object.assign({}, item, { id: k, bin: resolved, sources: [item.source] });
5873
+ found.set(k, v);
5874
+ return v;
5875
+ };
5876
+ for (const { dir, kind } of desktopDirs(env, home)) {
5877
+ let names = [];
5878
+ try {
5879
+ names = listDir(dir).filter((f) => f.endsWith(".desktop"));
5880
+ } catch {
5881
+ continue;
5882
+ }
5883
+ for (const f of names) {
5884
+ let text = null;
5885
+ try {
5886
+ text = readFile(PJ(dir, f));
5887
+ } catch {
5888
+ continue;
5889
+ }
5890
+ const item = browserFromDesktop(text, kind, f);
5891
+ if (item) usable(item);
5892
+ }
5893
+ }
5894
+ let defaultId = null;
5895
+ let defaultSource = null;
5896
+ const fromMime = linuxDefaultFromMimeApps(readFile, exists, env, home, note);
5897
+ if (fromMime) {
5898
+ const item = usable(fromMime);
5899
+ if (item) {
5900
+ defaultId = item.id;
5901
+ defaultSource = "mimeapps";
5902
+ }
5903
+ }
5904
+ const id = runOut("xdg-settings", ["get", "default-web-browser"]);
5905
+ const desktopId = id && String(id).trim();
5906
+ if (!defaultId && desktopId && /^[A-Za-z0-9._-]+\.desktop$/.test(desktopId)) {
5907
+ for (const { dir } of desktopDirs(env, home)) {
5908
+ const p = PJ(dir, desktopId);
5909
+ let text = null;
5910
+ try {
5911
+ if (exists(p)) text = readFile(p);
5912
+ } catch {
5913
+ continue;
5914
+ }
5915
+ if (text === null) continue;
5916
+ const item = browserFromDesktop(text, "xdg-settings", desktopId);
5917
+ const used = item && usable(item);
5918
+ if (used) {
5919
+ defaultId = used.id;
5920
+ defaultSource = "xdg-settings";
5921
+ break;
5922
+ }
5923
+ }
5924
+ }
5925
+ if (!desktopId) note("xdg-settings", "\u65E0\u8F93\u51FA");
5926
+ else if (defaultSource === "xdg-settings") note("xdg-settings", "\u9ED8\u8BA4=" + defaultId);
5927
+ else note("xdg-settings", "\u62A5\u51FA " + desktopId + (defaultId ? "\uFF08\u9ED8\u8BA4\u5DF2\u7531 " + defaultSource + " \u5B9A\u51FA\uFF09" : " \u4F46\u4E0D\u53EF\u7528"));
5928
+ if (!defaultId && found.size === 1) {
5929
+ defaultId = [...found.keys()][0];
5930
+ defaultSource = "only-installed";
5931
+ }
5932
+ return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5933
+ }
5934
+ function linuxDefaultFromMimeApps(readFile, exists, env, home, note) {
5935
+ const cfgHome = env.XDG_CONFIG_HOME || PJ(home, ".config");
5936
+ const cfgDirs = String(env.XDG_CONFIG_DIRS || "/etc/xdg").split(":").filter(Boolean);
5937
+ const dataDirs = String(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":").filter(Boolean);
5938
+ const files = [
5939
+ PJ(cfgHome, "mimeapps.list"),
5940
+ ...cfgDirs.map((d) => PJ(d, "mimeapps.list")),
5941
+ ...dataDirs.map((d) => PJ(d, "applications", "mimeapps.list"))
5942
+ ];
5943
+ for (const f of files) {
5944
+ if (!exists(f)) continue;
5945
+ let text = null;
5946
+ try {
5947
+ text = readFile(f);
5948
+ } catch {
5949
+ continue;
5950
+ }
5951
+ const id = mimeAppsDefault(text);
5952
+ if (!id) continue;
5953
+ note("mimeapps", path2.basename(f) + " -> " + id);
5954
+ for (const { dir, kind } of desktopDirs(env, home)) {
5955
+ const p = PJ(dir, id);
5956
+ if (!exists(p)) continue;
5957
+ let dt = null;
5958
+ try {
5959
+ dt = readFile(p);
5960
+ } catch {
5961
+ continue;
5962
+ }
5963
+ const item = browserFromDesktop(dt, kind, id);
5964
+ if (item) return item;
5965
+ }
5966
+ return null;
5967
+ }
5968
+ note("mimeapps", "\u65E0\u9ED8\u8BA4\u9879");
5969
+ return null;
5970
+ }
5971
+ function mimeAppsDefault(text) {
5972
+ let inDefault = false;
5973
+ for (const line of String(text || "").split(/\r?\n/)) {
5974
+ if (/^\s*\[/.test(line)) {
5975
+ inDefault = /^\s*\[Default Applications\]\s*$/.test(line);
5976
+ continue;
5977
+ }
5978
+ if (!inDefault) continue;
5979
+ const m = line.match(/^\s*x-scheme-handler\/https?\s*=\s*(.*)$/i);
5980
+ if (!m) continue;
5981
+ const ids = m[1].split(";").map((s) => s.trim()).filter((s) => /^[A-Za-z0-9._-]+\.desktop$/.test(s));
5982
+ if (ids.length) return ids[0];
5983
+ }
5984
+ return null;
5985
+ }
5986
+ function probe(platform, d) {
5987
+ const pl = platform || process.platform;
5988
+ const dd = Object.assign({}, d, { canExec: d.canExec || ((p) => isExecutableFile(p, pl)) });
5989
+ if (pl === "win32") return probeWin(dd);
5990
+ if (pl === "darwin") return probeMac(dd);
5991
+ return probeLinux(dd);
5992
+ }
5993
+ var CACHE = /* @__PURE__ */ Object.create(null);
5994
+ function inventory(platform, o) {
5995
+ const ov = o || {};
5996
+ const pl = platform || process.platform;
5997
+ const now = ov.now || (() => Date.now());
5998
+ const ttl = ov.ttlMs === void 0 ? 6e4 : ov.ttlMs;
5999
+ const cached = CACHE[pl];
6000
+ if (!ov.force && cached && now() - cached.at < ttl) return Object.assign({ cached: true }, cached.value);
6001
+ const runOut = ov.runOut || ((bin, args) => exec.runOut(bin, args, { timeoutMs: PROBE_TIMEOUT_MS }));
6002
+ const readFile = ov.readFile || ((p) => fs2.readFileSync(p, "utf8"));
6003
+ const exists = ov.exists || ((p) => fs2.existsSync(p));
6004
+ const listDir = ov.listDir || ((p) => fs2.readdirSync(p));
6005
+ let value;
6006
+ try {
6007
+ value = probe(pl, { runOut, readFile, exists, listDir, canExec: ov.canExec, env: ov.env || process.env, home: ov.home || os2.homedir() });
6008
+ } catch (e) {
6009
+ value = { browsers: [], defaultId: null, defaultSource: null, probed: [{ source: "probe-error", detail: String(e && e.message || e) }] };
6010
+ }
6011
+ value.at = now();
6012
+ value.platform = pl;
6013
+ value.browsers.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
6014
+ CACHE[pl] = { at: now(), value };
6015
+ return Object.assign({ cached: false }, value);
6016
+ }
6017
+ function invalidate(platform) {
6018
+ if (platform) delete CACHE[platform];
6019
+ else for (const k of Object.keys(CACHE)) delete CACHE[k];
6020
+ }
6021
+ module2.exports = {
6022
+ inventory,
6023
+ invalidate,
6024
+ probe,
6025
+ probeWin,
6026
+ probeMac,
6027
+ probeLinux,
6028
+ engineOf,
6029
+ tokenizeExec,
6030
+ parseExecLine,
6031
+ regValueOf,
6032
+ expandEnvVars,
6033
+ exeFromCmdLine,
6034
+ regSubkeys,
6035
+ regKeyFull,
6036
+ regValueTargets,
6037
+ safeRegKeyPart,
6038
+ browserFromDesktop,
6039
+ desktopDirs,
6040
+ resolveLinuxBin,
6041
+ mimeAppsDefault,
6042
+ linuxDefaultFromMimeApps,
6043
+ WIN_APP_PATHS,
6044
+ PROBE_TIMEOUT_MS
6045
+ };
6046
+ }
6047
+ });
6048
+
5455
6049
  // src/platform/os/browser.js
5456
6050
  var require_browser = __commonJS({
5457
6051
  "src/platform/os/browser.js"(exports2, module2) {
5458
6052
  "use strict";
5459
- var fs2 = require("node:fs");
5460
6053
  var path2 = require("node:path");
5461
- var os2 = require("node:os");
5462
6054
  var spawnOS = require_spawn();
5463
6055
  var desktop = require_desktop();
5464
6056
  var CAPABILITY_PROFILES = require_capability_profile();
5465
6057
  var { resolveExecutable, isExecutableFile } = require_exec_path();
5466
- var exec = require_exec();
6058
+ var detector = require_browser_inventory();
6059
+ var { engineOf } = detector;
5467
6060
  function isSafeHttpUrl(url) {
5468
6061
  try {
5469
6062
  const u = new URL(String(url));
@@ -5475,7 +6068,7 @@ var require_browser = __commonJS({
5475
6068
  function openCommand(platform, url) {
5476
6069
  const pl = platform || process.platform;
5477
6070
  if (pl === "darwin") return { cmd: "open", args: [url] };
5478
- if (pl === "win32") return { cmd: "explorer.exe", args: [url] };
6071
+ if (pl === "win32") return null;
5479
6072
  return { cmd: "xdg-open", args: [url] };
5480
6073
  }
5481
6074
  var SUPPORTED_OPEN_PLATFORMS = Object.keys(CAPABILITY_PROFILES).filter((k) => CAPABILITY_PROFILES[k].openBrowser === true);
@@ -5496,26 +6089,58 @@ var require_browser = __commonJS({
5496
6089
  };
5497
6090
  }
5498
6091
  var CONFIRMED_TEXT = "\u5DF2\u5728\u7CFB\u7EDF\u6D4F\u89C8\u5668\u6253\u5F00";
5499
- var HANDEDOFF_TEXT = "\u5DF2\u628A\u5730\u5740\u4EA4\u7ED9\u7CFB\u7EDF\uFF0C\u4F46\u65E0\u6CD5\u786E\u8BA4\u6D4F\u89C8\u5668\u7A97\u53E3\u662F\u5426\u51FA\u73B0";
6092
+ var HANDEDOFF_TEXT = "\u5DF2\u628A\u5730\u5740\u4EA4\u7ED9\u7CFB\u7EDF\uFF0C\u4F46\u8FD9\u6B21\u542F\u52A8\u62FF\u4E0D\u5230\u7A97\u53E3\u662F\u5426\u51FA\u73B0\u7684\u8BC1\u636E";
5500
6093
  var FAILURE_TEXT = {
5501
6094
  "unsafe-url": "\u5730\u5740\u4E0D\u662F http(s) \u7EDD\u5BF9 URL\uFF0C\u5DF2\u62D2\u7EDD\u4EA4\u7ED9\u6D4F\u89C8\u5668",
5502
- "no-launcher": "\u672A\u627E\u5230\u53EF\u7528\u7684\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00\u8BE5\u5730\u5740",
6095
+ "no-launcher": "\u672A\u80FD\u786E\u5B9A\u8BE5\u7528\u54EA\u4E2A\u6D4F\u89C8\u5668\u6253\u5F00\uFF08\u7CFB\u7EDF\u91CC\u8BFB\u4E0D\u5230\u9ED8\u8BA4\u6D4F\u89C8\u5668\u8BBE\u7F6E\uFF09\uFF0C\u8BF7\u5728\u7CFB\u7EDF\u8BBE\u7F6E\u91CC\u6307\u5B9A\u9ED8\u8BA4\u6D4F\u89C8\u5668\u6216\u624B\u52A8\u6253\u5F00\u8BE5\u5730\u5740",
5503
6096
  "spawn-failed": "\u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25\uFF08\u7CFB\u7EDF\u62D2\u7EDD\u4E86\u8BE5\u547D\u4EE4\uFF09",
5504
- "exit-nonzero": "\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\u4EE5\u975E 0 \u9000\u51FA\uFF0C\u7A97\u53E3\u672A\u51FA\u73B0",
5505
- "killed-by-signal": "\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\u88AB\u7CFB\u7EDF\u7EC8\u6B62",
6097
+ "exit-nonzero": "\u7CFB\u7EDF\u62D2\u7EDD\u4E86\u8FD9\u4E2A\u5730\u5740\uFF08\u542F\u52A8\u547D\u4EE4\u975E 0 \u9000\u51FA\uFF09\uFF0C\u7A97\u53E3\u672A\u51FA\u73B0",
6098
+ "killed-by-signal": "\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\u88AB\u7CFB\u7EDF\u7EC8\u6B62\uFF0C\u7A97\u53E3\u672A\u51FA\u73B0",
5506
6099
  "no-desktop-session": "\u5F53\u524D\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\uFF0C\u65E0\u6CD5\u8C03\u8D77\u6D4F\u89C8\u5668",
5507
6100
  "unsupported-platform": "\u5F53\u524D\u5E73\u53F0\u4E0D\u5728\u4EA7\u54C1\u652F\u6301\u7684\u684C\u9762\u5E73\u53F0\u5185\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00\u8BE5\u5730\u5740"
5508
6101
  };
6102
+ function ownsItsWindow(via, platform) {
6103
+ if (via !== "dispatcher") return false;
6104
+ return platform !== "win32";
6105
+ }
6106
+ function pickLauncher(platform, inv) {
6107
+ const list = inv && Array.isArray(inv.browsers) ? inv.browsers : [];
6108
+ const id = inv && inv.defaultId;
6109
+ const hit = id ? list.find((b) => b.id === id) : null;
6110
+ if (hit) return { browser: hit, how: inv && inv.defaultSource || "default" };
6111
+ if (list.length === 1) return { browser: list[0], how: "only-installed" };
6112
+ return { browser: null, how: list.length ? "no-default" : "none-found" };
6113
+ }
6114
+ function formOfBin(pl, b) {
6115
+ const engine = b && b.bin ? engineOf(b.bin) : "other";
6116
+ return { engine, direct: !!b && (engine !== "other" || pl === "win32") };
6117
+ }
5509
6118
  function openPlan(platform, url, opts) {
5510
6119
  const o = opts || {};
5511
6120
  const pl = platform || process.platform;
5512
- const db = o.defaultBrowser;
5513
- const engine = db && db.bin ? engineOf(db.bin) : "other";
5514
- if (engine === "chromium" || engine === "firefox") {
5515
- return { bin: db.bin, engine, via: "browser", baseArgs: (db.baseArgs || []).concat([url]), trustExit: true };
6121
+ const picked = o.pick || pickLauncher(pl, o.inventory);
6122
+ const b = picked.browser;
6123
+ const form = formOfBin(pl, b);
6124
+ if (form.direct) {
6125
+ return {
6126
+ bin: b.bin,
6127
+ engine: form.engine,
6128
+ via: "browser",
6129
+ pick: picked.how,
6130
+ baseArgs: (b.baseArgs || []).concat([url]),
6131
+ exitIsEvidence: ownsItsWindow("browser", pl)
6132
+ };
5516
6133
  }
5517
6134
  const c = openCommand(pl, url);
5518
- return { bin: c.cmd, engine: "other", via: "dispatcher", baseArgs: c.args, trustExit: pl !== "win32" };
6135
+ if (!c) return { bin: null, engine: form.engine, via: "none", pick: picked.how, baseArgs: [], exitIsEvidence: false };
6136
+ return {
6137
+ bin: c.cmd,
6138
+ engine: "other",
6139
+ via: "dispatcher",
6140
+ pick: picked.how,
6141
+ baseArgs: c.args,
6142
+ exitIsEvidence: ownsItsWindow("dispatcher", pl)
6143
+ };
5519
6144
  }
5520
6145
  function observeSpawn(child, windowMs, setTimeoutFn) {
5521
6146
  return new Promise((resolve) => {
@@ -5534,6 +6159,21 @@ var require_browser = __commonJS({
5534
6159
  });
5535
6160
  }
5536
6161
  var OPEN_OBSERVE_MS = 1500;
6162
+ function launchDiagnostics(inv, plan, how) {
6163
+ const found = (inv && inv.browsers || []).map((b) => ({
6164
+ name: b.name,
6165
+ engine: b.engine || engineOf(b.bin),
6166
+ via: (b.sources && b.sources.length ? b.sources : [b.source || "unknown"]).join("+")
6167
+ }));
6168
+ return {
6169
+ platform: inv && inv.platform || null,
6170
+ pick: how || plan && plan.pick || "none",
6171
+ bin: plan && plan.bin || null,
6172
+ default: inv && inv.defaultId ? { id: inv.defaultId, source: inv.defaultSource || null } : null,
6173
+ found,
6174
+ probed: inv && inv.probed || []
6175
+ };
6176
+ }
5537
6177
  async function openBrowser(url, o) {
5538
6178
  const opts = o || {};
5539
6179
  const pl = opts.platform || process.platform;
@@ -5547,16 +6187,27 @@ var require_browser = __commonJS({
5547
6187
  const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetachedIgnored;
5548
6188
  const avail = typeof opts.binAvailable === "function" ? opts.binAvailable : binAvailable;
5549
6189
  const observe = typeof opts.observe === "function" ? opts.observe : observeSpawn;
5550
- let db;
5551
- if ("defaultBrowser" in opts) db = opts.defaultBrowser;
5552
- else if (typeof opts.resolveDefault === "function") db = opts.resolveDefault(pl, opts.resolveDeps || {});
5553
- else db = resolveDefaultBrowser(pl, opts.resolveDeps || {});
5554
- const plan = openPlan(pl, url, { defaultBrowser: db });
5555
- const evidence = { bin: plan.bin, engine: plan.engine, via: plan.via, exitCode: null, exitSignal: null, error: null };
6190
+ const resolveInv = typeof opts.resolveInventory === "function" ? opts.resolveInventory : ((platform, deps) => detector.inventory(platform, deps));
6191
+ const inv = "inventory" in opts ? opts.inventory : resolveInv(pl, opts.resolveDeps || {});
6192
+ const plan = openPlan(pl, url, { inventory: inv, pick: opts.pick });
6193
+ const diagnostics = launchDiagnostics(inv, plan, plan.pick);
6194
+ const evidence = {
6195
+ bin: plan.bin,
6196
+ engine: plan.engine,
6197
+ via: plan.via,
6198
+ ownsWindow: plan.exitIsEvidence === true,
6199
+ exitCode: null,
6200
+ exitSignal: null,
6201
+ error: null,
6202
+ diagnostics
6203
+ };
5556
6204
  const desktopAvailable = typeof opts.desktopAvailable === "function" ? opts.desktopAvailable : desktop.sessionAvailable;
5557
6205
  if (pl === "linux" && !desktopAvailable()) {
5558
6206
  return outcome({ ok: false, reason: "no-desktop-session", url, evidence });
5559
6207
  }
6208
+ if (!plan.bin) {
6209
+ return outcome({ ok: false, reason: "no-launcher", url, evidence });
6210
+ }
5560
6211
  if (!avail(plan.bin)) {
5561
6212
  return outcome({ ok: false, reason: "no-launcher", url, evidence });
5562
6213
  }
@@ -5573,7 +6224,8 @@ var require_browser = __commonJS({
5573
6224
  evidence.exitCode = seen.code === void 0 ? null : seen.code;
5574
6225
  evidence.exitSignal = seen.signal === void 0 ? null : seen.signal;
5575
6226
  if (seen.stage === "error") return outcome({ ok: false, reason: "spawn-failed", url, evidence });
5576
- if (seen.stage === "exit" && (seen.code !== 0 || seen.signal)) {
6227
+ const exitDecides = seen.stage === "exit" && plan.exitIsEvidence === true;
6228
+ if (exitDecides && (seen.code !== 0 || seen.signal)) {
5577
6229
  return outcome({
5578
6230
  ok: false,
5579
6231
  reason: seen.signal ? "killed-by-signal" : "exit-nonzero",
@@ -5582,8 +6234,29 @@ var require_browser = __commonJS({
5582
6234
  error: FAILURE_TEXT[seen.signal ? "killed-by-signal" : "exit-nonzero"] + "\uFF08" + (seen.signal || seen.code) + "\uFF09"
5583
6235
  });
5584
6236
  }
5585
- const exitEvidence = seen.stage === "exit" && plan.trustExit;
5586
- return outcome({ ok: true, confirmed: exitEvidence, handedOff: !exitEvidence, url, evidence });
6237
+ return outcome({ ok: true, confirmed: exitDecides, handedOff: !exitDecides, url, evidence });
6238
+ }
6239
+ function listBrowsers(o) {
6240
+ const ov = o || {};
6241
+ const inv = detector.inventory(ov.platform, { force: ov.force === true });
6242
+ return {
6243
+ ok: true,
6244
+ platform: inv.platform,
6245
+ cached: inv.cached === true,
6246
+ default: inv.defaultId ? { id: inv.defaultId, source: inv.defaultSource || null } : null,
6247
+ browsers: (inv.browsers || []).map((b) => ({
6248
+ id: b.id,
6249
+ name: b.name,
6250
+ engine: b.engine || engineOf(b.bin),
6251
+ bin: b.bin,
6252
+ sources: b.sources && b.sources.length ? b.sources : [b.source],
6253
+ isDefault: b.id === inv.defaultId
6254
+ })),
6255
+ probed: inv.probed || []
6256
+ };
6257
+ }
6258
+ function invalidateBrowsers(platform) {
6259
+ return detector.invalidate(platform);
5587
6260
  }
5588
6261
  function _spawnDetachedIgnored(bin, args) {
5589
6262
  try {
@@ -5614,178 +6287,37 @@ var require_browser = __commonJS({
5614
6287
  child.unref();
5615
6288
  return child;
5616
6289
  }
5617
- function engineOf(bin) {
5618
- const base = String(bin || "").toLowerCase().split(/[\\/]/).pop();
5619
- if (/(chrome|chromium|msedge|edge|brave|vivaldi|opera|thorium)/.test(base)) return "chromium";
5620
- if (/(firefox|librewolf|waterfox)/.test(base)) return "firefox";
5621
- return "other";
5622
- }
5623
- function tokenizeExec(line) {
5624
- const toks = [];
5625
- let cur = "";
5626
- let q = null;
5627
- let esc = false;
5628
- let has = false;
5629
- for (const ch of String(line)) {
5630
- if (esc) {
5631
- cur += ch;
5632
- esc = false;
5633
- continue;
5634
- }
5635
- if (ch === "\\") {
5636
- esc = true;
5637
- has = true;
5638
- continue;
5639
- }
5640
- if (q) {
5641
- if (ch === q) q = null;
5642
- else cur += ch;
5643
- continue;
5644
- }
5645
- if (ch === "'" || ch === '"') {
5646
- q = ch;
5647
- has = true;
5648
- continue;
5649
- }
5650
- if (/\s/.test(ch)) {
5651
- if (has) {
5652
- toks.push(cur);
5653
- cur = "";
5654
- has = false;
5655
- }
5656
- continue;
5657
- }
5658
- cur += ch;
5659
- has = true;
5660
- }
5661
- if (has) toks.push(cur);
5662
- return toks;
5663
- }
5664
- function parseExecLine(line) {
5665
- let toks = tokenizeExec(line).filter((t) => t !== "%%" && !/^%[a-zA-Z]$/.test(t));
5666
- if (toks[0] === "env") {
5667
- let i = 1;
5668
- while (i < toks.length && /=/.test(toks[i])) i++;
5669
- toks = toks.slice(i);
5670
- }
5671
- if (!toks.length) return null;
5672
- return { bin: toks[0], baseArgs: toks.slice(1) };
5673
- }
5674
- function regValueOf(out) {
5675
- const m = String(out || "").match(/REG_SZ\s+(.*)/);
5676
- return m ? m[1].trim() : null;
5677
- }
5678
- function exeFromCmdLine(cmdLine) {
5679
- const s = String(cmdLine || "");
5680
- let m = s.match(/^\s*"([^"]+\.exe)"/i);
5681
- if (m) return m[1];
5682
- m = s.match(/^\s*(\S+\.exe)/i);
5683
- return m ? m[1] : null;
5684
- }
5685
- function resolveDefaultWin(runOut) {
5686
- const roots = ["HKCU\\Software\\Clients\\StartMenuInternet", "HKLM\\Software\\Clients\\StartMenuInternet"];
5687
- for (const root of roots) {
5688
- const progId = regValueOf(runOut("reg.exe", ["query", root, "/ve"]));
5689
- if (!progId || !/^[\x20-\x7e]+$/.test(progId)) continue;
5690
- for (const r2 of roots) {
5691
- const cmd2 = regValueOf(runOut("reg.exe", ["query", r2 + "\\" + progId + "\\shell\\open\\command", "/ve"]));
5692
- const bin = exeFromCmdLine(cmd2);
5693
- if (bin) return { bin, baseArgs: [] };
5694
- }
5695
- }
5696
- return null;
5697
- }
5698
- var MAC_JXA = [
5699
- "ObjC.import('CoreServices');ObjC.import('Foundation');",
5700
- "var b=ObjC.unwrap($.LSCopyDefaultHandlerForURLScheme(null,'https'))||ObjC.unwrap($.LSCopyDefaultHandlerForURLScheme(null,'http'));",
5701
- "if(!b){'EMPTY';}else{",
5702
- "var u=$.NSWorkspace.workspace.URLForApplicationWithBundleIdentifier(b);",
5703
- "if(u.isNil()){'EMPTY';}else{",
5704
- "var e=ObjC.unwrap($.NSBundle.bundleWithURL(u).objectForInfoDictionaryKey('CFBundleExecutable'));",
5705
- "b+'\\t'+ObjC.unwrap(u.path)+'\\t'+e;}}"
5706
- ].join("");
5707
- function resolveDefaultMac(runOut) {
5708
- const out = runOut("osascript", ["-l", "JavaScript", "-e", MAC_JXA]);
5709
- if (!out) return null;
5710
- const parts = out.trim().split(" ");
5711
- if (parts.length < 3 || parts[0] === "EMPTY" || !parts[2]) return null;
5712
- return { bin: path2.posix.join(parts[1], "Contents", "MacOS", parts[2]), baseArgs: [], bundleId: parts[0] };
5713
- }
5714
- function resolveDefaultLinux(runOut, readFile, exists, env, home) {
5715
- const id = runOut("xdg-settings", ["get", "default-web-browser"]);
5716
- const desktop2 = id && id.trim();
5717
- if (!desktop2 || !/^[A-Za-z0-9._-]+\.desktop$/.test(desktop2)) return null;
5718
- const dirs = [
5719
- env.XDG_DATA_HOME || path2.join(home, ".local", "share"),
5720
- "/usr/local/share",
5721
- "/usr/share",
5722
- "/var/lib/snapd/desktop"
5723
- ].map((d) => path2.join(d, "applications"));
5724
- for (const dir of dirs) {
5725
- const p = path2.join(dir, desktop2);
5726
- let text = null;
5727
- try {
5728
- if (exists(p)) text = readFile(p);
5729
- } catch {
5730
- continue;
5731
- }
5732
- if (text === null) continue;
5733
- let inMain = false;
5734
- for (const line of text.split(/\r?\n/)) {
5735
- if (/^\[Desktop Entry\]\s*$/.test(line)) {
5736
- inMain = true;
5737
- continue;
5738
- }
5739
- if (/^\[/.test(line)) break;
5740
- if (inMain) {
5741
- const m = line.match(/^Exec=(.+)/);
5742
- if (m) return parseExecLine(m[1]);
5743
- }
5744
- }
5745
- }
5746
- return null;
5747
- }
5748
- function resolveDefaultBrowser(platform, o) {
5749
- const ov = o || {};
5750
- const pl = platform || process.platform;
5751
- const runOut = ov.runOut || ((bin, args) => exec.runOut(bin, args, { timeoutMs: 5e3 }));
5752
- const readFile = ov.readFile || ((p) => fs2.readFileSync(p, "utf8"));
5753
- const exists = ov.exists || ((p) => fs2.existsSync(p));
5754
- const env = ov.env || process.env;
5755
- const home = ov.home || os2.homedir();
5756
- try {
5757
- if (pl === "win32") return resolveDefaultWin(runOut);
5758
- if (pl === "darwin") return resolveDefaultMac(runOut);
5759
- return resolveDefaultLinux(runOut, readFile, exists, env, home);
5760
- } catch {
5761
- return null;
5762
- }
5763
- }
5764
6290
  function isolatedPlan(platform, url, opts) {
5765
6291
  const o = opts || {};
5766
6292
  const pl = platform || process.platform;
5767
- const db = o.defaultBrowser;
5768
- const engine = db && db.bin ? engineOf(db.bin) : "other";
5769
- const baseArgs = db && db.baseArgs || [];
5770
- if (engine === "chromium" && o.profileDir) {
6293
+ const picked = o.pick || pickLauncher(pl, o.inventory);
6294
+ const b = picked.browser;
6295
+ const form = formOfBin(pl, b);
6296
+ const baseArgs = b && b.baseArgs || [];
6297
+ if (form.direct && form.engine === "chromium" && o.profileDir) {
5771
6298
  const args = ["--incognito", "--user-data-dir=" + o.profileDir];
5772
6299
  if (Array.isArray(o.size) && o.size.length === 2) args.push("--window-size=" + o.size[0] + "," + o.size[1]);
5773
6300
  if (o.lang) args.push("--lang=" + o.lang);
5774
6301
  args.push("--no-first-run", "--no-default-browser-check", "--disable-session-crashed-bubble");
5775
- return { bin: db.bin, args: [...baseArgs, ...args, url], isolated: true, watch: true, envKind: "anti", label: "chromium" };
6302
+ return { bin: b.bin, args: [...baseArgs, ...args, url], isolated: true, watch: true, envKind: "anti", label: "chromium", pick: picked.how };
5776
6303
  }
5777
- if (engine === "firefox" && o.profileDir) {
6304
+ if (form.direct && form.engine === "firefox" && o.profileDir) {
5778
6305
  return {
5779
- bin: db.bin,
6306
+ bin: b.bin,
5780
6307
  args: [...baseArgs, "--no-remote", "--profile", o.profileDir, "-private-window", url],
5781
6308
  isolated: true,
5782
6309
  watch: true,
5783
6310
  envKind: "anti",
5784
- label: "firefox"
6311
+ label: "firefox",
6312
+ pick: picked.how
5785
6313
  };
5786
6314
  }
6315
+ if (form.direct) {
6316
+ return { bin: b.bin, args: baseArgs.concat([url]), isolated: false, watch: false, envKind: "sys", label: path2.basename(b.bin), pick: picked.how };
6317
+ }
5787
6318
  const c = openCommand(pl, url);
5788
- return { bin: c.cmd, args: c.args, isolated: false, watch: false, envKind: "sys", label: c.cmd };
6319
+ if (!c) return { bin: null, args: [url], isolated: false, watch: false, envKind: "sys", label: null, pick: picked.how };
6320
+ return { bin: c.cmd, args: c.args, isolated: false, watch: false, envKind: "sys", label: c.cmd, pick: picked.how };
5789
6321
  }
5790
6322
  function binAvailable(bin) {
5791
6323
  if (!bin) return false;
@@ -5802,21 +6334,21 @@ var require_browser = __commonJS({
5802
6334
  const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetached;
5803
6335
  const fail = (reason, extra) => Object.assign(outcome(Object.assign({ ok: false, reason, url }, extra)), { bin: null, isolated: false });
5804
6336
  try {
5805
- let db;
5806
- if ("defaultBrowser" in opts) db = opts.defaultBrowser;
5807
- else if (typeof opts.resolveDefault === "function") db = opts.resolveDefault(process.platform, opts.resolveDeps || {});
5808
- else db = resolveDefaultBrowser(process.platform, opts.resolveDeps || {});
6337
+ const resolveInv = typeof opts.resolveInventory === "function" ? opts.resolveInventory : ((platform, deps) => detector.inventory(platform, deps));
6338
+ const inv = "inventory" in opts ? opts.inventory : resolveInv(process.platform, opts.resolveDeps || {});
5809
6339
  const plan = isolatedPlan(process.platform, url, {
5810
- defaultBrowser: db,
6340
+ inventory: inv,
6341
+ pick: opts.pick,
5811
6342
  profileDir: opts.profileDir,
5812
6343
  size: opts.size,
5813
6344
  lang: opts.lang
5814
6345
  });
5815
- if (!avail(plan.bin)) return fail("no-launcher", { evidence: { bin: plan.bin, engine: plan.label, via: plan.isolated ? "browser" : "dispatcher" } });
6346
+ const evidence = { bin: plan.bin, engine: plan.label, via: plan.isolated ? "browser" : "dispatcher", diagnostics: launchDiagnostics(inv, plan, plan.pick) };
6347
+ if (!avail(plan.bin)) return fail("no-launcher", { evidence });
5816
6348
  const env = plan.envKind === "anti" ? antiEnv : sysEnv;
5817
6349
  const p = spawnWith(plan.bin, plan.args, env, plan.watch ? onExit : void 0);
5818
- if (!p) return fail("spawn-failed");
5819
- return Object.assign(outcome({ ok: true, confirmed: false, handedOff: true, url }), { bin: plan.label, isolated: plan.isolated });
6350
+ if (!p) return fail("spawn-failed", { evidence });
6351
+ return Object.assign(outcome({ ok: true, confirmed: false, handedOff: true, url, evidence }), { bin: plan.label, isolated: plan.isolated });
5820
6352
  } catch (e) {
5821
6353
  return fail("spawn-failed", { error: FAILURE_TEXT["spawn-failed"] + "\uFF08" + (e && e.code || e) + "\uFF09" });
5822
6354
  }
@@ -5827,18 +6359,18 @@ var require_browser = __commonJS({
5827
6359
  openCommand,
5828
6360
  openPlan,
5829
6361
  isolatedPlan,
6362
+ pickLauncher,
6363
+ ownsItsWindow,
5830
6364
  observeSpawn,
5831
6365
  isSafeHttpUrl,
5832
- resolveDefaultBrowser,
5833
- resolveDefaultWin,
5834
- resolveDefaultMac,
5835
- resolveDefaultLinux,
6366
+ binAvailable,
6367
+ listBrowsers,
6368
+ invalidateBrowsers,
6369
+ launchDiagnostics,
6370
+ // 探测层的解析原语经此转出(唯一实现处在 ./browser-inventory.js):门禁与消费方按同一出口取用,
6371
+ // 不在别处再写第二份平台解析。
5836
6372
  engineOf,
5837
- parseExecLine,
5838
- tokenizeExec,
5839
- regValueOf,
5840
- exeFromCmdLine,
5841
- binAvailable
6373
+ detector
5842
6374
  };
5843
6375
  }
5844
6376
  });
@@ -17684,44 +18216,13 @@ var require_browser2 = __commonJS({
17684
18216
  "src/domains/router/ops/browser.js"(exports2, module2) {
17685
18217
  "use strict";
17686
18218
  var platform = require_os();
18219
+ var desktop = require_desktop();
17687
18220
  var fs2 = require("node:fs");
17688
18221
  var os2 = require("node:os");
17689
18222
  var path2 = require("node:path");
17690
18223
  var crypto = require("node:crypto");
17691
18224
  function graphicalEnv() {
17692
- const out = {};
17693
- try {
17694
- const uid = process.getuid ? String(process.getuid()) : "";
17695
- const xdgRun = process.env.XDG_RUNTIME_DIR || "/run/user/" + uid;
17696
- if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
17697
- const x11 = "/tmp/.X11-unix";
17698
- try {
17699
- if (fs2.existsSync(x11)) {
17700
- const socks = fs2.readdirSync(x11).filter((f) => /^X\d+$/.test(f)).map((f) => parseInt(f.slice(1), 10)).sort((a, b) => a - b);
17701
- if (socks.length > 0) {
17702
- out.DISPLAY = ":" + socks[0];
17703
- const xauth = path2.join(os2.homedir(), ".Xauthority");
17704
- if (fs2.existsSync(xauth)) out.XAUTHORITY = xauth;
17705
- }
17706
- }
17707
- } catch {
17708
- }
17709
- if (!out.DISPLAY) {
17710
- try {
17711
- if (fs2.existsSync(xdgRun)) {
17712
- const wl = fs2.readdirSync(xdgRun).filter((f) => f.startsWith("wayland-")).sort();
17713
- if (wl.length > 0) out.WAYLAND_DISPLAY = wl[0];
17714
- }
17715
- } catch {
17716
- }
17717
- }
17718
- }
17719
- if (!process.env.DBUS_SESSION_BUS_ADDRESS && fs2.existsSync(path2.join(xdgRun, "bus"))) {
17720
- out.DBUS_SESSION_BUS_ADDRESS = "unix:path=" + path2.join(xdgRun, "bus");
17721
- }
17722
- } catch {
17723
- }
17724
- return out;
18225
+ return desktop.sessionEnv();
17725
18226
  }
17726
18227
  function openInBrowser(url, onExit) {
17727
18228
  try {
@@ -17960,6 +18461,7 @@ var require_oauth = __commonJS({
17960
18461
  confirmed: false,
17961
18462
  handedOff: false,
17962
18463
  reason: evidence.reason || "no-launcher",
18464
+ evidence: evidence.evidence || null,
17963
18465
  error: (evidence.error || "\u65E0\u6CD5\u8C03\u8D77\u7CFB\u7EDF\u6D4F\u89C8\u5668") + "\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00\u4E0B\u65B9\u5730\u5740\u5B8C\u6210\u6388\u6743"
17964
18466
  };
17965
18467
  }
@@ -25507,7 +26009,7 @@ var require_guard = __commonJS({
25507
26009
  var fs2 = require("node:fs");
25508
26010
  var path2 = require("node:path");
25509
26011
  function owns(pathname) {
25510
- return pathname === "/changelog" || pathname.startsWith("/guard/") || pathname === "/autostart" || pathname.startsWith("/settings/") || pathname.startsWith("/self-update/") || pathname === "/env/dsh" || pathname === "/env/status" || pathname === "/env/node-lts" || pathname === "/env/open-url" || pathname === "/ports" || pathname === "/shutdown";
26012
+ return pathname === "/changelog" || pathname.startsWith("/guard/") || pathname === "/autostart" || pathname.startsWith("/settings/") || pathname.startsWith("/self-update/") || pathname === "/env/dsh" || pathname === "/env/status" || pathname === "/env/node-lts" || pathname === "/env/open-url" || pathname === "/env/browsers" || pathname === "/ports" || pathname === "/shutdown";
25511
26013
  }
25512
26014
  function fetchDshChangelog(res, sup) {
25513
26015
  const v = sup && sup.nativeManager ? sup.nativeManager.versionInfo() : {};
@@ -25683,6 +26185,14 @@ var require_guard = __commonJS({
25683
26185
  if (req.method === "GET" && pathname === "/env/node-lts") {
25684
26186
  return sup.nodeLtsStatus().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
25685
26187
  }
26188
+ if (req.method === "GET" && pathname === "/env/browsers") {
26189
+ if (!originAllowed(req, sup.config.apiPort)) {
26190
+ req.resume();
26191
+ return send(403, { ok: false, error: "cross-origin request rejected" });
26192
+ }
26193
+ const force = /[?&]force=1/.test(req.url || "");
26194
+ return Promise.resolve(browser.listBrowsers({ force })).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: "\u6D4F\u89C8\u5668\u63A2\u6D4B\u5931\u8D25\uFF1A" + (e && e.message || e) }));
26195
+ }
25686
26196
  if (req.method === "POST" && pathname === "/env/open-url") {
25687
26197
  if (!identity.loopback) {
25688
26198
  req.resume();