summaryrefslogtreecommitdiff
path: root/packages/web-util/src/hooks/useLocalStorage.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web-util/src/hooks/useLocalStorage.ts')
-rw-r--r--packages/web-util/src/hooks/useLocalStorage.ts139
1 files changed, 139 insertions, 0 deletions
diff --git a/packages/web-util/src/hooks/useLocalStorage.ts b/packages/web-util/src/hooks/useLocalStorage.ts
new file mode 100644
index 000000000..abd80bacc
--- /dev/null
+++ b/packages/web-util/src/hooks/useLocalStorage.ts
@@ -0,0 +1,139 @@
+/*
+ This file is part of GNU Anastasis
+ (C) 2021-2022 Anastasis SARL
+
+ GNU Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { AbsoluteTime, Codec, codecForString } from "@gnu-taler/taler-util";
+import { useEffect, useState } from "preact/hooks";
+import {
+ ObservableMap,
+ browserStorageMap,
+ localStorageMap,
+ memoryMap,
+} from "../utils/observable.js";
+
+declare const opaque_StorageKey: unique symbol;
+
+export type StorageKey<Key> = {
+ id: string;
+ [opaque_StorageKey]: true;
+ codec: Codec<Key>;
+};
+
+export function buildStorageKey<Key>(
+ name: string,
+ codec: Codec<Key>,
+): StorageKey<Key>;
+export function buildStorageKey(name: string): StorageKey<string>;
+export function buildStorageKey<Key = string>(
+ name: string,
+ codec?: Codec<Key>,
+): StorageKey<Key> {
+ return {
+ id: name,
+ codec: codec ?? (codecForString() as Codec<Key>),
+ } as StorageKey<Key>;
+}
+
+export interface StorageState<Type = string> {
+ value?: Type;
+ update: (s: Type) => void;
+ reset: () => void;
+}
+
+const supportLocalStorage = typeof window !== "undefined";
+const supportBrowserStorage =
+ typeof chrome !== "undefined" && typeof chrome.storage !== "undefined";
+
+/**
+ * Build setting storage
+ */
+const storage: ObservableMap<string, string> = (function buildStorage() {
+ if (supportBrowserStorage) {
+ //browser storage is like local storage but
+ //with app sync.
+ //Works for almost every browser
+ if (supportLocalStorage) {
+ return browserStorageMap(localStorageMap());
+ } else {
+ // service worker doesn't have local storage
+ return browserStorageMap(memoryMap<string>());
+ }
+ } else if (supportLocalStorage) {
+ // fallback if browser is too old
+ return localStorageMap();
+ } else {
+ // new need to save settings somewhere
+ return memoryMap<string>();
+ }
+})();
+//with initial value
+export function useLocalStorage<Type = string>(
+ key: StorageKey<Type>,
+ defaultValue: Type,
+): Required<StorageState<Type>>;
+//without initial value
+export function useLocalStorage<Type = string>(
+ key: StorageKey<Type>,
+): StorageState<Type>;
+// impl
+export function useLocalStorage<Type = string>(
+ key: StorageKey<Type>,
+ defaultValue?: Type,
+): StorageState<Type> {
+ const current = convert(storage.get(key.id), key, defaultValue);
+
+ const [_, setStoredValue] = useState(AbsoluteTime.now().t_ms);
+
+ useEffect(() => {
+ return storage.onUpdate(key.id, () => {
+ // const newValue = storage.get(key.id);
+ setStoredValue(AbsoluteTime.now().t_ms);
+ });
+ }, [key.id]);
+
+ const setValue = (value?: Type): void => {
+ if (value === undefined) {
+ storage.delete(key.id);
+ } else {
+ storage.set(
+ key.id,
+ key.codec ? JSON.stringify(value) : (value as string),
+ );
+ }
+ };
+
+ return {
+ value: current,
+ update: setValue,
+ reset: () => {
+ setValue(defaultValue);
+ },
+ };
+}
+
+function convert<Type>(updated: string | undefined, key: StorageKey<Type>, defaultValue?: Type): Type | undefined {
+ if (updated === undefined) return defaultValue; //optional
+ try {
+ return key.codec.decode(JSON.parse(updated));
+ } catch (e) {
+ //decode error
+ return defaultValue;
+ }
+}