* fix(desktop): stop sandboxed preload from requiring a local module
The preload imported PASEO_BROWSER_PROFILE_PARTITION from the local module
./features/browser-profile.js. The preload runs in Electron's sandbox and is
tsc-compiled (not bundled), so that import emits require("./features/
browser-profile.js") at the top of preload.js. A sandboxed preload cannot
require a local module, so the require throws before
contextBridge.exposeInMainWorld runs. window.paseoDesktop is then undefined,
the app no longer detects itself as the desktop host, and it never starts the
built-in daemon (users had to run `paseo start` by hand).
The import was added in 0.1.108; 0.1.107 is clean, which matches the reports
that downgrading fixes it. See #2103.
Inline the one value the preload needs (the partition string) so it no longer
loads any local module, and keep browser-profile.ts as the source of truth for
the main process. Add preload-sandbox.test.ts, which parses preload.ts with the
TypeScript compiler API and asserts the only runtime module load is "electron"
(covering value imports, side-effect imports, re-exports, require, dynamic
import, and import-equals; type-only imports are ignored), plus a drift check
that the inlined literal matches the canonical constant.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(desktop): clearer preload drift-check failure message
Assert the inlined PASEO_BROWSER_PROFILE_PARTITION is present before comparing its value, so a missing/renamed constant reports "not found" instead of a misleading value-drift mismatch (Greptile feedback on #2111).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
116 lines
5.2 KiB
TypeScript
116 lines
5.2 KiB
TypeScript
import { contextBridge, ipcRenderer, webUtils } from "electron";
|
|
|
|
// This preload runs in Electron's sandbox and is tsc-compiled (not bundled), so it MUST
|
|
// NOT emit any runtime module load other than "electron" — a require() of a local or
|
|
// third-party module throws and aborts the preload before exposeInMainWorld runs, leaving
|
|
// window.paseoDesktop undefined (the 0.1.108 regression, #2103). Keep this literal in sync
|
|
// with PASEO_BROWSER_PROFILE_PARTITION in features/browser-profile.ts; preload-sandbox.test.ts
|
|
// guards both the no-local-import rule and this drift. Type-only imports are fine (erased at emit).
|
|
const PASEO_BROWSER_PROFILE_PARTITION = "persist:paseo-browser";
|
|
|
|
type EventHandler = (payload: unknown) => void;
|
|
|
|
interface AttachedBrowserRegistration {
|
|
browserId: string;
|
|
workspaceId: string;
|
|
webContentsId: number;
|
|
}
|
|
|
|
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: {
|
|
openNew: (options?: { pendingOpenProjectPath?: string | null }) =>
|
|
ipcRenderer.invoke("paseo:window:openNew", options),
|
|
getCurrentWindow: () => ({
|
|
toggleMaximize: () => ipcRenderer.invoke("paseo:window:toggleMaximize"),
|
|
setFullscreen: (fullscreen: boolean) =>
|
|
ipcRenderer.invoke("paseo:window:setFullscreen", fullscreen),
|
|
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),
|
|
askWithCheckbox: (message: string, options: Record<string, unknown>) =>
|
|
ipcRenderer.invoke("paseo:dialog:askWithCheckbox", 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),
|
|
},
|
|
editor: {
|
|
listTargets: () => ipcRenderer.invoke("paseo:editor:listTargets"),
|
|
openTarget: (input: {
|
|
editorId: string;
|
|
path: string;
|
|
cwd?: string;
|
|
mode?: "open" | "reveal";
|
|
}) => ipcRenderer.invoke("paseo:editor:openTarget", input),
|
|
},
|
|
webUtils: {
|
|
getPathForFile: (file: File) => webUtils.getPathForFile(file),
|
|
},
|
|
menu: {
|
|
showContextMenu: (input?: Record<string, unknown>) =>
|
|
ipcRenderer.invoke("paseo:menu:showContextMenu", input),
|
|
setCapturingShortcut: (capturing: boolean) =>
|
|
ipcRenderer.invoke("paseo:menu:set-capturing-shortcut", capturing),
|
|
},
|
|
browser: {
|
|
profilePartition: PASEO_BROWSER_PROFILE_PARTITION,
|
|
registerAttachedBrowser: (input: AttachedBrowserRegistration) =>
|
|
ipcRenderer.invoke("paseo:browser:register-attached", input),
|
|
unregisterWorkspaceBrowser: (browserId: string) =>
|
|
ipcRenderer.invoke("paseo:browser:unregister-workspace-browser", browserId),
|
|
setWorkspaceActiveBrowser: (input: { workspaceId: string; browserId: string | null }) =>
|
|
ipcRenderer.invoke("paseo:browser:set-workspace-active-browser", input),
|
|
openDevTools: (browserId: string) =>
|
|
ipcRenderer.invoke("paseo:browser:open-devtools", browserId),
|
|
clearProfile: (legacyBrowserIds: string[]) =>
|
|
ipcRenderer.invoke("paseo:browser:clear-profile", legacyBrowserIds),
|
|
executeAutomationCommand: (request: Record<string, unknown>) =>
|
|
ipcRenderer.invoke("paseo:browser:execute-automation-command", request),
|
|
captureElement: (
|
|
browserId: string,
|
|
rect: { x: number; y: number; width: number; height: number },
|
|
) => ipcRenderer.invoke("paseo:browser:capture-element", browserId, rect),
|
|
copyElement: (payload: { text?: string; imageDataUrl?: string }) =>
|
|
ipcRenderer.invoke("paseo:browser:copy-element", payload),
|
|
},
|
|
});
|