From f58cfe9008f709c439a7acf1e2c6b7069039515e Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 3 May 2026 15:14:28 +0700 Subject: [PATCH] Open script service URLs in-app or external Adds a Service URLs setting (ask / in-Paseo / external browser) that controls where URLs from running scripts open on desktop. First click prompts with a "don't ask again" checkbox; in-Paseo opens a workspace browser tab. Closing a browser tab now clears its session partition. --- .../app/src/components/ui/dropdown-menu.tsx | 5 ++ packages/app/src/desktop/host.ts | 15 ++++ packages/app/src/hooks/use-settings.ts | 23 +++++++ packages/app/src/screens/settings-screen.tsx | 69 +++++++++++++++++++ .../workspace/workspace-bulk-close.test.ts | 11 ++- .../screens/workspace/workspace-bulk-close.ts | 8 +-- .../screens/workspace/workspace-screen.tsx | 34 +++++++-- .../workspace/workspace-scripts-button.tsx | 19 +++-- packages/app/src/utils/open-service-url.ts | 51 ++++++++++++++ packages/desktop/src/features/dialogs.ts | 26 +++++++ packages/desktop/src/main.ts | 10 ++- packages/desktop/src/preload.ts | 4 ++ 12 files changed, 257 insertions(+), 18 deletions(-) create mode 100644 packages/app/src/utils/open-service-url.ts diff --git a/packages/app/src/components/ui/dropdown-menu.tsx b/packages/app/src/components/ui/dropdown-menu.tsx index 837a89dce..eb2288d08 100644 --- a/packages/app/src/components/ui/dropdown-menu.tsx +++ b/packages/app/src/components/ui/dropdown-menu.tsx @@ -62,6 +62,11 @@ interface DropdownMenuContextValue { const DropdownMenuContext = createContext(null); +export function useDropdownMenuClose(): () => void { + const { setOpen } = useDropdownMenuContext("useDropdownMenuClose"); + return useCallback(() => setOpen(false), [setOpen]); +} + function useDropdownMenuContext(componentName: string): DropdownMenuContextValue { const ctx = useContext(DropdownMenuContext); if (!ctx) { diff --git a/packages/app/src/desktop/host.ts b/packages/app/src/desktop/host.ts index 4ec360a23..154c58c93 100644 --- a/packages/app/src/desktop/host.ts +++ b/packages/app/src/desktop/host.ts @@ -21,8 +21,22 @@ export interface DesktopDialogOpenOptions { }>; } +export interface DesktopDialogAskWithCheckboxOptions extends DesktopDialogAskOptions { + checkboxLabel: string; + checkboxChecked?: boolean; +} + +export interface DesktopDialogAskWithCheckboxResult { + confirmed: boolean; + dontAskAgain: boolean; +} + export interface DesktopDialogBridge { ask?: (message: string, options?: DesktopDialogAskOptions) => Promise; + askWithCheckbox?: ( + message: string, + options: DesktopDialogAskWithCheckboxOptions, + ) => Promise; open?: (options?: DesktopDialogOpenOptions) => Promise; } @@ -76,6 +90,7 @@ export interface DesktopBrowserShortcutEvent { export interface DesktopBrowserBridge { setActivePane?: (browserId: string | null) => Promise; + clearPartition?: (browserId: string) => Promise; } export interface DesktopInvokeBridge { diff --git a/packages/app/src/hooks/use-settings.ts b/packages/app/src/hooks/use-settings.ts index c964004e1..d569567c3 100644 --- a/packages/app/src/hooks/use-settings.ts +++ b/packages/app/src/hooks/use-settings.ts @@ -1,6 +1,7 @@ import { useCallback } from "react"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { queryClient as appQueryClient } from "@/query/query-client"; import { DEFAULT_DESKTOP_SETTINGS, loadDesktopSettings, @@ -16,12 +17,15 @@ const APP_SETTINGS_QUERY_KEY = ["app-settings"]; export type SendBehavior = "interrupt" | "queue"; export type ReleaseChannel = "stable" | "beta"; +export type ServiceUrlBehavior = "ask" | "in-app" | "external"; const VALID_THEMES = new Set([...Object.keys(THEME_TO_UNISTYLES), "auto"]); +const VALID_SERVICE_URL_BEHAVIORS = new Set(["ask", "in-app", "external"]); export interface AppSettings { theme: ThemeName | "auto"; sendBehavior: SendBehavior; + serviceUrlBehavior: ServiceUrlBehavior; } export interface Settings extends AppSettings { @@ -32,6 +36,7 @@ export interface Settings extends AppSettings { export const DEFAULT_CLIENT_SETTINGS: AppSettings = { theme: "auto", sendBehavior: "interrupt", + serviceUrlBehavior: "ask", }; export const DEFAULT_APP_SETTINGS: Settings = { @@ -114,6 +119,9 @@ export function useSettings(): UseSettingsReturn { if (updates.sendBehavior !== undefined) { appUpdates.sendBehavior = updates.sendBehavior; } + if (updates.serviceUrlBehavior !== undefined) { + appUpdates.serviceUrlBehavior = updates.serviceUrlBehavior; + } const promises: Promise[] = []; if (Object.keys(appUpdates).length > 0) { @@ -162,6 +170,15 @@ export function useSettings(): UseSettingsReturn { }; } +export async function persistAppSettings(updates: Partial): Promise { + const current = + appQueryClient.getQueryData(APP_SETTINGS_QUERY_KEY) ?? + (await loadAppSettingsFromStorage()); + const next = { ...current, ...updates }; + appQueryClient.setQueryData(APP_SETTINGS_QUERY_KEY, next); + await AsyncStorage.setItem(APP_SETTINGS_KEY, JSON.stringify(next)); +} + export async function loadAppSettingsFromStorage(): Promise { try { const stored = await AsyncStorage.getItem(APP_SETTINGS_KEY); @@ -223,6 +240,12 @@ function pickAppSettings(stored: Partial): Partial { if (stored.sendBehavior === "interrupt" || stored.sendBehavior === "queue") { result.sendBehavior = stored.sendBehavior; } + if ( + typeof stored.serviceUrlBehavior === "string" && + VALID_SERVICE_URL_BEHAVIORS.has(stored.serviceUrlBehavior as ServiceUrlBehavior) + ) { + result.serviceUrlBehavior = stored.serviceUrlBehavior as ServiceUrlBehavior; + } return result; } diff --git a/packages/app/src/screens/settings-screen.tsx b/packages/app/src/screens/settings-screen.tsx index 7f5980888..f8fef6dfe 100644 --- a/packages/app/src/screens/settings-screen.tsx +++ b/packages/app/src/screens/settings-screen.tsx @@ -39,6 +39,7 @@ import { useSettings, type AppSettings, type SendBehavior, + type ServiceUrlBehavior, type Settings as EffectiveSettings, } from "@/hooks/use-settings"; import { THEME_SWATCHES } from "@/styles/theme"; @@ -190,14 +191,24 @@ const RELEASE_CHANNEL_OPTIONS = [ { value: "beta" as const, label: "Beta" }, ]; +const SERVICE_URL_BEHAVIOR_LABELS: Record = { + ask: "Ask", + "in-app": "In Paseo", + external: "External browser", +}; + +const SERVICE_URL_BEHAVIOR_VALUES: ServiceUrlBehavior[] = ["ask", "in-app", "external"]; + // --------------------------------------------------------------------------- // Section components // --------------------------------------------------------------------------- interface GeneralSectionProps { settings: AppSettings; + isDesktopApp: boolean; handleThemeChange: (theme: AppSettings["theme"]) => void; handleSendBehaviorChange: (behavior: SendBehavior) => void; + handleServiceUrlBehaviorChange: (behavior: ServiceUrlBehavior) => void; } interface ThemeMenuItemProps { @@ -229,10 +240,33 @@ function ThemeMenuItem({ ); } +interface ServiceUrlBehaviorMenuItemProps { + value: ServiceUrlBehavior; + selected: boolean; + onChange: (value: ServiceUrlBehavior) => void; +} + +function ServiceUrlBehaviorMenuItem({ + value, + selected, + onChange, +}: ServiceUrlBehaviorMenuItemProps) { + const handleSelect = useCallback(() => { + onChange(value); + }, [onChange, value]); + return ( + + {SERVICE_URL_BEHAVIOR_LABELS[value]} + + ); +} + function GeneralSection({ settings, + isDesktopApp, handleThemeChange, handleSendBehaviorChange, + handleServiceUrlBehaviorChange, }: GeneralSectionProps) { const { theme } = useUnistyles(); const iconSize = theme.iconSize.md; @@ -290,6 +324,32 @@ function GeneralSection({ options={SEND_BEHAVIOR_OPTIONS} /> + {isDesktopApp ? ( + + + Service URLs + Where to open URLs from running scripts + + + + + {SERVICE_URL_BEHAVIOR_LABELS[settings.serviceUrlBehavior]} + + + + + {SERVICE_URL_BEHAVIOR_VALUES.map((value) => ( + + ))} + + + + ) : null} ); @@ -768,6 +828,13 @@ export default function SettingsScreen({ view }: SettingsScreenProps) { [updateSettings], ); + const handleServiceUrlBehaviorChange = useCallback( + (behavior: ServiceUrlBehavior) => { + void updateSettings({ serviceUrlBehavior: behavior }); + }, + [updateSettings], + ); + const handlePlaybackTest = useCallback(async () => { if (!voiceAudioEngine || isPlaybackTestRunning) { return; @@ -949,8 +1016,10 @@ export default function SettingsScreen({ view }: SettingsScreenProps) { return ( ); case "shortcuts": diff --git a/packages/app/src/screens/workspace/workspace-bulk-close.test.ts b/packages/app/src/screens/workspace/workspace-bulk-close.test.ts index 7327b253a..efa3b333b 100644 --- a/packages/app/src/screens/workspace/workspace-bulk-close.test.ts +++ b/packages/app/src/screens/workspace/workspace-bulk-close.test.ts @@ -44,7 +44,12 @@ describe("workspace bulk close helpers", () => { expect(groups).toEqual({ agentTabs: [{ tabId: "agent_a1", agentId: "a1" }], terminalTabs: [{ tabId: "terminal_t1", terminalId: "t1" }], - otherTabs: [{ tabId: "file_/repo/README.md" }], + otherTabs: [ + { + tabId: "file_/repo/README.md", + target: { kind: "file", path: "/repo/README.md" }, + }, + ], }); }); @@ -119,7 +124,7 @@ describe("workspace bulk close helpers", () => { { tabId: "agent_a1", target: { kind: "agent", agentId: "a1" } }, { tabId: "terminal_t1", target: { kind: "terminal", terminalId: "t1" } }, { tabId: "terminal_t2", target: { kind: "terminal", terminalId: "t2" } }, - { tabId: "file_/repo/README.md" }, + { tabId: "file_/repo/README.md", target: { kind: "file", path: "/repo/README.md" } }, ]); }); @@ -158,7 +163,7 @@ describe("workspace bulk close helpers", () => { expect(cleanupCalls).toEqual([ { tabId: "agent_a1", target: { kind: "agent", agentId: "a1" } }, { tabId: "terminal_t1", target: { kind: "terminal", terminalId: "t1" } }, - { tabId: "file_/repo/README.md" }, + { tabId: "file_/repo/README.md", target: { kind: "file", path: "/repo/README.md" } }, ]); }); }); diff --git a/packages/app/src/screens/workspace/workspace-bulk-close.ts b/packages/app/src/screens/workspace/workspace-bulk-close.ts index 73fe19049..821057725 100644 --- a/packages/app/src/screens/workspace/workspace-bulk-close.ts +++ b/packages/app/src/screens/workspace/workspace-bulk-close.ts @@ -4,7 +4,7 @@ import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs- export interface BulkClosableTabGroups { agentTabs: Array<{ tabId: string; agentId: string }>; terminalTabs: Array<{ tabId: string; terminalId: string }>; - otherTabs: Array<{ tabId: string }>; + otherTabs: Array<{ tabId: string; target: WorkspaceTabDescriptor["target"] }>; } interface CloseWorkspaceTabWithCleanupInput { @@ -37,7 +37,7 @@ export function classifyBulkClosableTabs(tabs: WorkspaceTabDescriptor[]): BulkCl groups.terminalTabs.push({ tabId: tab.tabId, terminalId: tab.target.terminalId }); continue; } - groups.otherTabs.push({ tabId: tab.tabId }); + groups.otherTabs.push({ tabId: tab.tabId, target: tab.target }); } return groups; @@ -103,9 +103,9 @@ export async function closeBulkWorkspaceTabs(input: CloseBulkWorkspaceTabsInput) }); } - for (const { tabId } of groups.otherTabs) { + for (const { tabId, target } of groups.otherTabs) { void closeTab(tabId, async () => { - closeWorkspaceTabWithCleanup({ tabId }); + closeWorkspaceTabWithCleanup({ tabId, target }); }); } } diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index e30af940a..842af421a 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -97,7 +97,8 @@ import { upsertTerminalListEntry } from "@/utils/terminal-list"; import { confirmDialog } from "@/utils/confirm-dialog"; import { useArchiveAgent } from "@/hooks/use-archive-agent"; import { useStableEvent } from "@/hooks/use-stable-event"; -import { createWorkspaceBrowser } from "@/stores/browser-store"; +import { createWorkspaceBrowser, useBrowserStore } from "@/stores/browser-store"; +import { getDesktopHost } from "@/desktop/host"; import { buildProviderCommand } from "@/utils/provider-command-templates"; import { generateDraftId } from "@/stores/draft-keys"; import { @@ -1785,6 +1786,11 @@ function WorkspaceScreenContent({ unpinWorkspaceAgent(persistenceKey, input.target.agentId); hideWorkspaceAgent(persistenceKey, input.target.agentId); } + if (input.target?.kind === "browser") { + const { browserId } = input.target; + useBrowserStore.getState().removeBrowser(browserId); + void getDesktopHost()?.browser?.clearPartition?.(browserId); + } closeWorkspaceTab(persistenceKey, normalizedTabId); }, [closeWorkspaceTab, hideWorkspaceAgent, persistenceKey, unpinWorkspaceAgent], @@ -2160,6 +2166,17 @@ function WorkspaceScreenContent({ [focusWorkspacePane, openWorkspaceTabFocused, persistenceKey], ); + const handleOpenUrlInBrowserTab = useCallback( + (url: string) => { + if (!persistenceKey || !getIsElectron()) { + return; + } + const { browserId } = createWorkspaceBrowser({ initialUrl: url }); + openWorkspaceTabFocused(persistenceKey, { kind: "browser", browserId }); + }, + [openWorkspaceTabFocused, persistenceKey], + ); + const handleSelectSwitcherTab = useCallback( (key: string) => { navigateToTabId(key); @@ -2271,11 +2288,14 @@ function WorkspaceScreenContent({ ); const handleCloseDraftOrFileTab = useCallback( - function handleCloseDraftOrFileTab(tabId: string) { - setHoveredTabKey((current) => (current === tabId ? null : current)); - setHoveredCloseTabKey((current) => (current === tabId ? null : current)); + function handleCloseDraftOrFileTab(input: { + tabId: string; + target?: WorkspaceTabTarget | null; + }) { + setHoveredTabKey((current) => (current === input.tabId ? null : current)); + setHoveredCloseTabKey((current) => (current === input.tabId ? null : current)); if (persistenceKey) { - closeWorkspaceTabWithCleanup({ tabId }); + closeWorkspaceTabWithCleanup({ tabId: input.tabId, target: input.target }); } }, [closeWorkspaceTabWithCleanup, persistenceKey], @@ -2295,7 +2315,7 @@ function WorkspaceScreenContent({ await handleCloseAgentTab({ tabId, agentId: tab.target.agentId }); return; } - handleCloseDraftOrFileTab(tabId); + handleCloseDraftOrFileTab({ tabId, target: tab.target }); }, [allTabDescriptorsById, handleCloseAgentTab, handleCloseDraftOrFileTab, handleCloseTerminalTab], ); @@ -2925,6 +2945,7 @@ function WorkspaceScreenContent({ liveTerminalIds={liveTerminalIds} onScriptTerminalStarted={handleScriptTerminalStarted} onViewTerminal={handleViewScriptTerminal} + onOpenUrlInBrowserTab={handleOpenUrlInBrowserTab} hideLabels={showCompactButtonLabels} /> ) : null} @@ -3041,6 +3062,7 @@ function WorkspaceScreenContent({ liveTerminalIds, handleScriptTerminalStarted, handleViewScriptTerminal, + handleOpenUrlInBrowserTab, showCompactButtonLabels, isGitCheckout, handleToggleExplorer, diff --git a/packages/app/src/screens/workspace/workspace-scripts-button.tsx b/packages/app/src/screens/workspace/workspace-scripts-button.tsx index 77aff3b8d..c5dd381d6 100644 --- a/packages/app/src/screens/workspace/workspace-scripts-button.tsx +++ b/packages/app/src/screens/workspace/workspace-scripts-button.tsx @@ -12,10 +12,11 @@ import { DropdownMenuContent, DropdownMenuSeparator, DropdownMenuTrigger, + useDropdownMenuClose, } from "@/components/ui/dropdown-menu"; import { useToast } from "@/contexts/toast-context"; import { isNative } from "@/constants/platform"; -import { openExternalUrl } from "@/utils/open-external-url"; +import { openServiceUrl } from "@/utils/open-service-url"; import { resolveWorkspaceScriptLink } from "@/utils/workspace-script-links"; import type { Theme } from "@/styles/theme"; @@ -28,6 +29,7 @@ interface WorkspaceScriptsButtonProps { liveTerminalIds?: readonly string[]; onScriptTerminalStarted?: (terminalId: string) => void; onViewTerminal?: (terminalId: string) => void; + onOpenUrlInBrowserTab?: (url: string) => void; hideLabels?: boolean; } @@ -136,6 +138,7 @@ interface HostLinkProps { label: string; url: string | null; scriptName: string; + onOpenInBrowserTab?: (url: string) => void; } interface HostLinkChildrenProps { @@ -161,15 +164,18 @@ function HostLinkChildren({ hovered, disabled, label }: HostLinkChildrenProps): ); } -function HostLinkRow({ label, url, scriptName }: HostLinkProps): ReactElement { +function HostLinkRow({ label, url, scriptName, onOpenInBrowserTab }: HostLinkProps): ReactElement { const disabled = !url; + const closeMenu = useDropdownMenuClose(); const handlePress = useCallback( (event: GestureResponderEvent) => { event.stopPropagation(); - if (url) void openExternalUrl(url); + if (!url) return; + closeMenu(); + void openServiceUrl(url, { openInApp: onOpenInBrowserTab }); }, - [url], + [url, onOpenInBrowserTab, closeMenu], ); const renderChildren = useCallback( @@ -219,6 +225,7 @@ interface ScriptRowProps { isStartPending: boolean; onStartScript: (scriptName: string) => void; onViewTerminal?: (terminalId: string) => void; + onOpenUrlInBrowserTab?: (url: string) => void; } function resolveScriptIconColorMapping(args: { @@ -244,6 +251,7 @@ function ScriptRow({ isStartPending, onStartScript, onViewTerminal, + onOpenUrlInBrowserTab, }: ScriptRowProps): ReactElement { const isRunning = script.lifecycle === "running"; const isService = (script.type ?? "service") === "service"; @@ -340,6 +348,7 @@ function ScriptRow({ label={link.label} url={link.url} scriptName={script.scriptName} + onOpenInBrowserTab={onOpenUrlInBrowserTab} /> ))} @@ -355,6 +364,7 @@ export function WorkspaceScriptsButton({ liveTerminalIds = [], onScriptTerminalStarted, onViewTerminal, + onOpenUrlInBrowserTab, hideLabels, }: WorkspaceScriptsButtonProps): ReactElement | null { const toast = useToast(); @@ -438,6 +448,7 @@ export function WorkspaceScriptsButton({ isStartPending={startScriptMutation.isPending} onStartScript={handleStartScript} onViewTerminal={onViewTerminal} + onOpenUrlInBrowserTab={onOpenUrlInBrowserTab} /> ))} diff --git a/packages/app/src/utils/open-service-url.ts b/packages/app/src/utils/open-service-url.ts new file mode 100644 index 000000000..66dfef58d --- /dev/null +++ b/packages/app/src/utils/open-service-url.ts @@ -0,0 +1,51 @@ +import { getDesktopHost, isElectronRuntime } from "@/desktop/host"; +import { + loadAppSettingsFromStorage, + persistAppSettings, + type ServiceUrlBehavior, +} from "@/hooks/use-settings"; +import { openExternalUrl } from "@/utils/open-external-url"; + +export interface OpenServiceUrlOptions { + openInApp?: (url: string) => void; +} + +export async function openServiceUrl(url: string, options?: OpenServiceUrlOptions): Promise { + const openInApp = options?.openInApp; + if (!openInApp || !isElectronRuntime()) { + await openExternalUrl(url); + return; + } + + const behavior = await resolveBehavior(url); + if (behavior === "in-app") { + openInApp(url); + return; + } + await openExternalUrl(url); +} + +async function resolveBehavior(url: string): Promise> { + const settings = await loadAppSettingsFromStorage(); + if (settings.serviceUrlBehavior === "in-app" || settings.serviceUrlBehavior === "external") { + return settings.serviceUrlBehavior; + } + + const askWithCheckbox = getDesktopHost()?.dialog?.askWithCheckbox; + if (typeof askWithCheckbox !== "function") { + return "external"; + } + + const result = await askWithCheckbox(`Open ${url}?`, { + title: "Open service URL", + okLabel: "In Paseo", + cancelLabel: "External browser", + checkboxLabel: "Don't ask again", + }); + + const choice: Exclude = result.confirmed ? "in-app" : "external"; + if (result.dontAskAgain) { + await persistAppSettings({ serviceUrlBehavior: choice }); + } + return choice; +} diff --git a/packages/desktop/src/features/dialogs.ts b/packages/desktop/src/features/dialogs.ts index d5920878a..7f6fd934f 100644 --- a/packages/desktop/src/features/dialogs.ts +++ b/packages/desktop/src/features/dialogs.ts @@ -7,6 +7,11 @@ interface AskOptions { kind?: "info" | "warning" | "error"; } +interface AskWithCheckboxOptions extends AskOptions { + checkboxLabel: string; + checkboxChecked?: boolean; +} + interface OpenOptions { title?: string; defaultPath?: string; @@ -35,6 +40,27 @@ export function registerDialogHandlers(): void { return result.response === 1; }); + ipcMain.handle( + "paseo:dialog:askWithCheckbox", + async (event, message: string, options: AskWithCheckboxOptions) => { + const win = BrowserWindow.fromWebContents(event.sender); + const result = await dialog.showMessageBox(win ?? BrowserWindow.getFocusedWindow()!, { + type: resolveDialogType(options.kind), + title: options.title ?? "Confirm", + message, + buttons: [options.cancelLabel ?? "Cancel", options.okLabel ?? "OK"], + defaultId: 1, + cancelId: 0, + checkboxLabel: options.checkboxLabel, + checkboxChecked: options.checkboxChecked ?? false, + }); + return { + confirmed: result.response === 1, + dontAskAgain: result.checkboxChecked, + }; + }, + ); + ipcMain.handle("paseo:dialog:open", async (event, options?: OpenOptions) => { const win = BrowserWindow.fromWebContents(event.sender); const properties: Electron.OpenDialogOptions["properties"] = []; diff --git a/packages/desktop/src/main.ts b/packages/desktop/src/main.ts index 5ddef207a..2e8c793d9 100644 --- a/packages/desktop/src/main.ts +++ b/packages/desktop/src/main.ts @@ -9,7 +9,7 @@ import path from "node:path"; import { pathToFileURL } from "node:url"; import { existsSync } from "node:fs"; import { execFileSync } from "node:child_process"; -import { app, BrowserWindow, ipcMain, nativeImage, net, protocol } from "electron"; +import { app, BrowserWindow, ipcMain, nativeImage, net, protocol, session } from "electron"; import { createDaemonCommandHandlers, registerDaemonManager } from "./daemon/daemon-manager.js"; import { parseCliPassthroughArgsFromArgv, @@ -219,6 +219,14 @@ ipcMain.handle("paseo:browser:set-active-pane", (_event, browserId: unknown) => setActivePaseoBrowserPaneId(typeof browserId === "string" ? browserId : null); }); +ipcMain.handle("paseo:browser:clear-partition", async (_event, browserId: unknown) => { + if (typeof browserId !== "string" || browserId.trim().length === 0) { + return; + } + const partition = `persist:paseo-browser-${browserId}`; + await session.fromPartition(partition).clearStorageData(); +}); + protocol.registerSchemesAsPrivileged([ { scheme: APP_SCHEME, privileges: { standard: true, secure: true, supportFetchAPI: true } }, ]); diff --git a/packages/desktop/src/preload.ts b/packages/desktop/src/preload.ts index 3159e7641..e0cea10e4 100644 --- a/packages/desktop/src/preload.ts +++ b/packages/desktop/src/preload.ts @@ -43,6 +43,8 @@ contextBridge.exposeInMainWorld("paseoDesktop", { dialog: { ask: (message: string, options?: Record) => ipcRenderer.invoke("paseo:dialog:ask", message, options), + askWithCheckbox: (message: string, options: Record) => + ipcRenderer.invoke("paseo:dialog:askWithCheckbox", message, options), open: (options?: Record) => ipcRenderer.invoke("paseo:dialog:open", options), }, notification: { @@ -60,5 +62,7 @@ contextBridge.exposeInMainWorld("paseoDesktop", { browser: { setActivePane: (browserId: string | null) => ipcRenderer.invoke("paseo:browser:set-active-pane", browserId), + clearPartition: (browserId: string) => + ipcRenderer.invoke("paseo:browser:clear-partition", browserId), }, });