mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-15 09:51:21 +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",
|
"bloom-filters": "^3.0.4",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"compressorjs": "^1.2.1",
|
"compressorjs": "^1.2.1",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
"react-intersection-observer": "^9.16.0",
|
"react-intersection-observer": "^9.16.0",
|
||||||
@ -1595,6 +1596,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
||||||
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw=="
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"bloom-filters": "^3.0.4",
|
"bloom-filters": "^3.0.4",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"compressorjs": "^1.2.1",
|
"compressorjs": "^1.2.1",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
"react-intersection-observer": "^9.16.0",
|
"react-intersection-observer": "^9.16.0",
|
||||||
|
@ -77,7 +77,7 @@ const displayedItems = disablePagination ? items : items?.length < (displayedLim
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
>
|
>
|
||||||
{!disablePagination && displayedItems?.length <= (displayedLimit * 3) && (
|
{!disablePagination && displayedItems?.length >= (displayedLimit * 3) && (
|
||||||
<LazyLoad
|
<LazyLoad
|
||||||
onLoadMore={async () => {
|
onLoadMore={async () => {
|
||||||
await onLoadMore(displayedLimit);
|
await onLoadMore(displayedLimit);
|
||||||
|
@ -89,7 +89,7 @@ export const VerticalPaginatedList = ({
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!disablePagination && displayedItems?.length <= (displayedLimit * 3) && (
|
{!disablePagination && displayedItems?.length >= (displayedLimit * 3) && (
|
||||||
<LazyLoad
|
<LazyLoad
|
||||||
onLoadMore={async () => {
|
onLoadMore={async () => {
|
||||||
await onLoadMore(displayedLimit);
|
await onLoadMore(displayedLimit);
|
||||||
|
@ -2,11 +2,12 @@ import React, { createContext, useContext, useMemo } from "react";
|
|||||||
import { useAuth, UseAuthProps } from "../hooks/useAuth";
|
import { useAuth, UseAuthProps } from "../hooks/useAuth";
|
||||||
import { useResources } from "../hooks/useResources";
|
import { useResources } from "../hooks/useResources";
|
||||||
import { useAppInfo } from "../hooks/useAppInfo";
|
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 { useIdentifiers } from "../hooks/useIdentifiers";
|
||||||
import { objectToBase64 } from "../utils/base64";
|
import { objectToBase64 } from "../utils/base64";
|
||||||
import { base64ToObject } from "../utils/publish";
|
import { base64ToObject } from "../utils/publish";
|
||||||
import { generateBloomFilterBase64, isInsideBloom } from "../utils/bloomFilter";
|
import { generateBloomFilterBase64, isInsideBloom } from "../utils/bloomFilter";
|
||||||
|
import { formatTimestamp } from "../utils/time";
|
||||||
|
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
@ -16,7 +17,8 @@ const utils = {
|
|||||||
encryptWithSymmetricKeys,
|
encryptWithSymmetricKeys,
|
||||||
decryptWithSymmetricKeys,
|
decryptWithSymmetricKeys,
|
||||||
generateBloomFilterBase64,
|
generateBloomFilterBase64,
|
||||||
isInsideBloom
|
isInsideBloom,
|
||||||
|
formatTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +41,6 @@ interface GlobalProviderProps {
|
|||||||
appName: string;
|
appName: string;
|
||||||
publicSalt: string
|
publicSalt: string
|
||||||
};
|
};
|
||||||
identifierBuilder?: IdentifierBuilder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Create Context with Proper Type
|
// ✅ Create Context with Proper Type
|
||||||
@ -48,12 +49,12 @@ const GlobalContext = createContext<GlobalContextType | null>(null);
|
|||||||
|
|
||||||
|
|
||||||
// 🔹 Global Provider (Handles Multiple Hooks)
|
// 🔹 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
|
// ✅ Call hooks and pass in options dynamically
|
||||||
const auth = useAuth(config?.auth || {});
|
const auth = useAuth(config?.auth || {});
|
||||||
const appInfo = useAppInfo(config?.appName, config?.publicSalt)
|
const appInfo = useAppInfo(config.appName, config?.publicSalt)
|
||||||
const lists = useResources()
|
const lists = useResources()
|
||||||
const identifierOperations = useIdentifiers(identifierBuilder, config?.publicSalt)
|
const identifierOperations = useIdentifiers(config.publicSalt, config.appName)
|
||||||
|
|
||||||
// ✅ Merge all hooks into a single `contextValue`
|
// ✅ Merge all hooks into a single `contextValue`
|
||||||
const contextValue = useMemo(() => ({ auth, lists, appInfo, identifierOperations, utils }), [auth, lists, appInfo, identifierOperations]);
|
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 React, { useCallback, useEffect, useMemo } from "react";
|
||||||
import { useAuthStore } from "../state/auth";
|
|
||||||
import { useAppStore } from "../state/app";
|
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) => {
|
export const useIdentifiers = (publicSalt: string, appName: string) => {
|
||||||
const setIdentifierBuilder = useAppStore().setIdentifierBuilder
|
|
||||||
const identifierBuilder = useAppStore().identifierBuilder
|
|
||||||
const appName = useAppStore().appName
|
|
||||||
|
|
||||||
const stringifiedBuilder = useMemo(()=> {
|
|
||||||
return JSON.stringify(builder)
|
|
||||||
}, [builder])
|
|
||||||
|
|
||||||
const buildIdentifierFunc = useCallback(( entityType: string,
|
const buildIdentifierFunc = useCallback(( entityType: string,
|
||||||
parentId: string | null)=> {
|
parentId: string | null)=> {
|
||||||
if(!appName || !publicSalt || !identifierBuilder) return null
|
return buildIdentifier(appName, publicSalt, entityType, parentId)
|
||||||
return buildIdentifier(appName, publicSalt, entityType, parentId, identifierBuilder)
|
}, [appName, publicSalt])
|
||||||
}, [appName, publicSalt, identifierBuilder])
|
|
||||||
|
|
||||||
const buildSearchPrefixFunc = useCallback(( entityType: string,
|
const buildSearchPrefixFunc = useCallback(( entityType: string,
|
||||||
parentId: string | null)=> {
|
parentId: string | null)=> {
|
||||||
if(!appName || !publicSalt || !identifierBuilder) return null
|
return buildSearchPrefix(appName, publicSalt, entityType, parentId)
|
||||||
return buildSearchPrefix(appName, publicSalt, entityType, parentId, identifierBuilder)
|
}, [appName, publicSalt])
|
||||||
}, [appName, publicSalt, identifierBuilder])
|
|
||||||
|
|
||||||
const createSingleIdentifier = useCallback(async ( partialIdentifier: string)=> {
|
const createSingleIdentifier = useCallback(async ( partialIdentifier: string)=> {
|
||||||
if(!partialIdentifier || !appName || !publicSalt) return null
|
|
||||||
const appNameHashed = await hashWord(appName, EnumCollisionStrength.HIGH, publicSalt)
|
const appNameHashed = await hashWord(appName, EnumCollisionStrength.HIGH, publicSalt)
|
||||||
return appNameHashed + '_' + partialIdentifier
|
return appNameHashed + '_' + partialIdentifier
|
||||||
}, [appName, publicSalt])
|
}, [appName, publicSalt])
|
||||||
|
|
||||||
const hashQortalName = useCallback(async ( qortalName: string)=> {
|
const hashQortalName = useCallback(async ( qortalName: string)=> {
|
||||||
if(!qortalName || !publicSalt) return null
|
|
||||||
const hashedQortalName = await hashWord(qortalName, EnumCollisionStrength.HIGH, publicSalt)
|
const hashedQortalName = await hashWord(qortalName, EnumCollisionStrength.HIGH, publicSalt)
|
||||||
return hashedQortalName
|
return hashedQortalName
|
||||||
}, [publicSalt])
|
}, [publicSalt])
|
||||||
|
|
||||||
|
|
||||||
useEffect(()=> {
|
|
||||||
if(stringifiedBuilder){
|
|
||||||
setIdentifierBuilder(JSON.parse(stringifiedBuilder))
|
|
||||||
}
|
|
||||||
}, [stringifiedBuilder])
|
|
||||||
return {
|
return {
|
||||||
buildIdentifier: buildIdentifierFunc,
|
buildIdentifier: buildIdentifierFunc,
|
||||||
buildSearchPrefix: buildSearchPrefixFunc,
|
buildSearchPrefix: buildSearchPrefixFunc,
|
||||||
|
@ -7,4 +7,6 @@ export {QortalSearchParams} from './types/interfaces/resources'
|
|||||||
export {ImagePicker} from './common/ImagePicker'
|
export {ImagePicker} from './common/ImagePicker'
|
||||||
export {useNameSearch} from './hooks/useNameSearch'
|
export {useNameSearch} from './hooks/useNameSearch'
|
||||||
export {Resource} from './hooks/useResources'
|
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 { create } from "zustand";
|
||||||
import { IdentifierBuilder } from "../utils/encryption";
|
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
appName: string | null;
|
appName: string | null;
|
||||||
publicSalt: string | null;
|
publicSalt: string | null;
|
||||||
appNameHashed: string | null;
|
appNameHashed: string | null;
|
||||||
identifierBuilder?: IdentifierBuilder | null
|
|
||||||
// Methods
|
// Methods
|
||||||
setAppState: (appState: { appName: string; publicSalt: string; appNameHashed: string }) => void;
|
setAppState: (appState: { appName: string; publicSalt: string; appNameHashed: string }) => void;
|
||||||
setIdentifierBuilder: (builder: IdentifierBuilder) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Typed Zustand Store
|
// ✅ Typed Zustand Store
|
||||||
@ -16,10 +13,7 @@ export const useAppStore = create<AppState>((set) => ({
|
|||||||
appName: null,
|
appName: null,
|
||||||
publicSalt: null,
|
publicSalt: null,
|
||||||
appNameHashed: null,
|
appNameHashed: null,
|
||||||
identifierBuilder: null,
|
|
||||||
// Methods
|
// Methods
|
||||||
setAppState: (appState) =>
|
setAppState: (appState) =>
|
||||||
set({ appName: appState.appName, publicSalt: appState.publicSalt, appNameHashed: appState.appNameHashed }),
|
set({ appName: appState.appName, publicSalt: appState.publicSalt, appNameHashed: appState.appNameHashed }),
|
||||||
setIdentifierBuilder: (identifierBuilder) =>
|
|
||||||
set({ identifierBuilder })
|
|
||||||
}));
|
}));
|
||||||
|
@ -39,36 +39,15 @@ interface EntityConfig {
|
|||||||
children?: Record<string, 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
|
// Function to generate a prefix for searching
|
||||||
export async function buildSearchPrefix(
|
export async function buildSearchPrefix(
|
||||||
appName: string,
|
appName: string,
|
||||||
publicSalt: string,
|
publicSalt: string,
|
||||||
entityType: string,
|
entityType: string,
|
||||||
parentId: string | null,
|
parentId: string | null
|
||||||
identifierBuilder: IdentifierBuilder
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// Hash app name (11 chars)
|
// Hash app name (11 chars)
|
||||||
const appHash: string = await hashWord(
|
const appHash: string = await hashWord(
|
||||||
@ -84,12 +63,11 @@ export async function buildSearchPrefix(
|
|||||||
publicSalt
|
publicSalt
|
||||||
);
|
);
|
||||||
|
|
||||||
// ✅ Detect if this entity is actually a root entity
|
|
||||||
const isRootEntity = !!identifierBuilder[entityType];
|
|
||||||
|
|
||||||
// Determine parent reference
|
// Determine parent reference
|
||||||
let parentRef = "";
|
let parentRef = "";
|
||||||
if (isRootEntity && parentId === null) {
|
if (parentId === null) {
|
||||||
parentRef = "00000000000000"; // ✅ Only for true root entities
|
parentRef = "00000000000000"; // ✅ Only for true root entities
|
||||||
} else if (parentId) {
|
} else if (parentId) {
|
||||||
parentRef = await hashWord(
|
parentRef = await hashWord(
|
||||||
@ -110,8 +88,7 @@ export async function buildIdentifier(
|
|||||||
appName: string,
|
appName: string,
|
||||||
publicSalt: string,
|
publicSalt: string,
|
||||||
entityType: string, // ✅ Now takes only the entity type
|
entityType: string, // ✅ Now takes only the entity type
|
||||||
parentId: string | null,
|
parentId: string | null
|
||||||
identifierBuilder: IdentifierBuilder
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// Hash app name (11 chars)
|
// Hash app name (11 chars)
|
||||||
const appHash: string = await hashWord(
|
const appHash: string = await hashWord(
|
||||||
@ -297,12 +274,14 @@ export interface SecretKeyValue {
|
|||||||
messageKey: string;
|
messageKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SymmetricKeys = Record<number, SecretKeyValue>
|
||||||
|
|
||||||
export const decryptWithSymmetricKeys = async ({
|
export const decryptWithSymmetricKeys = async ({
|
||||||
base64,
|
base64,
|
||||||
secretKeyObject,
|
secretKeyObject,
|
||||||
}: {
|
}: {
|
||||||
base64: string;
|
base64: string;
|
||||||
secretKeyObject: Record<number, SecretKeyValue>;
|
secretKeyObject: SymmetricKeys;
|
||||||
}) => {
|
}) => {
|
||||||
// First, decode the base64-encoded input (if skipDecodeBase64 is not set)
|
// First, decode the base64-encoded input (if skipDecodeBase64 is not set)
|
||||||
const decodedData = base64;
|
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