From fdbec04703dc979016927a6f02d14671ab4ca924 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Wed, 26 Nov 2025 11:34:47 +0530 Subject: [PATCH] fix: guard tauri calls with kernel check (#5619) --- .../hoppscotch-common/src/components.d.ts | 11 +--- .../hoppscotch-common/src/kernel/store.ts | 61 ++++++++++++++++--- .../src/pages/view/_id/_version.vue | 19 +++--- .../src/views/PortableHome.vue | 8 +-- .../src/kernel/store.ts | 61 ++++++++++++++++--- 5 files changed, 123 insertions(+), 37 deletions(-) diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 460b9d62..54bff878 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -64,10 +64,8 @@ declare module 'vue' { CollectionsDocumentationSectionsCurlView: typeof import('./components/collections/documentation/sections/CurlView.vue')['default'] CollectionsDocumentationSectionsHeaders: typeof import('./components/collections/documentation/sections/Headers.vue')['default'] CollectionsDocumentationSectionsParameters: typeof import('./components/collections/documentation/sections/Parameters.vue')['default'] - CollectionsDocumentationSectionsPreRequestScript: typeof import('./components/collections/documentation/sections/PreRequestScript.vue')['default'] CollectionsDocumentationSectionsRequestBody: typeof import('./components/collections/documentation/sections/RequestBody.vue')['default'] CollectionsDocumentationSectionsResponse: typeof import('./components/collections/documentation/sections/Response.vue')['default'] - CollectionsDocumentationSectionsTestScript: typeof import('./components/collections/documentation/sections/TestScript.vue')['default'] CollectionsDocumentationSectionsVariables: typeof import('./components/collections/documentation/sections/Variables.vue')['default'] CollectionsEdit: typeof import('./components/collections/Edit.vue')['default'] CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default'] @@ -155,7 +153,6 @@ declare module 'vue' { HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox'] HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip'] - HoppSmartIcon: typeof import('@hoppscotch/ui')['HoppSmartIcon'] HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'] HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection'] HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] @@ -234,12 +231,10 @@ declare module 'vue' { IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default'] + IconLucideBrush: typeof import('~icons/lucide/brush')['default'] IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] - IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default'] IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] IconLucideCircleCheck: typeof import('~icons/lucide/circle-check')['default'] - IconLucideCode2: typeof import('~icons/lucide/code2')['default'] - IconLucideEyeOff: typeof import('~icons/lucide/eye-off')['default'] IconLucideFileQuestion: typeof import('~icons/lucide/file-question')['default'] IconLucideFileText: typeof import('~icons/lucide/file-text')['default'] IconLucideFolder: typeof import('~icons/lucide/folder')['default'] @@ -249,13 +244,12 @@ declare module 'vue' { IconLucideInbox: typeof import('~icons/lucide/inbox')['default'] IconLucideInfo: typeof import('~icons/lucide/info')['default'] IconLucideLayers: typeof import('~icons/lucide/layers')['default'] - IconLucideLightbulb: typeof import('~icons/lucide/lightbulb')['default'] IconLucideListEnd: typeof import('~icons/lucide/list-end')['default'] - IconLucideLoader: typeof import('~icons/lucide/loader')['default'] IconLucideLoader2: typeof import('~icons/lucide/loader2')['default'] IconLucideLock: typeof import('~icons/lucide/lock')['default'] IconLucideMinus: typeof import('~icons/lucide/minus')['default'] IconLucidePlusCircle: typeof import('~icons/lucide/plus-circle')['default'] + IconLucideRss: typeof import('~icons/lucide/rss')['default'] IconLucideSearch: typeof import('~icons/lucide/search')['default'] IconLucideTerminal: typeof import('~icons/lucide/terminal')['default'] IconLucideTriangleAlert: typeof import('~icons/lucide/triangle-alert')['default'] @@ -290,7 +284,6 @@ declare module 'vue' { LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default'] LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default'] MockServerConfigureMockServerModal: typeof import('./components/mockServer/ConfigureMockServerModal.vue')['default'] - MockServerCreateMockServer: typeof import('./components/mockServer/CreateMockServer.vue')['default'] MockServerCreateNewMockServerModal: typeof import('./components/mockServer/CreateNewMockServerModal.vue')['default'] MockServerEditMockServer: typeof import('./components/mockServer/EditMockServer.vue')['default'] MockServerLogSection: typeof import('./components/mockServer/LogSection.vue')['default'] diff --git a/packages/hoppscotch-common/src/kernel/store.ts b/packages/hoppscotch-common/src/kernel/store.ts index 4c709543..1da7ed77 100644 --- a/packages/hoppscotch-common/src/kernel/store.ts +++ b/packages/hoppscotch-common/src/kernel/store.ts @@ -6,39 +6,84 @@ import type { } from "@hoppscotch/kernel" import * as E from "fp-ts/Either" import { getModule } from "." -import { invoke } from "@tauri-apps/api/core" -import { join } from "@tauri-apps/api/path" +import { getKernelMode } from "@hoppscotch/kernel" const STORE_PATH = `${window.location.host}.hoppscotch.store` +// These are only defined functions if in desktop mode. +// For more context, take a look at how `hoppscotch-kernel/.../store/v1/` works +// and how the `web` mode store kernel ignores the first file directory input. +let invoke: + | ((cmd: string, args?: Record) => Promise) + | undefined +let join: ((...paths: string[]) => Promise) | undefined + +// Single init promise to avoid multiple imports and race conditions +let initPromise: Promise | undefined + +const isInitd = async () => { + if (getKernelMode() !== "desktop") return + + if (!initPromise) { + initPromise = Promise.all([ + import("@tauri-apps/api/core").then((module) => { + invoke = module.invoke + }), + import("@tauri-apps/api/path").then((module) => { + join = module.join + }), + ]).then(() => {}) + } + + await initPromise +} + export const getConfigDir = async (): Promise => { + await isInitd() + if (!invoke) throw new Error("getConfigDir is only available in desktop mode") return await invoke("get_config_dir") } export const getBackupDir = async (): Promise => { + await isInitd() + if (!invoke) throw new Error("getBackupDir is only available in desktop mode") return await invoke("get_backup_dir") } export const getLatestDir = async (): Promise => { + await isInitd() + if (!invoke) throw new Error("getLatestDir is only available in desktop mode") return await invoke("get_latest_dir") } export const getStoreDir = async (): Promise => { + await isInitd() + if (!invoke) throw new Error("getStoreDir is only available in desktop mode") return await invoke("get_store_dir") } export const getInstanceDir = async (): Promise => { + await isInitd() + if (!invoke) + throw new Error("getInstanceDir is only available in desktop mode") return await invoke("get_instance_dir") } const getStorePath = async (): Promise => { - try { - const storeDir = await getStoreDir() - return join(storeDir, STORE_PATH) - } catch (error) { - console.error("Failed to get store directory:", error) - return "hoppscotch-unified.store" + if (getKernelMode() === "desktop") { + await isInitd() + if (join) { + try { + const storeDir = await getStoreDir() + return await join(storeDir, STORE_PATH) + } catch (error) { + console.error("Failed to get store directory:", error) + return STORE_PATH + } + } } + + return STORE_PATH } export const Store = (() => { diff --git a/packages/hoppscotch-common/src/pages/view/_id/_version.vue b/packages/hoppscotch-common/src/pages/view/_id/_version.vue index 4d5bb567..b17f7eae 100644 --- a/packages/hoppscotch-common/src/pages/view/_id/_version.vue +++ b/packages/hoppscotch-common/src/pages/view/_id/_version.vue @@ -44,25 +44,28 @@ import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data" import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties" import { PublishedDocs } from "~/helpers/backend/graphql" import { getKernelMode } from "@hoppscotch/kernel" -import { useService } from "dioc/vue" -import { InstanceSwitcherService } from "~/services/instance-switcher.service" +import { platform } from "~/platform" import { useReadonlyStream } from "~/composables/stream" const route = useRoute() const t = useI18n() const kernelMode = getKernelMode() -const instanceSwitcherService = - kernelMode === "desktop" ? useService(InstanceSwitcherService) : null + +const instancePlatform = platform.instance const currentState = - kernelMode === "desktop" && instanceSwitcherService + kernelMode === "desktop" && + instancePlatform?.instanceSwitchingEnabled && + instancePlatform.getConnectionStateStream ? useReadonlyStream( - instanceSwitcherService.getStateStream(), - instanceSwitcherService.getCurrentState().value + instancePlatform.getConnectionStateStream(), + instancePlatform.getCurrentConnectionState?.() ?? { + status: "disconnected" as const, + } ) : ref({ - status: "disconnected", + status: "disconnected" as const, instance: { displayName: "Hoppscotch" }, }) diff --git a/packages/hoppscotch-desktop/src/views/PortableHome.vue b/packages/hoppscotch-desktop/src/views/PortableHome.vue index e37768c3..fa9910cb 100644 --- a/packages/hoppscotch-desktop/src/views/PortableHome.vue +++ b/packages/hoppscotch-desktop/src/views/PortableHome.vue @@ -54,20 +54,20 @@
diff --git a/packages/hoppscotch-selfhost-web/src/kernel/store.ts b/packages/hoppscotch-selfhost-web/src/kernel/store.ts index a8b8372e..f9d1e4c5 100644 --- a/packages/hoppscotch-selfhost-web/src/kernel/store.ts +++ b/packages/hoppscotch-selfhost-web/src/kernel/store.ts @@ -6,39 +6,84 @@ import type { } from "@hoppscotch/kernel" import * as E from "fp-ts/Either" import { getModule } from "." -import { invoke } from "@tauri-apps/api/core" -import { join } from "@tauri-apps/api/path" +import { getKernelMode } from "@hoppscotch/kernel" const STORE_PATH = "hoppscotch-unified.store" +// These are only defined functions if in desktop mode. +// For more context, take a look at how `hoppscotch-kernel/.../store/v1/` works +// and how the `web` mode store kernel ignores the first file directory input. +let invoke: + | ((cmd: string, args?: Record) => Promise) + | undefined +let join: ((...paths: string[]) => Promise) | undefined + +// Single init promise to avoid multiple imports and race conditions +let initPromise: Promise | undefined + +const isInitd = async () => { + if (getKernelMode() !== "desktop") return + + if (!initPromise) { + initPromise = Promise.all([ + import("@tauri-apps/api/core").then((module) => { + invoke = module.invoke + }), + import("@tauri-apps/api/path").then((module) => { + join = module.join + }), + ]).then(() => {}) + } + + await initPromise +} + export const getConfigDir = async (): Promise => { + await isInitd() + if (!invoke) throw new Error("getConfigDir is only available in desktop mode") return await invoke("get_config_dir") } export const getBackupDir = async (): Promise => { + await isInitd() + if (!invoke) throw new Error("getBackupDir is only available in desktop mode") return await invoke("get_backup_dir") } export const getLatestDir = async (): Promise => { + await isInitd() + if (!invoke) throw new Error("getLatestDir is only available in desktop mode") return await invoke("get_latest_dir") } export const getStoreDir = async (): Promise => { + await isInitd() + if (!invoke) throw new Error("getStoreDir is only available in desktop mode") return await invoke("get_store_dir") } export const getInstanceDir = async (): Promise => { + await isInitd() + if (!invoke) + throw new Error("getInstanceDir is only available in desktop mode") return await invoke("get_instance_dir") } const getStorePath = async (): Promise => { - try { - const instanceDir = await getInstanceDir() - return await join(instanceDir, STORE_PATH) - } catch (error) { - console.error("Failed to get instance directory:", error) - return "hoppscotch-unified.store" + if (getKernelMode() === "desktop") { + await isInitd() + if (join) { + try { + const instanceDir = await getInstanceDir() + return await join(instanceDir, STORE_PATH) + } catch (error) { + console.error("Failed to get instance directory:", error) + return STORE_PATH + } + } } + + return STORE_PATH } export const Store = (() => {