diff --git a/packages/taler-wallet-webextension/src/hooks/useSettings.ts b/packages/taler-wallet-webextension/src/hooks/useSettings.ts index ebb1e991c..7e7b26a39 100644 --- a/packages/taler-wallet-webextension/src/hooks/useSettings.ts +++ b/packages/taler-wallet-webextension/src/hooks/useSettings.ts @@ -14,8 +14,13 @@ GNU Taler; see the file COPYING. If not, see */ -import { useLocalStorage } from "@gnu-taler/web-util/browser"; +import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; import { Settings, defaultSettings } from "../platform/api.js"; +import { + Codec, + buildCodecForObject, + codecForBoolean, +} from "@gnu-taler/taler-util"; function parse_json_or_undefined(str: string | undefined): T | undefined { if (str === undefined) return undefined; @@ -26,17 +31,30 @@ function parse_json_or_undefined(str: string | undefined): T | undefined { } } +export const codecForSettings = (): Codec => + buildCodecForObject() + .property("walletAllowHttp", codecForBoolean()) + .property("walletBatchWithdrawal", codecForBoolean()) + .property("injectTalerSupport", codecForBoolean()) + .property("advanceMode", codecForBoolean()) + .property("backup", codecForBoolean()) + .property("langSelector", codecForBoolean()) + .property("showJsonOnError", codecForBoolean()) + .property("extendedAccountTypes", codecForBoolean()) + .property("deleteActiveTransactions", codecForBoolean()) + .build("Settings"); + +const SETTINGS_KEY = buildStorageKey("wallet-settings", codecForSettings()); + export function useSettings(): [ Readonly, (key: T, value: Settings[T]) => void, ] { - const { value, update } = useLocalStorage("wallet-settings"); + const { value, update } = useLocalStorage(SETTINGS_KEY); - const parsed: Settings = parse_json_or_undefined(value) ?? defaultSettings; + const parsed: Settings = value ?? defaultSettings; function updateField(k: T, v: Settings[T]) { - const newValue = { ...parsed, [k]: v }; - const json = JSON.stringify(newValue); - update(json); + update({ ...parsed, [k]: v }); } return [parsed, updateField]; } diff --git a/packages/web-util/src/hooks/index.ts b/packages/web-util/src/hooks/index.ts index ae8872497..a3a2053e6 100644 --- a/packages/web-util/src/hooks/index.ts +++ b/packages/web-util/src/hooks/index.ts @@ -1,5 +1,5 @@ export { useLang } from "./useLang.js"; -export { useLocalStorage } from "./useLocalStorage.js"; +export { useLocalStorage, buildStorageKey } from "./useLocalStorage.js"; export { useMemoryStorage } from "./useMemoryStorage.js"; export { useNotifications, diff --git a/packages/web-util/src/hooks/useLang.ts b/packages/web-util/src/hooks/useLang.ts index d64cf6e1a..448cd8aba 100644 --- a/packages/web-util/src/hooks/useLang.ts +++ b/packages/web-util/src/hooks/useLang.ts @@ -14,7 +14,11 @@ GNU Anastasis; see the file COPYING. If not, see */ -import { LocalStorageState, useLocalStorage } from "./useLocalStorage.js"; +import { + StorageState, + buildStorageKey, + useLocalStorage, +} from "./useLocalStorage.js"; function getBrowserLang(): string | undefined { if (typeof window === "undefined") return undefined; @@ -23,7 +27,9 @@ function getBrowserLang(): string | undefined { return undefined; } -export function useLang(initial?: string): Required { +const langPreferenceKey = buildStorageKey("lang-preference"); + +export function useLang(initial?: string): Required { const defaultValue = (getBrowserLang() || initial || "en").substring(0, 2); - return useLocalStorage("lang-preference", { defaultValue: defaultValue }); + return useLocalStorage(langPreferenceKey, defaultValue); } diff --git a/packages/web-util/src/hooks/useLocalStorage.ts b/packages/web-util/src/hooks/useLocalStorage.ts index 55efd01cb..45b7abd3c 100644 --- a/packages/web-util/src/hooks/useLocalStorage.ts +++ b/packages/web-util/src/hooks/useLocalStorage.ts @@ -19,7 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { Codec } from "@gnu-taler/taler-util"; +import { Codec, codecForString } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; import { ObservableMap, @@ -28,7 +28,26 @@ import { memoryMap, } from "../utils/observable.js"; -export interface LocalStorageState { +declare const opaque_StorageKey: unique symbol; + +export type StorageKey = { + id: string; + [opaque_StorageKey]: true; + codec: Codec; +}; + +export function buildStorageKey( + name: string, + codec?: Codec, +): StorageKey { + return { + id: name, + codec: codec ?? (codecForString() as Codec), + [opaque_StorageKey]: true, + }; +} + +export interface StorageState { value?: Type; update: (s: Type) => void; reset: () => void; @@ -50,59 +69,48 @@ const storage: ObservableMap = (function buildStorage() { //with initial value export function useLocalStorage( - key: string, - options?: { - defaultValue: Type; - codec?: Codec; - }, -): Required>; + key: StorageKey, + defaultValue: Type, +): Required>; //without initial value export function useLocalStorage( - key: string, - options?: { - codec?: Codec; - }, -): LocalStorageState; + key: StorageKey, +): StorageState; // impl export function useLocalStorage( - key: string, - options?: { - defaultValue?: Type; - codec?: Codec; - }, -): LocalStorageState { + key: StorageKey, + defaultValue?: Type, +): StorageState { function convert(updated: string | undefined): Type | undefined { - if (updated === undefined) return options?.defaultValue; //optional + if (updated === undefined) return defaultValue; //optional try { - return !options?.codec - ? (updated as Type) - : options.codec.decode(JSON.parse(updated)); + return key.codec.decode(JSON.parse(updated)); } catch (e) { //decode error - return options?.defaultValue; + return defaultValue; } } const [storedValue, setStoredValue] = useState( (): Type | undefined => { - const prev = storage.get(key); + const prev = storage.get(key.id); return convert(prev); }, ); useEffect(() => { - return storage.onUpdate(key, () => { - const newValue = storage.get(key); + return storage.onUpdate(key.id, () => { + const newValue = storage.get(key.id); setStoredValue(convert(newValue)); }); }, []); const setValue = (value?: Type): void => { if (value === undefined) { - storage.delete(key); + storage.delete(key.id); } else { storage.set( - key, - options?.codec ? JSON.stringify(value) : (value as string), + key.id, + key.codec ? JSON.stringify(value) : (value as string), ); } }; @@ -111,7 +119,7 @@ export function useLocalStorage( value: storedValue, update: setValue, reset: () => { - setValue(options?.defaultValue); + setValue(defaultValue); }, }; } diff --git a/packages/web-util/src/hooks/useMemoryStorage.ts b/packages/web-util/src/hooks/useMemoryStorage.ts index 7160b035e..d8be24c1e 100644 --- a/packages/web-util/src/hooks/useMemoryStorage.ts +++ b/packages/web-util/src/hooks/useMemoryStorage.ts @@ -20,48 +20,44 @@ */ import { useEffect, useState } from "preact/hooks"; -import { - ObservableMap, - browserStorageMap, - localStorageMap, - memoryMap, -} from "../utils/observable.js"; +import { ObservableMap, memoryMap } from "../utils/observable.js"; +import { StorageKey, StorageState } from "./useLocalStorage.js"; -export interface LocalStorageState { - value?: string; - update: (s: string) => void; - reset: () => void; -} +const storage: ObservableMap = memoryMap(); -const storage: ObservableMap = memoryMap(); - -export function useMemoryStorage( - key: string, - initialValue: string, -): Required; -export function useMemoryStorage(key: string): LocalStorageState; -export function useMemoryStorage( - key: string, - initialValue?: string, -): LocalStorageState { - const [storedValue, setStoredValue] = useState( - (): string | undefined => { - return storage.get(key) ?? initialValue; +//with initial value +export function useMemoryStorage( + key: StorageKey, + defaultValue: Type, +): Required>; +//with initial value +export function useMemoryStorage( + key: StorageKey, +): StorageState; +// impl +export function useMemoryStorage( + key: StorageKey, + defaultValue?: Type, +): StorageState { + const [storedValue, setStoredValue] = useState( + (): Type | undefined => { + const prev = storage.get(key.id); + return prev; }, ); useEffect(() => { - return storage.onUpdate(key, () => { - const newValue = storage.get(key); - setStoredValue(newValue ?? initialValue); + return storage.onUpdate(key.id, () => { + const newValue = storage.get(key.id); + setStoredValue(newValue); }); }, []); - const setValue = (value?: string): void => { + const setValue = (value?: Type): void => { if (value === undefined) { - storage.delete(key); + storage.delete(key.id); } else { - storage.set(key, value); + storage.set(key.id, value); } }; @@ -69,7 +65,7 @@ export function useMemoryStorage( value: storedValue, update: setValue, reset: () => { - setValue(initialValue); + setValue(defaultValue); }, }; }