@morlay/dsh-desktop-shell 0.2.9 → 0.2.10-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- //#region src/appconfig.ts
3
+ // src/appconfig.ts
4
4
  const PROFILE_NAME = "desktop";
5
5
  const PROFILE_PATCH_NAME = "cordis.patch.yml";
6
6
  function isRecord(value) {
@@ -35,5 +35,4 @@ async function loadAppConfig(exeDir) {
35
35
  async function writeAppConfig(exeDir, config) {
36
36
  await writeFile(join(exeDir, "appconfig.json"), `${JSON.stringify(config, void 0, 2)}\n`, { mode: 384 });
37
37
  }
38
- //#endregion
39
38
  export { writeAppConfig as i, PROFILE_PATCH_NAME as n, loadAppConfig as r, PROFILE_NAME as t };
@@ -1,4 +1,3 @@
1
- //#region src/appconfig.d.ts
2
1
  export declare const PROFILE_NAME = "desktop";
3
2
  export declare const PROFILE_PATCH_NAME = "cordis.patch.yml";
4
3
  export interface AppWindowConfig {
@@ -17,5 +16,4 @@ export interface AppConfig {
17
16
  readonly window: AppWindowConfig;
18
17
  }
19
18
  export declare function loadAppConfig(exeDir: string): Promise<AppConfig>;
20
- export declare function writeAppConfig(exeDir: string, config: AppConfig): Promise<void>;
21
- //#endregion
19
+ export declare function writeAppConfig(exeDir: string, config: AppConfig): Promise<void>;
@@ -1 +1 @@
1
- export { t as PROFILE_NAME, n as PROFILE_PATCH_NAME, r as loadAppConfig, i as writeAppConfig } from "./appconfig-CSZh3JG7.mjs";
1
+ export { t as PROFILE_NAME, n as PROFILE_PATCH_NAME, r as loadAppConfig, i as writeAppConfig } from "./appconfig-Ci991oVH.mjs";
@@ -1,6 +1,6 @@
1
1
  import { isAbsolute, join, resolve } from "node:path";
2
2
  import { homedir } from "node:os";
3
- //#region src/dshhome.ts
3
+ // src/dshhome.ts
4
4
  function xdgDataHome() {
5
5
  if (process.platform === "darwin") return join(homedir(), "Library", "Application Support");
6
6
  const configured = process.env.XDG_DATA_HOME;
@@ -18,5 +18,4 @@ function resolveDshHome(config) {
18
18
  if (override !== void 0 && override.trim() !== "") return resolve(override);
19
19
  return resolveConfiguredHome(config.name, config.dataDir);
20
20
  }
21
- //#endregion
22
21
  export { resolveDshHome as n, xdgDataHome as r, resolveConfiguredHome as t };
@@ -1,6 +1,4 @@
1
1
  import { AppConfig } from "./appconfig.mjs";
2
- //#region src/dshhome.d.ts
3
2
  export declare function xdgDataHome(): string;
4
3
  export declare function resolveConfiguredHome(name: string, spec: string): string | undefined;
5
- export declare function resolveDshHome(config: AppConfig): string | undefined;
6
- //#endregion
4
+ export declare function resolveDshHome(config: AppConfig): string | undefined;
package/dist/dshhome.mjs CHANGED
@@ -1 +1 @@
1
- export { t as resolveConfiguredHome, n as resolveDshHome, r as xdgDataHome } from "./dshhome-DAJ9dkn2.mjs";
1
+ export { t as resolveConfiguredHome, n as resolveDshHome, r as xdgDataHome } from "./dshhome-wAhMMZji.mjs";
package/dist/index.d.mts CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/index.d.ts
2
1
  declare global {
3
2
  /** 壳的包根:由引导(`bootstrap.mjs`,它就在包根)写入;发布包里没有引导时为 undefined。 */
4
3
  var __DSH_SHELL_ROOT__: string | undefined;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { t as DESKTOP_HOST_PACKAGE } from "./official-BmgBsdhq.mjs";
2
- import { r as loadAppConfig, t as PROFILE_NAME } from "./appconfig-CSZh3JG7.mjs";
3
- import { n as resolveDshHome } from "./dshhome-DAJ9dkn2.mjs";
4
- import { a as ensureSeedProfile, i as SEED_RUNTIME_DIR_NAME } from "./seed-Bxr0BsCk.mjs";
1
+ import { t as DESKTOP_HOST_PACKAGE } from "./official-NWEpqAc6.mjs";
2
+ import { r as loadAppConfig, t as PROFILE_NAME } from "./appconfig-Ci991oVH.mjs";
3
+ import { n as resolveDshHome } from "./dshhome-wAhMMZji.mjs";
4
+ import { a as ensureSeedProfile, i as SEED_RUNTIME_DIR_NAME } from "./seed-Z7k1Wt9H.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { access, appendFile, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
7
7
  import { basename, dirname, join, resolve } from "node:path";
@@ -10,7 +10,7 @@ import "rolldown";
10
10
  import { spawn } from "node:child_process";
11
11
  import { once } from "node:events";
12
12
  import { randomBytes } from "node:crypto";
13
- //#region src/electron.ts
13
+ // src/electron.ts
14
14
  /**
15
15
  * `electron` 模块的**唯一入口**(壳自己的其它文件都从这里取)。
16
16
  *
@@ -31,13 +31,11 @@ const nativeTheme = electron.nativeTheme;
31
31
  const protocol = electron.protocol;
32
32
  const Menu = electron.Menu;
33
33
  const MenuItem = electron.MenuItem;
34
- //#endregion
35
- //#region ../dsh-desktop-host/src/preload-source.ts
34
+ // ../dsh-desktop-host/src/preload-source.ts
36
35
  /** 壳取 preload 的路径(`GET` 它得到可执行的 CJS 字节)。 */
37
36
  const PRELOAD_SOURCE_PATH = "/@dsh/preload.cjs";
38
37
  join("dist", "preload.cjs");
39
- //#endregion
40
- //#region src/ipc.ts
38
+ // src/ipc.ts
41
39
  const DESKTOP_IPC = {
42
40
  directoryPick: "dsh-desktop:directory-pick",
43
41
  nativeThemeSet: "dsh-desktop:native-theme-set",
@@ -61,20 +59,33 @@ function desktopScheme(name) {
61
59
  if (!/^[a-z][a-z0-9+.-]*$/u.test(bare)) throw new Error(`dsh desktop: application name "${name}" does not yield a usable URL scheme (${bare})`);
62
60
  return bare;
63
61
  }
64
- function assertDesktopSender(event, scheme, hostnames) {
62
+ /**
63
+ * 校验一次 IPC 来自**本应用的页面**:senderFrame 的 origin 必须在 `allowedOrigins` 里。
64
+ *
65
+ * 页面从宿主 webServer 的 origin(`http://127.0.0.1:<port>`)载入——早先那条自定义协议
66
+ * `scheme://app/` 已经不再承载页面(它没有 origin,settings 的 describe 流拿不到答复)。所以这里比的是
67
+ * origin,而不是 scheme + hostname:后者会让所有真实页面都被判成 unowned。
68
+ * @param event - Electron 的 invoke 事件。
69
+ * @param allowedOrigins - 允许的页面 origin(应用自己的宿主 origin)。
70
+ */
71
+ function assertDesktopSender(event, allowedOrigins) {
65
72
  const senderFrame = event.senderFrame;
66
73
  if (senderFrame === null) throw new Error("dsh desktop: rejected IPC without a sender frame");
67
- const url = new URL(senderFrame.url);
68
- if (url.protocol !== `${scheme}:` || !hostnames.includes(url.hostname)) throw new Error("dsh desktop: rejected IPC from an unowned renderer");
74
+ let origin;
75
+ try {
76
+ origin = new URL(senderFrame.url).origin;
77
+ } catch {
78
+ throw new Error("dsh desktop: rejected IPC with an unreadable sender URL");
79
+ }
80
+ if (!allowedOrigins.includes(origin)) throw new Error("dsh desktop: rejected IPC from an unowned renderer");
69
81
  }
70
- //#endregion
71
- //#region src/directory-picker.ts
72
- function installDesktopDirectoryPicker(getWindow, scheme) {
82
+ // src/directory-picker.ts
83
+ function installDesktopDirectoryPicker(getWindow, getOrigin) {
73
84
  const pending = /* @__PURE__ */ new WeakMap();
74
85
  ipcMain.handle(DESKTOP_IPC.directoryPick, async (event) => {
75
86
  const window = getWindow();
76
87
  if (window === void 0 || window.isDestroyed() || event.sender !== window.webContents || event.senderFrame !== window.webContents.mainFrame) throw new Error("dsh desktop: rejected directory picker from an unowned renderer");
77
- assertDesktopSender(event, scheme, ["app"]);
88
+ assertDesktopSender(event, [getOrigin()]);
78
89
  const existing = pending.get(window);
79
90
  if (existing !== void 0) return existing;
80
91
  if (window.isMinimized()) window.restore();
@@ -87,12 +98,10 @@ function installDesktopDirectoryPicker(getWindow, scheme) {
87
98
  return result;
88
99
  });
89
100
  }
90
- //#endregion
91
- //#region ../dsh-desktop-host/src/paths.ts
101
+ // ../dsh-desktop-host/src/paths.ts
92
102
  /** 应用 origin 下托管调试窗口前端产物的前缀。 */
93
103
  const DEVTOOLS_ASSETS_PREFIX = "/node-devtools";
94
- //#endregion
95
- //#region src/developer-endpoint.ts
104
+ // src/developer-endpoint.ts
96
105
  /** DevTools 前端在托管前缀下的入口路径(与上游底部面板用的是同一份产物)。 */
97
106
  const DEVTOOLS_ENTRY = "devtools_app.html";
98
107
  /**
@@ -110,8 +119,7 @@ function nodeInspectorDevtoolsUrl(scheme, endpoint) {
110
119
  if (!/^[\u0021-\u007e]+$/u.test(socket) || socket.includes("&") || socket.includes("#")) throw new Error(`dsh desktop: the inspector endpoint is not usable in a query: ${endpoint}`);
111
120
  return `${scheme}://app${DEVTOOLS_ASSETS_PREFIX}/${DEVTOOLS_ENTRY}?ws=${socket}&panel=console&disableLocaleInfoBar=true`;
112
121
  }
113
- //#endregion
114
- //#region src/developer-menu.ts
122
+ // src/developer-menu.ts
115
123
  function reportFailure(options, title, detail) {
116
124
  console.error(`[dsh-shell] ${title}: ${detail}`);
117
125
  const parent = options.mainWindow();
@@ -408,8 +416,7 @@ function isDesktopHostEvent(value) {
408
416
  if (value.type === "inspected") return (value.url === null || typeof value.url === "string") && (value.message === void 0 || typeof value.message === "string");
409
417
  return false;
410
418
  }
411
- //#endregion
412
- //#region src/host-process.ts
419
+ // src/host-process.ts
413
420
  function errorOf(reason, fallback) {
414
421
  return reason instanceof Error ? reason : new Error(fallback);
415
422
  }
@@ -802,8 +809,7 @@ var DesktopHostProcess = class {
802
809
  this.responsePipe?.resume();
803
810
  }
804
811
  };
805
- //#endregion
806
- //#region ../../../vendor/deepseek-harness/packages/client/shortcuts/src/binding.ts
812
+ // ../../../vendor/deepseek-harness/packages/client/shortcuts/src/binding.ts
807
813
  const keyNames = {
808
814
  Slash: "/",
809
815
  Comma: ",",
@@ -889,8 +895,7 @@ function isWebBindingAllowed(binding, platform) {
889
895
  }
890
896
  ].some((candidate) => bindingKey(binding) === bindingKey(normalizeBinding(candidate, platform)));
891
897
  }
892
- //#endregion
893
- //#region ../../../vendor/deepseek-harness/packages/util/values/src/partial-json.ts
898
+ // ../../../vendor/deepseek-harness/packages/util/values/src/partial-json.ts
894
899
  /**
895
900
  * Lazily scanned view of one JSON object's top-level fields, built from text
896
901
  * that may still be streaming or from an already parsed object. Nothing is
@@ -1513,8 +1518,7 @@ function isHex(c) {
1513
1518
  this.#current = null;
1514
1519
  }
1515
1520
  });
1516
- //#endregion
1517
- //#region ../../../vendor/deepseek-harness/packages/util/values/src/index.ts
1521
+ // ../../../vendor/deepseek-harness/packages/util/values/src/index.ts
1518
1522
  /**
1519
1523
  * Mark an unreachable closed-union branch.
1520
1524
  * @param value - impossible value; an unhandled typed variant fails at the call site.
@@ -1525,8 +1529,7 @@ function assertNever(value, context) {
1525
1529
  const rendered = JSON.stringify(value) ?? String(value);
1526
1530
  throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
1527
1531
  }
1528
- //#endregion
1529
- //#region ../../../vendor/deepseek-harness/packages/client/shortcuts/src/configuration.ts
1532
+ // ../../../vendor/deepseek-harness/packages/client/shortcuts/src/configuration.ts
1530
1533
  /** Validated preference documents and deterministic conflict resolution, without browser dependencies. */
1531
1534
  const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1532
1535
  const commandPattern = /^[a-z][a-zA-Z0-9-]*(?:\.[a-zA-Z][a-zA-Z0-9-]*)+$/u;
@@ -1773,8 +1776,7 @@ function parseShortcutDefinitions(value) {
1773
1776
  }
1774
1777
  return definitions;
1775
1778
  }
1776
- //#endregion
1777
- //#region ../../../vendor/deepseek-harness/packages/util/crypto/src/index.ts
1779
+ // ../../../vendor/deepseek-harness/packages/util/crypto/src/index.ts
1778
1780
  /**
1779
1781
  * Random v4 UUID, minted from `crypto.getRandomValues`.
1780
1782
  * @returns the UUID string.
@@ -1786,8 +1788,7 @@ function randomUUID() {
1786
1788
  }).join("");
1787
1789
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1788
1790
  }
1789
- //#endregion
1790
- //#region ../../../vendor/deepseek-harness/packages/client/shortcuts/src/persistence.ts
1791
+ // ../../../vendor/deepseek-harness/packages/client/shortcuts/src/persistence.ts
1791
1792
  /** Serialized preference transactions; storage owners publish only accepted writes or read diagnostics. */
1792
1793
  /**
1793
1794
  * Create a disabled initial snapshot for asynchronous adapter startup.
@@ -1935,8 +1936,7 @@ var ShortcutPersistence = class {
1935
1936
  return this.snapshot;
1936
1937
  }
1937
1938
  };
1938
- //#endregion
1939
- //#region ../../../vendor/deepseek-harness/packages/util/atomic-write/src/index.ts
1939
+ // ../../../vendor/deepseek-harness/packages/util/atomic-write/src/index.ts
1940
1940
  /**
1941
1941
  * Zero-dependency atomic file replacement and writer coordination.
1942
1942
  * `writeFileAtomic` writes a random-suffix sibling with exclusive create and
@@ -2010,8 +2010,7 @@ async function writeFileAtomic(filename, content, options) {
2010
2010
  throw error;
2011
2011
  }
2012
2012
  }
2013
- //#endregion
2014
- //#region src/keybindings.ts
2013
+ // src/keybindings.ts
2015
2014
  function desktopKeybindings(userData, platform, publish) {
2016
2015
  const path = join(userData, "keybindings.json");
2017
2016
  return new ShortcutPersistence({
@@ -2029,9 +2028,8 @@ function desktopKeybindings(userData, platform, publish) {
2029
2028
  })
2030
2029
  }, "desktop", platform, false, publish);
2031
2030
  }
2032
- //#endregion
2033
- //#region src/shortcuts.ts
2034
- function installDesktopShortcuts(getWindow, userData, platform, scheme) {
2031
+ // src/shortcuts.ts
2032
+ function installDesktopShortcuts(getWindow, userData, platform, getOrigin) {
2035
2033
  let definitions = [];
2036
2034
  let recording = false;
2037
2035
  let revision;
@@ -2045,7 +2043,7 @@ function installDesktopShortcuts(getWindow, userData, platform, scheme) {
2045
2043
  };
2046
2044
  const persistence = desktopKeybindings(userData, platform, publish);
2047
2045
  const windowOf = (event) => {
2048
- assertDesktopSender(event, scheme, ["app"]);
2046
+ assertDesktopSender(event, [getOrigin()]);
2049
2047
  const window = getWindow();
2050
2048
  if (window === void 0 || window.isDestroyed() || event.sender !== window.webContents) throw new Error("desktop shortcuts: rejected sender");
2051
2049
  return window;
@@ -2207,8 +2205,7 @@ function installDesktopShortcuts(getWindow, userData, platform, scheme) {
2207
2205
  }
2208
2206
  };
2209
2207
  }
2210
- //#endregion
2211
- //#region src/shell-env.ts
2208
+ // src/shell-env.ts
2212
2209
  function rcFileFor(shell) {
2213
2210
  switch (basename(shell)) {
2214
2211
  case "bash": return "~/.bashrc";
@@ -2234,8 +2231,7 @@ function shellWrappedSpawn(command, args, options) {
2234
2231
  detached: process.platform !== "win32"
2235
2232
  });
2236
2233
  }
2237
- //#endregion
2238
- //#region src/index.ts
2234
+ // src/index.ts
2239
2235
  let focusPrimaryWindow = () => {};
2240
2236
  /** app 目录(dev 是装配目录、打包是 `Resources/app`):由引导写进全局。 */
2241
2237
  function shellRoot() {
@@ -2493,7 +2489,7 @@ async function main() {
2493
2489
  if (!app.isPackaged) shellLog(`${request.method} ${url.pathname} → ${String(response.status)}`);
2494
2490
  return response;
2495
2491
  });
2496
- installDesktopDirectoryPicker(() => mainWindow, SCHEME);
2492
+ installDesktopDirectoryPicker(() => mainWindow, () => hostOrigin);
2497
2493
  let nextStreamId = 1;
2498
2494
  const streamEncoder = new TextEncoder();
2499
2495
  const activeStreams = /* @__PURE__ */ new Map();
@@ -2508,7 +2504,7 @@ async function main() {
2508
2504
  }
2509
2505
  };
2510
2506
  ipcMain.handle(DESKTOP_IPC.streamOpen, async (event, endpoint, payload) => {
2511
- assertDesktopSender(event, SCHEME, ["app"]);
2507
+ assertDesktopSender(event, [hostOrigin]);
2512
2508
  if (typeof endpoint !== "string") throw new Error("dsh desktop: stream endpoint must be text");
2513
2509
  const active = host;
2514
2510
  if (active === void 0) throw new Error("dsh desktop: Host is unavailable");
@@ -2593,7 +2589,7 @@ async function main() {
2593
2589
  if (mainWindow === void 0 || event.sender !== mainWindow.webContents) return;
2594
2590
  if (source === "light" || source === "dark" || source === "system") nativeTheme.themeSource = source;
2595
2591
  });
2596
- const shortcuts = installDesktopShortcuts(() => mainWindow, app.getPath("userData"), process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux", SCHEME);
2592
+ const shortcuts = installDesktopShortcuts(() => mainWindow, app.getPath("userData"), process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux", () => hostOrigin);
2597
2593
  app.on("will-quit", () => {
2598
2594
  shortcuts.dispose();
2599
2595
  });
@@ -2693,5 +2689,4 @@ if ((() => {
2693
2689
  dialog.showErrorBox("dsh desktop startup failed", message);
2694
2690
  app.exit(1);
2695
2691
  });
2696
- //#endregion
2697
2692
  export {};
@@ -1,4 +1,3 @@
1
- //#region src/inline-packages.d.ts
2
1
  /**
3
2
  * 壳产物里要**内联**的包(而不是留在产物外)。
4
3
  *
@@ -10,5 +9,4 @@
10
9
  * 这份名单是**一个事实**:壳自己的构建([`tsdown.config.ts`](../../tsdown.config.ts))与 `@local/dshdev` 的
11
10
  * 装配(`prepareShell`)都用它——两边不一致的话,装配出来的产物就会缺依赖。
12
11
  */
13
- export declare const SHELL_INLINE_PACKAGES: readonly string[];
14
- //#endregion
12
+ export declare const SHELL_INLINE_PACKAGES: readonly string[];
@@ -1,4 +1,4 @@
1
- //#region src/inline-packages.ts
1
+ // src/inline-packages.ts
2
2
  /**
3
3
  * 壳产物里要**内联**的包(而不是留在产物外)。
4
4
  *
@@ -17,5 +17,4 @@ const SHELL_INLINE_PACKAGES = [
17
17
  "@deepseek-ai/dsh-util-values",
18
18
  "@deepseek-ai/dsh-util-crypto"
19
19
  ];
20
- //#endregion
21
20
  export { SHELL_INLINE_PACKAGES };
@@ -1,5 +1,4 @@
1
- //#region src/official.ts
1
+ // src/official.ts
2
2
  const DESKTOP_HOST_PACKAGE = "@morlay/dsh-desktop-host";
3
3
  const OFFICIAL_PROFILE_BUNDLES = ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"];
4
- //#endregion
5
4
  export { OFFICIAL_PROFILE_BUNDLES as n, DESKTOP_HOST_PACKAGE as t };
@@ -1,4 +1,2 @@
1
- //#region src/official.d.ts
2
1
  export declare const DESKTOP_HOST_PACKAGE = "@morlay/dsh-desktop-host";
3
- export declare const OFFICIAL_PROFILE_BUNDLES: readonly string[];
4
- //#endregion
2
+ export declare const OFFICIAL_PROFILE_BUNDLES: readonly string[];
package/dist/official.mjs CHANGED
@@ -1 +1 @@
1
- export { t as DESKTOP_HOST_PACKAGE, n as OFFICIAL_PROFILE_BUNDLES } from "./official-BmgBsdhq.mjs";
1
+ export { t as DESKTOP_HOST_PACKAGE, n as OFFICIAL_PROFILE_BUNDLES } from "./official-NWEpqAc6.mjs";
@@ -32,8 +32,7 @@ function syncNativeTheme(platform, root, send) {
32
32
  if (root.readyState === "loading") root.defaultView?.addEventListener("DOMContentLoaded", observe, { once: true });
33
33
  else observe();
34
34
  }
35
- //#endregion
36
- //#region src/ipc.ts
35
+ // src/ipc.ts
37
36
  /** 壳把宿主回环的 ws 端口经 preload 递进页面(`additionalArguments`,与 scheme 同一手法)。 */
38
37
  const DESKTOP_WS_PORT_ARGUMENT = "--dsh-desktop-ws-port";
39
38
  const DESKTOP_IPC = {
@@ -54,8 +53,7 @@ const DESKTOP_IPC = {
54
53
  shortcutsChanged: "dsh-desktop:shortcuts-changed"
55
54
  };
56
55
  const DESKTOP_SCHEME_ARGUMENT = "--dsh-desktop-scheme";
57
- //#endregion
58
- //#region src/stream-uplink.ts
56
+ // src/stream-uplink.ts
59
57
  function createStreamUplink(send, end) {
60
58
  let streamId;
61
59
  let closed = false;
@@ -80,8 +78,7 @@ function createStreamUplink(send, end) {
80
78
  }
81
79
  };
82
80
  }
83
- //#endregion
84
- //#region src/preload-app.ts
81
+ // src/preload-app.ts
85
82
  const scheme = process.argv.find((argument) => argument.startsWith(`${DESKTOP_SCHEME_ARGUMENT}=`))?.slice(21);
86
83
  const wsPort = process.argv.find((argument) => argument.startsWith(`${DESKTOP_WS_PORT_ARGUMENT}=`))?.slice(22);
87
84
  if (scheme !== void 0 && scheme !== "") {
@@ -170,4 +167,3 @@ electron.contextBridge.exposeInMainWorld("dshDesktop", {
170
167
  syncNativeTheme(process.platform, document, (source) => {
171
168
  electron.ipcRenderer.send(DESKTOP_IPC.nativeThemeSet, source);
172
169
  });
173
- //#endregion
@@ -1,7 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { spawn } from "node:child_process";
4
- //#region src/profile-project.ts
4
+ // src/profile-project.ts
5
5
  const PROFILE_WORKSPACE_NAME = "pnpm-workspace.yaml";
6
6
  const PROFILE_RUNTIME_REPORT_NAME = "desktop-runtime-packages.json";
7
7
  const PROFILE_VENDOR_DIR_NAME = "vendor";
@@ -67,5 +67,4 @@ function installProfile(options) {
67
67
  });
68
68
  });
69
69
  }
70
- //#endregion
71
70
  export { runtimeOverrides as a, installProfile as i, PROFILE_VENDOR_DIR_NAME as n, workspaceWithOverrides as o, PROFILE_WORKSPACE_NAME as r, PROFILE_RUNTIME_REPORT_NAME as t };
@@ -1,5 +1,4 @@
1
1
  import { spawn } from "node:child_process";
2
- //#region src/profile-project.d.ts
3
2
  export declare const PROFILE_WORKSPACE_NAME = "pnpm-workspace.yaml";
4
3
  export declare const PROFILE_RUNTIME_REPORT_NAME = "desktop-runtime-packages.json";
5
4
  export declare const PROFILE_VENDOR_DIR_NAME = "vendor";
@@ -16,5 +15,4 @@ export interface ProfileInstallOptions {
16
15
  readonly profileDir: string;
17
16
  readonly spawn?: ProfileInstallSpawn;
18
17
  }
19
- export declare function installProfile(options: ProfileInstallOptions): Promise<void>;
20
- //#endregion
18
+ export declare function installProfile(options: ProfileInstallOptions): Promise<void>;
@@ -1 +1 @@
1
- export { t as PROFILE_RUNTIME_REPORT_NAME, n as PROFILE_VENDOR_DIR_NAME, r as PROFILE_WORKSPACE_NAME, i as installProfile, a as runtimeOverrides, o as workspaceWithOverrides } from "./profile-project-BgQILDbu.mjs";
1
+ export { t as PROFILE_RUNTIME_REPORT_NAME, n as PROFILE_VENDOR_DIR_NAME, r as PROFILE_WORKSPACE_NAME, i as installProfile, a as runtimeOverrides, o as workspaceWithOverrides } from "./profile-project-DsOmRRGB.mjs";
@@ -1,8 +1,8 @@
1
- import { n as PROFILE_PATCH_NAME, t as PROFILE_NAME } from "./appconfig-CSZh3JG7.mjs";
1
+ import { n as PROFILE_PATCH_NAME, t as PROFILE_NAME } from "./appconfig-Ci991oVH.mjs";
2
2
  import { access, chmod, copyFile, lstat, mkdir, readFile, readdir, readlink, rm, symlink, unlink, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import { spawn } from "node:child_process";
5
- //#region src/seed.ts
5
+ // src/seed.ts
6
6
  const SEED_DIR_NAME = "dsh-home";
7
7
  const SEED_HASH_NAME = ".seed-hash";
8
8
  const SEED_RUNTIME_DIR_NAME = "runtime";
@@ -137,5 +137,4 @@ async function copySeed(source, target) {
137
137
  };
138
138
  await visit(source, "");
139
139
  }
140
- //#endregion
141
140
  export { ensureSeedProfile as a, SEED_RUNTIME_DIR_NAME as i, SEED_DIR_NAME as n, SEED_HASH_NAME as r, PROFILE_MODULES_ARCHIVE_NAME as t };
package/dist/seed.d.mts CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/seed.d.ts
2
1
  export declare const SEED_DIR_NAME = "dsh-home";
3
2
  export declare const SEED_HASH_NAME = ".seed-hash";
4
3
  export declare const SEED_RUNTIME_DIR_NAME = "runtime";
@@ -17,5 +16,4 @@ export type SeedOutcome = "seeded" | "up-to-date" | "no-seed";
17
16
  * @returns 种了(`seeded`)、已是最新(`up-to-date`)、或随包根本没有这份种(`no-seed`)。
18
17
  * @throws 归档解压失败、或解压后没有依赖面时。
19
18
  */
20
- export declare function ensureSeedProfile(seedDir: string, home: string): Promise<SeedOutcome>;
21
- //#endregion
19
+ export declare function ensureSeedProfile(seedDir: string, home: string): Promise<SeedOutcome>;
package/dist/seed.mjs CHANGED
@@ -1 +1 @@
1
- export { t as PROFILE_MODULES_ARCHIVE_NAME, n as SEED_DIR_NAME, r as SEED_HASH_NAME, i as SEED_RUNTIME_DIR_NAME, a as ensureSeedProfile } from "./seed-Bxr0BsCk.mjs";
1
+ export { t as PROFILE_MODULES_ARCHIVE_NAME, n as SEED_DIR_NAME, r as SEED_HASH_NAME, i as SEED_RUNTIME_DIR_NAME, a as ensureSeedProfile } from "./seed-Z7k1Wt9H.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/dsh-desktop-shell",
3
- "version": "0.2.9",
3
+ "version": "0.2.10-alpha.0",
4
4
  "description": "Electron shell for dsh desktop apps: serves the app's own <name>:// scheme, boots the desktop profile through the @morlay/dsh-desktop-host child over byte pipes, and installs the profile seed on first launch. The CLI that packages a workspace around it is @local/dshdev.",
5
5
  "keywords": [
6
6
  "desktop",
@@ -54,9 +54,9 @@
54
54
  "./locale/*.json": "./locale/*.json"
55
55
  },
56
56
  "dependencies": {
57
- "@deepseek-ai/dsh-util-crypto": "^0.2.1-rc.1",
58
- "@deepseek-ai/dsh-util-values": "^0.2.1-rc.1",
59
- "@morlay/dsh-desktop-host": "0.2.9"
57
+ "@deepseek-ai/dsh-util-crypto": "^0.2.2-alpha.2",
58
+ "@deepseek-ai/dsh-util-values": "^0.2.2-alpha.2",
59
+ "@morlay/dsh-desktop-host": "0.2.10-alpha.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@local/devkit": "0.1.0",
@@ -65,12 +65,12 @@
65
65
  "electron": "44.7.0"
66
66
  },
67
67
  "peerDependencies": {
68
- "@deepseek-ai/dsh-app-boot": "^0.2.1-rc.1",
69
- "@deepseek-ai/dsh-atomic-write": "^0.2.1-rc.1",
70
- "@deepseek-ai/dsh-client-shortcuts": "^0.2.1-rc.1"
68
+ "@deepseek-ai/dsh-app-boot": "^0.2.2-alpha.2",
69
+ "@deepseek-ai/dsh-atomic-write": "^0.2.2-alpha.2",
70
+ "@deepseek-ai/dsh-client-shortcuts": "^0.2.2-alpha.2"
71
71
  },
72
72
  "engines": {
73
- "dsh": ">= 0.2.1-rc.1 < 0.2.2",
73
+ "dsh": ">= 0.2.2-alpha.2 < 0.2.3",
74
74
  "node": ">=24.11.0"
75
75
  },
76
76
  "scripts": {
@@ -22,13 +22,18 @@ describe("桌面标识取自 app 名", () => {
22
22
  }
23
23
  });
24
24
 
25
- it("发送者校验用传入的 scheme,而不是写死的 dsh-app", () => {
26
- const scheme = desktopScheme("dsh-custom-next");
27
- expect(() => assertDesktopSender(sender(`${scheme}://app/`), scheme, ["app"])).not.toThrow();
28
- expect(() => assertDesktopSender(sender("dsh-app://app/"), scheme, ["app"])).toThrow(
25
+ it("发送者校验比的是页面 origin(页面从宿主 origin 载入,不是自定义协议)", () => {
26
+ const origin = "http://127.0.0.1:51234";
27
+ expect(() => assertDesktopSender(sender(`${origin}/?token=x`), [origin])).not.toThrow();
28
+ // 自定义协议那条老路:页面早已不从它载入,不能再被当成本应用。
29
+ expect(() => assertDesktopSender(sender("dsh-custom-next://app/"), [origin])).toThrow(
29
30
  /unowned renderer/u,
30
31
  );
31
- expect(() => assertDesktopSender(sender(`${scheme}://other/`), scheme, ["app"])).toThrow(
32
+ // 别的 origin(另一个本地端口、外部站点)同样拒绝。
33
+ expect(() => assertDesktopSender(sender("http://127.0.0.1:59999/"), [origin])).toThrow(
34
+ /unowned renderer/u,
35
+ );
36
+ expect(() => assertDesktopSender(sender("https://example.com/"), [origin])).toThrow(
32
37
  /unowned renderer/u,
33
38
  );
34
39
  });
@@ -4,7 +4,7 @@ import { DESKTOP_IPC, assertDesktopSender } from "./ipc.ts";
4
4
 
5
5
  export function installDesktopDirectoryPicker(
6
6
  getWindow: () => BrowserWindowType | undefined,
7
- scheme: string,
7
+ getOrigin: () => string,
8
8
  ): void {
9
9
  const pending = new WeakMap<BrowserWindowType, Promise<string | null>>();
10
10
  ipcMain.handle(DESKTOP_IPC.directoryPick, async (event) => {
@@ -17,7 +17,7 @@ export function installDesktopDirectoryPicker(
17
17
  ) {
18
18
  throw new Error("dsh desktop: rejected directory picker from an unowned renderer");
19
19
  }
20
- assertDesktopSender(event, scheme, ["app"]);
20
+ assertDesktopSender(event, [getOrigin()]);
21
21
  const existing = pending.get(window);
22
22
  if (existing !== undefined) return existing;
23
23
  if (window.isMinimized()) window.restore();
package/src/index.ts CHANGED
@@ -457,7 +457,7 @@ async function main(): Promise<void> {
457
457
  return response;
458
458
  });
459
459
 
460
- installDesktopDirectoryPicker(() => mainWindow, SCHEME);
460
+ installDesktopDirectoryPicker(() => mainWindow, () => hostOrigin);
461
461
 
462
462
  // 桌面流载体:主进程自己构造 Request(body 是流——首行定 endpoint/payload,后续行是上行项),
463
463
  // 把响应体逐块推回页面——页面 fetch 到自定义协议的 POST body 在 Chromium 上不可靠。
@@ -481,7 +481,7 @@ async function main(): Promise<void> {
481
481
  }
482
482
  };
483
483
  ipcMain.handle(DESKTOP_IPC.streamOpen, async (event, endpoint: unknown, payload: unknown) => {
484
- assertDesktopSender(event, SCHEME, ["app"]);
484
+ assertDesktopSender(event, [hostOrigin]);
485
485
  if (typeof endpoint !== "string") throw new Error("dsh desktop: stream endpoint must be text");
486
486
  const active = host;
487
487
  if (active === undefined) throw new Error("dsh desktop: Host is unavailable");
@@ -571,7 +571,7 @@ async function main(): Promise<void> {
571
571
  () => mainWindow,
572
572
  app.getPath("userData"),
573
573
  process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux",
574
- SCHEME,
574
+ () => hostOrigin,
575
575
  );
576
576
  app.on("will-quit", () => {
577
577
  shortcuts.dispose();
package/src/ipc.ts CHANGED
@@ -44,15 +44,28 @@ export function desktopScheme(name: string): string {
44
44
  return bare;
45
45
  }
46
46
 
47
+ /**
48
+ * 校验一次 IPC 来自**本应用的页面**:senderFrame 的 origin 必须在 `allowedOrigins` 里。
49
+ *
50
+ * 页面从宿主 webServer 的 origin(`http://127.0.0.1:<port>`)载入——早先那条自定义协议
51
+ * `scheme://app/` 已经不再承载页面(它没有 origin,settings 的 describe 流拿不到答复)。所以这里比的是
52
+ * origin,而不是 scheme + hostname:后者会让所有真实页面都被判成 unowned。
53
+ * @param event - Electron 的 invoke 事件。
54
+ * @param allowedOrigins - 允许的页面 origin(应用自己的宿主 origin)。
55
+ */
47
56
  export function assertDesktopSender(
48
57
  event: IpcMainInvokeEvent,
49
- scheme: string,
50
- hostnames: readonly string[],
58
+ allowedOrigins: readonly string[],
51
59
  ): void {
52
60
  const senderFrame = event.senderFrame;
53
61
  if (senderFrame === null) throw new Error("dsh desktop: rejected IPC without a sender frame");
54
- const url = new URL(senderFrame.url);
55
- if (url.protocol !== `${scheme}:` || !hostnames.includes(url.hostname)) {
62
+ let origin: string;
63
+ try {
64
+ origin = new URL(senderFrame.url).origin;
65
+ } catch {
66
+ throw new Error("dsh desktop: rejected IPC with an unreadable sender URL");
67
+ }
68
+ if (!allowedOrigins.includes(origin)) {
56
69
  throw new Error("dsh desktop: rejected IPC from an unowned renderer");
57
70
  }
58
71
  }
package/src/shortcuts.ts CHANGED
@@ -33,7 +33,7 @@ export function installDesktopShortcuts(
33
33
  getWindow: () => BrowserWindowType | undefined,
34
34
  userData: string,
35
35
  platform: ShortcutPlatform,
36
- scheme: string,
36
+ getOrigin: () => string,
37
37
  ): DesktopShortcuts {
38
38
  let definitions: readonly ShortcutDefinition[] = [];
39
39
  let recording = false;
@@ -63,7 +63,7 @@ export function installDesktopShortcuts(
63
63
  const persistence = desktopKeybindings(userData, platform, publish);
64
64
 
65
65
  const windowOf = (event: Electron.IpcMainInvokeEvent): BrowserWindowType => {
66
- assertDesktopSender(event, scheme, ["app"]);
66
+ assertDesktopSender(event, [getOrigin()]);
67
67
  const window = getWindow();
68
68
  if (window === undefined || window.isDestroyed() || event.sender !== window.webContents)
69
69
  throw new Error("desktop shortcuts: rejected sender");