gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 02/02: sync with chrome storage


From: gnunet
Subject: [taler-wallet-core] 02/02: sync with chrome storage
Date: Tue, 18 Apr 2023 15:46:35 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

commit b1a0d034fc1be0824e1eac46661604558264beee
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Apr 18 10:46:27 2023 -0300

    sync with chrome storage
---
 packages/web-util/src/hooks/useLocalStorage.ts |  19 +++-
 packages/web-util/src/utils/observable.ts      | 140 +++++++++++++++++++++----
 pnpm-lock.yaml                                 |   7 +-
 3 files changed, 141 insertions(+), 25 deletions(-)

diff --git a/packages/web-util/src/hooks/useLocalStorage.ts 
b/packages/web-util/src/hooks/useLocalStorage.ts
index dd6c5def8..495c9b0f8 100644
--- a/packages/web-util/src/hooks/useLocalStorage.ts
+++ b/packages/web-util/src/hooks/useLocalStorage.ts
@@ -20,7 +20,12 @@
  */
 
 import { useEffect, useState } from "preact/hooks";
-import { localStorageMap, memoryMap } from "../utils/observable.js";
+import {
+  ObservableMap,
+  browserStorageMap,
+  localStorageMap,
+  memoryMap,
+} from "../utils/observable.js";
 
 export interface LocalStorageState {
   value?: string;
@@ -29,8 +34,18 @@ export interface LocalStorageState {
 }
 
 const supportLocalStorage = typeof window !== "undefined";
+const supportBrowserStorage =
+  typeof chrome !== "undefined" && typeof chrome.storage !== "undefined";
 
-const storage = supportLocalStorage ? localStorageMap() : memoryMap<string>();
+const storage: ObservableMap<string, string> = (function buildStorage() {
+  if (supportBrowserStorage) {
+    return browserStorageMap(memoryMap<string>());
+  } else if (supportLocalStorage) {
+    return localStorageMap();
+  } else {
+    return memoryMap<string>();
+  }
+})();
 
 export function useLocalStorage(
   key: string,
diff --git a/packages/web-util/src/utils/observable.ts 
b/packages/web-util/src/utils/observable.ts
index dfa434635..01e655eaa 100644
--- a/packages/web-util/src/utils/observable.ts
+++ b/packages/web-util/src/utils/observable.ts
@@ -1,17 +1,25 @@
+import { isArrayBufferView } from "util/types";
+
 export type ObservableMap<K, V> = Map<K, V> & {
+  onAnyUpdate: (callback: () => void) => () => void;
   onUpdate: (key: string, callback: () => void) => () => void;
 };
 
-const UPDATE_EVENT_NAME = "update";
-
 //FIXME: allow different type for different properties
-export function memoryMap<T>(): ObservableMap<string, T> {
+export function memoryMap<T>(
+  backend: Map<string, T> = new Map<string, T>(),
+): ObservableMap<string, T> {
   const obs = new EventTarget();
-  const theMap = new Map<string, T>();
   const theMemoryMap: ObservableMap<string, T> = {
+    onAnyUpdate: (handler) => {
+      obs.addEventListener(`update`, handler);
+      obs.addEventListener(`clear`, handler);
+      return () => {
+        obs.removeEventListener(`update`, handler);
+        obs.removeEventListener(`clear`, handler);
+      };
+    },
     onUpdate: (key, handler) => {
-      //@ts-ignore
-      theMemoryMap.size = theMap.length;
       obs.addEventListener(`update-${key}`, handler);
       obs.addEventListener(`clear`, handler);
       return () => {
@@ -20,38 +28,56 @@ export function memoryMap<T>(): ObservableMap<string, T> {
       };
     },
     delete: (key: string) => {
-      const result = theMap.delete(key);
+      const result = backend.delete(key);
+      //@ts-ignore
+      theMemoryMap.size = backend.length;
       obs.dispatchEvent(new Event(`update-${key}`));
+      obs.dispatchEvent(new Event(`update`));
       return result;
     },
     set: (key: string, value: T) => {
-      theMap.set(key, value);
+      backend.set(key, value);
+      //@ts-ignore
+      theMemoryMap.size = backend.length;
       obs.dispatchEvent(new Event(`update-${key}`));
+      obs.dispatchEvent(new Event(`update`));
       return theMemoryMap;
     },
     clear: () => {
-      theMap.clear();
+      backend.clear();
       obs.dispatchEvent(new Event(`clear`));
     },
-    entries: theMap.entries.bind(theMap),
-    forEach: theMap.forEach.bind(theMap),
-    get: theMap.get.bind(theMap),
-    has: theMap.has.bind(theMap),
-    keys: theMap.keys.bind(theMap),
-    size: theMap.size,
-    values: theMap.values.bind(theMap),
-    [Symbol.iterator]: theMap[Symbol.iterator],
+    entries: backend.entries.bind(backend),
+    forEach: backend.forEach.bind(backend),
+    get: backend.get.bind(backend),
+    has: backend.has.bind(backend),
+    keys: backend.keys.bind(backend),
+    size: backend.size,
+    values: backend.values.bind(backend),
+    [Symbol.iterator]: backend[Symbol.iterator],
     [Symbol.toStringTag]: "theMemoryMap",
   };
   return theMemoryMap;
 }
 
+//FIXME: change this implementation to match the
+// browser storage. instead of creating a sync implementation
+// of observable map it should reuse the memoryMap and
+// sync the state with local storage
 export function localStorageMap(): ObservableMap<string, string> {
   const obs = new EventTarget();
   const theLocalStorageMap: ObservableMap<string, string> = {
+    onAnyUpdate: (handler) => {
+      obs.addEventListener(`update`, handler);
+      obs.addEventListener(`clear`, handler);
+      window.addEventListener("storage", handler);
+      return () => {
+        window.removeEventListener("storage", handler);
+        obs.removeEventListener(`update`, handler);
+        obs.removeEventListener(`clear`, handler);
+      };
+    },
     onUpdate: (key, handler) => {
-      //@ts-ignore
-      theLocalStorageMap.size = localStorage.length;
       obs.addEventListener(`update-${key}`, handler);
       obs.addEventListener(`clear`, handler);
       function handleStorageEvent(ev: StorageEvent) {
@@ -69,12 +95,18 @@ export function localStorageMap(): ObservableMap<string, 
string> {
     delete: (key: string) => {
       const exists = localStorage.getItem(key) !== null;
       localStorage.removeItem(key);
+      //@ts-ignore
+      theLocalStorageMap.size = localStorage.length;
       obs.dispatchEvent(new Event(`update-${key}`));
+      obs.dispatchEvent(new Event(`update`));
       return exists;
     },
     set: (key: string, v: string) => {
       localStorage.setItem(key, v);
+      //@ts-ignore
+      theLocalStorageMap.size = localStorage.length;
       obs.dispatchEvent(new Event(`update-${key}`));
+      obs.dispatchEvent(new Event(`update`));
       return theLocalStorageMap;
     },
     clear: () => {
@@ -179,3 +211,73 @@ export function localStorageMap(): ObservableMap<string, 
string> {
   };
   return theLocalStorageMap;
 }
+
+const isFirefox =
+  typeof (window as any) !== "undefined" &&
+  typeof (window as any)["InstallTrigger"] !== "undefined";
+
+async function getAllContent() {
+  //Firefox and Chrome has different storage api
+  if (isFirefox) {
+    // @ts-ignore
+    return browser.storage.local.get();
+  } else {
+    return chrome.storage.local.get();
+  }
+}
+
+async function updateContent(obj: Record<string, any>) {
+  if (isFirefox) {
+    // @ts-ignore
+    return browser.storage.local.set(obj);
+  } else {
+    return chrome.storage.local.set(obj);
+  }
+}
+type Changes = { [key: string]: { oldValue?: any; newValue?: any } };
+function onBrowserStorageUpdate(cb: (changes: Changes) => void): void {
+  if (isFirefox) {
+    // @ts-ignore
+    browser.storage.local.onChanged.addListener(cb);
+  } else {
+    chrome.storage.local.onChanged.addListener(cb);
+  }
+}
+
+export function browserStorageMap(
+  backend: ObservableMap<string, string>,
+): ObservableMap<string, string> {
+  getAllContent().then((content) => {
+    Object.entries(content ?? {}).forEach(([k, v]) => {
+      backend.set(k, v as string);
+    });
+  });
+
+  backend.onAnyUpdate(async () => {
+    const result: Record<string, string> = {};
+    for (const [key, value] of backend.entries()) {
+      result[key] = value;
+    }
+    await updateContent(result);
+  });
+
+  onBrowserStorageUpdate((changes) => {
+    //another chrome instance made the change
+    const changedItems = Object.keys(changes);
+    if (changedItems.length === 0) {
+      backend.clear();
+    } else {
+      for (const key of changedItems) {
+        if (!changes[key].newValue) {
+          backend.delete(key);
+        } else {
+          if (changes[key].newValue !== changes[key].oldValue) {
+            backend.set(key, changes[key].newValue);
+          }
+        }
+      }
+    }
+  });
+
+  return backend;
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 56f678e46..c7b2df4bf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -560,6 +560,7 @@ importers:
   packages/web-util:
     specifiers:
       '@gnu-taler/taler-util': workspace:*
+      '@types/chrome': 0.0.197
       '@types/express': ^4.17.14
       '@types/node': ^18.11.17
       '@types/web': ^0.0.82
@@ -576,6 +577,8 @@ importers:
       tslib: ^2.4.0
       typescript: ^4.9.4
       ws: 7.4.5
+    dependencies:
+      '@types/chrome': 0.0.197
     devDependencies:
       '@gnu-taler/taler-util': link:../taler-util
       '@types/express': 4.17.14
@@ -3775,7 +3778,6 @@ packages:
     dependencies:
       '@types/filesystem': 0.0.32
       '@types/har-format': 1.2.9
-    dev: true
 
   /@types/connect-history-api-fallback/1.3.5:
     resolution: {integrity: 
sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==}
@@ -3815,15 +3817,12 @@ packages:
     resolution: {integrity: 
sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==}
     dependencies:
       '@types/filewriter': 0.0.29
-    dev: true
 
   /@types/filewriter/0.0.29:
     resolution: {integrity: 
sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==}
-    dev: true
 
   /@types/har-format/1.2.9:
     resolution: {integrity: 
sha512-rffW6MhQ9yoa75bdNi+rjZBAvu2HhehWJXlhuWXnWdENeuKe82wUgAwxYOb7KRKKmxYN+D/iRKd2NDQMLqlUmg==}
-    dev: true
 
   /@types/history/4.7.11:
     resolution: {integrity: 
sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]