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

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.
Files changed (22) hide show
  1. package/core.cjs +1638 -1316
  2. package/package.json +1 -1
  3. package/ui-react/assets/{InstancesPage-DM0dMQHh.js → InstancesPage-DhR8E6q2.js} +1 -1
  4. package/ui-react/assets/{LanPage-B5oThF8O.js → LanPage-BymkZ5Lp.js} +1 -1
  5. package/ui-react/assets/OverviewPage-BwAeZq3_.js +2 -0
  6. package/ui-react/assets/{PluginsPage-C1llxTDk.js → PluginsPage-D9nLksEp.js} +1 -1
  7. package/ui-react/assets/{RouterPage-CUKMV2Q9.js → RouterPage-B_dPmJbu.js} +1 -1
  8. package/ui-react/assets/{SettingsPage-BZG-EvR3.js → SettingsPage-C-iKRaHC.js} +1 -1
  9. package/ui-react/assets/{TasksPage-haZMZbBh.js → TasksPage-CceWxOiG.js} +1 -1
  10. package/ui-react/assets/openExternal-RPwenHBw.js +1 -0
  11. package/ui-react/assets/{refresh-cw-B-VAKpDt.js → refresh-cw-qHR0pOo6.js} +1 -1
  12. package/ui-react/assets/{rocket-CLAmwatt.js → rocket-qAs1Npqn.js} +1 -1
  13. package/ui-react/assets/{shield-check-CMRwmPpk.js → shield-check-DdnUZAa0.js} +1 -1
  14. package/ui-react/assets/{supervisor-BCZW2z-5.js → supervisor-Af5v0BTw.js} +3 -3
  15. package/ui-react/assets/supervisor-C1zJELaq.css +2 -0
  16. package/ui-react/assets/{supervisor-BbSavtNN.js → supervisor-CWSt_-Ws.js} +1 -1
  17. package/ui-react/assets/{useSupervisorAction-q0YJmhui.js → useSupervisorAction-CeZGHqFZ.js} +1 -1
  18. package/ui-react/assets/{widgets-BbRkZm-Z.js → widgets-BhlrHuez.js} +1 -1
  19. package/ui-react/supervisor.html +3 -3
  20. package/ui-react/assets/OverviewPage-DH8PtnPI.js +0 -2
  21. package/ui-react/assets/openExternal-CzqErnSy.js +0 -1
  22. package/ui-react/assets/supervisor-YWIsBeZU.css +0 -2
