diff --git a/client/src/components/Panels/utilities.ts b/client/src/components/Panels/utilities.ts index 27dde0d1cf86..38f7f4d335fe 100644 --- a/client/src/components/Panels/utilities.ts +++ b/client/src/components/Panels/utilities.ts @@ -4,12 +4,7 @@ */ import { orderBy } from "lodash"; -import { - type FilterSettings as ToolFilters, - type Tool, - type ToolSection, - type ToolSectionLabel, -} from "@/stores/toolStore"; +import type { FilterSettings as ToolFilters, Tool, ToolSection, ToolSectionLabel } from "@/stores/toolStore"; import levenshteinDistance from "@/utils/levenshtein"; const FILTER_KEYS = { diff --git a/client/src/composables/hashedUserId.ts b/client/src/composables/hashedUserId.ts index 5d86b2dd8c42..a7f651a3c628 100644 --- a/client/src/composables/hashedUserId.ts +++ b/client/src/composables/hashedUserId.ts @@ -1,10 +1,11 @@ -import { useLocalStorage } from "@vueuse/core"; import { storeToRefs } from "pinia"; import { computed, type Ref, ref, watch } from "vue"; import { type AnyUser } from "@/api"; import { useUserStore } from "@/stores/userStore"; +import { usePersistentRef } from "./persistentRef"; + async function hash32(value: string): Promise { const valueUtf8 = new TextEncoder().encode(value); const hashBuffer = await crypto.subtle.digest("SHA-256", valueUtf8); @@ -43,7 +44,7 @@ export function useHashedUserId(user?: Ref) { } // salt the local store, to make a user untraceable by id across different clients - const localStorageSalt = useLocalStorage("local-storage-salt", createSalt()); + const localStorageSalt = usePersistentRef("local-storage-salt", createSalt()); watch( () => currentUser.value, diff --git a/client/src/composables/persistentRef.ts b/client/src/composables/persistentRef.ts new file mode 100644 index 000000000000..98c9e3542029 --- /dev/null +++ b/client/src/composables/persistentRef.ts @@ -0,0 +1,59 @@ +/* + vueuse local storage has some strange, buggy side-effects, + so were re-implementing just the parts we need here +*/ + +import { type Ref, ref, watch } from "vue"; + +import { match } from "@/utils/utils"; + +function stringify(value: unknown): string { + if (typeof value !== "object") { + return `${value}`; + } else { + return JSON.stringify(value); + } +} + +function parse(value: string, type: "string" | "number" | "boolean" | "object"): T { + return match(type, { + string: () => value as T, + number: () => parseFloat(value) as T, + boolean: () => (value.toLowerCase().trim() === "true" ? true : false) as T, + object: () => JSON.parse(value), + }); +} + +export function usePersistentRef(key: string, defaultValue: string): Ref; +export function usePersistentRef(key: string, defaultValue: number): Ref; +export function usePersistentRef(key: string, defaultValue: T): Ref; +export function usePersistentRef( + key: string, + defaultValue: T +): Ref { + const storageSyncedRef = ref(defaultValue) as Ref; + + const stored = window?.localStorage?.getItem(key); + + if (stored) { + try { + storageSyncedRef.value = parse(stored, typeof defaultValue as "string" | "number" | "boolean" | "object"); + } catch (e) { + console.error(`Failed to parse value "${stored}" from local storage key "${key}". Resetting key`); + window?.localStorage?.removeItem(key); + } + } else { + const stringified = stringify(storageSyncedRef.value); + window?.localStorage?.setItem(key, stringified); + } + + watch( + () => storageSyncedRef.value, + () => { + const stringified = stringify(storageSyncedRef.value); + window?.localStorage?.setItem(key, stringified); + } + ); + + return storageSyncedRef; +} diff --git a/client/src/composables/userLocalStorage.ts b/client/src/composables/userLocalStorage.ts index f0787732a12d..f97388cd2d59 100644 --- a/client/src/composables/userLocalStorage.ts +++ b/client/src/composables/userLocalStorage.ts @@ -1,9 +1,9 @@ -import { useLocalStorage } from "@vueuse/core"; import { computed, customRef, type Ref, ref } from "vue"; import { type AnyUser } from "@/api"; import { useHashedUserId } from "./hashedUserId"; +import { usePersistentRef } from "./persistentRef"; /** * Local storage composable specific to current user. @@ -15,7 +15,7 @@ export function useUserLocalStorage(key: string, initialValue: T, user?: Ref< const storedRef = computed(() => { if (hashedUserId.value) { - return useLocalStorage(`${key}-${hashedUserId.value}`, initialValue); + return usePersistentRef(`${key}-${hashedUserId.value}`, initialValue); } else { return ref(initialValue); } diff --git a/client/src/onload/console.js b/client/src/onload/console.js index 3cc2afb908d9..de552ac937c0 100644 --- a/client/src/onload/console.js +++ b/client/src/onload/console.js @@ -4,9 +4,10 @@ * using the global functions * enableDebugging() and disableDebugging() */ -import { useLocalStorage } from "@vueuse/core"; import { watch } from "vue"; +import { usePersistentRef } from "@/composables/persistentRef"; + export function overrideProductionConsole() { let defaultEnabled = true; @@ -14,7 +15,7 @@ export function overrideProductionConsole() { defaultEnabled = false; } - const isEnabled = useLocalStorage("console-debugging-enabled", defaultEnabled); + const isEnabled = usePersistentRef("console-debugging-enabled", defaultEnabled); let storedConsole = null; diff --git a/client/src/stores/workflowNodeInspectorStore.ts b/client/src/stores/workflowNodeInspectorStore.ts index 6591baf1c8bb..e92683ac853d 100644 --- a/client/src/stores/workflowNodeInspectorStore.ts +++ b/client/src/stores/workflowNodeInspectorStore.ts @@ -1,7 +1,7 @@ -import { useLocalStorage } from "@vueuse/core"; import { defineStore } from "pinia"; import { computed, del, ref, set } from "vue"; +import { usePersistentRef } from "@/composables/persistentRef"; import { ensureDefined } from "@/utils/assertions"; import { getShortToolId } from "@/utils/tool"; import { match } from "@/utils/utils"; @@ -26,10 +26,10 @@ function getContentId(step: Step) { export const useWorkflowNodeInspectorStore = defineStore("workflowNodeInspectorStore", () => { /** width of the node inspector if no other width is stored */ - const generalWidth = useLocalStorage("workflowNodeInspectorGeneralWidth", 300); + const generalWidth = usePersistentRef("workflowNodeInspectorGeneralWidth", 300); /** maximized state of the node inspector if no other value is stored */ const generalMaximized = ref(false); - const storedSizes = useLocalStorage>("workflowNodeInspectorStoredSizes", {}); + const storedSizes = usePersistentRef>("workflowNodeInspectorStoredSizes", {}); const isStored = computed(() => (step: Step) => { const id = getContentId(step);