From a2e8a44848d9dbbde23b5a43e9089f7f7892821e Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 23 Jul 2026 20:36:22 +0200 Subject: [PATCH] fix(desktop): recognize managed import targets Desktop ownership is explicit application state. Avoid inferring it from host addresses or launching a second status probe, while retaining validation when an import starts. --- .../app/e2e/conductor-import.electron.spec.ts | 1 + .../src/desktop/hooks/use-daemon-status.ts | 8 +- .../use-enable-built-in-daemon-option.ts | 10 +- packages/app/src/desktop/host.ts | 11 -- .../app/src/desktop/imports/conductor.svg | 119 +++++++++++++++++- packages/app/src/desktop/imports/conductor.ts | 1 + .../src/desktop/imports/external-import.tsx | 40 ++++-- .../app/src/desktop/imports/import-sheet.tsx | 39 ------ packages/app/src/i18n/resources/ar.ts | 5 - packages/app/src/i18n/resources/en.ts | 5 - packages/app/src/i18n/resources/es.ts | 6 - packages/app/src/i18n/resources/fr.ts | 6 - packages/app/src/i18n/resources/ja.ts | 6 - packages/app/src/i18n/resources/pt-BR.ts | 6 - packages/app/src/i18n/resources/ru.ts | 5 - packages/app/src/i18n/resources/zh-CN.ts | 5 - .../desktop/src/integrations/imports/ipc.ts | 3 - .../src/integrations/imports/runner.test.ts | 34 ++--- .../src/integrations/imports/runner.ts | 66 +--------- packages/desktop/src/preload.ts | 12 -- 20 files changed, 179 insertions(+), 209 deletions(-) diff --git a/packages/app/e2e/conductor-import.electron.spec.ts b/packages/app/e2e/conductor-import.electron.spec.ts index 040ce1345f..b91fbfa57d 100644 --- a/packages/app/e2e/conductor-import.electron.spec.ts +++ b/packages/app/e2e/conductor-import.electron.spec.ts @@ -41,6 +41,7 @@ test("imports from the product Integrations row and renders success, failure, an electronApp = await launchProduct(); const page = await electronApp.firstWindow(); await openIntegrations(page); + await expect(page.getByRole("button", { name: "Enable built-in daemon" })).toHaveCount(0); await openImport(page); await expect( diff --git a/packages/app/src/desktop/hooks/use-daemon-status.ts b/packages/app/src/desktop/hooks/use-daemon-status.ts index 7ec16b43d8..9a8aa746bf 100644 --- a/packages/app/src/desktop/hooks/use-daemon-status.ts +++ b/packages/app/src/desktop/hooks/use-daemon-status.ts @@ -17,7 +17,11 @@ interface DaemonStatusData { logs: DesktopDaemonLogs; } -export function useDaemonStatus() { +interface UseDaemonStatusOptions { + refetchOnMount?: boolean | "always"; +} + +export function useDaemonStatus(options: UseDaemonStatusOptions = {}) { const { t } = useTranslation(); const queryClient = useQueryClient(); const enabled = shouldUseDesktopDaemon(); @@ -26,7 +30,7 @@ export function useDaemonStatus() { queryKey: DAEMON_STATUS_QUERY_KEY, enabled, staleTime: 30_000, - refetchOnMount: "always", + refetchOnMount: options.refetchOnMount ?? "always", retry: false, queryFn: async () => { const [status, logs] = await Promise.all([getDesktopDaemonStatus(), getDesktopDaemonLogs()]); diff --git a/packages/app/src/desktop/hooks/use-enable-built-in-daemon-option.ts b/packages/app/src/desktop/hooks/use-enable-built-in-daemon-option.ts index 4d2e2f103c..080e11b04a 100644 --- a/packages/app/src/desktop/hooks/use-enable-built-in-daemon-option.ts +++ b/packages/app/src/desktop/hooks/use-enable-built-in-daemon-option.ts @@ -1,8 +1,6 @@ import { useCallback } from "react"; import { router } from "expo-router"; import { getIsElectron } from "@/constants/platform"; -import { useLocalDaemonServerIdState } from "@/hooks/use-is-local-daemon"; -import { useHosts } from "@/runtime/host-runtime"; import { useDesktopSettings } from "@/desktop/settings/desktop-settings"; import { useDaemonStatus } from "@/desktop/hooks/use-daemon-status"; import { useBuiltInDaemonManagement } from "@/desktop/hooks/use-built-in-daemon-management"; @@ -15,8 +13,6 @@ export interface EnableBuiltInDaemonOption { export function useEnableBuiltInDaemonOption(): EnableBuiltInDaemonOption { const isElectron = getIsElectron(); - const localDaemon = useLocalDaemonServerIdState(); - const hosts = useHosts(); const { settings, updateSettings } = useDesktopSettings(); const { data, setStatus, refetch } = useDaemonStatus(); const { enable } = useBuiltInDaemonManagement({ @@ -27,11 +23,7 @@ export function useEnableBuiltInDaemonOption(): EnableBuiltInDaemonOption { refreshStatus: refetch, }); - const isLocalhostConfigured = - localDaemon.status === "resolved" && - localDaemon.serverId !== null && - hosts.some((host) => host.serverId === localDaemon.serverId); - const visible = isElectron && localDaemon.status === "resolved" && !isLocalhostConfigured; + const visible = isElectron && data?.status.desktopManaged === false; const onPress = useCallback(() => { void (async () => { diff --git a/packages/app/src/desktop/host.ts b/packages/app/src/desktop/host.ts index 2bd358ea14..24fbc1376a 100644 --- a/packages/app/src/desktop/host.ts +++ b/packages/app/src/desktop/host.ts @@ -174,17 +174,6 @@ export type DesktopImportOutput = | { runId: string; type: "status"; succeeded: boolean }; export interface DesktopImportsBridge { - getAvailability?: (input: { source: string }) => Promise<{ - available: boolean; - reason: - | "unsupported-source" - | "host-not-running" - | "nonlocal-host" - | "password-protected" - | "host-version-mismatch" - | "unavailable" - | null; - }>; run?: (input: { source: string }) => Promise<{ runId: string }>; onOutput?: (handler: (output: DesktopImportOutput) => void) => () => void; } diff --git a/packages/app/src/desktop/imports/conductor.svg b/packages/app/src/desktop/imports/conductor.svg index 9d43c44ad2..ea4040d5ab 100644 --- a/packages/app/src/desktop/imports/conductor.svg +++ b/packages/app/src/desktop/imports/conductor.svg @@ -1,5 +1,116 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/app/src/desktop/imports/conductor.ts b/packages/app/src/desktop/imports/conductor.ts index ee6ed87366..e85f779bca 100644 --- a/packages/app/src/desktop/imports/conductor.ts +++ b/packages/app/src/desktop/imports/conductor.ts @@ -3,5 +3,6 @@ import type { ImportSourceDescriptor } from "./import-sheet"; export const conductorImportSource: ImportSourceDescriptor = { id: "conductor", title: "Conductor", + // Official light letter mark from https://www.conductor.build/brandkit. icon: require("./conductor.svg"), }; diff --git a/packages/app/src/desktop/imports/external-import.tsx b/packages/app/src/desktop/imports/external-import.tsx index 0b7044342a..0dd364e291 100644 --- a/packages/app/src/desktop/imports/external-import.tsx +++ b/packages/app/src/desktop/imports/external-import.tsx @@ -1,16 +1,30 @@ +import { useCallback, useState } from "react"; import { Image, Text, View } from "react-native"; import { StyleSheet } from "react-native-unistyles"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { isElectronRuntimeMac } from "@/desktop/host"; +import { useDaemonStatus } from "@/desktop/hooks/use-daemon-status"; import { settingsStyles } from "@/styles/settings"; -import { ImportSheet, type ImportSourceDescriptor, useImportAvailability } from "./import-sheet"; +import { ImportSheet, type ImportSourceDescriptor } from "./import-sheet"; export function ExternalImport({ source }: { source: ImportSourceDescriptor }) { const { t } = useTranslation(); - const importFlow = useImportAvailability(source.id); + const { data } = useDaemonStatus({ refetchOnMount: false }); + const desktopManaged = data?.status.desktopManaged; + const [visible, setVisible] = useState(false); + const open = useCallback(() => setVisible(true), []); + const close = useCallback(() => setVisible(false), []); if (!isElectronRuntimeMac()) return null; + const available = desktopManaged === true; + let reason: string | null = null; + if (desktopManaged === undefined) { + reason = t("desktop.integrations.import.availability.checking"); + } else if (!available) { + reason = t("desktop.integrations.import.availability.host-not-running"); + } + return ( <> - + + + {source.title} - {importFlow.reason ?? t("desktop.integrations.import.description")} + {reason ?? t("desktop.integrations.import.description")} - + ); } const styles = StyleSheet.create((theme) => ({ titleRow: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2] }, - icon: { width: theme.iconSize.md, height: theme.iconSize.md }, + iconFrame: { + width: theme.iconSize.md, + height: theme.iconSize.md, + alignItems: "center", + justifyContent: "center", + borderRadius: theme.borderRadius.sm, + backgroundColor: "#282423", + }, + icon: { width: 12, height: 18 }, })); diff --git a/packages/app/src/desktop/imports/import-sheet.tsx b/packages/app/src/desktop/imports/import-sheet.tsx index 1cdfc4c82d..2226434f64 100644 --- a/packages/app/src/desktop/imports/import-sheet.tsx +++ b/packages/app/src/desktop/imports/import-sheet.tsx @@ -19,45 +19,6 @@ export interface ImportSourceDescriptor { title: string; } -export function useImportAvailability(source: string) { - const { t } = useTranslation(); - const [availability, setAvailability] = useState<{ - available: boolean; - reason: string | null; - }>({ - available: false, - reason: t("desktop.integrations.import.availability.checking"), - }); - const [visible, setVisible] = useState(false); - useEffect(() => { - let active = true; - let retry: ReturnType | null = null; - async function loadAvailability() { - const next = await getDesktopHost()?.imports?.getAvailability?.({ source }); - if (active && next) { - setAvailability({ - available: next.available, - reason: next.reason ? t(`desktop.integrations.import.availability.${next.reason}`) : null, - }); - if (next.reason === "host-not-running") { - retry = setTimeout(() => void loadAvailability(), 2_000); - } - } - } - void loadAvailability(); - return () => { - active = false; - if (retry) clearTimeout(retry); - }; - }, [source, t]); - return { - ...availability, - visible, - open: () => setVisible(true), - close: () => setVisible(false), - }; -} - export function ImportSheet({ source, visible, diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index 3270480e95..8c9743be71 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -1120,12 +1120,7 @@ export const ar: TranslationResources = { import: { availability: { checking: "جارٍ التحقق من توفّر الاستيراد…", - "unsupported-source": "مصدر الاستيراد هذا غير متاح.", "host-not-running": "شغّل المضيف المُدار بواسطة تطبيق سطح المكتب قبل الاستيراد.", - "nonlocal-host": "الاستيراد غير متاح لمضيف غير محلي.", - "password-protected": "الاستيراد غير متاح أثناء حماية المضيف المحلي بكلمة مرور.", - "host-version-mismatch": "حدّث المضيف المُدار بواسطة تطبيق سطح المكتب قبل الاستيراد.", - unavailable: "استيراد تطبيق سطح المكتب غير متاح.", }, actions: { import: "استيراد", importing: "جارٍ الاستيراد…", done: "تم" }, unavailable: "الاستيراد عبر تطبيق سطح المكتب غير متاح.", diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index a2cdf18bdf..ae7a437cbe 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -1131,12 +1131,7 @@ export const en = { import: { availability: { checking: "Checking import availability…", - "unsupported-source": "This import source is unavailable.", "host-not-running": "Start the Desktop-managed host before importing.", - "nonlocal-host": "Import is unavailable for a nonlocal host.", - "password-protected": "Import is unavailable while the local host is password-protected.", - "host-version-mismatch": "Update the Desktop-managed host before importing.", - unavailable: "Desktop import is unavailable.", }, actions: { import: "Import", importing: "Importing…", done: "Done" }, unavailable: "Desktop import is unavailable.", diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts index 09ee6030dc..1aad826b6b 100644 --- a/packages/app/src/i18n/resources/es.ts +++ b/packages/app/src/i18n/resources/es.ts @@ -1160,13 +1160,7 @@ export const es: TranslationResources = { import: { availability: { checking: "Comprobando la disponibilidad de la importación…", - "unsupported-source": "Esta fuente de importación no está disponible.", "host-not-running": "Inicia el host gestionado por Desktop antes de importar.", - "nonlocal-host": "La importación no está disponible para un host no local.", - "password-protected": - "La importación no está disponible mientras el host local esté protegido con contraseña.", - "host-version-mismatch": "Actualiza el host gestionado por Desktop antes de importar.", - unavailable: "La importación de Desktop no está disponible.", }, actions: { import: "Importar", importing: "Importando…", done: "Listo" }, unavailable: "La importación de escritorio no está disponible.", diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts index ba1b71a4ef..5a94934e0d 100644 --- a/packages/app/src/i18n/resources/fr.ts +++ b/packages/app/src/i18n/resources/fr.ts @@ -1162,13 +1162,7 @@ export const fr: TranslationResources = { import: { availability: { checking: "Vérification de la disponibilité de l’importation…", - "unsupported-source": "Cette source d’importation est indisponible.", "host-not-running": "Démarrez l’hôte géré par Desktop avant l’importation.", - "nonlocal-host": "L’importation est indisponible pour un hôte distant.", - "password-protected": - "L’importation est indisponible tant que l’hôte local est protégé par mot de passe.", - "host-version-mismatch": "Mettez à jour l’hôte géré par Desktop avant l’importation.", - unavailable: "L’importation Desktop est indisponible.", }, actions: { import: "Importer", importing: "Importation…", done: "Terminé" }, unavailable: "L’importation depuis l’application de bureau est indisponible.", diff --git a/packages/app/src/i18n/resources/ja.ts b/packages/app/src/i18n/resources/ja.ts index c73ffa7dff..98eca381c2 100644 --- a/packages/app/src/i18n/resources/ja.ts +++ b/packages/app/src/i18n/resources/ja.ts @@ -1135,13 +1135,7 @@ export const ja: TranslationResources = { import: { availability: { checking: "インポートが利用可能か確認しています…", - "unsupported-source": "このインポート元は利用できません。", "host-not-running": "インポートする前にDesktop管理ホストを起動してください。", - "nonlocal-host": "ローカル以外のホストではインポートできません。", - "password-protected": - "ローカルホストがパスワード保護されている間はインポートできません。", - "host-version-mismatch": "インポートする前にDesktop管理ホストを更新してください。", - unavailable: "Desktopインポートは利用できません。", }, actions: { import: "インポート", importing: "インポート中…", done: "完了" }, unavailable: "デスクトップインポートは利用できません。", diff --git a/packages/app/src/i18n/resources/pt-BR.ts b/packages/app/src/i18n/resources/pt-BR.ts index 81374da6b6..0895b59800 100644 --- a/packages/app/src/i18n/resources/pt-BR.ts +++ b/packages/app/src/i18n/resources/pt-BR.ts @@ -1147,13 +1147,7 @@ export const ptBR: TranslationResources = { import: { availability: { checking: "Verificando a disponibilidade da importação…", - "unsupported-source": "Esta fonte de importação não está disponível.", "host-not-running": "Inicie o host gerenciado pelo Desktop antes de importar.", - "nonlocal-host": "A importação não está disponível para um host não local.", - "password-protected": - "A importação não está disponível enquanto o host local estiver protegido por senha.", - "host-version-mismatch": "Atualize o host gerenciado pelo Desktop antes de importar.", - unavailable: "A importação do Desktop não está disponível.", }, actions: { import: "Importar", importing: "Importando…", done: "Concluído" }, unavailable: "A importação pelo aplicativo para desktop não está disponível.", diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts index b8b01e7570..99499e0051 100644 --- a/packages/app/src/i18n/resources/ru.ts +++ b/packages/app/src/i18n/resources/ru.ts @@ -1150,12 +1150,7 @@ export const ru: TranslationResources = { import: { availability: { checking: "Проверка доступности импорта…", - "unsupported-source": "Этот источник импорта недоступен.", "host-not-running": "Запустите управляемый Desktop хост перед импортом.", - "nonlocal-host": "Импорт недоступен для нелокального хоста.", - "password-protected": "Импорт недоступен, пока локальный хост защищён паролем.", - "host-version-mismatch": "Обновите управляемый Desktop хост перед импортом.", - unavailable: "Импорт в Desktop недоступен.", }, actions: { import: "Импортировать", importing: "Импорт…", done: "Готово" }, unavailable: "Импорт в настольном приложении недоступен.", diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts index 96541b974f..458f1969ac 100644 --- a/packages/app/src/i18n/resources/zh-CN.ts +++ b/packages/app/src/i18n/resources/zh-CN.ts @@ -1106,12 +1106,7 @@ export const zhCN: TranslationResources = { import: { availability: { checking: "正在检查导入是否可用…", - "unsupported-source": "此导入来源不可用。", "host-not-running": "请先启动由桌面端管理的主机再导入。", - "nonlocal-host": "非本地主机无法使用导入。", - "password-protected": "本地主机受密码保护时无法导入。", - "host-version-mismatch": "请先更新由桌面端管理的主机再导入。", - unavailable: "桌面端导入不可用。", }, actions: { import: "导入", importing: "正在导入…", done: "完成" }, unavailable: "桌面端导入不可用。", diff --git a/packages/desktop/src/integrations/imports/ipc.ts b/packages/desktop/src/integrations/imports/ipc.ts index d03366ddd9..5ac5d4b667 100644 --- a/packages/desktop/src/integrations/imports/ipc.ts +++ b/packages/desktop/src/integrations/imports/ipc.ts @@ -33,9 +33,6 @@ const imports = new DesktopImportRunner({ }); export function registerImportIpc(): void { - ipcMain.handle("paseo:imports:availability", (_event, input: unknown) => - imports.availability(readSource(input)), - ); ipcMain.handle("paseo:imports:run", async (event, input: unknown) => { const source = readSource(input); return { diff --git a/packages/desktop/src/integrations/imports/runner.test.ts b/packages/desktop/src/integrations/imports/runner.test.ts index 654c41ebee..ce6e78661e 100644 --- a/packages/desktop/src/integrations/imports/runner.test.ts +++ b/packages/desktop/src/integrations/imports/runner.test.ts @@ -79,26 +79,30 @@ test("reports library failures and closes the host", async () => { }); test.each([ - [{ ...eligibleTarget, passwordProtected: true }, "password-protected"], - [{ ...eligibleTarget, listen: "10.0.0.5:6767" }, "nonlocal-host"], - [{ ...eligibleTarget, status: "stopped" as const }, "host-not-running"], - [{ ...eligibleTarget, daemonVersion: "0.1.109" }, "host-version-mismatch"], -] as const)("returns a localizable availability reason", async (target, reason) => { + [ + { ...eligibleTarget, passwordProtected: true }, + "Import is unavailable while the local host is password-protected.", + ], + [ + { ...eligibleTarget, status: "stopped" as const }, + "Import requires the running Paseo Desktop-managed host.", + ], + [ + { ...eligibleTarget, daemonVersion: "0.1.109" }, + "Update the Desktop-managed host before importing.", + ], +] as const)("refuses an ineligible import target", async (target, message) => { const runner = createRunner({ target }); - await expect(runner.availability("source-fixture")).resolves.toEqual({ - available: false, - reason, - }); + await expect(runner.run("source-fixture", () => undefined)).rejects.toThrow(message); }); -test.each(["[::1]:6767", "::1:6767"])("accepts local IPv6 listen address %s", async (listen) => { - const runner = createRunner({ target: { ...eligibleTarget, listen } }); - - await expect(runner.availability("source-fixture")).resolves.toEqual({ - available: true, - reason: null, +test("accepts a Desktop-managed host without inferring ownership from its listen address", async () => { + const runner = createRunner({ + target: { ...eligibleTarget, listen: "0.0.0.0:6767" }, }); + + await expect(runner.run("source-fixture", () => undefined)).resolves.toBe("run-1"); }); test("rejects an unregistered source before target lookup or connection", async () => { diff --git a/packages/desktop/src/integrations/imports/runner.ts b/packages/desktop/src/integrations/imports/runner.ts index 6719b3defe..323c2025e5 100644 --- a/packages/desktop/src/integrations/imports/runner.ts +++ b/packages/desktop/src/integrations/imports/runner.ts @@ -21,14 +21,6 @@ export type DesktopImportOutput = | { runId: string; type: "event"; event: ImportEvent } | { runId: string; type: "status"; succeeded: boolean }; -export type ImportUnavailableReason = - | "unsupported-source" - | "host-not-running" - | "nonlocal-host" - | "password-protected" - | "host-version-mismatch" - | "unavailable"; - type ConnectedImportHost = PaseoImportHost & { close(): Promise }; interface ImportRunnerDependencies { @@ -44,21 +36,6 @@ export class DesktopImportRunner { constructor(private readonly dependencies: ImportRunnerDependencies) {} - async availability( - sourceId: string, - ): Promise<{ available: boolean; reason: ImportUnavailableReason | null }> { - try { - this.requireSource(sourceId); - assertEligibleTarget(await this.dependencies.getTarget()); - return { available: true, reason: null }; - } catch (error) { - return { - available: false, - reason: error instanceof ImportEligibilityError ? error.reason : "unavailable", - }; - } - } - async run(sourceId: string, emit: (output: DesktopImportOutput) => void): Promise { const source = this.requireSource(sourceId); if (this.activeRunId) throw new Error("An import is already running."); @@ -110,10 +87,7 @@ export class DesktopImportRunner { private requireSource(sourceId: string): ImportSourceInput { const source = this.dependencies.sources.get(sourceId); if (!source) { - throw new ImportEligibilityError( - "unsupported-source", - `Unsupported import source: ${sourceId}`, - ); + throw new Error(`Unsupported import source: ${sourceId}`); } return source; } @@ -121,48 +95,16 @@ export class DesktopImportRunner { export function assertEligibleTarget(target: ImportTarget): void { if (target.status !== "running" || !target.desktopManaged) { - throw new ImportEligibilityError( - "host-not-running", - "Import requires the running Paseo Desktop-managed host.", - ); - } - if (!isLocalListen(target.listen)) { - throw new ImportEligibilityError("nonlocal-host", "Import is unavailable for a nonlocal host."); + throw new Error("Import requires the running Paseo Desktop-managed host."); } if (target.passwordProtected) { - throw new ImportEligibilityError( - "password-protected", - "Import is unavailable while the local host is password-protected.", - ); + throw new Error("Import is unavailable while the local host is password-protected."); } if (normalizeVersion(target.daemonVersion) !== normalizeVersion(target.appVersion)) { - throw new ImportEligibilityError( - "host-version-mismatch", - "Update the Desktop-managed host before importing.", - ); + throw new Error("Update the Desktop-managed host before importing."); } } -class ImportEligibilityError extends Error { - constructor( - readonly reason: ImportUnavailableReason, - message: string, - ) { - super(message); - } -} - -function isLocalListen(listen: string | null): boolean { - if (!listen) return false; - if (listen.startsWith("unix://") || listen.startsWith("pipe://") || listen.startsWith("/")) { - return true; - } - const endpoint = listen.replace(/^tcp:\/\//, "").toLowerCase(); - if (endpoint.startsWith("[::1]:") || endpoint.startsWith("::1:")) return true; - const host = endpoint.split(":")[0]; - return host === "127.0.0.1" || host === "localhost" || host === "[::1]"; -} - function normalizeVersion(version: string | null): string | null { return version?.trim().replace(/^v/i, "") || null; } diff --git a/packages/desktop/src/preload.ts b/packages/desktop/src/preload.ts index f8a8037903..0267db3d00 100644 --- a/packages/desktop/src/preload.ts +++ b/packages/desktop/src/preload.ts @@ -43,18 +43,6 @@ contextBridge.exposeInMainWorld("paseoDesktop", { }, }, imports: { - getAvailability: (input: { source: string }) => - ipcRenderer.invoke("paseo:imports:availability", input) as Promise<{ - available: boolean; - reason: - | "unsupported-source" - | "host-not-running" - | "nonlocal-host" - | "password-protected" - | "host-version-mismatch" - | "unavailable" - | null; - }>, run: (input: { source: string }) => ipcRenderer.invoke("paseo:imports:run", input) as Promise<{ runId: string }>, onOutput: (handler: (output: DesktopImportOutput) => void): (() => void) => {