From f0c1b956ac0c3d59e8df1054c6533b653251cfd4 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 3 May 2025 23:38:33 +0300 Subject: [PATCH] modified localstorage data --- package-lock.json | 11 +++- package.json | 3 +- src/context/GlobalProvider.tsx | 8 +-- src/hooks/useLocalStorage.tsx | 37 -------------- src/hooks/usePersistentStore.tsx | 88 ++++++++++++++++++++++++++++++++ src/utils/persistentDb.ts | 26 ++++++++++ 6 files changed, 129 insertions(+), 44 deletions(-) delete mode 100644 src/hooks/useLocalStorage.tsx create mode 100644 src/hooks/usePersistentStore.tsx create mode 100644 src/utils/persistentDb.ts diff --git a/package-lock.json b/package-lock.json index c0562d5..512575f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "qapp-core", - "version": "1.0.19", + "version": "1.0.22", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "qapp-core", - "version": "1.0.19", + "version": "1.0.22", "license": "MIT", "dependencies": { "@tanstack/react-virtual": "^3.13.2", @@ -15,6 +15,7 @@ "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", "dayjs": "^1.11.13", + "dexie": "^4.0.11", "dompurify": "^3.2.4", "react-dropzone": "^14.3.8", "react-hot-toast": "^2.5.2", @@ -1871,6 +1872,12 @@ } } }, + "node_modules/dexie": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.11.tgz", + "integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==", + "license": "Apache-2.0" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", diff --git a/package.json b/package.json index 0189b5d..9fad691 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qapp-core", - "version": "1.0.22", + "version": "1.0.24", "description": "Qortal's core React library with global state, UI components, and utilities", "main": "dist/index.js", "module": "dist/index.mjs", @@ -29,6 +29,7 @@ "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", "dayjs": "^1.11.13", + "dexie": "^4.0.11", "dompurify": "^3.2.4", "react-dropzone": "^14.3.8", "react-hot-toast": "^2.5.2", diff --git a/src/context/GlobalProvider.tsx b/src/context/GlobalProvider.tsx index 3e486af..491fb7c 100644 --- a/src/context/GlobalProvider.tsx +++ b/src/context/GlobalProvider.tsx @@ -4,7 +4,7 @@ import { useResources } from "../hooks/useResources"; import { useAppInfo } from "../hooks/useAppInfo"; import { useIdentifiers } from "../hooks/useIdentifiers"; import { Toaster } from "react-hot-toast"; -import { useLocalStorage } from "../hooks/useLocalStorage"; +import { usePersistentStore } from "../hooks/usePersistentStore"; import { IndexManager } from "../components/IndexManager/IndexManager"; import { useIndexes } from "../hooks/useIndexes"; @@ -18,7 +18,7 @@ auth: ReturnType; lists: ReturnType; appInfo: ReturnType; identifierOperations: ReturnType -localStorageOperations: ReturnType +persistentOperations: ReturnType indexOperations: ReturnType } @@ -48,10 +48,10 @@ export const GlobalProvider = ({ children, config, toastStyle = {} }: GlobalProv const appInfo = useAppInfo(config.appName, config?.publicSalt) const lists = useResources() const identifierOperations = useIdentifiers(config.publicSalt, config.appName) - const localStorageOperations = useLocalStorage(config.publicSalt, config.appName) + const persistentOperations = usePersistentStore(config.publicSalt, config.appName, auth?.address) const indexOperations = useIndexes() // ✅ Merge all hooks into a single `contextValue` - const contextValue = useMemo(() => ({ auth, lists, appInfo, identifierOperations, localStorageOperations, indexOperations }), [auth, lists, appInfo, identifierOperations, localStorageOperations]); + const contextValue = useMemo(() => ({ auth, lists, appInfo, identifierOperations, persistentOperations, indexOperations }), [auth, lists, appInfo, identifierOperations, persistentOperations]); return ( diff --git a/src/hooks/useLocalStorage.tsx b/src/hooks/useLocalStorage.tsx deleted file mode 100644 index 84f2e3c..0000000 --- a/src/hooks/useLocalStorage.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { useCallback, useEffect, useMemo } from "react"; -import { useAppStore } from "../state/app"; -import { EnumCollisionStrength, hashWord } from "../utils/encryption"; - - -export const useLocalStorage = (publicSalt: string, appName: string) => { - - - const setTimestamp = useCallback(async (timestamp: number, storageId: string)=> { - const hashedString = await hashWord(`${appName}-${storageId}`, EnumCollisionStrength.HIGH, publicSalt) - localStorage.setItem(hashedString, JSON.stringify(timestamp)); - return true - }, [appName, publicSalt]) - - const getTimestamp = useCallback(async ( storageId: string)=> { - const hashedString = await hashWord(`${appName}-${storageId}`, EnumCollisionStrength.HIGH, publicSalt) - const stored = localStorage.getItem(hashedString); - if(stored){ - return JSON.parse(stored) - } else return null - }, [appName, publicSalt]) - - const isNewTimestamp = useCallback(async( storageId: string, differenceTimestamp: number)=> { - const hashedString = await hashWord(`${appName}-${storageId}`, EnumCollisionStrength.HIGH, publicSalt) - const stored = localStorage.getItem(hashedString); - if(stored){ - const storedTimestamp = JSON.parse(stored) - return (Date.now() - storedTimestamp) > differenceTimestamp - } else return true - }, [appName, publicSalt]) - return { - setTimestamp, - getTimestamp, - isNewTimestamp - - }; -}; diff --git a/src/hooks/usePersistentStore.tsx b/src/hooks/usePersistentStore.tsx new file mode 100644 index 0000000..5c4f116 --- /dev/null +++ b/src/hooks/usePersistentStore.tsx @@ -0,0 +1,88 @@ +import { useCallback } from 'react'; +import { EnumCollisionStrength, hashWord } from '../utils/encryption'; +import { db } from '../utils/persistentDb'; + +export const usePersistentStore = ( + publicSalt: string, + appName: string, + qortalAddress?: string | null +) => { + const getHashedId = useCallback( + async (id: string) => { + const key = `${appName}-${qortalAddress ?? 'no-address'}-${id}`; + return await hashWord(key, EnumCollisionStrength.HIGH, publicSalt); + }, + [appName, publicSalt, qortalAddress] + ); + + // --- TIMESTAMP FUNCTIONS --- + + const setTimestamp = useCallback( + async (timestamp: number, storageId: string) => { + const id = await getHashedId(storageId); + await db.timestamps.put({ id, timestamp }); + return true; + }, + [getHashedId] + ); + + const getTimestamp = useCallback( + async (storageId: string) => { + const id = await getHashedId(storageId); + const entry = await db.timestamps.get(id); + return entry?.timestamp ?? null; + }, + [getHashedId] + ); + + const isNewTimestamp = useCallback( + async (storageId: string, differenceTimestamp: number) => { + const id = await getHashedId(storageId); + const entry = await db.timestamps.get(id); + if (!entry) return true; + return Date.now() - entry.timestamp > differenceTimestamp; + }, + [getHashedId] + ); + + // --- GENERIC CRUD FOR DYNAMIC DATA --- + + const saveData = useCallback( + async (id: string, data: any) => { + const hashedId = await getHashedId(id); + await db.dynamicData.put({ id: hashedId, data }); + }, + [getHashedId] + ); + + const getData = useCallback( + async (id: string) => { + const hashedId = await getHashedId(id); + const entry = await db.dynamicData.get(hashedId); + return entry?.data ?? null; + }, + [getHashedId] + ); + + const deleteData = useCallback( + async (id: string) => { + const hashedId = await getHashedId(id); + await db.dynamicData.delete(hashedId); + }, + [getHashedId] + ); + + const listAllData = useCallback(async () => { + return await db.dynamicData.toArray(); + }, []); + + return { + setTimestamp, + getTimestamp, + isNewTimestamp, + saveData, + getData, + deleteData, + listAllData, + }; +}; diff --git a/src/utils/persistentDb.ts b/src/utils/persistentDb.ts new file mode 100644 index 0000000..074cd30 --- /dev/null +++ b/src/utils/persistentDb.ts @@ -0,0 +1,26 @@ +import Dexie, { Table } from 'dexie'; + +export interface TimestampEntry { + id: string; // hashed key + timestamp: number; +} + +export interface DynamicEntry { + id: string; + data: any; +} + +class AppDatabase extends Dexie { + timestamps!: Table; + dynamicData!: Table; + + constructor() { + super('MyAppDB'); + this.version(1).stores({ + timestamps: 'id', + dynamicData: 'id', + }); + } +} + +export const db = new AppDatabase();