package/core.cjs CHANGED
@@ -833,7 +833,11 @@ var require_config = __commonJS({
833
833
  // 不配置 = LAN 受 RFC1918 白名单约束,FRP 暴露仍强制 remoteToken。
834
834
  apiAccessKey: null,
835
835
  // 关闭窗口行为(系统级):'hide' = 隐藏至托盘、服务常驻(默认);'exit' = 退出管家并停全部服务链。
836
- closeAction: "hide"
836
+ closeAction: "hide",
837
+ // 外部打开的浏览器偏好(系统级,本机配置):存环境表单候选清单里的 id,null = 未选。
838
+ // 与「系统默认浏览器」分两个字段是刻意的:前者是用户在本产品里的选择,后者是操作系统的事实;
839
+ // 系统说不出默认时按候选次序分发并如实标注,用户可在这里定一次覆盖它(见 platform/os/environment.js)。
840
+ externalBrowser: null
837
841
  };
838
842
  function buildDefaults2(ext) {
839
843
  const pending = normalizeExtension(ext).defaults.slice();
@@ -947,7 +951,7 @@ var require_version = __commonJS({
947
951
  var fs2 = require("node:fs");
948
952
  var path2 = require("node:path");
949
953
  function guardVersion() {
950
- if (true) return String("0.1.6-BETA.13");
954
+ if (true) return String("0.1.6-BETA.14");
951
955
  try {
952
956
  return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
953
957
  } catch {
@@ -1080,6 +1084,7 @@ var require_fs = __commonJS({
1080
1084
  "src/platform/util/fs.js"(exports2, module2) {
1081
1085
  "use strict";
1082
1086
  var fs2 = require("node:fs");
1087
+ var os2 = require("node:os");
1083
1088
  var path2 = require("node:path");
1084
1089
  function dirSizeBytes(root) {
1085
1090
  let total = 0;
@@ -1140,7 +1145,26 @@ var require_fs = __commonJS({
1140
1145
  throw e;
1141
1146
  }
1142
1147
  }
1143
- module2.exports = { dirSizeBytes, writeAtomic };
1148
+ function removeTreeDeferred(dir, ms) {
1149
+ if (!dir) return null;
1150
+ const t = setTimeout(() => {
1151
+ try {
1152
+ fs2.rmSync(dir, { recursive: true, force: true });
1153
+ } catch {
1154
+ }
1155
+ }, ms === void 0 ? 6e4 : ms);
1156
+ if (t.unref) t.unref();
1157
+ return t;
1158
+ }
1159
+ function allocTempDir(prefix) {
1160
+ const dir = fs2.mkdtempSync(path2.join(os2.tmpdir(), prefix || "dsh-"));
1161
+ try {
1162
+ fs2.chmodSync(dir, 448);
1163
+ } catch {
1164
+ }
1165
+ return dir;
1166
+ }
1167
+ module2.exports = { dirSizeBytes, writeAtomic, removeTreeDeferred, allocTempDir };
1144
1168
  }
1145
1169
  });
1146
1170
 
@@ -4831,1218 +4855,1446 @@ var require_capability_profile = __commonJS({
4831
4855
  }
4832
4856
  });
4833
4857
 
4834
- // src/platform/os/process.js
4835
- var require_process = __commonJS({
4836
- "src/platform/os/process.js"(exports2, module2) {
4837
- "use strict";
4838
- var ex2 = require_exec();
4839
- var isWindows = process.platform === "win32";
4840
- function signalProcess(pid, sig) {
4841
- if (!Number.isInteger(pid) || pid <= 0) return;
4842
- if (isWindows) {
4843
- try {
4844
- process.kill(pid, sig);
4845
- } catch {
4846
- }
4847
- return;
4848
- }
4849
- try {
4850
- process.kill(-pid, sig);
4851
- } catch {
4852
- try {
4853
- process.kill(pid, sig);
4854
- } catch {
4855
- }
4856
- }
4857
- }
4858
- function killTree(pid, sig, cb, opts) {
4859
- if (!Number.isInteger(pid) || pid <= 0) {
4860
- if (cb) cb(new Error("invalid pid"));
4861
- return;
4862
- }
4863
- if (isWindows) {
4864
- ex2.runAsync("taskkill", ["/PID", String(pid), "/T", "/F"], { timeoutMs: 1e4 }).then((r) => {
4865
- if (cb) cb(r.ok ? null : new Error(r.error || "taskkill failed"));
4866
- });
4867
- return;
4868
- }
4869
- if (opts && opts.ownGroup === true) {
4870
- signalProcess(pid, sig || "SIGTERM");
4871
- } else {
4872
- try {
4873
- process.kill(pid, sig || "SIGTERM");
4874
- } catch {
4875
- }
4876
- }
4877
- if (cb) process.nextTick(cb, null);
4878
- }
4879
- module2.exports = { signalProcess, killTree };
4880
- }
4881
- });
4882
-
4883
- // src/platform/os/file-protect.js
4884
- var require_file_protect = __commonJS({
4885
- "src/platform/os/file-protect.js"(exports2, module2) {
4858
+ // src/platform/os/browser-inventory.js
4859
+ var require_browser_inventory = __commonJS({
4860
+ "src/platform/os/browser-inventory.js"(exports2, module2) {
4886
4861
  "use strict";
4887
4862
  var fs2 = require("node:fs");
4888
- var os2 = require("node:os");
4889
4863
  var path2 = require("node:path");
4890
- var ex2 = require_exec();
4891
- var IS_WINDOWS2 = process.platform === "win32";
4892
- var _icacls = null;
4893
- function hasIcacls(platform) {
4894
- if ((platform || process.platform) !== "win32") return false;
4895
- if (_icacls !== null) return _icacls;
4896
- _icacls = ex2.runOut("icacls", ["/?"], { timeoutMs: 3e3 }) !== null;
4897
- return _icacls;
4864
+ var os2 = require("node:os");
4865
+ var exec = require_exec();
4866
+ var { isExecutableFile } = require_exec_path();
4867
+ var PROBE_TIMEOUT_MS = 1500;
4868
+ function engineOf(bin) {
4869
+ const base = String(bin || "").toLowerCase().split(/[\\/]/).pop();
4870
+ if (/(chrome|chromium|msedge|edge|brave|vivaldi|opera|thorium)/.test(base)) return "chromium";
4871
+ if (/(firefox|librewolf|waterfox)/.test(base)) return "firefox";
4872
+ return "other";
4898
4873
  }
4899
- function currentUser() {
4900
- return process.env.USERNAME || process.env.USER || (() => {
4901
- try {
4902
- return os2.userInfo().username;
4903
- } catch {
4904
- return null;
4874
+ function tokenizeExec(line) {
4875
+ const toks = [];
4876
+ let cur = "";
4877
+ let q = null;
4878
+ let esc = false;
4879
+ let has = false;
4880
+ for (const ch of String(line)) {
4881
+ if (esc) {
4882
+ cur += ch;
4883
+ esc = false;
4884
+ continue;
4905
4885
  }
4906
- })();
4907
- }
4908
- function protectFile(file) {
4909
- if (!IS_WINDOWS2) {
4910
- try {
4911
- fs2.chmodSync(file, 384);
4912
- return { ok: true, mode: "posix-0600" };
4913
- } catch (e) {
4914
- return { ok: false, mode: "posix-0600", reason: e.message };
4886
+ if (ch === "\\") {
4887
+ esc = true;
4888
+ has = true;
4889
+ continue;
4915
4890
  }
4916
- }
4917
- if (!hasIcacls()) return { ok: false, mode: "none", reason: "icacls \u4E0D\u53EF\u7528" };
4918
- const user = currentUser();
4919
- if (!user) return { ok: false, mode: "icacls", reason: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u7528\u6237" };
4920
- const r = ex2.runDetail("icacls", [file, "/inheritance:r", "/grant:r", user + ":F"], { stdio: "ignore", timeoutMs: 5e3 });
4921
- return r.ok ? { ok: true, mode: "icacls-file" } : { ok: false, mode: "icacls-file", reason: r.error || "\u9000\u51FA\u7801 " + r.code };
4922
- }
4923
- function protectDir(dir) {
4924
- if (!IS_WINDOWS2) {
4925
- try {
4926
- fs2.chmodSync(dir, 448);
4927
- return { ok: true, mode: "posix-0700" };
4928
- } catch (e) {
4929
- return { ok: false, mode: "posix-0700", reason: e.message };
4891
+ if (q) {
4892
+ if (ch === q) q = null;
4893
+ else cur += ch;
4894
+ continue;
4930
4895
  }
4931
- }
4932
- if (!hasIcacls()) return { ok: false, mode: "none", reason: "icacls \u4E0D\u53EF\u7528" };
4933
- const user = currentUser();
4934
- if (!user) return { ok: false, mode: "icacls", reason: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u7528\u6237" };
4935
- const r = ex2.runDetail("icacls", [dir, "/inheritance:r", "/grant:r", user + ":(OI)(CI)F"], { stdio: "ignore", timeoutMs: 1e4 });
4936
- return r.ok ? { ok: true, mode: "icacls-dir" } : { ok: false, mode: "icacls-dir", reason: r.error || "\u9000\u51FA\u7801 " + r.code };
4937
- }
4938
- function ensurePrivateDir(dir) {
4939
- try {
4940
- fs2.mkdirSync(dir, { recursive: true });
4941
- } catch (e) {
4942
- return { ok: false, mode: "mkdir", reason: e.message };
4943
- }
4944
- return protectDir(dir);
4945
- }
4946
- function writePrivate(file, data) {
4947
- try {
4948
- const dir = path2.dirname(file);
4949
- try {
4950
- fs2.mkdirSync(dir, { recursive: true });
4951
- } catch {
4896
+ if (ch === "'" || ch === '"') {
4897
+ q = ch;
4898
+ has = true;
4899
+ continue;
4952
4900
  }
4953
- const tmp = file + ".tmp" + process.pid;
4954
- fs2.writeFileSync(tmp, data, { mode: 384 });
4955
- const p1 = protectFile(tmp);
4956
- if (p1 && p1.ok === false) {
4957
- try {
4958
- fs2.rmSync(tmp, { force: true });
4959
- } catch {
4901
+ if (/\s/.test(ch)) {
4902
+ if (has) {
4903
+ toks.push(cur);
4904
+ cur = "";
4905
+ has = false;
4960
4906
  }
4961
- return { ok: false, reason: "protect(tmp): " + (p1.reason || p1.mode), mode: p1.mode };
4907
+ continue;
4962
4908
  }
4963
- fs2.renameSync(tmp, file);
4964
- const p2 = protectFile(file);
4965
- if (p2 && p2.ok === false) return { ok: false, reason: "protect(file): " + (p2.reason || p2.mode), mode: p2.mode };
4966
- return { ok: true };
4967
- } catch (e) {
4968
- return { ok: false, reason: e.message };
4909
+ cur += ch;
4910
+ has = true;
4969
4911
  }
4912
+ if (has) toks.push(cur);
4913
+ return toks;
4970
4914
  }
4971
- module2.exports = { protectFile, protectDir, ensurePrivateDir, writePrivate, hasIcacls, currentUser, IS_WINDOWS: IS_WINDOWS2 };
4972
- }
4973
- });
4974
-
4975
- // src/platform/util/input.js
4976
- var require_input = __commonJS({
4977
- "src/platform/util/input.js"(exports2, module2) {
4978
- "use strict";
4979
- var PKG_NAME_RE = /^(@[a-zA-Z0-9._-]+\/)?[a-zA-Z0-9._-]+$/;
4980
- var ARGV_UNSAFE_RE = /[\s;|&<>`'"$(){}\\*?~#]/;
4981
- var WIN_ABS_PATH_RE = /^[A-Za-z]:\\[^;|&<>`'"$*?~#\s]*$/;
4982
- var UNIT_NAME_RE = /^[A-Za-z0-9:@._-]{1,128}$/;
4983
- var UNSAFE_LEDGER_KEYS = ["__proto__", "prototype", "constructor"];
4984
- var LEDGER_UNSAFE_RE = /[\s\u0000-\u001f\u007f]/;
4985
- function argvViolation(arg) {
4986
- const s = String(arg == null ? "" : arg);
4987
- if (!s) return "\u7A7A\u7684 argv \u9879";
4988
- if (ARGV_UNSAFE_RE.test(s) && !WIN_ABS_PATH_RE.test(s)) return "argv \u9879\u542B\u7981\u7528\u5B57\u7B26\uFF08\u7A7A\u767D/shell \u5143\u5B57\u7B26\uFF09: " + s.slice(0, 80);
4989
- return null;
4990
- }
4991
- var CONTROL_RE = /[\u0000-\u001f\u007f]/;
4992
- function isAbsoluteForm(p) {
4993
- const s = String(p == null ? "" : p);
4994
- return s.charCodeAt(0) === 47 || /^[A-Za-z]:[\\/]/.test(s) || s.charCodeAt(0) === 92 && s.charCodeAt(1) === 92;
4915
+ function parseExecLine(line) {
4916
+ let toks = tokenizeExec(line).filter((t) => t !== "%%" && !/^%[a-zA-Z]$/.test(t));
4917
+ if (toks[0] === "env") {
4918
+ let i = 1;
4919
+ while (i < toks.length && /=/.test(toks[i])) i++;
4920
+ toks = toks.slice(i);
4921
+ }
4922
+ if (!toks.length) return null;
4923
+ return { bin: toks[0], baseArgs: toks.slice(1) };
4995
4924
  }
4996
- function prefixViolation(prefix) {
4997
- const s = String(prefix == null ? "" : prefix);
4998
- if (!s.trim()) return "\u7A7A\u7684\u5B89\u88C5\u524D\u7F00";
4999
- if (CONTROL_RE.test(s)) return "\u5B89\u88C5\u524D\u7F00\u542B\u63A7\u5236\u7B26: " + s.slice(0, 80);
5000
- if (s.length > 4096) return "\u5B89\u88C5\u524D\u7F00\u8D85\u957F: " + s.slice(0, 60) + "...";
5001
- if (s[0] === "-") return "\u5B89\u88C5\u524D\u7F00\u4EE5 - \u5F00\u5934\uFF08\u4F1A\u88AB\u5F53\u4F5C npm \u9009\u9879\uFF09: " + s.slice(0, 80);
5002
- if (!isAbsoluteForm(s)) return "\u5B89\u88C5\u524D\u7F00\u987B\u4E3A\u7EDD\u5BF9\u8DEF\u5F84\uFF08/ \u5F00\u5934\u3001win \u76D8\u7B26\u6216 UNC\uFF09: " + s.slice(0, 80);
5003
- return null;
4925
+ function regValueOf(out) {
4926
+ const m = String(out || "").match(/REG_(?:EXPAND_SZ|SZ)\s+(.*)/);
4927
+ if (!m) return null;
4928
+ return m[1].trim() || null;
5004
4929
  }
5005
- function pkgNameViolation(pkg) {
5006
- const s = String(pkg == null ? "" : pkg);
5007
- return PKG_NAME_RE.test(s) ? null : "\u975E\u6CD5\u5305\u540D\uFF08\u5B57\u7B26\u96C6\u767D\u540D\u5355\u4E0D\u901A\u8FC7\uFF09: " + s.slice(0, 80);
4930
+ function expandEnvVars(value, env) {
4931
+ const e = env || process.env;
4932
+ return String(value).replace(/%([^%]+)%/g, (all, name) => e[name] === void 0 ? all : e[name]);
5008
4933
  }
5009
- function unitNameViolation(unit) {
5010
- const s = String(unit == null ? "" : unit);
5011
- if (!UNIT_NAME_RE.test(s)) return "\u975E\u6CD5\u5355\u5143\u540D\uFF08\u5B57\u7B26\u96C6/\u957F\u5EA6\u767D\u540D\u5355\u4E0D\u901A\u8FC7\uFF09: " + s.slice(0, 80);
5012
- const dot = s.indexOf(".");
5013
- if (dot >= 0 && !/\.service$/.test(s)) return "\u5355\u5143\u540D\u540E\u7F00\u4E0D\u53D7\u652F\u6301\uFF08\u4EC5\u5141\u8BB8 .service \u6216\u65E0\u540E\u7F00\uFF09: " + s.slice(0, 80);
5014
- return null;
4934
+ function exeFromCmdLine(cmdLine) {
4935
+ const s = String(cmdLine || "");
4936
+ let m = s.match(/^\s*"([^"]+\.exe)"/i);
4937
+ if (m) return m[1];
4938
+ m = s.match(/^\s*(.*?\.exe)/i);
4939
+ return m ? m[1] : null;
5015
4940
  }
5016
- function ledgerKey(raw, opts) {
5017
- const o = opts || {};
5018
- const max = typeof o.max === "number" ? o.max : 128;
5019
- const empty = o.empty == null ? "unknown" : o.empty;
5020
- const unsafe = o.unsafe == null ? "(other)" : o.unsafe;
5021
- if (typeof raw !== "string" || !raw) return empty;
5022
- const s = raw.length > max ? raw.slice(0, max) : raw;
5023
- if (UNSAFE_LEDGER_KEYS.indexOf(s) >= 0 || LEDGER_UNSAFE_RE.test(s)) return unsafe;
5024
- return s;
4941
+ function safeRegKeyPart(s) {
4942
+ return typeof s === "string" && s.length > 0 && s.length <= 200 && /^[\x20-\x7e]+$/.test(s) && !/[;|&`$()<>^"'\r\n]/.test(s);
5025
4943
  }
5026
- module2.exports = {
5027
- PKG_NAME_RE,
5028
- ARGV_UNSAFE_RE,
5029
- WIN_ABS_PATH_RE,
5030
- UNIT_NAME_RE,
5031
- UNSAFE_LEDGER_KEYS,
5032
- LEDGER_UNSAFE_KEYS_RE: LEDGER_UNSAFE_RE,
5033
- argvViolation,
5034
- pkgNameViolation,
5035
- unitNameViolation,
5036
- ledgerKey,
5037
- CONTROL_RE,
5038
- isAbsoluteForm,
5039
- prefixViolation
5040
- };
5041
- }
5042
- });
5043
-
5044
- // src/platform/os/spawn.js
5045
- var require_spawn = __commonJS({
5046
- "src/platform/os/spawn.js"(exports2, module2) {
5047
- "use strict";
5048
- var { spawn } = require("node:child_process");
5049
- function detached(cmd2, args, opts) {
5050
- const o = opts || {};
5051
- return spawn(cmd2, args, Object.assign({}, o, {
5052
- detached: true,
5053
- windowsHide: true,
5054
- // 默认 ignore;显式 undefined 也回落 ignore,避免 stdio:undefined 的歧义。
5055
- stdio: o.stdio === void 0 ? "ignore" : o.stdio
5056
- }));
5057
- }
5058
- function piped(cmd2, args, opts) {
5059
- const o = opts || {};
5060
- return spawn(cmd2, args, Object.assign({}, o, {
5061
- stdio: ["ignore", "pipe", "pipe"],
5062
- windowsHide: true,
5063
- detached: o.detached === true
5064
- }));
4944
+ function regValue(runner, note, key, name) {
4945
+ const args = name ? ["query", key, "/v", name] : ["query", key, "/ve"];
4946
+ const out = runner("reg.exe", args);
4947
+ const v = regValueOf(out);
4948
+ note(name ? key + " /v " + name : key, v ? "ok" : "empty");
4949
+ return v;
5065
4950
  }
5066
- function detachedIgnored(cmd2, args, opts) {
5067
- const o = opts || {};
5068
- return spawn(cmd2, args, Object.assign({}, o, {
5069
- detached: true,
5070
- windowsHide: true,
5071
- stdio: "ignore"
5072
- }));
4951
+ var REG_HIVE_ALIAS = { HKLM: "HKEY_LOCAL_MACHINE", HKCU: "HKEY_CURRENT_USER", HKCR: "HKEY_CLASSES_ROOT", HKU: "HKEY_USERS", HKCC: "HKEY_CURRENT_CONFIG" };
4952
+ function regKeyFull(key) {
4953
+ const s = String(key || "");
4954
+ const i = s.indexOf("\\");
4955
+ const root = i < 0 ? s : s.slice(0, i);
4956
+ return (REG_HIVE_ALIAS[root.toUpperCase()] || root) + (i < 0 ? "" : s.slice(i));
5073
4957
  }
5074
- module2.exports = { detached, piped, detachedIgnored };
5075
- }
5076
- });
5077
-
5078
- // src/platform/os/portable.js
5079
- var require_portable = __commonJS({
5080
- "src/platform/os/portable.js"(exports2, module2) {
5081
- "use strict";
5082
- var fs2 = require("node:fs");
5083
- var pidlookup = require_pidlookup();
5084
- var spawner = require_spawn();
5085
- var procOS = require_process();
5086
- var STOP_WAIT_CAP_MS = 3e3;
5087
- var NAP_MS = 150;
5088
- function nap(ms) {
5089
- try {
5090
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
5091
- } catch {
4958
+ function regSubkeys(runner, note, key) {
4959
+ const out = runner("reg.exe", ["query", key]);
4960
+ if (!out) {
4961
+ note(key, "unreadable");
4962
+ return [];
5092
4963
  }
5093
- }
5094
- function readPidFile(pidFile) {
5095
- if (!pidFile) return null;
5096
- try {
5097
- const n = parseInt(String(fs2.readFileSync(pidFile, "utf8")).trim(), 10);
5098
- return Number.isInteger(n) && n > 0 ? n : null;
5099
- } catch {
5100
- return null;
4964
+ const prefix = regKeyFull(key) + "\\";
4965
+ const names = [];
4966
+ for (const line of String(out).split(/\r?\n/)) {
4967
+ const l = line.trim();
4968
+ if (!/^HKEY_/i.test(l) || l.length <= prefix.length) continue;
4969
+ if (l.slice(0, prefix.length).toUpperCase() !== prefix.toUpperCase()) continue;
4970
+ const rest = l.slice(prefix.length);
4971
+ if (rest && !rest.includes("\\") && safeRegKeyPart(rest)) names.push(rest);
5101
4972
  }
4973
+ note(key, names.length ? names.length + " \u9879" : "\u65E0\u5B50\u952E");
4974
+ return names;
5102
4975
  }
5103
- function matchesAnchors(pid, anchors) {
5104
- if (!pid || !Array.isArray(anchors) || !anchors.length) return false;
5105
- const cmd2 = pidlookup.readCmdline(pid);
5106
- if (!cmd2) return false;
5107
- return anchors.some((a) => a && cmd2.indexOf(String(a)) !== -1);
5108
- }
5109
- var portOf = (o) => {
5110
- const p = Number(o && o.port);
5111
- return Number.isInteger(p) && p > 0 ? p : 0;
5112
- };
5113
- var pidFileOf = (o) => o && o.pidFile || null;
5114
- var anchorsOf = (o) => Array.isArray(o && o.anchors) ? o.anchors : [];
5115
- function findOurs(o) {
5116
- const anchors = anchorsOf(o);
5117
- const fp = readPidFile(pidFileOf(o));
5118
- if (fp !== null && pidlookup.isAlive(fp) && matchesAnchors(fp, anchors)) return { pid: fp, ownGroup: true };
5119
- const port = portOf(o);
5120
- if (port) {
5121
- const q = pidlookup.findListeningPid(port);
5122
- if (q !== null && q !== fp && matchesAnchors(q, anchors)) return { pid: q, ownGroup: false };
4976
+ var WIN_APP_PATHS = ["msedge.exe", "chrome.exe", "firefox.exe", "brave.exe", "opera.exe", "vivaldi.exe", "chromium.exe", "thorium.exe", "librewolf.exe"];
4977
+ function winExeOfProgId(runner, note, progId, env) {
4978
+ if (!safeRegKeyPart(progId)) return null;
4979
+ for (const root of ["HKCU\\Software\\Classes", "HKLM\\Software\\Classes"]) {
4980
+ const cmd2 = regValue(runner, note, root + "\\" + progId + "\\shell\\open\\command");
4981
+ const exe = cmd2 ? exeFromCmdLine(expandEnvVars(cmd2, env)) : null;
4982
+ if (exe) return exe;
5123
4983
  }
5124
4984
  return null;
5125
4985
  }
5126
- function removePidFile(o) {
5127
- const f = pidFileOf(o);
5128
- if (f) {
5129
- try {
5130
- fs2.unlinkSync(f);
5131
- } catch {
4986
+ function probeWin(d) {
4987
+ const { runOut, env, canExec } = d;
4988
+ const notes = [];
4989
+ const note = (source, detail) => {
4990
+ notes.push({ source, detail });
4991
+ };
4992
+ const found = /* @__PURE__ */ new Map();
4993
+ const keyOf = (exe) => String(exe || "").toLowerCase().replace(/[\\/]/g, "\\");
4994
+ const push = (exe, how) => {
4995
+ const full = expandEnvVars(exe, env).trim();
4996
+ if (!/\.exe$/i.test(full)) {
4997
+ note(how, "\u6765\u6E90\u7ED9\u51FA\u7684\u4E0D\u662F exe \u8DEF\u5F84: " + full);
4998
+ return null;
5132
4999
  }
5133
- }
5134
- }
5135
- var portable = {
5136
- kind: "portable",
5137
- supportsUnits: false,
5138
- supportsTransient: true,
5139
- // 无操作 = 成功(与 systemd 的动词返回形态一致,调用方无需按 provider 分支)。
5140
- daemonReload() {
5141
- return true;
5142
- },
5143
- resetFailed() {
5144
- return true;
5145
- },
5146
- /** 拉起:spawn(detached)——POSIX 自成进程组(kill(-pid) 语义的前提)、win32 windowsHide +
5147
- * CREATE_NEW_PROCESS_GROUP(NO-CONSOLE-WINDOW 纪律收口在 platform/os/spawn.js)。
5148
- * 调用方传来的 props(cgroup 语义)在此如实忽略:本档无内核强制可施加。
5149
- * @param {{cmd:string[], env?:object, workingDir?:string, pidFile?:string, port?:number, anchors?:string[]}} o */
5150
- startTransient(o) {
5151
- const opts = o || {};
5152
- const cmd2 = opts.cmd || [];
5153
- if (!cmd2.length) throw new Error("portable \u62C9\u8D77\u62D2\u7EDD\uFF1A\u7A7A\u547D\u4EE4");
5154
- const child = spawner.detached(cmd2[0], cmd2.slice(1), {
5155
- cwd: opts.workingDir || void 0,
5156
- env: Object.assign({}, process.env, opts.env || {})
5157
- });
5158
- child.on("error", () => {
5159
- });
5160
- if (!child.pid) {
5161
- try {
5162
- child.kill();
5163
- } catch {
5164
- }
5165
- throw new Error("portable \u62C9\u8D77\u5931\u8D25\uFF1Aspawn \u672A\u4EA7\u751F\u8FDB\u7A0B");
5000
+ const k = keyOf(full);
5001
+ const hit = found.get(k);
5002
+ if (hit) {
5003
+ if (!hit.sources.includes(how)) hit.sources.push(how);
5004
+ return hit;
5166
5005
  }
5167
- try {
5168
- child.unref();
5169
- } catch {
5006
+ if (!canExec(full)) {
5007
+ note(how, "\u6307\u5411\u7684\u6587\u4EF6\u4E0D\u53EF\u6267\u884C: " + full);
5008
+ return null;
5170
5009
  }
5171
- if (opts.pidFile) {
5172
- try {
5173
- fs2.writeFileSync(opts.pidFile, String(child.pid));
5174
- } catch {
5175
- }
5010
+ const item = { id: k, name: path2.basename(full).replace(/\.exe$/i, ""), engine: engineOf(full), bin: full, source: how, sources: [how] };
5011
+ found.set(k, item);
5012
+ note(how, item.name);
5013
+ return item;
5014
+ };
5015
+ const progIds = /* @__PURE__ */ new Map();
5016
+ const addProgId = (pid, how) => {
5017
+ const s = String(pid || "").trim();
5018
+ if (!safeRegKeyPart(s)) return;
5019
+ if (progIds.get(s) === "userchoice") return;
5020
+ progIds.set(s, how);
5021
+ };
5022
+ let defaultId = null;
5023
+ let defaultSource = null;
5024
+ const DEF_RANK = { userchoice: 1, "scheme-association": 2, "only-installed": 9 };
5025
+ let defRank = 99;
5026
+ const setDefault = (item, how) => {
5027
+ const r = DEF_RANK[how] === void 0 ? 99 : DEF_RANK[how];
5028
+ if (item && r < defRank) {
5029
+ defRank = r;
5030
+ defaultId = item.id;
5031
+ defaultSource = how;
5176
5032
  }
5177
- return true;
5178
- },
5179
- /** 停止(有界同步复核):终止我们的进程树,确认「端口锚点与 run.pid 锚点双双消失」才 true。
5180
- * 无可杀对象(含 run.pid 为陈旧/被 PID 复用)-> 清文件后如实 true(幂等)。 */
5181
- stopUnit(unit, o) {
5182
- const opts = o || {};
5183
- const ours = findOurs(opts);
5184
- if (!ours) {
5185
- removePidFile(opts);
5186
- return true;
5033
+ };
5034
+ const cmdToExe = (raw) => raw ? exeFromCmdLine(expandEnvVars(raw, env)) : null;
5035
+ const uc = regValue(runOut, note, "HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice", "ProgId");
5036
+ if (uc) addProgId(uc, "userchoice");
5037
+ for (const root of ["HKCU\\Software\\Classes", "HKLM\\Software\\Classes"]) {
5038
+ const pid = regValue(runOut, note, root + "\\https");
5039
+ if (pid) addProgId(pid, "scheme-association");
5040
+ const exe = cmdToExe(regValue(runOut, note, root + "\\https\\shell\\open\\command"));
5041
+ if (exe) setDefault(push(exe, "scheme-association"), "scheme-association");
5042
+ }
5043
+ for (const name of regSubkeys(runOut, note, "HKLM\\SOFTWARE\\Clients\\StartMenuInternet")) {
5044
+ addProgId(name, "startmenu-catalog");
5045
+ const exe = cmdToExe(regValue(runOut, note, "HKLM\\SOFTWARE\\Clients\\StartMenuInternet\\" + name + "\\shell\\open\\command")) || winExeOfProgId(runOut, note, name, env);
5046
+ if (exe) push(exe, "startmenu-catalog");
5047
+ }
5048
+ for (const root of ["HKLM\\SOFTWARE", "HKCU\\SOFTWARE"]) {
5049
+ for (const capPath of regValueTargets(runOut, note, root + "\\RegisteredApplications")) {
5050
+ const https = regValue(runOut, note, capPath + "\\UrlAssociations\\https");
5051
+ const sid = regValue(runOut, note, capPath, "StartMenuInternet");
5052
+ const pid = https || sid;
5053
+ if (pid) addProgId(pid, "registered-apps");
5187
5054
  }
5188
- try {
5189
- procOS.killTree(ours.pid, "SIGTERM", void 0, { ownGroup: ours.ownGroup });
5190
- } catch {
5055
+ }
5056
+ for (const exe of WIN_APP_PATHS) {
5057
+ for (const root of ["HKCU", "HKLM"]) {
5058
+ const v = regValue(runOut, note, root + "\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + exe);
5059
+ if (v && push(v, "app-paths")) break;
5191
5060
  }
5192
- const budget = Math.max(0, Math.min(typeof opts.timeoutMs === "number" ? opts.timeoutMs : STOP_WAIT_CAP_MS, STOP_WAIT_CAP_MS));
5193
- const t0 = Date.now();
5194
- for (; ; ) {
5195
- if (!findOurs(opts)) {
5196
- removePidFile(opts);
5197
- return true;
5198
- }
5199
- if (Date.now() - t0 >= budget) break;
5200
- nap(NAP_MS);
5201
- }
5202
- try {
5203
- procOS.killTree(ours.pid, "SIGKILL", void 0, { ownGroup: ours.ownGroup });
5204
- } catch {
5205
- }
5206
- nap(NAP_MS);
5207
- if (!findOurs(opts)) {
5208
- removePidFile(opts);
5209
- return true;
5061
+ }
5062
+ for (const [pid, how] of progIds) {
5063
+ const exe = winExeOfProgId(runOut, note, pid, env);
5064
+ if (!exe) continue;
5065
+ const item = push(exe, how);
5066
+ if (how === "userchoice") setDefault(item, "userchoice");
5067
+ }
5068
+ if (found.size === 1) setDefault([...found.values()][0], "only-installed");
5069
+ return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5070
+ }
5071
+ function regValueTargets(runOut, note, key) {
5072
+ const out = runOut("reg.exe", ["query", key]);
5073
+ if (!out) {
5074
+ note(key, "unreadable");
5075
+ return [];
5076
+ }
5077
+ const paths = [];
5078
+ for (const line of String(out).split(/\r?\n/)) {
5079
+ const m = line.trim().match(/REG_SZ\s+(.*)/);
5080
+ if (!m) continue;
5081
+ const rel = m[1].trim();
5082
+ if (!safeRegKeyPart(rel)) continue;
5083
+ const root = key.slice(0, key.indexOf("\\"));
5084
+ paths.push(root + "\\" + rel);
5085
+ }
5086
+ note(key, paths.length ? paths.length + " \u4E2A\u80FD\u529B\u8DEF\u5F84" : "\u65E0\u503C");
5087
+ return paths;
5088
+ }
5089
+ var MAC_JXA_LIST = [
5090
+ "ObjC.import('Foundation');",
5091
+ "var ws=$.NSWorkspace.sharedWorkspace;",
5092
+ "var u=$.NSURL.URLWithString('https://dsh.local/probe');",
5093
+ "var out=[];var def='';",
5094
+ "try{var d=ws.URLForApplicationToOpenURL(u); if(!d.isNil()) def=ObjC.unwrap(d.path)||'';}catch(e){}",
5095
+ "var list=null;",
5096
+ "try{list=ws.urlsForApplicationsToOpenURL(u);}catch(e){}",
5097
+ "if(list===null||typeof list.count!=='number'){if(def){out.push(def+'\\t\\t1');}}else{",
5098
+ "var n=Number(list.count);",
5099
+ "for(var i=0;i<n;i++){",
5100
+ "var p=ObjC.unwrap(list.objectAtIndex(i).path)||''; if(!p) continue;",
5101
+ "var exe='';var bid='';",
5102
+ "var b=$.NSBundle.bundleWithPath(p);",
5103
+ "if(!b.isNil()){exe=ObjC.unwrap(b.executablePath)||'';bid=ObjC.unwrap(b.bundleIdentifier)||'';}",
5104
+ "out.push((exe||p)+'\\t'+bid+'\\t'+(p&&def&&p===def?1:0));}",
5105
+ "}",
5106
+ // 末句必须是裸表达式:osascript 取的是最后一条**语句**的值,if/else 语句不产结果,
5107
+ // 写在分支里会让整段查询输出空字符串(表现为「系统未报任何可用应用」)。
5108
+ "out.join('\\n');"
5109
+ ].join("");
5110
+ function probeMac(d) {
5111
+ const { runOut, canExec } = d;
5112
+ const notes = [];
5113
+ const note = (source, detail) => {
5114
+ notes.push({ source, detail });
5115
+ };
5116
+ const out = runOut("osascript", ["-l", "JavaScript", "-e", MAC_JXA_LIST]);
5117
+ if (!out || !out.trim()) {
5118
+ note("launchservices", "\u67E5\u8BE2\u65E0\u8F93\u51FA\uFF08\u65E7\u7CFB\u7EDF\u6216\u88AB\u6743\u9650\u62E6\u622A\uFF09");
5119
+ return { browsers: [], defaultId: null, defaultSource: null, probed: notes };
5120
+ }
5121
+ const keyOf = (exe) => String(exe || "").toLowerCase();
5122
+ const found = /* @__PURE__ */ new Map();
5123
+ let defaultId = null;
5124
+ let defaultSource = null;
5125
+ for (const line of out.trim().split("\n")) {
5126
+ const parts = line.split(" ");
5127
+ const bin = (parts[0] || "").trim();
5128
+ if (!bin) continue;
5129
+ if (!canExec(bin)) {
5130
+ note("launchservices", "\u4E0D\u53EF\u6267\u884C: " + bin);
5131
+ continue;
5210
5132
  }
5211
- return false;
5212
- },
5213
- /** 活跃判定(三态):port 与 pidFile 都缺 = 无从查询 -> null,删除保护路径不得把它当「已停止」。
5214
- * anchors 为空时降级为「端口有监听即活跃」(monitor.probeInstance 同口径,该调用方本就以此判在线);
5215
- * 杀进程(stopUnit)仍要求锚点命中 —— 判定可宽,动手必严。
5216
- * 存活 pid 的 cmdline 读不到 = 分不清「他人复用」与「查询失败」-> null。 */
5217
- isUnitActive(unit, o) {
5218
- const opts = o || {};
5219
- if (findOurs(opts)) return true;
5220
- const port = portOf(opts);
5221
- const pidFile = pidFileOf(opts);
5222
- const anchors = anchorsOf(opts);
5223
- if (!port && !pidFile) return null;
5224
- if (!anchors.length) {
5225
- const fp2 = readPidFile(pidFile);
5226
- if (fp2 !== null && pidlookup.isAlive(fp2)) return true;
5227
- if (port) {
5228
- const q = pidlookup.findListeningPid(port);
5229
- if (q !== null) return true;
5230
- if (fp2 !== null) return false;
5231
- return null;
5133
+ const k = keyOf(bin);
5134
+ if (!found.has(k)) found.set(k, { id: k, name: path2.basename(bin), engine: engineOf(bin), bin, source: "launchservices", sources: ["launchservices"], isDefault: false });
5135
+ if (parts[2] === "1") {
5136
+ found.get(k).isDefault = true;
5137
+ if (!defaultId) {
5138
+ defaultId = k;
5139
+ defaultSource = "launchservices";
5232
5140
  }
5233
- return fp2 !== null ? false : null;
5234
5141
  }
5235
- const fp = readPidFile(pidFile);
5236
- if (fp !== null && pidlookup.isAlive(fp) && !pidlookup.readCmdline(fp)) return null;
5237
- return false;
5238
- },
5239
- // portable 无 systemd transient 单元文件;run.pid 的清理归 cleanTransient。
5240
- transientUnitFile() {
5241
- return null;
5242
- },
5243
- /** 清理同名残留:先停掉仍活着的旧进程(重启自愈路径:旧进程未监听端口时端口探测拦不住),再清 run.pid。 */
5244
- cleanTransient(unit, o) {
5245
- const opts = o || {};
5246
- const errors = [];
5247
- if (this.stopUnit(unit, Object.assign({}, opts, { timeoutMs: 1500 })) === false) errors.push("stop-unconfirmed");
5248
- removePidFile(opts);
5249
- return { ok: errors.length === 0, errors };
5250
- },
5251
- /** 返回值只表示「是否改动了内核强制」;governor 不因 false 走降级分支 —— 违规处置链本就平台无关。 */
5252
- setLimits() {
5253
- return false;
5254
5142
  }
5255
- };
5256
- module2.exports = { portable, findOurs, matchesAnchors, _test: { readPidFile, matchesAnchors, findOurs } };
5257
- }
5258
- });
5259
-
5260
- // src/platform/os/service.js
5261
- var require_service = __commonJS({
5262
- "src/platform/os/service.js"(exports2, module2) {
5263
- "use strict";
5264
- var fs2 = require("node:fs");
5265
- var os2 = require("node:os");
5266
- var path2 = require("node:path");
5267
- var exec = require_exec();
5268
- var input = require_input();
5269
- var execPath = require_exec_path();
5270
- var { portable } = require_portable();
5271
- var CapabilityError = class extends Error {
5272
- constructor(msg) {
5273
- super(msg);
5274
- this.name = "CapabilityError";
5275
- this.code = "CAPABILITY_UNSUPPORTED";
5143
+ note("launchservices", found.size ? found.size + " \u4E2A\u53EF\u6253\u5F00 https \u7684\u5E94\u7528" : "\u7CFB\u7EDF\u672A\u62A5\u4EFB\u4F55\u53EF\u7528\u5E94\u7528");
5144
+ if (!defaultId && found.size === 1) {
5145
+ defaultId = [...found.keys()][0];
5146
+ defaultSource = "only-installed";
5276
5147
  }
5277
- };
5278
- var PLATFORM = process.platform;
5279
- function run(cmd2, args, opts) {
5280
- return exec.run(cmd2, args, opts || {});
5148
+ return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5281
5149
  }
5282
- var UNIT_NAME_RE = input.UNIT_NAME_RE;
5283
- function unitNameViolation(unit) {
5284
- return input.unitNameViolation(unit);
5150
+ var PJ = path2.posix.join;
5151
+ function desktopDirs(env, home) {
5152
+ const dataHome = env.XDG_DATA_HOME || PJ(home, ".local", "share");
5153
+ const dataDirs = String(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":").filter(Boolean);
5154
+ const dirs = [[dataHome, "xdg"], ...dataDirs.map((p) => [p, "xdg"])];
5155
+ dirs.push([PJ(home, ".local", "share"), "xdg"]);
5156
+ dirs.push(["/var/lib/flatpak/exports/share", "flatpak"]);
5157
+ dirs.push([PJ(home, ".local", "share", "flatpak", "exports", "share"), "flatpak"]);
5158
+ dirs.push(["/var/lib/snapd/desktop", "snap"]);
5159
+ const seen = [];
5160
+ return dirs.map(([root, kind]) => ({ dir: PJ(root, "applications"), kind })).filter((x) => {
5161
+ if (seen.includes(x.dir)) return false;
5162
+ seen.push(x.dir);
5163
+ return true;
5164
+ });
5285
5165
  }
5286
- var systemd = {
5287
- kind: "systemd",
5288
- supportsUnits: true,
5289
- supportsTransient: true,
5290
- daemonReload() {
5291
- try {
5292
- return run("systemctl", ["--user", "daemon-reload"], { timeoutMs: 15e3 }) !== null;
5293
- } catch {
5294
- return false;
5295
- }
5296
- },
5297
- stopUnit(unit, opts) {
5298
- if (unitNameViolation(unit)) return false;
5299
- const o = opts || {};
5300
- try {
5301
- return run("systemctl", ["--user", "stop", unit], { timeoutMs: o.timeoutMs || 15e3 }) !== null;
5302
- } catch {
5303
- return false;
5166
+ function browserFromDesktop(text, kind, file) {
5167
+ let inMain = false;
5168
+ const got = {};
5169
+ for (const line of String(text).split(/\r?\n/)) {
5170
+ if (/^\[Desktop Entry\]\s*$/.test(line)) {
5171
+ inMain = true;
5172
+ continue;
5304
5173
  }
5305
- },
5306
- // run() 失败返回 null 而不抛,try/catch 包裹会恒返回 true;如实回传成败。
5307
- resetFailed(unit) {
5308
- if (unitNameViolation(unit)) return false;
5309
- return run("systemctl", ["--user", "reset-failed", unit], { timeoutMs: 1e4 }) !== null;
5310
- },
5311
- /** 单元活跃判定(三态):确认 active -> true;确认不活跃 -> false;查询未完成 -> null(未知)。
5312
- * run() 失败只返回 null,「查询未完成」不可折成 false:instance/ops.js 删除数据目录前
5313
- * 按「!== false 才放行删除」消费本函数,折假即超时窗口内误删沙箱数据。 */
5314
- isUnitActive(unit) {
5315
- if (!unit) return true;
5316
- if (unitNameViolation(unit)) return false;
5317
- try {
5318
- const r = exec.runDetail("systemctl", ["--user", "is-active", unit], { timeoutMs: 8e3 });
5319
- if (r.timedOut) return null;
5320
- const state = String(r.stdout || "").trim();
5321
- if (state === "active") return true;
5322
- if (state || r.code !== null) return false;
5323
- return null;
5324
- } catch {
5174
+ if (/^\[/.test(line)) break;
5175
+ if (!inMain) continue;
5176
+ const m = line.match(/^(Exec|Name|TryExec|Categories)=(.*)$/);
5177
+ if (!m) continue;
5178
+ if (m[1] === "Name" && got.Name) continue;
5179
+ got[m[1]] = m[2].trim();
5180
+ }
5181
+ if (!got.Exec) return null;
5182
+ const categories = String(got.Categories || "");
5183
+ const parsed = parseExecLine(got.Exec);
5184
+ if (!parsed) return null;
5185
+ const engine = engineOf(parsed.bin);
5186
+ const claimsBrowser = /(^|;)WebBrowser(;|$)/.test(categories);
5187
+ if (!claimsBrowser && engine === "other") return null;
5188
+ if (engine === "other") return null;
5189
+ return {
5190
+ // id 恒为归一后的可执行文件路径:defaultId 靠它在清单里定位条目,三平台同一契约
5191
+ id: parsed.bin.toLowerCase(),
5192
+ name: got.Name || path2.basename(file, ".desktop"),
5193
+ engine,
5194
+ bin: parsed.bin,
5195
+ baseArgs: parsed.baseArgs,
5196
+ source: kind,
5197
+ desktopFile: file
5198
+ };
5199
+ }
5200
+ function resolveLinuxBin(bin, canExec, env) {
5201
+ const b = String(bin || "").trim();
5202
+ if (!b) return null;
5203
+ if (b.startsWith("/")) return canExec(b) ? b : null;
5204
+ if (b.includes("/")) return null;
5205
+ for (const dir of String(env.PATH || "").split(":")) {
5206
+ if (!dir) continue;
5207
+ const p = PJ(dir.replace(/\/+$/, ""), b);
5208
+ if (canExec(p)) return p;
5209
+ }
5210
+ return null;
5211
+ }
5212
+ function probeLinux(d) {
5213
+ const { runOut, readFile, exists, listDir, canExec, env, home } = d;
5214
+ const notes = [];
5215
+ const note = (source, detail) => {
5216
+ notes.push({ source, detail });
5217
+ };
5218
+ const found = /* @__PURE__ */ new Map();
5219
+ const usable = (item) => {
5220
+ const resolved = resolveLinuxBin(item.bin, canExec, env);
5221
+ if (!resolved) {
5222
+ note(item.source, "\u4E0D\u53EF\u6267\u884C: " + item.bin);
5325
5223
  return null;
5326
5224
  }
5327
- },
5328
- transientUnitFile(unit) {
5329
- if (unitNameViolation(unit)) return null;
5330
- let uid = 0;
5331
- try {
5332
- uid = os2.userInfo().uid;
5333
- } catch {
5225
+ const k = resolved.toLowerCase();
5226
+ const hit = found.get(k);
5227
+ if (hit) {
5228
+ if (!hit.sources.includes(item.source)) hit.sources.push(item.source);
5229
+ return hit;
5334
5230
  }
5335
- const rt = process.env.XDG_RUNTIME_DIR || "/run/user/" + uid;
5336
- return path2.join(rt, "systemd", "transient", unit + ".service");
5337
- },
5338
- /** 清理 stale transient 单元:stop/reset-failed/删单元文件/daemon-reload。
5339
- * 必须 reload:删除文件后 systemd 仍缓存该单元为 loaded,否则 systemd-run 拒绝重建同名单元。
5340
- * 返回 {ok, errors}:stop/reset-failed 对「从未加载的单元」非零退出属正常,不计入 ok;
5341
- * 硬失败只有删单元文件与 daemon-reload(run() 失败返回 null 不抛,成败须如实回传)。 */
5342
- cleanTransient(unit) {
5343
- const errors = [];
5344
- const bad = unitNameViolation(unit);
5345
- if (bad) return { ok: false, errors: [bad] };
5346
- exec.runDetail("systemctl", ["--user", "stop", unit + ".service"], { timeoutMs: 1e4 });
5347
- exec.runDetail("systemctl", ["--user", "reset-failed", unit + ".service"], { timeoutMs: 1e4 });
5348
- let unlinkOk = true;
5231
+ const v = Object.assign({}, item, { id: k, bin: resolved, sources: [item.source] });
5232
+ found.set(k, v);
5233
+ return v;
5234
+ };
5235
+ for (const { dir, kind } of desktopDirs(env, home)) {
5236
+ let names = [];
5349
5237
  try {
5350
- const f = this.transientUnitFile(unit);
5351
- if (f) fs2.unlinkSync(f);
5238
+ names = listDir(dir).filter((f) => f.endsWith(".desktop"));
5352
5239
  } catch {
5353
- unlinkOk = false;
5354
- errors.push("unlink " + unit + ".service");
5240
+ continue;
5241
+ }
5242
+ for (const f of names) {
5243
+ let text = null;
5244
+ try {
5245
+ text = readFile(PJ(dir, f));
5246
+ } catch {
5247
+ continue;
5248
+ }
5249
+ const item = browserFromDesktop(text, kind, f);
5250
+ if (item) usable(item);
5355
5251
  }
5356
- if (!exec.runDetail("systemctl", ["--user", "daemon-reload"], { timeoutMs: 15e3 }).ok) errors.push("daemon-reload");
5357
- return { ok: unlinkOk && errors.length === 0, errors };
5358
- },
5359
- /** 以 transient 单元启动(独立 cgroup;每实例隔离)。
5360
- * @param {{unit:string, cmd:string[], env?:object, props?:string[], workingDir?:string, description?:string, timeoutMs?:number}} o
5361
- * props 为 systemd 属性(如 KillMode=process、MemoryMax=8G),平台层只拼装成 --property,不解释语义。 */
5362
- startTransient(o) {
5363
- const opts = o || {};
5364
- const bad = unitNameViolation(opts.unit);
5365
- if (bad) throw new Error("systemd-run \u62D2\u7EDD: " + bad);
5366
- const args = ["--user", "--unit=" + opts.unit];
5367
- if (opts.description) args.push("--description=" + opts.description);
5368
- for (const p of opts.props || []) args.push("--property=" + p);
5369
- for (const [k, v] of Object.entries(opts.env || {})) args.push("--setenv=" + k + "=" + v);
5370
- if (opts.workingDir) args.push("--working-directory=" + opts.workingDir);
5371
- args.push("--", ...opts.cmd || []);
5372
- const r = exec.runDetail("systemd-run", args, { timeoutMs: opts.timeoutMs || 2e4 });
5373
- if (!r.ok) throw new Error("systemd-run \u5931\u8D25: " + (r.error || "unknown") + (r.stderr ? " | " + String(r.stderr).trim() : ""));
5374
- return true;
5375
- },
5376
- /** 运行期改限额(W3 动态化):systemctl --user set-property 立即生效,无需重启单元。
5377
- * 必带 --runtime:transient 单元本不落盘,不带它会把 drop-in 写进用户配置目录,陈旧下限永久黏住。
5378
- * 值只来自 sandbox.unitProps 同源的 alloc(平台层翻译语义、不决定数额)。 */
5379
- setLimits(unit, alloc) {
5380
- if (unitNameViolation(unit)) return false;
5381
- const a = alloc || {};
5382
- const props = [];
5383
- if (a.memoryMax) props.push("MemoryMax=" + a.memoryMax);
5384
- if (a.memoryHigh) props.push("MemoryHigh=" + a.memoryHigh);
5385
- if (a.cpuQuota) props.push("CPUQuota=" + a.cpuQuota);
5386
- if (!props.length) return false;
5387
- return exec.runDetail(
5388
- "systemctl",
5389
- ["--user", "set-property", "--runtime", unit].concat(props),
5390
- { timeoutMs: 1e4 }
5391
- ).ok;
5392
5252
  }
5393
- };
5394
- function makeUnsupported(kind, label) {
5395
- return {
5396
- kind,
5397
- supportsUnits: false,
5398
- supportsTransient: false,
5399
- daemonReload() {
5400
- return false;
5401
- },
5402
- stopUnit() {
5403
- throw new CapabilityError(label + "\uFF1A\u4E0D\u652F\u6301\u4EE5\u7528\u6237\u5355\u5143\u65B9\u5F0F\u7BA1\u7406\u88AB\u7BA1\u5B9E\u4F8B");
5404
- },
5405
- resetFailed() {
5406
- return false;
5407
- },
5408
- isUnitActive(unit) {
5409
- return unit ? false : true;
5410
- },
5411
- transientUnitFile() {
5412
- return null;
5413
- },
5414
- // 无 transient 单元可清 = 成功;返回形态与 systemd 一致,调用方无需分支。
5415
- cleanTransient() {
5416
- return { ok: true, errors: [] };
5417
- },
5418
- startTransient() {
5419
- throw new CapabilityError(label + "\uFF1A\u4E0D\u652F\u6301\u62C9\u8D77\u5B9E\u4F8B\u8231\uFF08portable \u6863\u672A\u542F\u7528\uFF09");
5420
- },
5421
- setLimits() {
5422
- return false;
5253
+ let defaultId = null;
5254
+ let defaultSource = null;
5255
+ const fromMime = linuxDefaultFromMimeApps(readFile, exists, env, home, note);
5256
+ if (fromMime) {
5257
+ const item = usable(fromMime);
5258
+ if (item) {
5259
+ defaultId = item.id;
5260
+ defaultSource = "mimeapps";
5261
+ }
5262
+ }
5263
+ const id = runOut("xdg-settings", ["get", "default-web-browser"]);
5264
+ const desktopId = id && String(id).trim();
5265
+ if (!defaultId && desktopId && /^[A-Za-z0-9._-]+\.desktop$/.test(desktopId)) {
5266
+ for (const { dir } of desktopDirs(env, home)) {
5267
+ const p = PJ(dir, desktopId);
5268
+ let text = null;
5269
+ try {
5270
+ if (exists(p)) text = readFile(p);
5271
+ } catch {
5272
+ continue;
5273
+ }
5274
+ if (text === null) continue;
5275
+ const item = browserFromDesktop(text, "xdg-settings", desktopId);
5276
+ const used = item && usable(item);
5277
+ if (used) {
5278
+ defaultId = used.id;
5279
+ defaultSource = "xdg-settings";
5280
+ break;
5281
+ }
5282
+ }
5283
+ }
5284
+ if (!desktopId) note("xdg-settings", "\u65E0\u8F93\u51FA");
5285
+ else if (defaultSource === "xdg-settings") note("xdg-settings", "\u9ED8\u8BA4=" + defaultId);
5286
+ else note("xdg-settings", "\u62A5\u51FA " + desktopId + (defaultId ? "\uFF08\u9ED8\u8BA4\u5DF2\u7531 " + defaultSource + " \u5B9A\u51FA\uFF09" : " \u4F46\u4E0D\u53EF\u7528"));
5287
+ if (!defaultId && found.size === 1) {
5288
+ defaultId = [...found.keys()][0];
5289
+ defaultSource = "only-installed";
5290
+ }
5291
+ return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5292
+ }
5293
+ function linuxDefaultFromMimeApps(readFile, exists, env, home, note) {
5294
+ const cfgHome = env.XDG_CONFIG_HOME || PJ(home, ".config");
5295
+ const cfgDirs = String(env.XDG_CONFIG_DIRS || "/etc/xdg").split(":").filter(Boolean);
5296
+ const dataDirs = String(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":").filter(Boolean);
5297
+ const files = [
5298
+ PJ(cfgHome, "mimeapps.list"),
5299
+ ...cfgDirs.map((d) => PJ(d, "mimeapps.list")),
5300
+ ...dataDirs.map((d) => PJ(d, "applications", "mimeapps.list"))
5301
+ ];
5302
+ for (const f of files) {
5303
+ if (!exists(f)) continue;
5304
+ let text = null;
5305
+ try {
5306
+ text = readFile(f);
5307
+ } catch {
5308
+ continue;
5423
5309
  }
5424
- };
5425
- }
5426
- var NONE = makeUnsupported("none", "\u5F53\u524D\u5E73\u53F0\u65E0\u670D\u52A1\u7BA1\u7406\u5668");
5427
- var _systemdRun = null;
5428
- function hasSystemdRun() {
5429
- if (_systemdRun === null) {
5430
- _systemdRun = !!execPath.resolveExecutable("systemd-run") || exec.runOut("systemd-run", ["--version"], { timeoutMs: 3e3 }) !== null;
5310
+ const id = mimeAppsDefault(text);
5311
+ if (!id) continue;
5312
+ note("mimeapps", path2.basename(f) + " -> " + id);
5313
+ for (const { dir, kind } of desktopDirs(env, home)) {
5314
+ const p = PJ(dir, id);
5315
+ if (!exists(p)) continue;
5316
+ let dt = null;
5317
+ try {
5318
+ dt = readFile(p);
5319
+ } catch {
5320
+ continue;
5321
+ }
5322
+ const item = browserFromDesktop(dt, kind, id);
5323
+ if (item) return item;
5324
+ }
5325
+ return null;
5431
5326
  }
5432
- return _systemdRun;
5433
- }
5434
- function current() {
5435
- if (PLATFORM === "linux") return hasSystemdRun() ? systemd : portable;
5436
- if (PLATFORM === "darwin" || PLATFORM === "win32") return portable;
5437
- return NONE;
5438
- }
5439
- module2.exports = { current, CapabilityError, kind: () => current().kind, PLATFORM, UNIT_NAME_RE, unitNameViolation, _testProviders: { systemd, portable, NONE } };
5440
- }
5441
- });
5442
-
5443
- // src/platform/os/notify.js
5444
- var require_notify = __commonJS({
5445
- "src/platform/os/notify.js"(exports2, module2) {
5446
- "use strict";
5447
- var spawnOS = require_spawn();
5448
- function appleScriptString(s) {
5449
- return JSON.stringify(String(s));
5450
- }
5451
- function powerShellString(s) {
5452
- return "'" + String(s).replace(/'/g, "''") + "'";
5327
+ note("mimeapps", "\u65E0\u9ED8\u8BA4\u9879");
5328
+ return null;
5453
5329
  }
5454
- function notifyCommand(platform, title, body) {
5455
- const pl = platform || process.platform;
5456
- const t = String(title == null ? "" : title);
5457
- const b = String(body == null ? "" : body);
5458
- if (pl === "linux") {
5459
- return { cmd: "notify-send", args: ["-a", "dsh-supervisor", t, b] };
5460
- }
5461
- if (pl === "darwin") {
5462
- const script = "display notification " + appleScriptString(b) + " with title " + appleScriptString(t);
5463
- return { cmd: "osascript", args: ["-e", script] };
5464
- }
5465
- if (pl === "win32") {
5466
- const ps = [
5467
- '[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null',
5468
- '[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null',
5469
- "$n = New-Object System.Windows.Forms.NotifyIcon",
5470
- "$n.Icon = [System.Drawing.SystemIcons]::Information",
5471
- "$n.Visible = $true",
5472
- "$n.ShowBalloonTip(4000, " + powerShellString(t) + ", " + powerShellString(b) + ", [System.Windows.Forms.ToolTipIcon]::None)",
5473
- "Start-Sleep -Milliseconds 4200",
5474
- "$n.Dispose(); $n.Visible = $false"
5475
- ].join("; ");
5476
- return { cmd: "powershell", args: ["-NoProfile", "-NonInteractive", "-Command", ps] };
5330
+ function mimeAppsDefault(text) {
5331
+ let inDefault = false;
5332
+ for (const line of String(text || "").split(/\r?\n/)) {
5333
+ if (/^\s*\[/.test(line)) {
5334
+ inDefault = /^\s*\[Default Applications\]\s*$/.test(line);
5335
+ continue;
5336
+ }
5337
+ if (!inDefault) continue;
5338
+ const m = line.match(/^\s*x-scheme-handler\/https?\s*=\s*(.*)$/i);
5339
+ if (!m) continue;
5340
+ const ids = m[1].split(";").map((s) => s.trim()).filter((s) => /^[A-Za-z0-9._-]+\.desktop$/.test(s));
5341
+ if (ids.length) return ids[0];
5477
5342
  }
5478
5343
  return null;
5479
5344
  }
5480
- function notify(title, body, onError) {
5345
+ function probe(platform, d) {
5346
+ const pl = platform || process.platform;
5347
+ const dd = Object.assign({}, d, { canExec: d.canExec || ((p) => isExecutableFile(p, pl)) });
5348
+ if (pl === "win32") return probeWin(dd);
5349
+ if (pl === "darwin") return probeMac(dd);
5350
+ return probeLinux(dd);
5351
+ }
5352
+ var CACHE = /* @__PURE__ */ Object.create(null);
5353
+ function inventory(platform, o) {
5354
+ const ov = o || {};
5355
+ const pl = platform || process.platform;
5356
+ const now = ov.now || (() => Date.now());
5357
+ const ttl = ov.ttlMs === void 0 ? 6e4 : ov.ttlMs;
5358
+ const cached = CACHE[pl];
5359
+ if (!ov.force && cached && now() - cached.at < ttl) return Object.assign({ cached: true }, cached.value);
5360
+ const runOut = ov.runOut || ((bin, args) => exec.runOut(bin, args, { timeoutMs: PROBE_TIMEOUT_MS }));
5361
+ const readFile = ov.readFile || ((p) => fs2.readFileSync(p, "utf8"));
5362
+ const exists = ov.exists || ((p) => fs2.existsSync(p));
5363
+ const listDir = ov.listDir || ((p) => fs2.readdirSync(p));
5364
+ let value;
5481
5365
  try {
5482
- const plan = notifyCommand(process.platform, title, body);
5483
- if (!plan) return false;
5484
- const c = spawnOS.detachedIgnored(plan.cmd, plan.args);
5485
- c.on("error", () => {
5486
- if (onError) onError();
5487
- });
5488
- c.unref();
5489
- return true;
5490
- } catch {
5491
- if (onError) onError();
5492
- return false;
5366
+ value = probe(pl, { runOut, readFile, exists, listDir, canExec: ov.canExec, env: ov.env || process.env, home: ov.home || os2.homedir() });
5367
+ } catch (e) {
5368
+ value = { browsers: [], defaultId: null, defaultSource: null, probed: [{ source: "probe-error", detail: String(e && e.message || e) }] };
5493
5369
  }
5370
+ value.at = now();
5371
+ value.platform = pl;
5372
+ value.browsers.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
5373
+ CACHE[pl] = { at: now(), value };
5374
+ return Object.assign({ cached: false }, value);
5494
5375
  }
5495
- module2.exports = { notify, notifyCommand, appleScriptString, powerShellString };
5376
+ function invalidate(platform) {
5377
+ if (platform) delete CACHE[platform];
5378
+ else for (const k of Object.keys(CACHE)) delete CACHE[k];
5379
+ }
5380
+ module2.exports = {
5381
+ inventory,
5382
+ invalidate,
5383
+ probe,
5384
+ probeWin,
5385
+ probeMac,
5386
+ probeLinux,
5387
+ engineOf,
5388
+ tokenizeExec,
5389
+ parseExecLine,
5390
+ regValueOf,
5391
+ expandEnvVars,
5392
+ exeFromCmdLine,
5393
+ regSubkeys,
5394
+ regKeyFull,
5395
+ regValueTargets,
5396
+ safeRegKeyPart,
5397
+ browserFromDesktop,
5398
+ desktopDirs,
5399
+ resolveLinuxBin,
5400
+ mimeAppsDefault,
5401
+ linuxDefaultFromMimeApps,
5402
+ WIN_APP_PATHS,
5403
+ PROBE_TIMEOUT_MS
5404
+ };
5496
5405
  }
5497
5406
  });
5498
5407
 
5499
- // src/platform/os/browser-inventory.js
5500
- var require_browser_inventory = __commonJS({
5501
- "src/platform/os/browser-inventory.js"(exports2, module2) {
5408
+ // src/platform/os/environment.js
5409
+ var require_environment = __commonJS({
5410
+ "src/platform/os/environment.js"(exports2, module2) {
5502
5411
  "use strict";
5503
5412
  var fs2 = require("node:fs");
5504
- var path2 = require("node:path");
5505
5413
  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";
5414
+ var path2 = require("node:path");
5415
+ var stateRoot2 = require_state_root();
5416
+ var { writeAtomic } = require_fs();
5417
+ var desktop = require_desktop();
5418
+ var detector = require_browser_inventory();
5419
+ var { engineOf } = detector;
5420
+ var SCHEMA = 1;
5421
+ var FILE_NAME = "environment.json";
5422
+ var FORM_TTL_MS = 6e4;
5423
+ var _sources = {};
5424
+ function bind(sources) {
5425
+ _sources = Object.assign({}, _sources, sources || {});
5426
+ return _sources;
5427
+ }
5428
+ function preferenceId() {
5429
+ const f = _sources.preference;
5430
+ if (typeof f !== "function") return null;
5431
+ const v = f();
5432
+ return typeof v === "string" && v.trim() ? v.trim() : null;
5514
5433
  }
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;
5434
+ function capabilities() {
5435
+ const f = _sources.capabilities;
5436
+ if (typeof f !== "function") return null;
5437
+ return f() || null;
5555
5438
  }
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);
5439
+ function identity() {
5440
+ let user = null;
5441
+ try {
5442
+ user = os2.userInfo().username;
5443
+ } catch {
5444
+ user = process.env.USER || process.env.USERNAME || null;
5562
5445
  }
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;
5446
+ return {
5447
+ platform: process.platform,
5448
+ arch: process.arch,
5449
+ hostname: os2.hostname(),
5450
+ user: user || null,
5451
+ home: os2.homedir(),
5452
+ node: process.version
5453
+ };
5581
5454
  }
5582
- function safeRegKeyPart(s) {
5583
- return typeof s === "string" && s.length > 0 && s.length <= 200 && /^[\x20-\x7e]+$/.test(s) && !/[;|&`$()<>^"'\r\n]/.test(s);
5455
+ function paths() {
5456
+ return { root: stateRoot2.root(), supervisor: stateRoot2.supervisorDir(), shell: stateRoot2.shellDir() };
5584
5457
  }
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;
5458
+ function normalizeInventory(inv, platform) {
5459
+ const list = inv && Array.isArray(inv.browsers) ? inv.browsers : [];
5460
+ const defaultId = inv && inv.defaultId ? inv.defaultId : null;
5461
+ return {
5462
+ platform: inv && inv.platform || platform || process.platform,
5463
+ cached: !!(inv && inv.cached),
5464
+ at: inv && inv.at !== void 0 ? inv.at : null,
5465
+ defaultId,
5466
+ defaultSource: inv && inv.defaultSource || null,
5467
+ browsers: list.map((b) => ({
5468
+ id: b.id,
5469
+ name: b.name,
5470
+ bin: b.bin,
5471
+ engine: b.engine || engineOf(b.bin),
5472
+ baseArgs: b.baseArgs || [],
5473
+ sources: b.sources && b.sources.length ? b.sources : [b.source || "unknown"],
5474
+ isDefault: !!defaultId && b.id === defaultId
5475
+ })),
5476
+ probed: inv && inv.probed || []
5477
+ };
5591
5478
  }
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));
5479
+ function browsers(o) {
5480
+ const ov = o || {};
5481
+ const pl = ov.platform || process.platform;
5482
+ if (ov.inventory) return normalizeInventory(ov.inventory, pl);
5483
+ const resolve = typeof ov.resolveInventory === "function" ? ov.resolveInventory : ((platform, deps) => detector.inventory(platform, deps));
5484
+ return normalizeInventory(resolve(pl, ov.resolveDeps || {}), pl);
5485
+ }
5486
+ var ENGINE_RANK = { chromium: 0, firefox: 1, other: 2 };
5487
+ function rankCandidates(list) {
5488
+ const rankOf = (b) => {
5489
+ const r = ENGINE_RANK[b && b.engine];
5490
+ return r === void 0 ? ENGINE_RANK.other : r;
5491
+ };
5492
+ return (Array.isArray(list) ? list : []).slice().sort((a, b) => {
5493
+ if (rankOf(a) !== rankOf(b)) return rankOf(a) - rankOf(b);
5494
+ const ia = String(a && a.id || "");
5495
+ const ib = String(b && b.id || "");
5496
+ return ia < ib ? -1 : ia > ib ? 1 : 0;
5497
+ });
5598
5498
  }
5599
- function regSubkeys(runner, note, key) {
5600
- const out = runner("reg.exe", ["query", key]);
5601
- if (!out) {
5602
- note(key, "unreadable");
5603
- return [];
5499
+ function pickLauncher(platform, inv, preference) {
5500
+ const list = inv && Array.isArray(inv.browsers) ? inv.browsers : [];
5501
+ const byId = /* @__PURE__ */ new Map();
5502
+ for (const b of list) byId.set(b.id, b);
5503
+ const wanted = preference === void 0 ? preferenceId() : typeof preference === "string" && preference ? preference : null;
5504
+ const stale = wanted !== null && !byId.has(wanted);
5505
+ const out = (browser, how) => ({ browser, how, wanted, stale });
5506
+ if (wanted !== null && byId.has(wanted)) return out(byId.get(wanted), "user-preference");
5507
+ const sys = inv && inv.defaultId ? byId.get(inv.defaultId) : null;
5508
+ if (sys) return out(sys, inv.defaultSource || "default");
5509
+ if (list.length === 1) return out(list[0], "only-installed");
5510
+ if (list.length > 1) return out(rankCandidates(list)[0], "candidate-rank");
5511
+ return out(null, "none-found");
5512
+ }
5513
+ function snapshotPath() {
5514
+ return path2.join(stateRoot2.supervisorDir(), FILE_NAME);
5515
+ }
5516
+ function readSnapshot() {
5517
+ try {
5518
+ const doc = JSON.parse(fs2.readFileSync(snapshotPath(), "utf8"));
5519
+ return doc && doc.schema === SCHEMA ? doc : null;
5520
+ } catch {
5521
+ return null;
5604
5522
  }
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);
5523
+ }
5524
+ var _formCache = null;
5525
+ function form(o) {
5526
+ const ov = o || {};
5527
+ const now = typeof ov.now === "function" ? ov.now : (() => Date.now());
5528
+ const stamp = now();
5529
+ const ttl = ov.ttlMs === void 0 ? FORM_TTL_MS : ov.ttlMs;
5530
+ if (!ov.force && _formCache && stamp - _formCache.at < ttl) {
5531
+ return Object.assign({}, _formCache.value, { cached: true });
5532
+ }
5533
+ const inv = browsers(ov);
5534
+ const caps = capabilities();
5535
+ const session = desktop.describe();
5536
+ const prefId = preferenceId();
5537
+ const prefEntry = prefId ? inv.browsers.find((b) => b.id === prefId) || null : null;
5538
+ const preference = {
5539
+ id: prefId,
5540
+ configured: !!prefId,
5541
+ matched: !!prefEntry,
5542
+ browser: prefEntry ? { id: prefEntry.id, name: prefEntry.name, engine: prefEntry.engine } : null,
5543
+ reason: !prefId ? "not-set" : prefEntry ? "matched" : "stale"
5544
+ };
5545
+ const pick = pickLauncher(inv.platform, inv, prefId);
5546
+ const probed = inv.probed.map((p) => ({ section: "browsers", source: p.source, detail: p.detail }));
5547
+ probed.push({ section: "session", source: "desktop", detail: session.reason + (session.available ? "\uFF08\u53EF\u7528\uFF09" : "\uFF08\u4E0D\u53EF\u7528\uFF09") });
5548
+ probed.push({
5549
+ section: "capabilities",
5550
+ source: "profile",
5551
+ detail: caps ? "openBrowser=" + (caps.openBrowser === true) : "\u672A\u7ED1\u5B9A\u80FD\u529B\u77E9\u9635\uFF08\u7531 platform/os/index.js \u88C5\u914D\u671F\u6CE8\u5165\uFF09"
5552
+ });
5553
+ probed.push({
5554
+ section: "preference",
5555
+ source: "config",
5556
+ detail: preference.reason === "not-set" ? "\u672A\u8BBE\u7F6E\uFF0C\u6309\u7CFB\u7EDF\u9ED8\u8BA4\u6216\u5019\u9009\u6B21\u5E8F\u5206\u53D1" : preference.reason === "matched" ? "\u5DF2\u9009 " + preference.browser.name : "\u504F\u597D\u6240\u6307\u5DF2\u4E0D\u5728\u5019\u9009\u6E05\u5355: " + prefId
5557
+ });
5558
+ probed.push({
5559
+ section: "pick",
5560
+ source: "form",
5561
+ detail: pick.how + (pick.browser ? "\uFF08" + pick.browser.name + "\uFF09" : "\uFF08\u65E0\u5019\u9009\uFF09") + (pick.stale ? "\uFF0C\u504F\u597D\u5DF2\u5931\u6548\u9700\u91CD\u9009" : "")
5562
+ });
5563
+ const value = {
5564
+ schema: SCHEMA,
5565
+ at: stamp,
5566
+ platform: inv.platform,
5567
+ identity: identity(),
5568
+ paths: paths(),
5569
+ session,
5570
+ capabilities: caps,
5571
+ preference,
5572
+ default: inv.defaultId ? { id: inv.defaultId, source: inv.defaultSource } : null,
5573
+ browsers: inv.browsers,
5574
+ pick: { how: pick.how, id: pick.browser ? pick.browser.id : null, name: pick.browser ? pick.browser.name : null, wanted: pick.wanted, stale: pick.stale },
5575
+ probed
5576
+ };
5577
+ const snapshot = { path: snapshotPath(), written: false, error: null };
5578
+ if (ov.persist === true) {
5579
+ try {
5580
+ writeAtomic(snapshot.path, JSON.stringify(value, null, 2) + "\n", { mode: 384 });
5581
+ snapshot.written = true;
5582
+ } catch (e) {
5583
+ snapshot.error = String(e && e.message || e);
5584
+ }
5585
+ probed.push({
5586
+ section: "snapshot",
5587
+ source: FILE_NAME,
5588
+ detail: snapshot.written ? "\u5DF2\u843D\u76D8 " + snapshot.path : "\u843D\u76D8\u5931\u8D25: " + snapshot.error
5589
+ });
5613
5590
  }
5614
- note(key, names.length ? names.length + " \u9879" : "\u65E0\u5B50\u952E");
5615
- return names;
5591
+ value.snapshot = snapshot;
5592
+ _formCache = { at: stamp, value };
5593
+ return Object.assign({ cached: false }, value);
5616
5594
  }
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;
5595
+ function invalidate(platform) {
5596
+ _formCache = null;
5597
+ return detector.invalidate(platform);
5598
+ }
5599
+ function checkPreference(value, form2) {
5600
+ const candidates = (form2 && form2.browsers || []).map((b) => ({
5601
+ id: b.id,
5602
+ name: b.name,
5603
+ engine: b.engine,
5604
+ isDefault: b.isDefault === true
5605
+ }));
5606
+ const id = typeof value === "string" ? value.trim() : "";
5607
+ if (!id) return { ok: true, id: null, browser: null, candidates };
5608
+ const hit = candidates.find((c) => c.id === id);
5609
+ if (!hit) {
5610
+ return { ok: false, id: null, browser: null, candidates, error: "\u8BE5\u6D4F\u89C8\u5668\u4E0D\u5728\u672C\u673A\u5019\u9009\u6E05\u5355\u91CC\uFF08\u53EF\u80FD\u5DF2\u5378\u8F7D\u6216\u8DEF\u5F84\u5931\u6548\uFF09\uFF0C\u8BF7\u5148\u5237\u65B0\u73AF\u5883\u8868\u5355" };
5624
5611
  }
5625
- return null;
5612
+ return { ok: true, id: hit.id, browser: hit, candidates };
5626
5613
  }
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;
5614
+ module2.exports = {
5615
+ SCHEMA,
5616
+ FILE_NAME,
5617
+ FORM_TTL_MS,
5618
+ bind,
5619
+ preferenceId,
5620
+ capabilities,
5621
+ identity,
5622
+ paths,
5623
+ browsers,
5624
+ normalizeInventory,
5625
+ rankCandidates,
5626
+ pickLauncher,
5627
+ checkPreference,
5628
+ form,
5629
+ snapshotPath,
5630
+ readSnapshot,
5631
+ invalidate
5632
+ };
5633
+ }
5634
+ });
5635
+
5636
+ // src/platform/os/process.js
5637
+ var require_process = __commonJS({
5638
+ "src/platform/os/process.js"(exports2, module2) {
5639
+ "use strict";
5640
+ var ex2 = require_exec();
5641
+ var isWindows = process.platform === "win32";
5642
+ function signalProcess(pid, sig) {
5643
+ if (!Number.isInteger(pid) || pid <= 0) return;
5644
+ if (isWindows) {
5645
+ try {
5646
+ process.kill(pid, sig);
5647
+ } catch {
5650
5648
  }
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;
5649
+ return;
5650
+ }
5651
+ try {
5652
+ process.kill(-pid, sig);
5653
+ } catch {
5654
+ try {
5655
+ process.kill(pid, sig);
5656
+ } catch {
5673
5657
  }
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
5658
  }
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");
5659
+ }
5660
+ function killTree(pid, sig, cb, opts) {
5661
+ if (!Number.isInteger(pid) || pid <= 0) {
5662
+ if (cb) cb(new Error("invalid pid"));
5663
+ return;
5688
5664
  }
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
- }
5665
+ if (isWindows) {
5666
+ ex2.runAsync("taskkill", ["/PID", String(pid), "/T", "/F"], { timeoutMs: 1e4 }).then((r) => {
5667
+ if (cb) cb(r.ok ? null : new Error(r.error || "taskkill failed"));
5668
+ });
5669
+ return;
5696
5670
  }
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;
5671
+ if (opts && opts.ownGroup === true) {
5672
+ signalProcess(pid, sig || "SIGTERM");
5673
+ } else {
5674
+ try {
5675
+ process.kill(pid, sig || "SIGTERM");
5676
+ } catch {
5701
5677
  }
5702
5678
  }
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");
5679
+ if (cb) process.nextTick(cb, null);
5680
+ }
5681
+ module2.exports = { signalProcess, killTree };
5682
+ }
5683
+ });
5684
+
5685
+ // src/platform/os/file-protect.js
5686
+ var require_file_protect = __commonJS({
5687
+ "src/platform/os/file-protect.js"(exports2, module2) {
5688
+ "use strict";
5689
+ var fs2 = require("node:fs");
5690
+ var os2 = require("node:os");
5691
+ var path2 = require("node:path");
5692
+ var ex2 = require_exec();
5693
+ var IS_WINDOWS2 = process.platform === "win32";
5694
+ var _icacls = null;
5695
+ function hasIcacls(platform) {
5696
+ if ((platform || process.platform) !== "win32") return false;
5697
+ if (_icacls !== null) return _icacls;
5698
+ _icacls = ex2.runOut("icacls", ["/?"], { timeoutMs: 3e3 }) !== null;
5699
+ return _icacls;
5700
+ }
5701
+ function currentUser() {
5702
+ return process.env.USERNAME || process.env.USER || (() => {
5703
+ try {
5704
+ return os2.userInfo().username;
5705
+ } catch {
5706
+ return null;
5707
+ }
5708
+ })();
5709
+ }
5710
+ function protectFile(file) {
5711
+ if (!IS_WINDOWS2) {
5712
+ try {
5713
+ fs2.chmodSync(file, 384);
5714
+ return { ok: true, mode: "posix-0600" };
5715
+ } catch (e) {
5716
+ return { ok: false, mode: "posix-0600", reason: e.message };
5717
+ }
5708
5718
  }
5709
- if (found.size === 1) setDefault([...found.values()][0], "only-installed");
5710
- return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5719
+ if (!hasIcacls()) return { ok: false, mode: "none", reason: "icacls \u4E0D\u53EF\u7528" };
5720
+ const user = currentUser();
5721
+ if (!user) return { ok: false, mode: "icacls", reason: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u7528\u6237" };
5722
+ const r = ex2.runDetail("icacls", [file, "/inheritance:r", "/grant:r", user + ":F"], { stdio: "ignore", timeoutMs: 5e3 });
5723
+ return r.ok ? { ok: true, mode: "icacls-file" } : { ok: false, mode: "icacls-file", reason: r.error || "\u9000\u51FA\u7801 " + r.code };
5711
5724
  }
5712
- function regValueTargets(runOut, note, key) {
5713
- const out = runOut("reg.exe", ["query", key]);
5714
- if (!out) {
5715
- note(key, "unreadable");
5716
- return [];
5725
+ function protectDir(dir) {
5726
+ if (!IS_WINDOWS2) {
5727
+ try {
5728
+ fs2.chmodSync(dir, 448);
5729
+ return { ok: true, mode: "posix-0700" };
5730
+ } catch (e) {
5731
+ return { ok: false, mode: "posix-0700", reason: e.message };
5732
+ }
5717
5733
  }
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);
5734
+ if (!hasIcacls()) return { ok: false, mode: "none", reason: "icacls \u4E0D\u53EF\u7528" };
5735
+ const user = currentUser();
5736
+ if (!user) return { ok: false, mode: "icacls", reason: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u7528\u6237" };
5737
+ const r = ex2.runDetail("icacls", [dir, "/inheritance:r", "/grant:r", user + ":(OI)(CI)F"], { stdio: "ignore", timeoutMs: 1e4 });
5738
+ return r.ok ? { ok: true, mode: "icacls-dir" } : { ok: false, mode: "icacls-dir", reason: r.error || "\u9000\u51FA\u7801 " + r.code };
5739
+ }
5740
+ function ensurePrivateDir(dir) {
5741
+ try {
5742
+ fs2.mkdirSync(dir, { recursive: true });
5743
+ } catch (e) {
5744
+ return { ok: false, mode: "mkdir", reason: e.message };
5726
5745
  }
5727
- note(key, paths.length ? paths.length + " \u4E2A\u80FD\u529B\u8DEF\u5F84" : "\u65E0\u503C");
5728
- return paths;
5746
+ return protectDir(dir);
5729
5747
  }
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;
5748
+ function writePrivate(file, data) {
5749
+ try {
5750
+ const dir = path2.dirname(file);
5751
+ try {
5752
+ fs2.mkdirSync(dir, { recursive: true });
5753
+ } catch {
5773
5754
  }
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";
5755
+ const tmp = file + ".tmp" + process.pid;
5756
+ fs2.writeFileSync(tmp, data, { mode: 384 });
5757
+ const p1 = protectFile(tmp);
5758
+ if (p1 && p1.ok === false) {
5759
+ try {
5760
+ fs2.rmSync(tmp, { force: true });
5761
+ } catch {
5781
5762
  }
5763
+ return { ok: false, reason: "protect(tmp): " + (p1.reason || p1.mode), mode: p1.mode };
5782
5764
  }
5765
+ fs2.renameSync(tmp, file);
5766
+ const p2 = protectFile(file);
5767
+ if (p2 && p2.ok === false) return { ok: false, reason: "protect(file): " + (p2.reason || p2.mode), mode: p2.mode };
5768
+ return { ok: true };
5769
+ } catch (e) {
5770
+ return { ok: false, reason: e.message };
5783
5771
  }
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
5772
  }
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
- });
5773
+ module2.exports = { protectFile, protectDir, ensurePrivateDir, writePrivate, hasIcacls, currentUser, IS_WINDOWS: IS_WINDOWS2 };
5774
+ }
5775
+ });
5776
+
5777
+ // src/platform/util/input.js
5778
+ var require_input = __commonJS({
5779
+ "src/platform/util/input.js"(exports2, module2) {
5780
+ "use strict";
5781
+ var PKG_NAME_RE = /^(@[a-zA-Z0-9._-]+\/)?[a-zA-Z0-9._-]+$/;
5782
+ var ARGV_UNSAFE_RE = /[\s;|&<>`'"$(){}\\*?~#]/;
5783
+ var WIN_ABS_PATH_RE = /^[A-Za-z]:\\[^;|&<>`'"$*?~#\s]*$/;
5784
+ var UNIT_NAME_RE = /^[A-Za-z0-9:@._-]{1,128}$/;
5785
+ var UNSAFE_LEDGER_KEYS = ["__proto__", "prototype", "constructor"];
5786
+ var LEDGER_UNSAFE_RE = /[\s\u0000-\u001f\u007f]/;
5787
+ function argvViolation(arg) {
5788
+ const s = String(arg == null ? "" : arg);
5789
+ if (!s) return "\u7A7A\u7684 argv \u9879";
5790
+ if (ARGV_UNSAFE_RE.test(s) && !WIN_ABS_PATH_RE.test(s)) return "argv \u9879\u542B\u7981\u7528\u5B57\u7B26\uFF08\u7A7A\u767D/shell \u5143\u5B57\u7B26\uFF09: " + s.slice(0, 80);
5791
+ return null;
5806
5792
  }
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();
5793
+ var CONTROL_RE = /[\u0000-\u001f\u007f]/;
5794
+ function isAbsoluteForm(p) {
5795
+ const s = String(p == null ? "" : p);
5796
+ return s.charCodeAt(0) === 47 || /^[A-Za-z]:[\\/]/.test(s) || s.charCodeAt(0) === 92 && s.charCodeAt(1) === 92;
5797
+ }
5798
+ function prefixViolation(prefix) {
5799
+ const s = String(prefix == null ? "" : prefix);
5800
+ if (!s.trim()) return "\u7A7A\u7684\u5B89\u88C5\u524D\u7F00";
5801
+ if (CONTROL_RE.test(s)) return "\u5B89\u88C5\u524D\u7F00\u542B\u63A7\u5236\u7B26: " + s.slice(0, 80);
5802
+ if (s.length > 4096) return "\u5B89\u88C5\u524D\u7F00\u8D85\u957F: " + s.slice(0, 60) + "...";
5803
+ if (s[0] === "-") return "\u5B89\u88C5\u524D\u7F00\u4EE5 - \u5F00\u5934\uFF08\u4F1A\u88AB\u5F53\u4F5C npm \u9009\u9879\uFF09: " + s.slice(0, 80);
5804
+ if (!isAbsoluteForm(s)) return "\u5B89\u88C5\u524D\u7F00\u987B\u4E3A\u7EDD\u5BF9\u8DEF\u5F84\uFF08/ \u5F00\u5934\u3001win \u76D8\u7B26\u6216 UNC\uFF09: " + s.slice(0, 80);
5805
+ return null;
5806
+ }
5807
+ function pkgNameViolation(pkg) {
5808
+ const s = String(pkg == null ? "" : pkg);
5809
+ return PKG_NAME_RE.test(s) ? null : "\u975E\u6CD5\u5305\u540D\uFF08\u5B57\u7B26\u96C6\u767D\u540D\u5355\u4E0D\u901A\u8FC7\uFF09: " + s.slice(0, 80);
5810
+ }
5811
+ function unitNameViolation(unit) {
5812
+ const s = String(unit == null ? "" : unit);
5813
+ if (!UNIT_NAME_RE.test(s)) return "\u975E\u6CD5\u5355\u5143\u540D\uFF08\u5B57\u7B26\u96C6/\u957F\u5EA6\u767D\u540D\u5355\u4E0D\u901A\u8FC7\uFF09: " + s.slice(0, 80);
5814
+ const dot = s.indexOf(".");
5815
+ if (dot >= 0 && !/\.service$/.test(s)) return "\u5355\u5143\u540D\u540E\u7F00\u4E0D\u53D7\u652F\u6301\uFF08\u4EC5\u5141\u8BB8 .service \u6216\u65E0\u540E\u7F00\uFF09: " + s.slice(0, 80);
5816
+ return null;
5817
+ }
5818
+ function ledgerKey(raw, opts) {
5819
+ const o = opts || {};
5820
+ const max = typeof o.max === "number" ? o.max : 128;
5821
+ const empty = o.empty == null ? "unknown" : o.empty;
5822
+ const unsafe = o.unsafe == null ? "(other)" : o.unsafe;
5823
+ if (typeof raw !== "string" || !raw) return empty;
5824
+ const s = raw.length > max ? raw.slice(0, max) : raw;
5825
+ if (UNSAFE_LEDGER_KEYS.indexOf(s) >= 0 || LEDGER_UNSAFE_RE.test(s)) return unsafe;
5826
+ return s;
5827
+ }
5828
+ module2.exports = {
5829
+ PKG_NAME_RE,
5830
+ ARGV_UNSAFE_RE,
5831
+ WIN_ABS_PATH_RE,
5832
+ UNIT_NAME_RE,
5833
+ UNSAFE_LEDGER_KEYS,
5834
+ LEDGER_UNSAFE_KEYS_RE: LEDGER_UNSAFE_RE,
5835
+ argvViolation,
5836
+ pkgNameViolation,
5837
+ unitNameViolation,
5838
+ ledgerKey,
5839
+ CONTROL_RE,
5840
+ isAbsoluteForm,
5841
+ prefixViolation
5842
+ };
5843
+ }
5844
+ });
5845
+
5846
+ // src/platform/os/spawn.js
5847
+ var require_spawn = __commonJS({
5848
+ "src/platform/os/spawn.js"(exports2, module2) {
5849
+ "use strict";
5850
+ var { spawn } = require("node:child_process");
5851
+ function detached(cmd2, args, opts) {
5852
+ const o = opts || {};
5853
+ return spawn(cmd2, args, Object.assign({}, o, {
5854
+ detached: true,
5855
+ windowsHide: true,
5856
+ // 默认 ignore;显式 undefined 也回落 ignore,避免 stdio:undefined 的歧义。
5857
+ stdio: o.stdio === void 0 ? "ignore" : o.stdio
5858
+ }));
5859
+ }
5860
+ function piped(cmd2, args, opts) {
5861
+ const o = opts || {};
5862
+ return spawn(cmd2, args, Object.assign({}, o, {
5863
+ stdio: ["ignore", "pipe", "pipe"],
5864
+ windowsHide: true,
5865
+ detached: o.detached === true
5866
+ }));
5867
+ }
5868
+ function detachedIgnored(cmd2, args, opts) {
5869
+ const o = opts || {};
5870
+ return spawn(cmd2, args, Object.assign({}, o, {
5871
+ detached: true,
5872
+ windowsHide: true,
5873
+ stdio: "ignore"
5874
+ }));
5875
+ }
5876
+ module2.exports = { detached, piped, detachedIgnored };
5877
+ }
5878
+ });
5879
+
5880
+ // src/platform/os/portable.js
5881
+ var require_portable = __commonJS({
5882
+ "src/platform/os/portable.js"(exports2, module2) {
5883
+ "use strict";
5884
+ var fs2 = require("node:fs");
5885
+ var pidlookup = require_pidlookup();
5886
+ var spawner = require_spawn();
5887
+ var procOS = require_process();
5888
+ var STOP_WAIT_CAP_MS = 3e3;
5889
+ var NAP_MS = 150;
5890
+ function nap(ms) {
5891
+ try {
5892
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
5893
+ } catch {
5821
5894
  }
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
5895
  }
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;
5896
+ function readPidFile(pidFile) {
5897
+ if (!pidFile) return null;
5898
+ try {
5899
+ const n = parseInt(String(fs2.readFileSync(pidFile, "utf8")).trim(), 10);
5900
+ return Number.isInteger(n) && n > 0 ? n : null;
5901
+ } catch {
5902
+ return null;
5903
+ }
5904
+ }
5905
+ function matchesAnchors(pid, anchors) {
5906
+ if (!pid || !Array.isArray(anchors) || !anchors.length) return false;
5907
+ const cmd2 = pidlookup.readCmdline(pid);
5908
+ if (!cmd2) return false;
5909
+ return anchors.some((a) => a && cmd2.indexOf(String(a)) !== -1);
5910
+ }
5911
+ var portOf = (o) => {
5912
+ const p = Number(o && o.port);
5913
+ return Number.isInteger(p) && p > 0 ? p : 0;
5914
+ };
5915
+ var pidFileOf = (o) => o && o.pidFile || null;
5916
+ var anchorsOf = (o) => Array.isArray(o && o.anchors) ? o.anchors : [];
5917
+ function findOurs(o) {
5918
+ const anchors = anchorsOf(o);
5919
+ const fp = readPidFile(pidFileOf(o));
5920
+ if (fp !== null && pidlookup.isAlive(fp) && matchesAnchors(fp, anchors)) return { pid: fp, ownGroup: true };
5921
+ const port = portOf(o);
5922
+ if (port) {
5923
+ const q = pidlookup.findListeningPid(port);
5924
+ if (q !== null && q !== fp && matchesAnchors(q, anchors)) return { pid: q, ownGroup: false };
5850
5925
  }
5851
5926
  return null;
5852
5927
  }
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 = [];
5928
+ function removePidFile(o) {
5929
+ const f = pidFileOf(o);
5930
+ if (f) {
5878
5931
  try {
5879
- names = listDir(dir).filter((f) => f.endsWith(".desktop"));
5932
+ fs2.unlinkSync(f);
5880
5933
  } catch {
5881
- continue;
5882
5934
  }
5883
- for (const f of names) {
5884
- let text = null;
5935
+ }
5936
+ }
5937
+ var portable = {
5938
+ kind: "portable",
5939
+ supportsUnits: false,
5940
+ supportsTransient: true,
5941
+ // 无操作 = 成功(与 systemd 的动词返回形态一致,调用方无需按 provider 分支)。
5942
+ daemonReload() {
5943
+ return true;
5944
+ },
5945
+ resetFailed() {
5946
+ return true;
5947
+ },
5948
+ /** 拉起:spawn(detached)——POSIX 自成进程组(kill(-pid) 语义的前提)、win32 windowsHide +
5949
+ * CREATE_NEW_PROCESS_GROUP(NO-CONSOLE-WINDOW 纪律收口在 platform/os/spawn.js)。
5950
+ * 调用方传来的 props(cgroup 语义)在此如实忽略:本档无内核强制可施加。
5951
+ * @param {{cmd:string[], env?:object, workingDir?:string, pidFile?:string, port?:number, anchors?:string[]}} o */
5952
+ startTransient(o) {
5953
+ const opts = o || {};
5954
+ const cmd2 = opts.cmd || [];
5955
+ if (!cmd2.length) throw new Error("portable \u62C9\u8D77\u62D2\u7EDD\uFF1A\u7A7A\u547D\u4EE4");
5956
+ const child = spawner.detached(cmd2[0], cmd2.slice(1), {
5957
+ cwd: opts.workingDir || void 0,
5958
+ env: Object.assign({}, process.env, opts.env || {})
5959
+ });
5960
+ child.on("error", () => {
5961
+ });
5962
+ if (!child.pid) {
5885
5963
  try {
5886
- text = readFile(PJ(dir, f));
5964
+ child.kill();
5887
5965
  } catch {
5888
- continue;
5889
5966
  }
5890
- const item = browserFromDesktop(text, kind, f);
5891
- if (item) usable(item);
5967
+ throw new Error("portable \u62C9\u8D77\u5931\u8D25\uFF1Aspawn \u672A\u4EA7\u751F\u8FDB\u7A0B");
5892
5968
  }
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";
5969
+ try {
5970
+ child.unref();
5971
+ } catch {
5902
5972
  }
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;
5973
+ if (opts.pidFile) {
5910
5974
  try {
5911
- if (exists(p)) text = readFile(p);
5975
+ fs2.writeFileSync(opts.pidFile, String(child.pid));
5912
5976
  } catch {
5913
- continue;
5914
5977
  }
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;
5978
+ }
5979
+ return true;
5980
+ },
5981
+ /** 停止(有界同步复核):终止我们的进程树,确认「端口锚点与 run.pid 锚点双双消失」才 true。
5982
+ * 无可杀对象(含 run.pid 为陈旧/被 PID 复用)-> 清文件后如实 true(幂等)。 */
5983
+ stopUnit(unit, o) {
5984
+ const opts = o || {};
5985
+ const ours = findOurs(opts);
5986
+ if (!ours) {
5987
+ removePidFile(opts);
5988
+ return true;
5989
+ }
5990
+ try {
5991
+ procOS.killTree(ours.pid, "SIGTERM", void 0, { ownGroup: ours.ownGroup });
5992
+ } catch {
5993
+ }
5994
+ const budget = Math.max(0, Math.min(typeof opts.timeoutMs === "number" ? opts.timeoutMs : STOP_WAIT_CAP_MS, STOP_WAIT_CAP_MS));
5995
+ const t0 = Date.now();
5996
+ for (; ; ) {
5997
+ if (!findOurs(opts)) {
5998
+ removePidFile(opts);
5999
+ return true;
6000
+ }
6001
+ if (Date.now() - t0 >= budget) break;
6002
+ nap(NAP_MS);
6003
+ }
6004
+ try {
6005
+ procOS.killTree(ours.pid, "SIGKILL", void 0, { ownGroup: ours.ownGroup });
6006
+ } catch {
6007
+ }
6008
+ nap(NAP_MS);
6009
+ if (!findOurs(opts)) {
6010
+ removePidFile(opts);
6011
+ return true;
6012
+ }
6013
+ return false;
6014
+ },
6015
+ /** 活跃判定(三态):port 与 pidFile 都缺 = 无从查询 -> null,删除保护路径不得把它当「已停止」。
6016
+ * anchors 为空时降级为「端口有监听即活跃」(monitor.probeInstance 同口径,该调用方本就以此判在线);
6017
+ * 杀进程(stopUnit)仍要求锚点命中 —— 判定可宽,动手必严。
6018
+ * 存活 pid 的 cmdline 读不到 = 分不清「他人复用」与「查询失败」-> null。 */
6019
+ isUnitActive(unit, o) {
6020
+ const opts = o || {};
6021
+ if (findOurs(opts)) return true;
6022
+ const port = portOf(opts);
6023
+ const pidFile = pidFileOf(opts);
6024
+ const anchors = anchorsOf(opts);
6025
+ if (!port && !pidFile) return null;
6026
+ if (!anchors.length) {
6027
+ const fp2 = readPidFile(pidFile);
6028
+ if (fp2 !== null && pidlookup.isAlive(fp2)) return true;
6029
+ if (port) {
6030
+ const q = pidlookup.findListeningPid(port);
6031
+ if (q !== null) return true;
6032
+ if (fp2 !== null) return false;
6033
+ return null;
5922
6034
  }
6035
+ return fp2 !== null ? false : null;
6036
+ }
6037
+ const fp = readPidFile(pidFile);
6038
+ if (fp !== null && pidlookup.isAlive(fp) && !pidlookup.readCmdline(fp)) return null;
6039
+ return false;
6040
+ },
6041
+ // portable 无 systemd transient 单元文件;run.pid 的清理归 cleanTransient。
6042
+ transientUnitFile() {
6043
+ return null;
6044
+ },
6045
+ /** 清理同名残留:先停掉仍活着的旧进程(重启自愈路径:旧进程未监听端口时端口探测拦不住),再清 run.pid。 */
6046
+ cleanTransient(unit, o) {
6047
+ const opts = o || {};
6048
+ const errors = [];
6049
+ if (this.stopUnit(unit, Object.assign({}, opts, { timeoutMs: 1500 })) === false) errors.push("stop-unconfirmed");
6050
+ removePidFile(opts);
6051
+ return { ok: errors.length === 0, errors };
6052
+ },
6053
+ /** 返回值只表示「是否改动了内核强制」;governor 不因 false 走降级分支 —— 违规处置链本就平台无关。 */
6054
+ setLimits() {
6055
+ return false;
6056
+ }
6057
+ };
6058
+ module2.exports = { portable, findOurs, matchesAnchors, _test: { readPidFile, matchesAnchors, findOurs } };
6059
+ }
6060
+ });
6061
+
6062
+ // src/platform/os/service.js
6063
+ var require_service = __commonJS({
6064
+ "src/platform/os/service.js"(exports2, module2) {
6065
+ "use strict";
6066
+ var fs2 = require("node:fs");
6067
+ var os2 = require("node:os");
6068
+ var path2 = require("node:path");
6069
+ var exec = require_exec();
6070
+ var input = require_input();
6071
+ var execPath = require_exec_path();
6072
+ var { portable } = require_portable();
6073
+ var CapabilityError = class extends Error {
6074
+ constructor(msg) {
6075
+ super(msg);
6076
+ this.name = "CapabilityError";
6077
+ this.code = "CAPABILITY_UNSUPPORTED";
6078
+ }
6079
+ };
6080
+ var PLATFORM = process.platform;
6081
+ function run(cmd2, args, opts) {
6082
+ return exec.run(cmd2, args, opts || {});
6083
+ }
6084
+ var UNIT_NAME_RE = input.UNIT_NAME_RE;
6085
+ function unitNameViolation(unit) {
6086
+ return input.unitNameViolation(unit);
6087
+ }
6088
+ var systemd = {
6089
+ kind: "systemd",
6090
+ supportsUnits: true,
6091
+ supportsTransient: true,
6092
+ daemonReload() {
6093
+ try {
6094
+ return run("systemctl", ["--user", "daemon-reload"], { timeoutMs: 15e3 }) !== null;
6095
+ } catch {
6096
+ return false;
6097
+ }
6098
+ },
6099
+ stopUnit(unit, opts) {
6100
+ if (unitNameViolation(unit)) return false;
6101
+ const o = opts || {};
6102
+ try {
6103
+ return run("systemctl", ["--user", "stop", unit], { timeoutMs: o.timeoutMs || 15e3 }) !== null;
6104
+ } catch {
6105
+ return false;
6106
+ }
6107
+ },
6108
+ // run() 失败返回 null 而不抛,try/catch 包裹会恒返回 true;如实回传成败。
6109
+ resetFailed(unit) {
6110
+ if (unitNameViolation(unit)) return false;
6111
+ return run("systemctl", ["--user", "reset-failed", unit], { timeoutMs: 1e4 }) !== null;
6112
+ },
6113
+ /** 单元活跃判定(三态):确认 active -> true;确认不活跃 -> false;查询未完成 -> null(未知)。
6114
+ * run() 失败只返回 null,「查询未完成」不可折成 false:instance/ops.js 删除数据目录前
6115
+ * 按「!== false 才放行删除」消费本函数,折假即超时窗口内误删沙箱数据。 */
6116
+ isUnitActive(unit) {
6117
+ if (!unit) return true;
6118
+ if (unitNameViolation(unit)) return false;
6119
+ try {
6120
+ const r = exec.runDetail("systemctl", ["--user", "is-active", unit], { timeoutMs: 8e3 });
6121
+ if (r.timedOut) return null;
6122
+ const state = String(r.stdout || "").trim();
6123
+ if (state === "active") return true;
6124
+ if (state || r.code !== null) return false;
6125
+ return null;
6126
+ } catch {
6127
+ return null;
5923
6128
  }
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;
6129
+ },
6130
+ transientUnitFile(unit) {
6131
+ if (unitNameViolation(unit)) return null;
6132
+ let uid = 0;
5946
6133
  try {
5947
- text = readFile(f);
6134
+ uid = os2.userInfo().uid;
5948
6135
  } catch {
5949
- continue;
5950
6136
  }
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;
6137
+ const rt = process.env.XDG_RUNTIME_DIR || "/run/user/" + uid;
6138
+ return path2.join(rt, "systemd", "transient", unit + ".service");
6139
+ },
6140
+ /** 清理 stale transient 单元:stop/reset-failed/删单元文件/daemon-reload。
6141
+ * 必须 reload:删除文件后 systemd 仍缓存该单元为 loaded,否则 systemd-run 拒绝重建同名单元。
6142
+ * 返回 {ok, errors}:stop/reset-failed 对「从未加载的单元」非零退出属正常,不计入 ok;
6143
+ * 硬失败只有删单元文件与 daemon-reload(run() 失败返回 null 不抛,成败须如实回传)。 */
6144
+ cleanTransient(unit) {
6145
+ const errors = [];
6146
+ const bad = unitNameViolation(unit);
6147
+ if (bad) return { ok: false, errors: [bad] };
6148
+ exec.runDetail("systemctl", ["--user", "stop", unit + ".service"], { timeoutMs: 1e4 });
6149
+ exec.runDetail("systemctl", ["--user", "reset-failed", unit + ".service"], { timeoutMs: 1e4 });
6150
+ let unlinkOk = true;
6151
+ try {
6152
+ const f = this.transientUnitFile(unit);
6153
+ if (f) fs2.unlinkSync(f);
6154
+ } catch {
6155
+ unlinkOk = false;
6156
+ errors.push("unlink " + unit + ".service");
5965
6157
  }
5966
- return null;
6158
+ if (!exec.runDetail("systemctl", ["--user", "daemon-reload"], { timeoutMs: 15e3 }).ok) errors.push("daemon-reload");
6159
+ return { ok: unlinkOk && errors.length === 0, errors };
6160
+ },
6161
+ /** 以 transient 单元启动(独立 cgroup;每实例隔离)。
6162
+ * @param {{unit:string, cmd:string[], env?:object, props?:string[], workingDir?:string, description?:string, timeoutMs?:number}} o
6163
+ * props 为 systemd 属性(如 KillMode=process、MemoryMax=8G),平台层只拼装成 --property,不解释语义。 */
6164
+ startTransient(o) {
6165
+ const opts = o || {};
6166
+ const bad = unitNameViolation(opts.unit);
6167
+ if (bad) throw new Error("systemd-run \u62D2\u7EDD: " + bad);
6168
+ const args = ["--user", "--unit=" + opts.unit];
6169
+ if (opts.description) args.push("--description=" + opts.description);
6170
+ for (const p of opts.props || []) args.push("--property=" + p);
6171
+ for (const [k, v] of Object.entries(opts.env || {})) args.push("--setenv=" + k + "=" + v);
6172
+ if (opts.workingDir) args.push("--working-directory=" + opts.workingDir);
6173
+ args.push("--", ...opts.cmd || []);
6174
+ const r = exec.runDetail("systemd-run", args, { timeoutMs: opts.timeoutMs || 2e4 });
6175
+ if (!r.ok) throw new Error("systemd-run \u5931\u8D25: " + (r.error || "unknown") + (r.stderr ? " | " + String(r.stderr).trim() : ""));
6176
+ return true;
6177
+ },
6178
+ /** 运行期改限额(W3 动态化):systemctl --user set-property 立即生效,无需重启单元。
6179
+ * 必带 --runtime:transient 单元本不落盘,不带它会把 drop-in 写进用户配置目录,陈旧下限永久黏住。
6180
+ * 值只来自 sandbox.unitProps 同源的 alloc(平台层翻译语义、不决定数额)。 */
6181
+ setLimits(unit, alloc) {
6182
+ if (unitNameViolation(unit)) return false;
6183
+ const a = alloc || {};
6184
+ const props = [];
6185
+ if (a.memoryMax) props.push("MemoryMax=" + a.memoryMax);
6186
+ if (a.memoryHigh) props.push("MemoryHigh=" + a.memoryHigh);
6187
+ if (a.cpuQuota) props.push("CPUQuota=" + a.cpuQuota);
6188
+ if (!props.length) return false;
6189
+ return exec.runDetail(
6190
+ "systemctl",
6191
+ ["--user", "set-property", "--runtime", unit].concat(props),
6192
+ { timeoutMs: 1e4 }
6193
+ ).ok;
5967
6194
  }
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;
6195
+ };
6196
+ function makeUnsupported(kind, label) {
6197
+ return {
6198
+ kind,
6199
+ supportsUnits: false,
6200
+ supportsTransient: false,
6201
+ daemonReload() {
6202
+ return false;
6203
+ },
6204
+ stopUnit() {
6205
+ throw new CapabilityError(label + "\uFF1A\u4E0D\u652F\u6301\u4EE5\u7528\u6237\u5355\u5143\u65B9\u5F0F\u7BA1\u7406\u88AB\u7BA1\u5B9E\u4F8B");
6206
+ },
6207
+ resetFailed() {
6208
+ return false;
6209
+ },
6210
+ isUnitActive(unit) {
6211
+ return unit ? false : true;
6212
+ },
6213
+ transientUnitFile() {
6214
+ return null;
6215
+ },
6216
+ // 无 transient 单元可清 = 成功;返回形态与 systemd 一致,调用方无需分支。
6217
+ cleanTransient() {
6218
+ return { ok: true, errors: [] };
6219
+ },
6220
+ startTransient() {
6221
+ throw new CapabilityError(label + "\uFF1A\u4E0D\u652F\u6301\u62C9\u8D77\u5B9E\u4F8B\u8231\uFF08portable \u6863\u672A\u542F\u7528\uFF09");
6222
+ },
6223
+ setLimits() {
6224
+ return false;
5977
6225
  }
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];
6226
+ };
6227
+ }
6228
+ var NONE = makeUnsupported("none", "\u5F53\u524D\u5E73\u53F0\u65E0\u670D\u52A1\u7BA1\u7406\u5668");
6229
+ var _systemdRun = null;
6230
+ function hasSystemdRun() {
6231
+ if (_systemdRun === null) {
6232
+ _systemdRun = !!execPath.resolveExecutable("systemd-run") || exec.runOut("systemd-run", ["--version"], { timeoutMs: 3e3 }) !== null;
5983
6233
  }
5984
- return null;
6234
+ return _systemdRun;
5985
6235
  }
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);
6236
+ function current() {
6237
+ if (PLATFORM === "linux") return hasSystemdRun() ? systemd : portable;
6238
+ if (PLATFORM === "darwin" || PLATFORM === "win32") return portable;
6239
+ return NONE;
5992
6240
  }
5993
- var CACHE = /* @__PURE__ */ Object.create(null);
5994
- function inventory(platform, o) {
5995
- const ov = o || {};
6241
+ module2.exports = { current, CapabilityError, kind: () => current().kind, PLATFORM, UNIT_NAME_RE, unitNameViolation, _testProviders: { systemd, portable, NONE } };
6242
+ }
6243
+ });
6244
+
6245
+ // src/platform/os/notify.js
6246
+ var require_notify = __commonJS({
6247
+ "src/platform/os/notify.js"(exports2, module2) {
6248
+ "use strict";
6249
+ var spawnOS = require_spawn();
6250
+ function appleScriptString(s) {
6251
+ return JSON.stringify(String(s));
6252
+ }
6253
+ function powerShellString(s) {
6254
+ return "'" + String(s).replace(/'/g, "''") + "'";
6255
+ }
6256
+ function notifyCommand(platform, title, body) {
5996
6257
  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) }] };
6258
+ const t = String(title == null ? "" : title);
6259
+ const b = String(body == null ? "" : body);
6260
+ if (pl === "linux") {
6261
+ return { cmd: "notify-send", args: ["-a", "dsh-supervisor", t, b] };
6010
6262
  }
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);
6263
+ if (pl === "darwin") {
6264
+ const script = "display notification " + appleScriptString(b) + " with title " + appleScriptString(t);
6265
+ return { cmd: "osascript", args: ["-e", script] };
6266
+ }
6267
+ if (pl === "win32") {
6268
+ const ps = [
6269
+ '[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null',
6270
+ '[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null',
6271
+ "$n = New-Object System.Windows.Forms.NotifyIcon",
6272
+ "$n.Icon = [System.Drawing.SystemIcons]::Information",
6273
+ "$n.Visible = $true",
6274
+ "$n.ShowBalloonTip(4000, " + powerShellString(t) + ", " + powerShellString(b) + ", [System.Windows.Forms.ToolTipIcon]::None)",
6275
+ "Start-Sleep -Milliseconds 4200",
6276
+ "$n.Dispose(); $n.Visible = $false"
6277
+ ].join("; ");
6278
+ return { cmd: "powershell", args: ["-NoProfile", "-NonInteractive", "-Command", ps] };
6279
+ }
6280
+ return null;
6016
6281
  }
6017
- function invalidate(platform) {
6018
- if (platform) delete CACHE[platform];
6019
- else for (const k of Object.keys(CACHE)) delete CACHE[k];
6282
+ function notify(title, body, onError) {
6283
+ try {
6284
+ const plan = notifyCommand(process.platform, title, body);
6285
+ if (!plan) return false;
6286
+ const c = spawnOS.detachedIgnored(plan.cmd, plan.args);
6287
+ c.on("error", () => {
6288
+ if (onError) onError();
6289
+ });
6290
+ c.unref();
6291
+ return true;
6292
+ } catch {
6293
+ if (onError) onError();
6294
+ return false;
6295
+ }
6020
6296
  }
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
- };
6297
+ module2.exports = { notify, notifyCommand, appleScriptString, powerShellString };
6046
6298
  }
6047
6299
  });
6048
6300
 
@@ -6055,8 +6307,10 @@ var require_browser = __commonJS({
6055
6307
  var desktop = require_desktop();
6056
6308
  var CAPABILITY_PROFILES = require_capability_profile();
6057
6309
  var { resolveExecutable, isExecutableFile } = require_exec_path();
6310
+ var environment = require_environment();
6058
6311
  var detector = require_browser_inventory();
6059
6312
  var { engineOf } = detector;
6313
+ var { allocTempDir, removeTreeDeferred } = require_fs();
6060
6314
  function isSafeHttpUrl(url) {
6061
6315
  try {
6062
6316
  const u = new URL(String(url));
@@ -6092,7 +6346,7 @@ var require_browser = __commonJS({
6092
6346
  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";
6093
6347
  var FAILURE_TEXT = {
6094
6348
  "unsafe-url": "\u5730\u5740\u4E0D\u662F http(s) \u7EDD\u5BF9 URL\uFF0C\u5DF2\u62D2\u7EDD\u4EA4\u7ED9\u6D4F\u89C8\u5668",
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",
6349
+ "no-launcher": "\u672C\u673A\u672A\u63A2\u5230\u53EF\u542F\u52A8\u7684\u6D4F\u89C8\u5668\uFF08\u6216\u6240\u9009\u6D4F\u89C8\u5668\u5DF2\u4E0D\u53EF\u6267\u884C\uFF09\uFF0C\u8BF7\u5728\u9762\u677F\u7684\u73AF\u5883\u8868\u5355\u91CC\u786E\u8BA4\uFF0C\u6216\u624B\u52A8\u6253\u5F00\u8BE5\u5730\u5740",
6096
6350
  "spawn-failed": "\u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25\uFF08\u7CFB\u7EDF\u62D2\u7EDD\u4E86\u8BE5\u547D\u4EE4\uFF09",
6097
6351
  "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
6352
  "killed-by-signal": "\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\u88AB\u7CFB\u7EDF\u7EC8\u6B62\uFF0C\u7A97\u53E3\u672A\u51FA\u73B0",
@@ -6100,17 +6354,10 @@ var require_browser = __commonJS({
6100
6354
  "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"
6101
6355
  };
6102
6356
  function ownsItsWindow(via, platform) {
6357
+ if (via === "isolated") return true;
6103
6358
  if (via !== "dispatcher") return false;
6104
6359
  return platform !== "win32";
6105
6360
  }
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
6361
  function formOfBin(pl, b) {
6115
6362
  const engine = b && b.bin ? engineOf(b.bin) : "other";
6116
6363
  return { engine, direct: !!b && (engine !== "other" || pl === "win32") };
@@ -6118,30 +6365,118 @@ var require_browser = __commonJS({
6118
6365
  function openPlan(platform, url, opts) {
6119
6366
  const o = opts || {};
6120
6367
  const pl = platform || process.platform;
6121
- const picked = o.pick || pickLauncher(pl, o.inventory);
6368
+ const picked = o.pick || environment.pickLauncher(pl, o.inventory, o.preference);
6122
6369
  const b = picked.browser;
6123
6370
  const form = formOfBin(pl, b);
6124
6371
  if (form.direct) {
6125
6372
  return {
6126
6373
  bin: b.bin,
6374
+ args: (b.baseArgs || []).concat([url]),
6127
6375
  engine: form.engine,
6128
6376
  via: "browser",
6377
+ isolated: false,
6378
+ watch: false,
6379
+ envKind: "sys",
6129
6380
  pick: picked.how,
6130
- baseArgs: (b.baseArgs || []).concat([url]),
6381
+ stale: picked.stale === true,
6131
6382
  exitIsEvidence: ownsItsWindow("browser", pl)
6132
6383
  };
6133
6384
  }
6134
6385
  const c = openCommand(pl, url);
6135
- if (!c) return { bin: null, engine: form.engine, via: "none", pick: picked.how, baseArgs: [], exitIsEvidence: false };
6386
+ if (!c) return {
6387
+ bin: null,
6388
+ args: [url],
6389
+ engine: form.engine,
6390
+ via: "none",
6391
+ isolated: false,
6392
+ watch: false,
6393
+ envKind: "sys",
6394
+ pick: picked.how,
6395
+ stale: picked.stale === true,
6396
+ exitIsEvidence: false
6397
+ };
6136
6398
  return {
6137
6399
  bin: c.cmd,
6400
+ args: c.args,
6138
6401
  engine: "other",
6139
6402
  via: "dispatcher",
6403
+ isolated: false,
6404
+ watch: false,
6405
+ envKind: "sys",
6140
6406
  pick: picked.how,
6141
- baseArgs: c.args,
6407
+ stale: picked.stale === true,
6142
6408
  exitIsEvidence: ownsItsWindow("dispatcher", pl)
6143
6409
  };
6144
6410
  }
6411
+ function isolatedPlan(platform, url, opts) {
6412
+ const o = opts || {};
6413
+ const pl = platform || process.platform;
6414
+ const picked = o.pick || environment.pickLauncher(pl, o.inventory, o.preference);
6415
+ const b = picked.browser;
6416
+ const form = formOfBin(pl, b);
6417
+ const baseArgs = b && b.baseArgs || [];
6418
+ const common = { pick: picked.how, stale: picked.stale === true, engine: form.engine };
6419
+ const plain = (bin, label) => Object.assign(common, {
6420
+ bin,
6421
+ args: baseArgs.concat([url]),
6422
+ via: "browser",
6423
+ isolated: false,
6424
+ watch: false,
6425
+ envKind: "sys",
6426
+ label,
6427
+ exitIsEvidence: ownsItsWindow("browser", pl)
6428
+ });
6429
+ if (form.direct && form.engine === "chromium" && o.profileDir) {
6430
+ const args = ["--incognito", "--user-data-dir=" + o.profileDir];
6431
+ if (Array.isArray(o.size) && o.size.length === 2) args.push("--window-size=" + o.size[0] + "," + o.size[1]);
6432
+ if (o.lang) args.push("--lang=" + o.lang);
6433
+ args.push("--no-first-run", "--no-default-browser-check", "--disable-session-crashed-bubble");
6434
+ return Object.assign(common, {
6435
+ bin: b.bin,
6436
+ args: [...baseArgs, ...args, url],
6437
+ via: "isolated",
6438
+ isolated: true,
6439
+ watch: true,
6440
+ envKind: "anti",
6441
+ label: "chromium",
6442
+ exitIsEvidence: ownsItsWindow("isolated", pl)
6443
+ });
6444
+ }
6445
+ if (form.direct && form.engine === "firefox" && o.profileDir) {
6446
+ return Object.assign(common, {
6447
+ bin: b.bin,
6448
+ args: [...baseArgs, "--no-remote", "--profile", o.profileDir, "-private-window", url],
6449
+ via: "isolated",
6450
+ isolated: true,
6451
+ watch: true,
6452
+ envKind: "anti",
6453
+ label: "firefox",
6454
+ exitIsEvidence: ownsItsWindow("isolated", pl)
6455
+ });
6456
+ }
6457
+ if (form.direct) return plain(b.bin, path2.basename(b.bin));
6458
+ const c = openCommand(pl, url);
6459
+ if (!c) return Object.assign(common, {
6460
+ bin: null,
6461
+ args: [url],
6462
+ via: "none",
6463
+ isolated: false,
6464
+ watch: false,
6465
+ envKind: "sys",
6466
+ label: null,
6467
+ exitIsEvidence: false
6468
+ });
6469
+ return Object.assign(common, {
6470
+ bin: c.cmd,
6471
+ args: c.args,
6472
+ via: "dispatcher",
6473
+ isolated: false,
6474
+ watch: false,
6475
+ envKind: "sys",
6476
+ label: c.cmd,
6477
+ exitIsEvidence: ownsItsWindow("dispatcher", pl)
6478
+ });
6479
+ }
6145
6480
  function observeSpawn(child, windowMs, setTimeoutFn) {
6146
6481
  return new Promise((resolve) => {
6147
6482
  let settled = false;
@@ -6159,24 +6494,72 @@ var require_browser = __commonJS({
6159
6494
  });
6160
6495
  }
6161
6496
  var OPEN_OBSERVE_MS = 1500;
6162
- function launchDiagnostics(inv, plan, how) {
6497
+ var LOGIN_TZ_POOL = ["Asia/Shanghai", "Asia/Seoul", "Asia/Tokyo", "Asia/Singapore", "Europe/Berlin", "Europe/London", "Europe/Paris", "America/New_York", "America/Los_Angeles", "Australia/Sydney"];
6498
+ var LOGIN_LANG_POOL = ["zh-CN", "en-US", "en-GB", "ja-JP", "ko-KR", "de-DE", "fr-FR", "zh-TW"];
6499
+ var LOGIN_SIZE_POOL = [[1280, 800], [1366, 768], [1440, 900], [1536, 864], [1600, 900], [1680, 1050], [1920, 1080], [1024, 768], [1152, 864], [1280, 720]];
6500
+ function loginEnv(rand) {
6501
+ const pick = (arr) => arr[Math.floor((typeof rand === "function" ? rand() : Math.random()) * arr.length)];
6502
+ const size = pick(LOGIN_SIZE_POOL);
6503
+ const lang = pick(LOGIN_LANG_POOL);
6504
+ const tz = pick(LOGIN_TZ_POOL);
6505
+ const sysEnv = Object.assign({}, process.env, desktop.sessionEnv());
6506
+ return { size, lang, sysEnv, antiEnv: Object.assign({}, sysEnv, { TZ: tz, LANG: lang }) };
6507
+ }
6508
+ function launchDiagnostics(inv, plan, picked) {
6163
6509
  const found = (inv && inv.browsers || []).map((b) => ({
6164
6510
  name: b.name,
6165
6511
  engine: b.engine || engineOf(b.bin),
6166
6512
  via: (b.sources && b.sources.length ? b.sources : [b.source || "unknown"]).join("+")
6167
6513
  }));
6514
+ const pk = picked || {};
6168
6515
  return {
6169
6516
  platform: inv && inv.platform || null,
6170
- pick: how || plan && plan.pick || "none",
6517
+ pick: pk.how || plan && plan.pick || "none",
6171
6518
  bin: plan && plan.bin || null,
6172
6519
  default: inv && inv.defaultId ? { id: inv.defaultId, source: inv.defaultSource || null } : null,
6520
+ preference: pk.wanted ? { id: pk.wanted, matched: pk.stale !== true } : null,
6173
6521
  found,
6174
6522
  probed: inv && inv.probed || []
6175
6523
  };
6176
6524
  }
6525
+ function _spawnDetachedIgnored(bin, args) {
6526
+ try {
6527
+ const p = spawnOS.detachedIgnored(bin, args);
6528
+ p.on("error", () => {
6529
+ });
6530
+ p.unref();
6531
+ return p;
6532
+ } catch {
6533
+ return null;
6534
+ }
6535
+ }
6536
+ function _spawnDetached(bin, args, env, onExit) {
6537
+ let child;
6538
+ try {
6539
+ child = spawnOS.detachedIgnored(bin, args, { env: env || process.env });
6540
+ } catch {
6541
+ return null;
6542
+ }
6543
+ child.on("error", () => {
6544
+ });
6545
+ if (typeof onExit === "function") child.on("exit", () => {
6546
+ try {
6547
+ onExit();
6548
+ } catch {
6549
+ }
6550
+ });
6551
+ child.unref();
6552
+ return child;
6553
+ }
6554
+ function binAvailable(bin) {
6555
+ if (!bin) return false;
6556
+ if (bin.includes("/") || bin.includes("\\") || /^[A-Za-z]:[\\/]/.test(bin)) return isExecutableFile(bin);
6557
+ return resolveExecutable(bin) !== null;
6558
+ }
6177
6559
  async function openBrowser(url, o) {
6178
6560
  const opts = o || {};
6179
6561
  const pl = opts.platform || process.platform;
6562
+ const intent = opts.intent === "isolated-login" ? "isolated-login" : "plain";
6180
6563
  if (!isSafeHttpUrl(url)) {
6181
6564
  return outcome({ ok: false, reason: "unsafe-url", url: String(url || ""), evidence: null });
6182
6565
  }
@@ -6184,41 +6567,64 @@ var require_browser = __commonJS({
6184
6567
  return outcome({ ok: false, reason: "unsupported-platform", url, evidence: { platform: pl } });
6185
6568
  }
6186
6569
  const observeMs = opts.observeMs === void 0 ? OPEN_OBSERVE_MS : opts.observeMs;
6187
- const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetachedIgnored;
6570
+ const spawnWith = typeof opts.spawn === "function" ? opts.spawn : intent === "isolated-login" ? _spawnDetached : _spawnDetachedIgnored;
6188
6571
  const avail = typeof opts.binAvailable === "function" ? opts.binAvailable : binAvailable;
6189
6572
  const observe = typeof opts.observe === "function" ? opts.observe : observeSpawn;
6190
- const resolveInv = typeof opts.resolveInventory === "function" ? opts.resolveInventory : ((platform, deps) => detector.inventory(platform, deps));
6573
+ const resolveInv = typeof opts.resolveInventory === "function" ? opts.resolveInventory : ((platform, deps) => environment.browsers(Object.assign({ platform }, deps)));
6191
6574
  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);
6575
+ const pref = "preference" in opts ? opts.preference : void 0;
6576
+ const login = intent === "isolated-login" ? loginEnv(opts.rand) : null;
6577
+ const picked = opts.pick || environment.pickLauncher(pl, inv, pref);
6578
+ const form = formOfBin(pl, picked.browser);
6579
+ const canIsolate = intent === "isolated-login" && form.direct && (form.engine === "chromium" || form.engine === "firefox");
6580
+ let profile = null;
6581
+ if (canIsolate) {
6582
+ const alloc = typeof opts.allocProfile === "function" ? opts.allocProfile : (() => allocTempDir("dsh-login-"));
6583
+ try {
6584
+ profile = opts.profileDir || alloc();
6585
+ } catch {
6586
+ profile = null;
6587
+ }
6588
+ }
6589
+ const plan = intent === "isolated-login" ? isolatedPlan(pl, url, { inventory: inv, pick: picked, profileDir: profile, size: login.size, lang: login.lang }) : openPlan(pl, url, { inventory: inv, pick: picked });
6590
+ const diagnostics = launchDiagnostics(inv, plan, picked);
6194
6591
  const evidence = {
6195
6592
  bin: plan.bin,
6196
6593
  engine: plan.engine,
6197
6594
  via: plan.via,
6198
6595
  ownsWindow: plan.exitIsEvidence === true,
6596
+ isolated: plan.isolated === true,
6597
+ profile: plan.isolated ? profile : null,
6598
+ watch: plan.watch === true,
6199
6599
  exitCode: null,
6200
6600
  exitSignal: null,
6201
6601
  error: null,
6202
6602
  diagnostics
6203
6603
  };
6604
+ const deferredRemove = typeof opts.rmTree === "function" ? opts.rmTree : removeTreeDeferred;
6605
+ const fail = (reason, patch) => {
6606
+ if (profile) deferredRemove(profile, 0);
6607
+ return outcome(Object.assign({ ok: false, reason, url, evidence }, patch));
6608
+ };
6204
6609
  const desktopAvailable = typeof opts.desktopAvailable === "function" ? opts.desktopAvailable : desktop.sessionAvailable;
6205
6610
  if (pl === "linux" && !desktopAvailable()) {
6206
- return outcome({ ok: false, reason: "no-desktop-session", url, evidence });
6611
+ return fail("no-desktop-session");
6207
6612
  }
6208
- if (!plan.bin) {
6209
- return outcome({ ok: false, reason: "no-launcher", url, evidence });
6210
- }
6211
- if (!avail(plan.bin)) {
6212
- return outcome({ ok: false, reason: "no-launcher", url, evidence });
6613
+ if (!plan.bin) return fail("no-launcher");
6614
+ if (!avail(plan.bin)) return fail("no-launcher");
6615
+ if (canIsolate && !profile) {
6616
+ evidence.error = "\u4E34\u65F6 profile \u76EE\u5F55\u5206\u914D\u5931\u8D25";
6617
+ return fail("spawn-failed");
6213
6618
  }
6214
6619
  let child;
6215
6620
  try {
6216
- child = spawnWith(plan.bin, plan.baseArgs);
6621
+ child = intent === "isolated-login" ? spawnWith(plan.bin, plan.args, plan.envKind === "anti" ? login.antiEnv : login.sysEnv, plan.watch ? opts.onExit : void 0) : spawnWith(plan.bin, plan.args);
6217
6622
  } catch (e) {
6218
6623
  evidence.error = e && e.code || String(e);
6219
- return outcome({ ok: false, reason: "spawn-failed", url, evidence });
6624
+ return fail("spawn-failed");
6220
6625
  }
6221
- if (!child) return outcome({ ok: false, reason: "spawn-failed", url, evidence });
6626
+ if (!child) return fail("spawn-failed");
6627
+ if (evidence.profile) deferredRemove(evidence.profile, opts.profileMs === void 0 ? 30 * 60 * 1e3 : opts.profileMs);
6222
6628
  const seen = await observe(child, observeMs, opts.setTimeout);
6223
6629
  evidence.error = seen.stage === "error" ? String(seen.code) : null;
6224
6630
  evidence.exitCode = seen.code === void 0 ? null : seen.code;
@@ -6236,137 +6642,17 @@ var require_browser = __commonJS({
6236
6642
  }
6237
6643
  return outcome({ ok: true, confirmed: exitDecides, handedOff: !exitDecides, url, evidence });
6238
6644
  }
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);
6260
- }
6261
- function _spawnDetachedIgnored(bin, args) {
6262
- try {
6263
- const p = spawnOS.detachedIgnored(bin, args);
6264
- p.on("error", () => {
6265
- });
6266
- p.unref();
6267
- return p;
6268
- } catch {
6269
- return null;
6270
- }
6271
- }
6272
- function _spawnDetached(bin, args, env, onExit) {
6273
- let child;
6274
- try {
6275
- child = spawnOS.detachedIgnored(bin, args, { env: env || process.env });
6276
- } catch {
6277
- return null;
6278
- }
6279
- child.on("error", () => {
6280
- });
6281
- if (typeof onExit === "function") child.on("exit", () => {
6282
- try {
6283
- onExit();
6284
- } catch {
6285
- }
6286
- });
6287
- child.unref();
6288
- return child;
6289
- }
6290
- function isolatedPlan(platform, url, opts) {
6291
- const o = opts || {};
6292
- const pl = platform || process.platform;
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) {
6298
- const args = ["--incognito", "--user-data-dir=" + o.profileDir];
6299
- if (Array.isArray(o.size) && o.size.length === 2) args.push("--window-size=" + o.size[0] + "," + o.size[1]);
6300
- if (o.lang) args.push("--lang=" + o.lang);
6301
- args.push("--no-first-run", "--no-default-browser-check", "--disable-session-crashed-bubble");
6302
- return { bin: b.bin, args: [...baseArgs, ...args, url], isolated: true, watch: true, envKind: "anti", label: "chromium", pick: picked.how };
6303
- }
6304
- if (form.direct && form.engine === "firefox" && o.profileDir) {
6305
- return {
6306
- bin: b.bin,
6307
- args: [...baseArgs, "--no-remote", "--profile", o.profileDir, "-private-window", url],
6308
- isolated: true,
6309
- watch: true,
6310
- envKind: "anti",
6311
- label: "firefox",
6312
- pick: picked.how
6313
- };
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
- }
6318
- const c = openCommand(pl, url);
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 };
6321
- }
6322
- function binAvailable(bin) {
6323
- if (!bin) return false;
6324
- if (bin.includes("/") || bin.includes("\\") || /^[A-Za-z]:[\\/]/.test(bin)) return isExecutableFile(bin);
6325
- return resolveExecutable(bin) !== null;
6326
- }
6327
- function launchIsolated(url, o) {
6328
- const opts = o || {};
6329
- if (!isSafeHttpUrl(url)) return Object.assign(outcome({ ok: false, reason: "unsafe-url", url: String(url || "") }), { bin: null, isolated: false });
6330
- const antiEnv = opts.antiEnv || opts.sysEnv || process.env;
6331
- const sysEnv = opts.sysEnv || process.env;
6332
- const onExit = opts.onExit;
6333
- const avail = typeof opts.binAvailable === "function" ? opts.binAvailable : binAvailable;
6334
- const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetached;
6335
- const fail = (reason, extra) => Object.assign(outcome(Object.assign({ ok: false, reason, url }, extra)), { bin: null, isolated: false });
6336
- try {
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 || {});
6339
- const plan = isolatedPlan(process.platform, url, {
6340
- inventory: inv,
6341
- pick: opts.pick,
6342
- profileDir: opts.profileDir,
6343
- size: opts.size,
6344
- lang: opts.lang
6345
- });
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 });
6348
- const env = plan.envKind === "anti" ? antiEnv : sysEnv;
6349
- const p = spawnWith(plan.bin, plan.args, env, plan.watch ? onExit : void 0);
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 });
6352
- } catch (e) {
6353
- return fail("spawn-failed", { error: FAILURE_TEXT["spawn-failed"] + "\uFF08" + (e && e.code || e) + "\uFF09" });
6354
- }
6355
- }
6356
6645
  module2.exports = {
6357
6646
  openBrowser,
6358
- launchIsolated,
6359
6647
  openCommand,
6360
6648
  openPlan,
6361
6649
  isolatedPlan,
6362
- pickLauncher,
6363
6650
  ownsItsWindow,
6364
6651
  observeSpawn,
6365
6652
  isSafeHttpUrl,
6366
6653
  binAvailable,
6367
- listBrowsers,
6368
- invalidateBrowsers,
6369
6654
  launchDiagnostics,
6655
+ loginEnv,
6370
6656
  // 探测层的解析原语经此转出(唯一实现处在 ./browser-inventory.js):门禁与消费方按同一出口取用,
6371
6657
  // 不在别处再写第二份平台解析。
6372
6658
  engineOf,
@@ -6723,6 +7009,7 @@ var require_os = __commonJS({
6723
7009
  var execPath = require_exec_path();
6724
7010
  var desktop = require_desktop();
6725
7011
  var CAPABILITY_PROFILES = require_capability_profile();
7012
+ var environment = require_environment();
6726
7013
  var PLATFORM = process.platform;
6727
7014
  var ARCH = process.arch;
6728
7015
  var isLinux = PLATFORM === "linux";
@@ -6777,6 +7064,7 @@ var require_os = __commonJS({
6777
7064
  }
6778
7065
  return p;
6779
7066
  }
7067
+ environment.bind({ capabilities });
6780
7068
  module2.exports = {
6781
7069
  PLATFORM,
6782
7070
  ARCH,
@@ -6800,6 +7088,8 @@ var require_os = __commonJS({
6800
7088
  // 导出模块对象会抛 platform.notify is not a function,把升级终态误判成失败。
6801
7089
  notify: require_notify().notify,
6802
7090
  browser: require_browser(),
7091
+ environment,
7092
+ // 环境表单(外部打开链路的事实底座 + 选路依据)
6803
7093
  desktop,
6804
7094
  // 图形会话可用性(Linux 需实测 socket)
6805
7095
  autostart: require_autostart()
@@ -12918,6 +13208,60 @@ var require_access = __commonJS({
12918
13208
  }
12919
13209
  });
12920
13210
 
13211
+ // src/app/settings/browser.js
13212
+ var require_browser2 = __commonJS({
13213
+ "src/app/settings/browser.js"(exports2, module2) {
13214
+ "use strict";
13215
+ var platform = require_os();
13216
+ var { verifyPersisted } = require_access();
13217
+ module2.exports = {
13218
+ methods: {
13219
+ /** 当前偏好 + 候选清单 + 这一拍的分发依据(面板据此渲染选择器并说明「现在实际会用谁」)。 */
13220
+ externalBrowserStatus() {
13221
+ try {
13222
+ const form = platform.environment.form();
13223
+ const v = platform.environment.checkPreference(platform.environment.preferenceId(), form);
13224
+ return {
13225
+ ok: true,
13226
+ configured: !!form.preference && form.preference.configured === true,
13227
+ value: form.preference && form.preference.id || null,
13228
+ // stale=true:偏好所指已不在候选清单(被卸载/路径失效),本次分发已回落,需用户重选。
13229
+ stale: !!(form.pick && form.pick.stale),
13230
+ browser: v.browser,
13231
+ candidates: v.candidates,
13232
+ pick: form.pick || null,
13233
+ platform: form.platform
13234
+ };
13235
+ } catch (e) {
13236
+ return { ok: false, error: e.message };
13237
+ }
13238
+ },
13239
+ /** 设置/清除浏览器偏好(空串=清除,回到「按系统默认或候选次序分发」)。 */
13240
+ setExternalBrowser(id) {
13241
+ try {
13242
+ const form = platform.environment.form();
13243
+ const v = platform.environment.checkPreference(id, form);
13244
+ if (!v.ok) return { ok: false, error: v.error };
13245
+ const cfg = this.config || {};
13246
+ cfg.externalBrowser = v.id;
13247
+ const patch = { externalBrowser: v.id };
13248
+ if (this.configPath) {
13249
+ this.state.persistConfigPatch(patch);
13250
+ const persistError = verifyPersisted(this.configPath, patch);
13251
+ if (persistError) return { ok: false, error: persistError };
13252
+ }
13253
+ platform.environment.invalidate();
13254
+ if (this.events) this.events.append("external_browser_changed", { id: v.id, name: v.browser ? v.browser.name : null });
13255
+ return { ok: true, configured: !!v.id, value: v.id, browser: v.browser };
13256
+ } catch (e) {
13257
+ return { ok: false, error: e.message };
13258
+ }
13259
+ }
13260
+ }
13261
+ };
13262
+ }
13263
+ });
13264
+
12921
13265
  // src/platform/os/netinfo.js
12922
13266
  var require_netinfo = __commonJS({
12923
13267
  "src/platform/os/netinfo.js"(exports2, module2) {
@@ -13135,6 +13479,7 @@ var require_facets = __commonJS({
13135
13479
  { name: "settings/node-lts", mod: require_node_lts() },
13136
13480
  { name: "settings/versions", mod: require_versions() },
13137
13481
  { name: "settings/access", mod: require_access() },
13482
+ { name: "settings/browser", mod: require_browser2() },
13138
13483
  { name: "settings/lan-panel", mod: require_lan_panel() }
13139
13484
  ];
13140
13485
  function installMethods(host2, methods) {
@@ -14677,6 +15022,7 @@ var require_core5 = __commonJS({
14677
15022
  function composeCore(host2, rawConfig, configPath) {
14678
15023
  host2.config = normalize(rawConfig, domainConfigExtension2());
14679
15024
  host2.configPath = typeof configPath === "string" ? configPath : null;
15025
+ platform.environment.bind({ preference: () => host2.config && host2.config.externalBrowser || null });
14680
15026
  host2._fileProtectStatus = null;
14681
15027
  try {
14682
15028
  const fp = platform.fileProtect;
@@ -18211,58 +18557,13 @@ var require_forward_core = __commonJS({
18211
18557
  }
18212
18558
  });
18213
18559
 
18214
- // src/domains/router/ops/browser.js
18215
- var require_browser2 = __commonJS({
18216
- "src/domains/router/ops/browser.js"(exports2, module2) {
18217
- "use strict";
18218
- var platform = require_os();
18219
- var desktop = require_desktop();
18220
- var fs2 = require("node:fs");
18221
- var os2 = require("node:os");
18222
- var path2 = require("node:path");
18223
- var crypto = require("node:crypto");
18224
- function graphicalEnv() {
18225
- return desktop.sessionEnv();
18226
- }
18227
- function openInBrowser(url, onExit) {
18228
- try {
18229
- const tmpProfile = path2.join(os2.tmpdir(), "dsh-oauth-" + crypto.randomBytes(8).toString("hex"));
18230
- const sysEnv = Object.assign({}, process.env, graphicalEnv());
18231
- const TZ_POOL = ["Asia/Shanghai", "Asia/Seoul", "Asia/Tokyo", "Asia/Singapore", "Europe/Berlin", "Europe/London", "Europe/Paris", "America/New_York", "America/Los_Angeles", "Australia/Sydney"];
18232
- const LANG_POOL = ["zh-CN", "en-US", "en-GB", "ja-JP", "ko-KR", "de-DE", "fr-FR", "zh-TW"];
18233
- const SIZE_POOL = [[1280, 800], [1366, 768], [1440, 900], [1536, 864], [1600, 900], [1680, 1050], [1920, 1080], [1024, 768], [1152, 864], [1280, 720]];
18234
- const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
18235
- const size = pick(SIZE_POOL);
18236
- const lang = pick(LANG_POOL);
18237
- const tz = pick(TZ_POOL);
18238
- const antiEnv = Object.assign({}, sysEnv, { TZ: tz, LANG: lang });
18239
- const r = platform.browser.launchIsolated(url, { profileDir: tmpProfile, size, lang, antiEnv, sysEnv, onExit });
18240
- if (!r || !r.ok) return { profile: null, result: r || { ok: false, reason: "spawn-failed" } };
18241
- if (r.isolated) {
18242
- const t30 = setTimeout(() => {
18243
- try {
18244
- fs2.rmSync(tmpProfile, { recursive: true, force: true });
18245
- } catch {
18246
- }
18247
- }, 30 * 60 * 1e3);
18248
- if (t30.unref) t30.unref();
18249
- }
18250
- return { profile: tmpProfile, result: r };
18251
- } catch (e) {
18252
- return { profile: null, result: { ok: false, reason: "spawn-failed", error: "\u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25\uFF1A" + (e && e.message || e) } };
18253
- }
18254
- }
18255
- module2.exports = { graphicalEnv, openInBrowser };
18256
- }
18257
- });
18258
-
18259
18560
  // src/domains/router/ops/oauth.js
18260
18561
  var require_oauth = __commonJS({
18261
18562
  "src/domains/router/ops/oauth.js"(exports2, module2) {
18262
18563
  "use strict";
18263
- var fs2 = require("node:fs");
18264
18564
  var crypto = require("node:crypto");
18265
18565
  var { createServer } = require("node:http");
18566
+ var { removeTreeDeferred } = require_fs();
18266
18567
  function createOAuthOps(deps) {
18267
18568
  const d = deps || {};
18268
18569
  const ports = d.ports;
@@ -18427,20 +18728,21 @@ var require_oauth = __commonJS({
18427
18728
  promise.catch(() => {
18428
18729
  });
18429
18730
  st._ccLoginPromise = promise;
18430
- const opened = openInBrowser(authUrl, () => {
18731
+ const opened = await openInBrowser(authUrl, () => {
18431
18732
  if (st._ccLoginRound !== roundId) return;
18432
18733
  if (st._ccLoginReject) {
18433
- const r = st._ccLoginReject;
18734
+ const r2 = st._ccLoginReject;
18434
18735
  st._ccLoginReject = null;
18435
18736
  st._ccLoginResolve = null;
18436
18737
  try {
18437
- r(new Error("\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED\uFF0C\u767B\u5F55\u5DF2\u53D6\u6D88"));
18738
+ r2(new Error("\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED\uFF0C\u767B\u5F55\u5DF2\u53D6\u6D88"));
18438
18739
  } catch {
18439
18740
  }
18440
18741
  }
18441
18742
  });
18442
- const evidence = opened.result || {};
18443
- if (!opened.profile) {
18743
+ const r = opened || { ok: false, reason: "spawn-failed" };
18744
+ const ev = r.evidence || {};
18745
+ if (!r.ok) {
18444
18746
  st._ccLoginPromise = null;
18445
18747
  st._ccLoginResolve = null;
18446
18748
  st._ccLoginReject = null;
@@ -18460,13 +18762,13 @@ var require_oauth = __commonJS({
18460
18762
  opened: false,
18461
18763
  confirmed: false,
18462
18764
  handedOff: false,
18463
- reason: evidence.reason || "no-launcher",
18464
- evidence: evidence.evidence || null,
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"
18765
+ reason: r.reason || "no-launcher",
18766
+ evidence: r.evidence || null,
18767
+ error: (r.error || "\u65E0\u6CD5\u8C03\u8D77\u7CFB\u7EDF\u6D4F\u89C8\u5668") + "\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00\u4E0B\u65B9\u5730\u5740\u5B8C\u6210\u6388\u6743"
18466
18768
  };
18467
18769
  }
18468
- st._ccLogin = { state, port, server, tmpProfile: opened.profile };
18469
- return Object.assign({}, evidence, {
18770
+ st._ccLogin = { state, port, server, tmpProfile: ev.profile || null };
18771
+ return Object.assign({}, r, {
18470
18772
  ok: true,
18471
18773
  authUrl,
18472
18774
  url: authUrl,
@@ -18474,8 +18776,8 @@ var require_oauth = __commonJS({
18474
18776
  port,
18475
18777
  waitMs: 18e4,
18476
18778
  opened: true,
18477
- // 非隔离引擎(Safari/snap 包装器)由平台层降级:账号隔离不成立,换账号只能靠超时重发或手动窗口。
18478
- isolated: evidence.isolated === true
18779
+ // 非隔离引擎(Safari/打包器包装)由平台层降级:账号隔离不成立,换账号只能靠超时重发或手动窗口。
18780
+ isolated: ev.isolated === true
18479
18781
  });
18480
18782
  }
18481
18783
  async function commandcodeLoginWait(timeoutMs) {
@@ -18521,15 +18823,7 @@ var require_oauth = __commonJS({
18521
18823
  st._ccLoginResolve = st._ccLoginReject = null;
18522
18824
  return { ok: false, error: e.message };
18523
18825
  } finally {
18524
- if (tmpProfile) {
18525
- const t60 = setTimeout(() => {
18526
- try {
18527
- fs2.rmSync(tmpProfile, { recursive: true, force: true });
18528
- } catch {
18529
- }
18530
- }, 60 * 1e3);
18531
- if (t60.unref) t60.unref();
18532
- }
18826
+ if (tmpProfile) removeTreeDeferred(tmpProfile, 60 * 1e3);
18533
18827
  }
18534
18828
  }
18535
18829
  return { commandcodeLoginStart, commandcodeLoginWait };
@@ -18983,8 +19277,8 @@ var require_router_ops = __commonJS({
18983
19277
  "use strict";
18984
19278
  require_port_segments();
18985
19279
  var ports = require_ports().shared;
19280
+ var platform = require_os();
18986
19281
  var { maskKey } = require_base();
18987
- var { openInBrowser } = require_browser2();
18988
19282
  var { createOAuthOps } = require_oauth();
18989
19283
  var { createAppsRegistryOps } = require_apps_registry();
18990
19284
  var { createQuotaSyncOps } = require_quotasync();
@@ -18992,6 +19286,7 @@ var require_router_ops = __commonJS({
18992
19286
  function createAuxCore(deps) {
18993
19287
  const d = deps || {};
18994
19288
  const getProviders = d.getProviders || (() => []);
19289
+ const openInBrowser = (url, onExit) => platform.browser.openBrowser(url, { intent: "isolated-login", onExit });
18995
19290
  const oauth = createOAuthOps({ ports, openInBrowser });
18996
19291
  const apps = createAppsRegistryOps({
18997
19292
  getProviders,
@@ -26009,7 +26304,7 @@ var require_guard = __commonJS({
26009
26304
  var fs2 = require("node:fs");
26010
26305
  var path2 = require("node:path");
26011
26306
  function owns(pathname) {
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";
26307
+ 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/environment" || pathname === "/ports" || pathname === "/shutdown";
26013
26308
  }
26014
26309
  function fetchDshChangelog(res, sup) {
26015
26310
  const v = sup && sup.nativeManager ? sup.nativeManager.versionInfo() : {};
@@ -26021,7 +26316,7 @@ var require_guard = __commonJS({
26021
26316
  return res.end(md);
26022
26317
  }
26023
26318
  function handle(ctx) {
26024
- const { sup, req, res, pathname, identity, send, collectBody, originAllowed, browser } = ctx;
26319
+ const { sup, req, res, pathname, identity, send, collectBody, originAllowed, browser, environment } = ctx;
26025
26320
  if (req.method === "GET" && pathname === "/changelog") {
26026
26321
  return fetchDshChangelog(res, sup);
26027
26322
  }
@@ -26127,6 +26422,31 @@ var require_guard = __commonJS({
26127
26422
  });
26128
26423
  return;
26129
26424
  }
26425
+ if (req.method === "GET" && pathname === "/settings/external-browser") {
26426
+ return send(200, sup.externalBrowserStatus());
26427
+ }
26428
+ if (req.method === "POST" && pathname === "/settings/external-browser") {
26429
+ if (!originAllowed(req, sup.config.apiPort)) {
26430
+ req.resume();
26431
+ return send(403, {});
26432
+ }
26433
+ collectBody(req, res, 2048, (body) => {
26434
+ let id = null;
26435
+ let given = false;
26436
+ try {
26437
+ const j = body ? JSON.parse(body) : {};
26438
+ if (typeof j.id === "string") {
26439
+ id = j.id;
26440
+ given = true;
26441
+ }
26442
+ } catch {
26443
+ }
26444
+ if (!given) return send(400, { ok: false, error: '\u9700\u8981 {"id":"<\u5019\u9009 id>"}\uFF08\u7A7A\u4E32\u6E05\u9664\u504F\u597D\uFF09' });
26445
+ const r = sup.setExternalBrowser(id);
26446
+ return send(r.ok ? 200 : 500, r);
26447
+ });
26448
+ return;
26449
+ }
26130
26450
  if (req.method === "POST" && pathname === "/shutdown") {
26131
26451
  if (!originAllowed(req, sup.config.apiPort)) {
26132
26452
  req.resume();
@@ -26185,13 +26505,13 @@ var require_guard = __commonJS({
26185
26505
  if (req.method === "GET" && pathname === "/env/node-lts") {
26186
26506
  return sup.nodeLtsStatus().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
26187
26507
  }
26188
- if (req.method === "GET" && pathname === "/env/browsers") {
26508
+ if (req.method === "GET" && pathname === "/env/environment") {
26189
26509
  if (!originAllowed(req, sup.config.apiPort)) {
26190
26510
  req.resume();
26191
26511
  return send(403, { ok: false, error: "cross-origin request rejected" });
26192
26512
  }
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) }));
26513
+ const force = /[?&]force=1/.test(String(req.url || ""));
26514
+ return Promise.resolve(environment.form({ force, persist: force })).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: "\u73AF\u5883\u8868\u5355\u88C5\u914D\u5931\u8D25\uFF1A" + (e && e.message || e) }));
26195
26515
  }
26196
26516
  if (req.method === "POST" && pathname === "/env/open-url") {
26197
26517
  if (!identity.loopback) {
@@ -27328,6 +27648,7 @@ var require_server = __commonJS({
27328
27648
  var { identify } = require_identity3();
27329
27649
  var { originAllowed, requestHasAccessKey, isShellOrigin } = require_security();
27330
27650
  var browserExit = require_browser();
27651
+ var environmentExit = require_environment();
27331
27652
  function safeFail(res, err, where) {
27332
27653
  try {
27333
27654
  const body = JSON.stringify({ ok: false, error: err && err.message || String(err) });
@@ -27347,6 +27668,7 @@ var require_server = __commonJS({
27347
27668
  }
27348
27669
  function createServer(sup, deps) {
27349
27670
  const browser = deps && deps.browser || browserExit;
27671
+ const environment = deps && deps.environment || environmentExit;
27350
27672
  return http2.createServer((req, res) => {
27351
27673
  const shellOrigin = (() => {
27352
27674
  const o = req.headers.origin;
@@ -27414,7 +27736,7 @@ var require_server = __commonJS({
27414
27736
  } catch (e) {
27415
27737
  return send(400, { error: "bad request encoding" });
27416
27738
  }
27417
- const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf, browser };
27739
+ const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf, browser, environment };
27418
27740
  for (const d of API_DOMAINS) {
27419
27741
  if (d.owns(pathname)) {
27420
27742
  try {