Files
paseo/packages/desktop/src/preload.ts
Mohamed Boudra 43542ac858 Fix CLI routing: single classifier, open-project for cold & hot start
- Add classify.ts as the single source of truth for CLI invocation
  routing (discriminated union, derives known commands from Commander)
- Remove all hardcoded command lists and duplicate path detection logic
- Simplify shell wrappers to dumb pipes (zero classification)
- Fix hot-start: use `open -n -g -a` (VS Code pattern) so second-instance
  event fires when app is already running
- Fix cold-start race: pull-based IPC (getPendingOpenProject) so renderer
  fetches the pending path after React mounts, instead of push event that
  arrived before the listener existed
- Fix asar read corruption: unpack node-entrypoint-runner.js from asar
  (Node.js v24 in Electron 41 can't parse package.json inside asar)
- Strip ELECTRON_RUN_AS_NODE from env when spawning desktop app from CLI
2026-04-04 19:21:12 +07:00

61 lines
2.4 KiB
TypeScript

import { contextBridge, ipcRenderer } from "electron";
type EventHandler = (payload: unknown) => void;
contextBridge.exposeInMainWorld("paseoDesktop", {
platform: process.platform,
invoke: (command: string, args?: Record<string, unknown>) =>
ipcRenderer.invoke("paseo:invoke", command, args),
getPendingOpenProject: () =>
ipcRenderer.invoke("paseo:get-pending-open-project") as Promise<string | null>,
events: {
on: (event: string, handler: EventHandler): Promise<() => void> => {
const listener = (_ipcEvent: Electron.IpcRendererEvent, payload: unknown) => {
handler(payload);
};
ipcRenderer.on(`paseo:event:${event}`, listener);
return Promise.resolve(() => {
ipcRenderer.removeListener(`paseo:event:${event}`, listener);
});
},
},
window: {
getCurrentWindow: () => ({
toggleMaximize: () => ipcRenderer.invoke("paseo:window:toggleMaximize"),
isFullscreen: () => ipcRenderer.invoke("paseo:window:isFullscreen"),
updateWindowControls: (update: {
height?: number;
backgroundColor?: string;
foregroundColor?: string;
}) => ipcRenderer.invoke("paseo:window:updateWindowControls", update),
onResized: (handler: EventHandler): (() => void) => {
const listener = (_ipcEvent: Electron.IpcRendererEvent, payload: unknown) => {
handler(payload);
};
ipcRenderer.on("paseo:window:resized", listener);
return () => {
ipcRenderer.removeListener("paseo:window:resized", listener);
};
},
setBadgeCount: (count?: number) => ipcRenderer.invoke("paseo:window:setBadgeCount", count),
}),
},
dialog: {
ask: (message: string, options?: Record<string, unknown>) =>
ipcRenderer.invoke("paseo:dialog:ask", message, options),
open: (options?: Record<string, unknown>) => ipcRenderer.invoke("paseo:dialog:open", options),
},
notification: {
isSupported: () => ipcRenderer.invoke("paseo:notification:isSupported"),
sendNotification: (payload: { title: string; body?: string; data?: Record<string, unknown> }) =>
ipcRenderer.invoke("paseo:notification:send", payload),
},
opener: {
openUrl: (url: string) => ipcRenderer.invoke("paseo:opener:openUrl", url),
},
menu: {
showContextMenu: (input?: Record<string, unknown>) =>
ipcRenderer.invoke("paseo:menu:showContextMenu", input),
},
});