mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-15 18:01:21 +00:00
fixes
This commit is contained in:
parent
d1580a3162
commit
fb70b91622
@ -77,7 +77,7 @@ const displayedItems = disablePagination ? items : items?.length < (displayedLim
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
>
|
>
|
||||||
|
{!disablePagination && displayedItems?.length <= (displayedLimit * 3) && (
|
||||||
<LazyLoad
|
<LazyLoad
|
||||||
onLoadMore={async () => {
|
onLoadMore={async () => {
|
||||||
await onLoadMore(displayedLimit);
|
await onLoadMore(displayedLimit);
|
||||||
@ -88,7 +88,7 @@ const displayedItems = disablePagination ? items : items?.length < (displayedLim
|
|||||||
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</DynamicGrid>
|
</DynamicGrid>
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,6 +50,8 @@ export interface DefaultLoaderParams {
|
|||||||
listItemErrorText?: string;
|
listItemErrorText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ReturnType = 'JSON' | 'BASE64'
|
||||||
|
|
||||||
interface BaseProps {
|
interface BaseProps {
|
||||||
search: QortalSearchParams;
|
search: QortalSearchParams;
|
||||||
entityParams?: EntityParams;
|
entityParams?: EntityParams;
|
||||||
@ -66,7 +68,8 @@ interface BaseProps {
|
|||||||
resourceCacheDuration?: number
|
resourceCacheDuration?: number
|
||||||
disablePagination?: boolean
|
disablePagination?: boolean
|
||||||
disableScrollTracker?: boolean
|
disableScrollTracker?: boolean
|
||||||
retryAttempts: number
|
retryAttempts: number,
|
||||||
|
returnType: 'JSON' | 'BASE64'
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Restrict `direction` only when `disableVirtualization = false`
|
// ✅ Restrict `direction` only when `disableVirtualization = false`
|
||||||
@ -101,6 +104,7 @@ export const MemorizedComponent = ({
|
|||||||
disablePagination,
|
disablePagination,
|
||||||
disableScrollTracker,
|
disableScrollTracker,
|
||||||
entityParams,
|
entityParams,
|
||||||
|
returnType = 'JSON',
|
||||||
retryAttempts = 2
|
retryAttempts = 2
|
||||||
}: PropsResourceListDisplay) => {
|
}: PropsResourceListDisplay) => {
|
||||||
const { filterOutDeletedResources } = useCacheStore();
|
const { filterOutDeletedResources } = useCacheStore();
|
||||||
@ -152,7 +156,6 @@ export const MemorizedComponent = ({
|
|||||||
|
|
||||||
const getResourceList = useCallback(async () => {
|
const getResourceList = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if(!generatedIdentifier) return
|
if(!generatedIdentifier) return
|
||||||
|
|
||||||
await new Promise((res)=> {
|
await new Promise((res)=> {
|
||||||
@ -163,7 +166,7 @@ export const MemorizedComponent = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const parsedParams = {...(JSON.parse(memoizedParams))};
|
const parsedParams = {...(JSON.parse(memoizedParams))};
|
||||||
parsedParams.identifier = generatedIdentifier
|
parsedParams.identifier = generatedIdentifier
|
||||||
const responseData = await lists.fetchResources(parsedParams, listName, true); // Awaiting the async function
|
const responseData = await lists.fetchResources(parsedParams, listName, returnType, true); // Awaiting the async function
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -221,7 +224,7 @@ export const MemorizedComponent = ({
|
|||||||
if(displayLimit){
|
if(displayLimit){
|
||||||
parsedParams.limit = displayLimit
|
parsedParams.limit = displayLimit
|
||||||
}
|
}
|
||||||
const responseData = await lists.fetchResources(parsedParams, listName); // Awaiting the async function
|
const responseData = await lists.fetchResources(parsedParams, listName, returnType); // Awaiting the async function
|
||||||
addItems(listName, responseData || [])
|
addItems(listName, responseData || [])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch resources:", error);
|
console.error("Failed to fetch resources:", error);
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
import React, {
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import DynamicGrid from "./DynamicGrid";
|
import DynamicGrid from "./DynamicGrid";
|
||||||
import LazyLoad from "../../common/LazyLoad";
|
import LazyLoad from "../../common/LazyLoad";
|
||||||
import { ListItem } from "../../state/cache";
|
import { ListItem } from "../../state/cache";
|
||||||
@ -11,9 +17,9 @@ interface VerticalPaginatedListProps {
|
|||||||
listItem: (item: ListItem, index: number) => React.ReactNode;
|
listItem: (item: ListItem, index: number) => React.ReactNode;
|
||||||
loaderItem?: (status: "LOADING" | "ERROR") => React.ReactNode;
|
loaderItem?: (status: "LOADING" | "ERROR") => React.ReactNode;
|
||||||
onLoadMore: (limit: number) => void;
|
onLoadMore: (limit: number) => void;
|
||||||
onLoadLess: (limit: number)=> void;
|
onLoadLess: (limit: number) => void;
|
||||||
limit: number,
|
limit: number;
|
||||||
disablePagination?: boolean
|
disablePagination?: boolean;
|
||||||
defaultLoaderParams?: DefaultLoaderParams;
|
defaultLoaderParams?: DefaultLoaderParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,63 +31,78 @@ export const VerticalPaginatedList = ({
|
|||||||
onLoadLess,
|
onLoadLess,
|
||||||
limit,
|
limit,
|
||||||
disablePagination,
|
disablePagination,
|
||||||
defaultLoaderParams
|
defaultLoaderParams,
|
||||||
}: VerticalPaginatedListProps) => {
|
}: VerticalPaginatedListProps) => {
|
||||||
|
const lastItemRef = useRef<any>(null);
|
||||||
|
const lastItemRef2 = useRef<any>(null);
|
||||||
|
|
||||||
const lastItemRef= useRef<any>(null)
|
const displayedLimit = limit || 20;
|
||||||
const lastItemRef2= useRef<any>(null)
|
|
||||||
|
|
||||||
const displayedLimit = limit || 20
|
const displayedItems = disablePagination
|
||||||
|
? items
|
||||||
const displayedItems = disablePagination ? items : items.slice(- (displayedLimit * 3))
|
: items.slice(-(displayedLimit * 3));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!disablePagination && items?.length > (displayedLimit * 3) && (
|
{!disablePagination && items?.length > displayedLimit * 3 && (
|
||||||
<LazyLoad
|
<LazyLoad
|
||||||
onLoadMore={async () => {
|
onLoadMore={async () => {
|
||||||
|
await onLoadLess(displayedLimit);
|
||||||
|
lastItemRef2.current.scrollIntoView({
|
||||||
|
behavior: "auto",
|
||||||
|
block: "start",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
window.scrollBy({ top: -50, behavior: "instant" }); // 'smooth' if needed
|
||||||
|
}, 0);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
await onLoadLess(displayedLimit);
|
{displayedItems?.map((item, index, list) => {
|
||||||
lastItemRef2.current.scrollIntoView({ behavior: "auto", block: "start" });
|
return (
|
||||||
setTimeout(() => {
|
<React.Fragment
|
||||||
window.scrollBy({ top: -50, behavior: "instant" }); // 'smooth' if needed
|
key={`${item?.name}-${item?.service}-${item?.identifier}`}
|
||||||
}, 0);
|
>
|
||||||
}}
|
<div
|
||||||
/>
|
style={{
|
||||||
)}
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
{displayedItems?.map((item, index, list) => {
|
justifyContent: "center",
|
||||||
return (
|
}}
|
||||||
<React.Fragment
|
ref={
|
||||||
key={`${item?.name}-${item?.service}-${item?.identifier}`}
|
index === displayedLimit
|
||||||
>
|
? lastItemRef2
|
||||||
<div style={{
|
: index === list.length - displayedLimit - 1
|
||||||
width: '100%',
|
? lastItemRef
|
||||||
display: 'flex',
|
: null
|
||||||
justifyContent: 'center'
|
}
|
||||||
}} ref={index === displayedLimit ? lastItemRef2 : index === list.length -displayedLimit - 1 ? lastItemRef : null}>
|
>
|
||||||
<ListItemWrapper
|
<ListItemWrapper
|
||||||
defaultLoaderParams={defaultLoaderParams}
|
defaultLoaderParams={defaultLoaderParams}
|
||||||
item={item}
|
item={item}
|
||||||
index={index}
|
index={index}
|
||||||
render={listItem}
|
render={listItem}
|
||||||
renderListItemLoader={loaderItem}
|
renderListItemLoader={loaderItem}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{!disablePagination && displayedItems?.length <= (displayedLimit * 3) && (
|
||||||
<LazyLoad
|
<LazyLoad
|
||||||
onLoadMore={async () => {
|
onLoadMore={async () => {
|
||||||
await onLoadMore(displayedLimit);
|
await onLoadMore(displayedLimit);
|
||||||
lastItemRef.current.scrollIntoView({ behavior: "auto", block: "end" });
|
lastItemRef.current.scrollIntoView({
|
||||||
setTimeout(() => {
|
behavior: "auto",
|
||||||
window.scrollBy({ top: 50, behavior: "instant" }); // 'smooth' if needed
|
block: "end",
|
||||||
}, 0);
|
});
|
||||||
|
setTimeout(() => {
|
||||||
}}
|
window.scrollBy({ top: 50, behavior: "instant" }); // 'smooth' if needed
|
||||||
/>
|
}, 0);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import { addAndEncryptSymmetricKeys, decryptWithSymmetricKeys, encryptWithSymmet
|
|||||||
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";
|
||||||
|
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
@ -13,7 +14,9 @@ const utils = {
|
|||||||
base64ToObject,
|
base64ToObject,
|
||||||
addAndEncryptSymmetricKeys,
|
addAndEncryptSymmetricKeys,
|
||||||
encryptWithSymmetricKeys,
|
encryptWithSymmetricKeys,
|
||||||
decryptWithSymmetricKeys
|
decryptWithSymmetricKeys,
|
||||||
|
generateBloomFilterBase64,
|
||||||
|
isInsideBloom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,6 +31,12 @@ export const useIdentifiers = (builder?: IdentifierBuilder, publicSalt?: string)
|
|||||||
return appNameHashed + '_' + partialIdentifier
|
return appNameHashed + '_' + partialIdentifier
|
||||||
}, [appName, publicSalt])
|
}, [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(()=> {
|
useEffect(()=> {
|
||||||
if(stringifiedBuilder){
|
if(stringifiedBuilder){
|
||||||
@ -40,6 +46,7 @@ export const useIdentifiers = (builder?: IdentifierBuilder, publicSalt?: string)
|
|||||||
return {
|
return {
|
||||||
buildIdentifier: buildIdentifierFunc,
|
buildIdentifier: buildIdentifierFunc,
|
||||||
buildSearchPrefix: buildSearchPrefixFunc,
|
buildSearchPrefix: buildSearchPrefixFunc,
|
||||||
createSingleIdentifier
|
createSingleIdentifier,
|
||||||
|
hashQortalName
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
52
src/hooks/useNameSearch.tsx
Normal file
52
src/hooks/useNameSearch.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
interface NameListItem {
|
||||||
|
name: string
|
||||||
|
address: string
|
||||||
|
}
|
||||||
|
export const useNameSearch = (value: string, limit = 20) => {
|
||||||
|
const [nameList, setNameList] = useState<NameListItem[]>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const checkIfNameExisits = useCallback(
|
||||||
|
async (name: string, listLimit: number) => {
|
||||||
|
try {
|
||||||
|
if(!name){
|
||||||
|
setNameList([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setIsLoading(true);
|
||||||
|
const res = await fetch(
|
||||||
|
`/names/search?query=${name}&prefix=true&limit=${listLimit}`
|
||||||
|
);
|
||||||
|
const data = await res.json();
|
||||||
|
setNameList(data?.map((item: any)=> {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
address: item.owner
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
// Debounce logic
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
checkIfNameExisits(value, limit);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Cleanup timeout if searchValue changes before the timeout completes
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
}, [value, limit]);
|
||||||
|
return {
|
||||||
|
isLoading,
|
||||||
|
results: nameList,
|
||||||
|
};
|
||||||
|
};
|
@ -3,6 +3,7 @@ import { usePublishStore } from "../state/publishes";
|
|||||||
import { QortalGetMetadata, QortalMetadata } from "../types/interfaces/resources";
|
import { QortalGetMetadata, QortalMetadata } from "../types/interfaces/resources";
|
||||||
import { base64ToObject, retryTransaction } from "../utils/publish";
|
import { base64ToObject, retryTransaction } from "../utils/publish";
|
||||||
import { useGlobal } from "../context/GlobalProvider";
|
import { useGlobal } from "../context/GlobalProvider";
|
||||||
|
import { ReturnType } from "../components/ResourceList/ResourceListDisplay";
|
||||||
|
|
||||||
const STORAGE_EXPIRY_DURATION = 5 * 60 * 1000;
|
const STORAGE_EXPIRY_DURATION = 5 * 60 * 1000;
|
||||||
interface StoredPublish {
|
interface StoredPublish {
|
||||||
@ -12,7 +13,7 @@ interface StoredPublish {
|
|||||||
}
|
}
|
||||||
export const usePublish = (
|
export const usePublish = (
|
||||||
maxFetchTries: number = 3,
|
maxFetchTries: number = 3,
|
||||||
returnType: "PUBLIC_JSON" = "PUBLIC_JSON",
|
returnType: ReturnType = "JSON",
|
||||||
metadata?: QortalGetMetadata
|
metadata?: QortalGetMetadata
|
||||||
) => {
|
) => {
|
||||||
const {auth, appInfo} = useGlobal()
|
const {auth, appInfo} = useGlobal()
|
||||||
@ -30,8 +31,11 @@ export const usePublish = (
|
|||||||
const url = `/arbitrary/${item?.service}/${item?.name}/${item?.identifier}?encoding=base64`;
|
const url = `/arbitrary/${item?.service}/${item?.name}/${item?.identifier}?encoding=base64`;
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
const data = await res.text();
|
const data = await res.text();
|
||||||
|
if(returnType === 'BASE64'){
|
||||||
|
return data
|
||||||
|
}
|
||||||
return base64ToObject(data);
|
return base64ToObject(data);
|
||||||
}, []);
|
}, [returnType]);
|
||||||
|
|
||||||
const getStorageKey = useCallback(() => {
|
const getStorageKey = useCallback(() => {
|
||||||
if (!username || !appNameHashed) return null;
|
if (!username || !appNameHashed) return null;
|
||||||
@ -66,7 +70,7 @@ export const usePublish = (
|
|||||||
const fetchPublish = useCallback(
|
const fetchPublish = useCallback(
|
||||||
async (
|
async (
|
||||||
metadataProp: QortalGetMetadata,
|
metadataProp: QortalGetMetadata,
|
||||||
returnTypeProp: "PUBLIC_JSON" = "PUBLIC_JSON"
|
returnTypeProp: ReturnType = "JSON"
|
||||||
) => {
|
) => {
|
||||||
let resourceExists = null;
|
let resourceExists = null;
|
||||||
let resource = null;
|
let resource = null;
|
||||||
|
@ -7,13 +7,14 @@ import { ListItem, useCacheStore } from "../state/cache";
|
|||||||
import { RequestQueueWithPromise } from "../utils/queue";
|
import { RequestQueueWithPromise } from "../utils/queue";
|
||||||
import { base64ToUint8Array, uint8ArrayToObject } from "../utils/base64";
|
import { base64ToUint8Array, uint8ArrayToObject } from "../utils/base64";
|
||||||
import { retryTransaction } from "../utils/publish";
|
import { retryTransaction } from "../utils/publish";
|
||||||
|
import { ReturnType } from "../components/ResourceList/ResourceListDisplay";
|
||||||
|
|
||||||
export const requestQueueProductPublishes = new RequestQueueWithPromise(20);
|
export const requestQueueProductPublishes = new RequestQueueWithPromise(20);
|
||||||
export const requestQueueProductPublishesBackup = new RequestQueueWithPromise(
|
export const requestQueueProductPublishesBackup = new RequestQueueWithPromise(
|
||||||
10
|
10
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface TemporaryResource {
|
export interface Resource {
|
||||||
qortalMetadata: QortalMetadata;
|
qortalMetadata: QortalMetadata;
|
||||||
data: any;
|
data: any;
|
||||||
}
|
}
|
||||||
@ -66,6 +67,7 @@ export const useResources = (retryAttempts: number = 2) => {
|
|||||||
const fetchIndividualPublishJson = useCallback(
|
const fetchIndividualPublishJson = useCallback(
|
||||||
async (
|
async (
|
||||||
item: QortalMetadata,
|
item: QortalMetadata,
|
||||||
|
returnType: ReturnType,
|
||||||
includeMetadata?: boolean
|
includeMetadata?: boolean
|
||||||
): Promise<false | ListItem | null | undefined> => {
|
): Promise<false | ListItem | null | undefined> => {
|
||||||
try {
|
try {
|
||||||
@ -150,6 +152,17 @@ export const useResources = (retryAttempts: number = 2) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (res) {
|
if (res) {
|
||||||
|
if(returnType === 'BASE64'){
|
||||||
|
const fullDataObject = {
|
||||||
|
data: res,
|
||||||
|
qortalMetadata: includeMetadata ? metadata : item,
|
||||||
|
};
|
||||||
|
setResourceCache(
|
||||||
|
`${item?.service}-${item?.name}-${item?.identifier}`,
|
||||||
|
fullDataObject
|
||||||
|
);
|
||||||
|
return fullDataObject;
|
||||||
|
}
|
||||||
const toUint = base64ToUint8Array(res);
|
const toUint = base64ToUint8Array(res);
|
||||||
const toObject = uint8ArrayToObject(toUint);
|
const toObject = uint8ArrayToObject(toUint);
|
||||||
const fullDataObject = {
|
const fullDataObject = {
|
||||||
@ -170,9 +183,9 @@ export const useResources = (retryAttempts: number = 2) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fetchDataFromResults = useCallback(
|
const fetchDataFromResults = useCallback(
|
||||||
(responseData: QortalMetadata[]): void => {
|
(responseData: QortalMetadata[], returnType: ReturnType): void => {
|
||||||
for (const item of responseData) {
|
for (const item of responseData) {
|
||||||
fetchIndividualPublishJson(item, false);
|
fetchIndividualPublishJson(item, returnType, false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[fetchIndividualPublishJson]
|
[fetchIndividualPublishJson]
|
||||||
@ -182,7 +195,8 @@ export const useResources = (retryAttempts: number = 2) => {
|
|||||||
async (
|
async (
|
||||||
params: QortalSearchParams,
|
params: QortalSearchParams,
|
||||||
listName: string,
|
listName: string,
|
||||||
cancelRequests?: boolean
|
returnType: ReturnType = 'JSON',
|
||||||
|
cancelRequests?: boolean,
|
||||||
): Promise<QortalMetadata[]> => {
|
): Promise<QortalMetadata[]> => {
|
||||||
if (cancelRequests) {
|
if (cancelRequests) {
|
||||||
cancelAllRequests();
|
cancelAllRequests();
|
||||||
@ -226,7 +240,7 @@ export const useResources = (retryAttempts: number = 2) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSearchCache(listName, cacheKey, filteredResults);
|
setSearchCache(listName, cacheKey, filteredResults);
|
||||||
fetchDataFromResults(filteredResults);
|
fetchDataFromResults(filteredResults, returnType);
|
||||||
|
|
||||||
return filteredResults;
|
return filteredResults;
|
||||||
},
|
},
|
||||||
@ -234,7 +248,7 @@ export const useResources = (retryAttempts: number = 2) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const addNewResources = useCallback(
|
const addNewResources = useCallback(
|
||||||
(listName: string, resources: TemporaryResource[]) => {
|
(listName: string, resources: Resource[]) => {
|
||||||
|
|
||||||
addTemporaryResource(
|
addTemporaryResource(
|
||||||
listName,
|
listName,
|
||||||
@ -250,7 +264,7 @@ export const useResources = (retryAttempts: number = 2) => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateNewResources = useCallback((resources: TemporaryResource[]) => {
|
const updateNewResources = useCallback((resources: Resource[]) => {
|
||||||
resources.forEach((temporaryResource) => {
|
resources.forEach((temporaryResource) => {
|
||||||
setResourceCache(
|
setResourceCache(
|
||||||
`${temporaryResource?.qortalMetadata?.service}-${temporaryResource?.qortalMetadata?.name}-${temporaryResource?.qortalMetadata?.identifier}`,
|
`${temporaryResource?.qortalMetadata?.service}-${temporaryResource?.qortalMetadata?.name}-${temporaryResource?.qortalMetadata?.identifier}`,
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import './index.css'
|
import './index.css'
|
||||||
|
export { RequestQueueWithPromise } from './utils/queue';
|
||||||
export { GlobalProvider, useGlobal } from "./context/GlobalProvider";
|
export { GlobalProvider, useGlobal } from "./context/GlobalProvider";
|
||||||
export {usePublish} from "./hooks/usePublish"
|
export {usePublish} from "./hooks/usePublish"
|
||||||
export {ResourceListDisplay} from "./components/ResourceList/ResourceListDisplay"
|
export {ResourceListDisplay} from "./components/ResourceList/ResourceListDisplay"
|
||||||
export {QortalSearchParams} from './types/interfaces/resources'
|
export {QortalSearchParams} from './types/interfaces/resources'
|
||||||
export {ImagePicker} from './common/ImagePicker'
|
export {ImagePicker} from './common/ImagePicker'
|
||||||
|
export {useNameSearch} from './hooks/useNameSearch'
|
||||||
|
export {Resource} from './hooks/useResources'
|
||||||
|
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { QortalGetMetadata } from "../types/interfaces/resources";
|
import { QortalGetMetadata } from "../types/interfaces/resources";
|
||||||
import { TemporaryResource } from "../hooks/useResources";
|
import { Resource } from "../hooks/useResources";
|
||||||
|
|
||||||
interface PublishCache {
|
interface PublishCache {
|
||||||
data: TemporaryResource | null;
|
data: Resource | null;
|
||||||
expiry: number;
|
expiry: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PublishState {
|
interface PublishState {
|
||||||
publishes: Record<string, PublishCache>;
|
publishes: Record<string, PublishCache>;
|
||||||
|
|
||||||
getPublish: (qortalGetMetadata: QortalGetMetadata | null, ignoreExpire?: boolean) => TemporaryResource | null;
|
getPublish: (qortalGetMetadata: QortalGetMetadata | null, ignoreExpire?: boolean) => Resource | null;
|
||||||
setPublish: (qortalGetMetadata: QortalGetMetadata, data: TemporaryResource | null, customExpiry?: number) => void;
|
setPublish: (qortalGetMetadata: QortalGetMetadata, data: Resource | null, customExpiry?: number) => void;
|
||||||
clearExpiredPublishes: () => void;
|
clearExpiredPublishes: () => void;
|
||||||
publishExpiryDuration: number; // Default expiry duration
|
publishExpiryDuration: number; // Default expiry duration
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,54 @@
|
|||||||
import { BloomFilter } from 'bloom-filters';
|
|
||||||
import { base64ToObject } from './base64';
|
import { base64ToObject } from './base64';
|
||||||
|
import { Buffer } from 'buffer'
|
||||||
|
|
||||||
|
// Polyfill Buffer first
|
||||||
|
if (!(globalThis as any).Buffer) {
|
||||||
|
;(globalThis as any).Buffer = Buffer
|
||||||
|
}
|
||||||
|
|
||||||
export async function hashPublicKey(publicKeyString: string) {
|
async function getBloomFilter() {
|
||||||
const encoder = new TextEncoder();
|
const { BloomFilter } = await import('bloom-filters')
|
||||||
const data = encoder.encode(publicKeyString);
|
return BloomFilter
|
||||||
|
|
||||||
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
||||||
const hashBytes = new Uint8Array(digest);
|
|
||||||
return Array.from(hashBytes)
|
|
||||||
.map((b) => b.toString(16).padStart(2, '0'))
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function generateBloomFilterBase64(publicKeys: string[]) {
|
|
||||||
if (publicKeys.length > 100) throw new Error("Max 100 users allowed");
|
|
||||||
|
|
||||||
const bloom = BloomFilter.create(100, 0.0004); // ~0.04% FPR
|
|
||||||
|
|
||||||
for (const pk of publicKeys) {
|
|
||||||
const hash = await hashPublicKey(pk);
|
|
||||||
bloom.add(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize to compact form
|
|
||||||
const byteArray = new Uint8Array(bloom.saveAsJSON()._data);
|
|
||||||
const base64 = btoa(String.fromCharCode(...byteArray));
|
|
||||||
|
|
||||||
if (byteArray.length > 230) {
|
|
||||||
throw new Error(`Bloom filter exceeds 230 bytes: ${byteArray.length}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function userCanProbablyDecrypt(base64Bloom: string, userPublicKey: string) {
|
|
||||||
const base64ToJson = base64ToObject(base64Bloom)
|
|
||||||
|
|
||||||
const bloom = BloomFilter.fromJSON(base64ToJson)
|
|
||||||
|
|
||||||
const hash = await hashPublicKey(userPublicKey);
|
|
||||||
return bloom.has(hash);
|
export async function generateBloomFilterBase64(values: string[]) {
|
||||||
|
const maxItems = 100
|
||||||
|
if (values.length > maxItems) {
|
||||||
|
throw new Error(`Max ${maxItems} items allowed`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create filter for the expected number of items and desired false positive rate
|
||||||
|
const BloomFilter = await getBloomFilter()
|
||||||
|
const bloom = BloomFilter.create(values.length, 0.025) // ~0.04% FPR
|
||||||
|
|
||||||
|
for (const value of values) {
|
||||||
|
bloom.add(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert filter to JSON, then to base64
|
||||||
|
const json = bloom.saveAsJSON()
|
||||||
|
const jsonString = JSON.stringify(json)
|
||||||
|
const base64 = Buffer.from(jsonString).toString('base64')
|
||||||
|
|
||||||
|
const size = Buffer.byteLength(jsonString)
|
||||||
|
if (size > 238) {
|
||||||
|
throw new Error(`Bloom filter exceeds 230 bytes: ${size}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function isInsideBloom(base64Bloom: string, userPublicKey: string) {
|
||||||
|
const base64ToJson = base64ToObject(base64Bloom)
|
||||||
|
const BloomFilter = await getBloomFilter()
|
||||||
|
const bloom = BloomFilter.fromJSON(base64ToJson)
|
||||||
|
|
||||||
|
|
||||||
|
return bloom.has(userPublicKey);
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +157,7 @@ const getPublicKeysByNames = async (names: string[]) => {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(`/names/${name}`);
|
const response = await fetch(`/names/${name}`);
|
||||||
const nameInfo = await response.json();
|
const nameInfo = await response.json();
|
||||||
const resAddress = await fetch(`/addresses/${nameInfo}`);
|
const resAddress = await fetch(`/addresses/${nameInfo.owner}`);
|
||||||
const resData = await resAddress.json();
|
const resData = await resAddress.json();
|
||||||
return resData.publicKey;
|
return resData.publicKey;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -186,7 +186,6 @@ export const addAndEncryptSymmetricKeys = async ({
|
|||||||
.map(Number)
|
.map(Number)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupmemberPublicKeys = await getPublicKeysByNames(names);
|
const groupmemberPublicKeys = await getPublicKeysByNames(names);
|
||||||
const symmetricKey = createSymmetricKeyAndNonce();
|
const symmetricKey = createSymmetricKeyAndNonce();
|
||||||
const nextNumber = highestKey + 1;
|
const nextNumber = highestKey + 1;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user