fix: guard tauri calls with kernel check (#5619)

This commit is contained in:
Shreyas 2025-11-26 11:34:47 +05:30 committed by GitHub
parent 7deaa136f4
commit fdbec04703
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 123 additions and 37 deletions

View file

@ -64,10 +64,8 @@ declare module 'vue' {
CollectionsDocumentationSectionsCurlView: typeof import('./components/collections/documentation/sections/CurlView.vue')['default'] CollectionsDocumentationSectionsCurlView: typeof import('./components/collections/documentation/sections/CurlView.vue')['default']
CollectionsDocumentationSectionsHeaders: typeof import('./components/collections/documentation/sections/Headers.vue')['default'] CollectionsDocumentationSectionsHeaders: typeof import('./components/collections/documentation/sections/Headers.vue')['default']
CollectionsDocumentationSectionsParameters: typeof import('./components/collections/documentation/sections/Parameters.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'] CollectionsDocumentationSectionsRequestBody: typeof import('./components/collections/documentation/sections/RequestBody.vue')['default']
CollectionsDocumentationSectionsResponse: typeof import('./components/collections/documentation/sections/Response.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'] CollectionsDocumentationSectionsVariables: typeof import('./components/collections/documentation/sections/Variables.vue')['default']
CollectionsEdit: typeof import('./components/collections/Edit.vue')['default'] CollectionsEdit: typeof import('./components/collections/Edit.vue')['default']
CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default'] CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default']
@ -155,7 +153,6 @@ declare module 'vue' {
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox'] HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip'] HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
HoppSmartIcon: typeof import('@hoppscotch/ui')['HoppSmartIcon']
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'] HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection'] HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
@ -234,12 +231,10 @@ declare module 'vue' {
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['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'] IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
IconLucideCircleCheck: typeof import('~icons/lucide/circle-check')['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'] IconLucideFileQuestion: typeof import('~icons/lucide/file-question')['default']
IconLucideFileText: typeof import('~icons/lucide/file-text')['default'] IconLucideFileText: typeof import('~icons/lucide/file-text')['default']
IconLucideFolder: typeof import('~icons/lucide/folder')['default'] IconLucideFolder: typeof import('~icons/lucide/folder')['default']
@ -249,13 +244,12 @@ declare module 'vue' {
IconLucideInbox: typeof import('~icons/lucide/inbox')['default'] IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
IconLucideInfo: typeof import('~icons/lucide/info')['default'] IconLucideInfo: typeof import('~icons/lucide/info')['default']
IconLucideLayers: typeof import('~icons/lucide/layers')['default'] IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideLightbulb: typeof import('~icons/lucide/lightbulb')['default']
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default'] IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideLoader: typeof import('~icons/lucide/loader')['default']
IconLucideLoader2: typeof import('~icons/lucide/loader2')['default'] IconLucideLoader2: typeof import('~icons/lucide/loader2')['default']
IconLucideLock: typeof import('~icons/lucide/lock')['default'] IconLucideLock: typeof import('~icons/lucide/lock')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default'] IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucidePlusCircle: typeof import('~icons/lucide/plus-circle')['default'] IconLucidePlusCircle: typeof import('~icons/lucide/plus-circle')['default']
IconLucideRss: typeof import('~icons/lucide/rss')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default'] IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideTerminal: typeof import('~icons/lucide/terminal')['default'] IconLucideTerminal: typeof import('~icons/lucide/terminal')['default']
IconLucideTriangleAlert: typeof import('~icons/lucide/triangle-alert')['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'] LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default'] LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
MockServerConfigureMockServerModal: typeof import('./components/mockServer/ConfigureMockServerModal.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'] MockServerCreateNewMockServerModal: typeof import('./components/mockServer/CreateNewMockServerModal.vue')['default']
MockServerEditMockServer: typeof import('./components/mockServer/EditMockServer.vue')['default'] MockServerEditMockServer: typeof import('./components/mockServer/EditMockServer.vue')['default']
MockServerLogSection: typeof import('./components/mockServer/LogSection.vue')['default'] MockServerLogSection: typeof import('./components/mockServer/LogSection.vue')['default']

View file

@ -6,39 +6,84 @@ import type {
} from "@hoppscotch/kernel" } from "@hoppscotch/kernel"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import { getModule } from "." import { getModule } from "."
import { invoke } from "@tauri-apps/api/core" import { getKernelMode } from "@hoppscotch/kernel"
import { join } from "@tauri-apps/api/path"
const STORE_PATH = `${window.location.host}.hoppscotch.store` 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:
| (<T>(cmd: string, args?: Record<string, unknown>) => Promise<T>)
| undefined
let join: ((...paths: string[]) => Promise<string>) | undefined
// Single init promise to avoid multiple imports and race conditions
let initPromise: Promise<void> | 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<string> => { export const getConfigDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getConfigDir is only available in desktop mode")
return await invoke<string>("get_config_dir") return await invoke<string>("get_config_dir")
} }
export const getBackupDir = async (): Promise<string> => { export const getBackupDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getBackupDir is only available in desktop mode")
return await invoke<string>("get_backup_dir") return await invoke<string>("get_backup_dir")
} }
export const getLatestDir = async (): Promise<string> => { export const getLatestDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getLatestDir is only available in desktop mode")
return await invoke<string>("get_latest_dir") return await invoke<string>("get_latest_dir")
} }
export const getStoreDir = async (): Promise<string> => { export const getStoreDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getStoreDir is only available in desktop mode")
return await invoke<string>("get_store_dir") return await invoke<string>("get_store_dir")
} }
export const getInstanceDir = async (): Promise<string> => { export const getInstanceDir = async (): Promise<string> => {
await isInitd()
if (!invoke)
throw new Error("getInstanceDir is only available in desktop mode")
return await invoke<string>("get_instance_dir") return await invoke<string>("get_instance_dir")
} }
const getStorePath = async (): Promise<string> => { const getStorePath = async (): Promise<string> => {
try { if (getKernelMode() === "desktop") {
const storeDir = await getStoreDir() await isInitd()
return join(storeDir, STORE_PATH) if (join) {
} catch (error) { try {
console.error("Failed to get store directory:", error) const storeDir = await getStoreDir()
return "hoppscotch-unified.store" 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 = (() => { export const Store = (() => {

View file

@ -44,25 +44,28 @@ import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties" import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { PublishedDocs } from "~/helpers/backend/graphql" import { PublishedDocs } from "~/helpers/backend/graphql"
import { getKernelMode } from "@hoppscotch/kernel" import { getKernelMode } from "@hoppscotch/kernel"
import { useService } from "dioc/vue" import { platform } from "~/platform"
import { InstanceSwitcherService } from "~/services/instance-switcher.service"
import { useReadonlyStream } from "~/composables/stream" import { useReadonlyStream } from "~/composables/stream"
const route = useRoute() const route = useRoute()
const t = useI18n() const t = useI18n()
const kernelMode = getKernelMode() const kernelMode = getKernelMode()
const instanceSwitcherService =
kernelMode === "desktop" ? useService(InstanceSwitcherService) : null const instancePlatform = platform.instance
const currentState = const currentState =
kernelMode === "desktop" && instanceSwitcherService kernelMode === "desktop" &&
instancePlatform?.instanceSwitchingEnabled &&
instancePlatform.getConnectionStateStream
? useReadonlyStream( ? useReadonlyStream(
instanceSwitcherService.getStateStream(), instancePlatform.getConnectionStateStream(),
instanceSwitcherService.getCurrentState().value instancePlatform.getCurrentConnectionState?.() ?? {
status: "disconnected" as const,
}
) )
: ref({ : ref({
status: "disconnected", status: "disconnected" as const,
instance: { displayName: "Hoppscotch" }, instance: { displayName: "Hoppscotch" },
}) })

View file

@ -54,20 +54,20 @@
<div class="flex gap-4 items-center justify-center mt-6"> <div class="flex gap-4 items-center justify-center mt-6">
<label class="flex items-center space-x-2 cursor-pointer"> <label class="flex items-center space-x-2 cursor-pointer">
<input <input
type="checkbox"
v-model="portableSettings.disableUpdateNotifications" v-model="portableSettings.disableUpdateNotifications"
@change="onUpdateNotificationsChange" type="checkbox"
class="form-checkbox h-4 w-4 text-accent" class="form-checkbox h-4 w-4 text-accent"
@change="onUpdateNotificationsChange"
/> />
<span class="text-sm">Don't notify about updates</span> <span class="text-sm">Don't notify about updates</span>
</label> </label>
<label class="flex items-center space-x-2 cursor-pointer"> <label class="flex items-center space-x-2 cursor-pointer">
<input <input
type="checkbox"
v-model="portableSettings.autoSkipWelcome" v-model="portableSettings.autoSkipWelcome"
@change="onAutoSkipChange" type="checkbox"
class="form-checkbox h-4 w-4 text-accent" class="form-checkbox h-4 w-4 text-accent"
@change="onAutoSkipChange"
/> />
<span class="text-sm">Don't show this again</span> <span class="text-sm">Don't show this again</span>
</label> </label>

View file

@ -6,39 +6,84 @@ import type {
} from "@hoppscotch/kernel" } from "@hoppscotch/kernel"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import { getModule } from "." import { getModule } from "."
import { invoke } from "@tauri-apps/api/core" import { getKernelMode } from "@hoppscotch/kernel"
import { join } from "@tauri-apps/api/path"
const STORE_PATH = "hoppscotch-unified.store" 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:
| (<T>(cmd: string, args?: Record<string, unknown>) => Promise<T>)
| undefined
let join: ((...paths: string[]) => Promise<string>) | undefined
// Single init promise to avoid multiple imports and race conditions
let initPromise: Promise<void> | 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<string> => { export const getConfigDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getConfigDir is only available in desktop mode")
return await invoke<string>("get_config_dir") return await invoke<string>("get_config_dir")
} }
export const getBackupDir = async (): Promise<string> => { export const getBackupDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getBackupDir is only available in desktop mode")
return await invoke<string>("get_backup_dir") return await invoke<string>("get_backup_dir")
} }
export const getLatestDir = async (): Promise<string> => { export const getLatestDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getLatestDir is only available in desktop mode")
return await invoke<string>("get_latest_dir") return await invoke<string>("get_latest_dir")
} }
export const getStoreDir = async (): Promise<string> => { export const getStoreDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getStoreDir is only available in desktop mode")
return await invoke<string>("get_store_dir") return await invoke<string>("get_store_dir")
} }
export const getInstanceDir = async (): Promise<string> => { export const getInstanceDir = async (): Promise<string> => {
await isInitd()
if (!invoke)
throw new Error("getInstanceDir is only available in desktop mode")
return await invoke<string>("get_instance_dir") return await invoke<string>("get_instance_dir")
} }
const getStorePath = async (): Promise<string> => { const getStorePath = async (): Promise<string> => {
try { if (getKernelMode() === "desktop") {
const instanceDir = await getInstanceDir() await isInitd()
return await join(instanceDir, STORE_PATH) if (join) {
} catch (error) { try {
console.error("Failed to get instance directory:", error) const instanceDir = await getInstanceDir()
return "hoppscotch-unified.store" 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 = (() => { export const Store = (() => {