mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-14 17:41:20 +00:00
updates
This commit is contained in:
parent
fb70b91622
commit
9ca28b0645
6
package-lock.json
generated
6
package-lock.json
generated
@ -18,6 +18,7 @@
|
||||
"bloom-filters": "^3.0.4",
|
||||
"buffer": "^6.0.3",
|
||||
"compressorjs": "^1.2.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"react": "^19.0.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-intersection-observer": "^9.16.0",
|
||||
@ -1595,6 +1596,11 @@
|
||||
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
||||
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw=="
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
|
@ -29,6 +29,7 @@
|
||||
"bloom-filters": "^3.0.4",
|
||||
"buffer": "^6.0.3",
|
||||
"compressorjs": "^1.2.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"react": "^19.0.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-intersection-observer": "^9.16.0",
|
||||
|
@ -77,7 +77,7 @@ const displayedItems = disablePagination ? items : items?.length < (displayedLim
|
||||
</React.Fragment>
|
||||
))}
|
||||
>
|
||||
{!disablePagination && displayedItems?.length <= (displayedLimit * 3) && (
|
||||
{!disablePagination && displayedItems?.length >= (displayedLimit * 3) && (
|
||||
<LazyLoad
|
||||
onLoadMore={async () => {
|
||||
await onLoadMore(displayedLimit);
|
||||
|
@ -89,7 +89,7 @@ export const VerticalPaginatedList = ({
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
{!disablePagination && displayedItems?.length <= (displayedLimit * 3) && (
|
||||
{!disablePagination && displayedItems?.length >= (displayedLimit * 3) && (
|
||||
<LazyLoad
|
||||
onLoadMore={async () => {
|
||||
await onLoadMore(displayedLimit);
|
||||
|
@ -2,11 +2,12 @@ import React, { createContext, useContext, useMemo } from "react";
|
||||
import { useAuth, UseAuthProps } from "../hooks/useAuth";
|
||||
import { useResources } from "../hooks/useResources";
|
||||
import { useAppInfo } from "../hooks/useAppInfo";
|
||||
import { addAndEncryptSymmetricKeys, decryptWithSymmetricKeys, encryptWithSymmetricKeys, IdentifierBuilder } from "../utils/encryption";
|
||||
import { addAndEncryptSymmetricKeys, decryptWithSymmetricKeys, encryptWithSymmetricKeys } from "../utils/encryption";
|
||||
import { useIdentifiers } from "../hooks/useIdentifiers";
|
||||
import { objectToBase64 } from "../utils/base64";
|
||||
import { base64ToObject } from "../utils/publish";
|
||||
import { generateBloomFilterBase64, isInsideBloom } from "../utils/bloomFilter";
|
||||
import { formatTimestamp } from "../utils/time";
|
||||
|
||||
|
||||
const utils = {
|
||||
@ -16,7 +17,8 @@ const utils = {
|
||||
encryptWithSymmetricKeys,
|
||||
decryptWithSymmetricKeys,
|
||||
generateBloomFilterBase64,
|
||||
isInsideBloom
|
||||
isInsideBloom,
|
||||
formatTimestamp
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +41,6 @@ interface GlobalProviderProps {
|
||||
appName: string;
|
||||
publicSalt: string
|
||||
};
|
||||
identifierBuilder?: IdentifierBuilder
|
||||
}
|
||||
|
||||
// ✅ Create Context with Proper Type
|
||||
@ -48,12 +49,12 @@ const GlobalContext = createContext<GlobalContextType | null>(null);
|
||||
|
||||
|
||||
// 🔹 Global Provider (Handles Multiple Hooks)
|
||||
export const GlobalProvider = ({ children, config, identifierBuilder }: GlobalProviderProps) => {
|
||||
export const GlobalProvider = ({ children, config }: GlobalProviderProps) => {
|
||||
// ✅ Call hooks and pass in options dynamically
|
||||
const auth = useAuth(config?.auth || {});
|
||||
const appInfo = useAppInfo(config?.appName, config?.publicSalt)
|
||||
const appInfo = useAppInfo(config.appName, config?.publicSalt)
|
||||
const lists = useResources()
|
||||
const identifierOperations = useIdentifiers(identifierBuilder, config?.publicSalt)
|
||||
const identifierOperations = useIdentifiers(config.publicSalt, config.appName)
|
||||
|
||||
// ✅ Merge all hooks into a single `contextValue`
|
||||
const contextValue = useMemo(() => ({ auth, lists, appInfo, identifierOperations, utils }), [auth, lists, appInfo, identifierOperations]);
|
||||
|
@ -1,48 +1,33 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useAuthStore } from "../state/auth";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import { useAppStore } from "../state/app";
|
||||
import { buildIdentifier, buildSearchPrefix, EnumCollisionStrength, hashWord, IdentifierBuilder } from "../utils/encryption";
|
||||
import { buildIdentifier, buildSearchPrefix, EnumCollisionStrength, hashWord } from "../utils/encryption";
|
||||
|
||||
|
||||
export const useIdentifiers = (builder?: IdentifierBuilder, publicSalt?: string) => {
|
||||
const setIdentifierBuilder = useAppStore().setIdentifierBuilder
|
||||
const identifierBuilder = useAppStore().identifierBuilder
|
||||
const appName = useAppStore().appName
|
||||
export const useIdentifiers = (publicSalt: string, appName: string) => {
|
||||
|
||||
const stringifiedBuilder = useMemo(()=> {
|
||||
return JSON.stringify(builder)
|
||||
}, [builder])
|
||||
|
||||
const buildIdentifierFunc = useCallback(( entityType: string,
|
||||
parentId: string | null)=> {
|
||||
if(!appName || !publicSalt || !identifierBuilder) return null
|
||||
return buildIdentifier(appName, publicSalt, entityType, parentId, identifierBuilder)
|
||||
}, [appName, publicSalt, identifierBuilder])
|
||||
return buildIdentifier(appName, publicSalt, entityType, parentId)
|
||||
}, [appName, publicSalt])
|
||||
|
||||
const buildSearchPrefixFunc = useCallback(( entityType: string,
|
||||
parentId: string | null)=> {
|
||||
if(!appName || !publicSalt || !identifierBuilder) return null
|
||||
return buildSearchPrefix(appName, publicSalt, entityType, parentId, identifierBuilder)
|
||||
}, [appName, publicSalt, identifierBuilder])
|
||||
return buildSearchPrefix(appName, publicSalt, entityType, parentId)
|
||||
}, [appName, publicSalt])
|
||||
|
||||
const createSingleIdentifier = useCallback(async ( partialIdentifier: string)=> {
|
||||
if(!partialIdentifier || !appName || !publicSalt) return null
|
||||
const appNameHashed = await hashWord(appName, EnumCollisionStrength.HIGH, publicSalt)
|
||||
return appNameHashed + '_' + partialIdentifier
|
||||
}, [appName, publicSalt])
|
||||
|
||||
const hashQortalName = useCallback(async ( qortalName: string)=> {
|
||||
if(!qortalName || !publicSalt) return null
|
||||
const hashedQortalName = await hashWord(qortalName, EnumCollisionStrength.HIGH, publicSalt)
|
||||
return hashedQortalName
|
||||
}, [publicSalt])
|
||||
|
||||
|
||||
useEffect(()=> {
|
||||
if(stringifiedBuilder){
|
||||
setIdentifierBuilder(JSON.parse(stringifiedBuilder))
|
||||
}
|
||||
}, [stringifiedBuilder])
|
||||
|
||||
return {
|
||||
buildIdentifier: buildIdentifierFunc,
|
||||
buildSearchPrefix: buildSearchPrefixFunc,
|
||||
|
@ -7,4 +7,6 @@ export {QortalSearchParams} from './types/interfaces/resources'
|
||||
export {ImagePicker} from './common/ImagePicker'
|
||||
export {useNameSearch} from './hooks/useNameSearch'
|
||||
export {Resource} from './hooks/useResources'
|
||||
|
||||
export {Service} from './types/interfaces/resources'
|
||||
export {ListItem} from './state/cache'
|
||||
export {SymmetricKeys} from './utils/encryption'
|
@ -1,14 +1,11 @@
|
||||
import { create } from "zustand";
|
||||
import { IdentifierBuilder } from "../utils/encryption";
|
||||
|
||||
interface AppState {
|
||||
appName: string | null;
|
||||
publicSalt: string | null;
|
||||
appNameHashed: string | null;
|
||||
identifierBuilder?: IdentifierBuilder | null
|
||||
// Methods
|
||||
setAppState: (appState: { appName: string; publicSalt: string; appNameHashed: string }) => void;
|
||||
setIdentifierBuilder: (builder: IdentifierBuilder) => void;
|
||||
}
|
||||
|
||||
// ✅ Typed Zustand Store
|
||||
@ -16,10 +13,7 @@ export const useAppStore = create<AppState>((set) => ({
|
||||
appName: null,
|
||||
publicSalt: null,
|
||||
appNameHashed: null,
|
||||
identifierBuilder: null,
|
||||
// Methods
|
||||
setAppState: (appState) =>
|
||||
set({ appName: appState.appName, publicSalt: appState.publicSalt, appNameHashed: appState.appNameHashed }),
|
||||
setIdentifierBuilder: (identifierBuilder) =>
|
||||
set({ identifierBuilder })
|
||||
}));
|
||||
|
@ -39,36 +39,15 @@ interface EntityConfig {
|
||||
children?: Record<string, EntityConfig>;
|
||||
}
|
||||
|
||||
export type IdentifierBuilder = {
|
||||
[key: string]: {
|
||||
children?: IdentifierBuilder;
|
||||
};
|
||||
};
|
||||
|
||||
// Recursive function to traverse identifierBuilder
|
||||
function findEntityConfig(
|
||||
identifierBuilder: IdentifierBuilder,
|
||||
path: string[]
|
||||
): EntityConfig {
|
||||
let current: EntityConfig | undefined = { children: identifierBuilder }; // ✅ Wrap it inside `{ children }` so it behaves like other levels
|
||||
|
||||
for (const key of path) {
|
||||
if (!current.children || !current.children[key]) {
|
||||
throw new Error(`Entity '${key}' is not defined in identifierBuilder`);
|
||||
}
|
||||
current = current.children[key];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
// Function to generate a prefix for searching
|
||||
export async function buildSearchPrefix(
|
||||
appName: string,
|
||||
publicSalt: string,
|
||||
entityType: string,
|
||||
parentId: string | null,
|
||||
identifierBuilder: IdentifierBuilder
|
||||
parentId: string | null
|
||||
): Promise<string> {
|
||||
// Hash app name (11 chars)
|
||||
const appHash: string = await hashWord(
|
||||
@ -84,12 +63,11 @@ export async function buildSearchPrefix(
|
||||
publicSalt
|
||||
);
|
||||
|
||||
// ✅ Detect if this entity is actually a root entity
|
||||
const isRootEntity = !!identifierBuilder[entityType];
|
||||
|
||||
|
||||
// Determine parent reference
|
||||
let parentRef = "";
|
||||
if (isRootEntity && parentId === null) {
|
||||
if (parentId === null) {
|
||||
parentRef = "00000000000000"; // ✅ Only for true root entities
|
||||
} else if (parentId) {
|
||||
parentRef = await hashWord(
|
||||
@ -110,8 +88,7 @@ export async function buildIdentifier(
|
||||
appName: string,
|
||||
publicSalt: string,
|
||||
entityType: string, // ✅ Now takes only the entity type
|
||||
parentId: string | null,
|
||||
identifierBuilder: IdentifierBuilder
|
||||
parentId: string | null
|
||||
): Promise<string> {
|
||||
// Hash app name (11 chars)
|
||||
const appHash: string = await hashWord(
|
||||
@ -297,12 +274,14 @@ export interface SecretKeyValue {
|
||||
messageKey: string;
|
||||
}
|
||||
|
||||
export type SymmetricKeys = Record<number, SecretKeyValue>
|
||||
|
||||
export const decryptWithSymmetricKeys = async ({
|
||||
base64,
|
||||
secretKeyObject,
|
||||
}: {
|
||||
base64: string;
|
||||
secretKeyObject: Record<number, SecretKeyValue>;
|
||||
secretKeyObject: SymmetricKeys;
|
||||
}) => {
|
||||
// First, decode the base64-encoded input (if skipDecodeBase64 is not set)
|
||||
const decodedData = base64;
|
||||
|
28
src/utils/time.ts
Normal file
28
src/utils/time.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(utc);
|
||||
|
||||
export function formatTimestamp(timestamp: number): string {
|
||||
const now = dayjs();
|
||||
const timestampDayJs = dayjs(timestamp);
|
||||
const elapsedTime = now.diff(timestampDayJs, "minute");
|
||||
|
||||
if (elapsedTime < 1) {
|
||||
return `Just now - ${timestampDayJs.format("h:mm A")}`;
|
||||
} else if (elapsedTime < 60) {
|
||||
return `${elapsedTime}m ago - ${timestampDayJs.format("h:mm A")}`;
|
||||
} else if (elapsedTime < 1440) {
|
||||
return `${Math.floor(elapsedTime / 60)}h ago - ${timestampDayJs.format("h:mm A")}`;
|
||||
} else {
|
||||
return timestampDayJs.format("MMM D, YYYY - h:mm A");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function oneMonthAgo(){
|
||||
const oneMonthAgoTimestamp = dayjs().subtract(1, "month").valueOf();
|
||||
return oneMonthAgoTimestamp
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user