idk if this actually works but ah well

This commit is contained in:
smolgrrr 2023-11-06 15:25:52 +11:00
parent 4fc156b744
commit 4f3bbbf054
6 changed files with 209 additions and 120 deletions

View File

@ -5,78 +5,101 @@ import { getPow } from "../utils/mine";
import { Event } from "nostr-tools"; import { Event } from "nostr-tools";
import { subGlobalFeed } from "../utils/subscriptions"; import { subGlobalFeed } from "../utils/subscriptions";
import { uniqBy } from "../utils/utils"; import { uniqBy } from "../utils/utils";
import PWAInstallPopup from "./Modals/PWACheckModal"; // import PWAInstallPopup from "./Modals/PWACheckModal"; // Removed as it's not being used
const Home = () => { const Home = () => {
// State declarations
const [events, setEvents] = useState<Event[]>([]); const [events, setEvents] = useState<Event[]>([]);
const [filterDifficulty, setFilterDifficulty] = useState( const [filterDifficulty, setFilterDifficulty] = useState(localStorage.getItem("filterDifficulty") || "20");
localStorage.getItem("filterDifficulty") || "20" const [sortByPoW, setSortByPoW] = useState(true);
); // const [inBrowser, setInBrowser] = useState(false); // Removed as it's not being used
const [inBrowser, setInBrowser] = useState(false);
// Function to handle new events
const onEvent = (event: Event) => { const onEvent = (event: Event) => {
setEvents((prevEvents) => [...prevEvents, event]); setEvents((prevEvents) => [...prevEvents, event]);
}; };
useEffect(() => { useEffect(() => {
// Subscribe to the global feed
subGlobalFeed(onEvent); subGlobalFeed(onEvent);
// If you eventually need a cleanup function, put it here
// Event listener to handle difficulty changes
const handleDifficultyChange = (event: any) => { const handleDifficultyChange = (event: any) => {
const customEvent = event as CustomEvent; const { filterDifficulty } = event.detail;
const { difficulty, filterDifficulty } = customEvent.detail;
setFilterDifficulty(filterDifficulty); setFilterDifficulty(filterDifficulty);
}; };
// if ((window.navigator as any).standalone || window.matchMedia('(display-mode: standalone)').matches) { // Attach event listener
// console.log('App is running in standalone mode.');
// } else {
// console.log('App is running in a browser.');
// setInBrowser(true)
// }
window.addEventListener("difficultyChanged", handleDifficultyChange); window.addEventListener("difficultyChanged", handleDifficultyChange);
// Cleanup listener on component unmount
return () => { return () => {
window.removeEventListener("difficultyChanged", handleDifficultyChange); window.removeEventListener("difficultyChanged", handleDifficultyChange);
}; };
}, []); }, []);
// Get unique events based on id
const uniqEvents = events.length > 0 ? uniqBy(events, "id") : []; const uniqEvents = events.length > 0 ? uniqBy(events, "id") : [];
const filteredAndSortedEvents = uniqEvents // Filter and sort events
.filter( const filteredEvents = uniqEvents
(event) => .filter((event) =>
getPow(event.id) > Number(filterDifficulty) && getPow(event.id) > Number(filterDifficulty) &&
event.kind === 1 && event.kind === 1 &&
!event.tags.some((tag) => tag[0] === "e") !event.tags.some((tag) => tag[0] === "e")
) )
.sort((a, b) => (b.created_at as any) - (a.created_at as any));
const toggleSort = () => {
setSortByPoW(prev => !prev);
};
// Events sorted by time
const eventsSortedByTime = [...filteredEvents].sort((a, b) => b.created_at - a.created_at);
// Events sorted by PoW (assuming `getPow` returns a numerical representation of the PoW)
const eventsSortedByPow = [...filteredEvents].sort((a, b) => getPow(b.id) - getPow(a.id));
const displayedEvents = sortByPoW ? eventsSortedByPow : eventsSortedByTime;
// Get metadata for an event
const getMetadataEvent = (event: Event) => { const getMetadataEvent = (event: Event) => {
const metadataEvent = uniqEvents.find( return uniqEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null;
(e) => e.pubkey === event.pubkey && e.kind === 0
);
if (metadataEvent) {
return metadataEvent;
}
return null;
}; };
// Count replies for an event
const countReplies = (event: Event) => { const countReplies = (event: Event) => {
return uniqEvents.filter((e) => return uniqEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id)).length;
e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id)
).length;
}; };
// Render the component
return ( return (
<main className="text-white mb-20"> <main className="text-white mb-20">
{/* {inBrowser && <PWAInstallPopup onClose={() => setInBrowser(false)} />} */}
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2"> <div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
<NewThreadCard /> <NewThreadCard />
</div> </div>
<div className="flex items-center justify-center w-full py-4">
<label htmlFor="toggleB" className="flex items-center cursor-pointer">
<div className="relative">
<input
id="toggleB"
type="checkbox"
className="sr-only"
checked={sortByPoW}
onChange={toggleSort}
/>
<div className="block bg-gray-600 w-10 h-6 rounded-full"></div>
<div
className={`dot absolute left-1 top-1 bg-white w-4 h-4 rounded-full transition ${sortByPoW ? 'transform translate-x-full bg-blue-400' : ''
}`}
></div>
</div>
<div className={`ml-3 text-neutral-500 font-medium ${sortByPoW ? 'text-neutral-500' : ''}`}>
{sortByPoW ? 'Sort by PoW' : 'Sort by recent'}
</div>
</label>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
{filteredAndSortedEvents.map((event) => ( {displayedEvents.map((event) => (
<PostCard <PostCard
key={event.id} key={event.id}
event={event} event={event}

View File

@ -44,8 +44,8 @@ const ContentPreview = ({ key, comment }: { key: string; comment: string }) => {
return ( return (
<div className="gap-2 flex flex-col break-words text-sm"> <div className="gap-2 flex flex-col break-words text-sm">
{isExpanded ? finalComment : finalComment.slice(0, 240)} {isExpanded ? finalComment : finalComment.slice(0, 350)}
{finalComment.length > 240 && ( {finalComment.length > 350 && (
<button <button
className="text-sm text-neutral-500" className="text-sm text-neutral-500"
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}

View File

@ -21,16 +21,30 @@ const NewThreadCard: React.FC = () => {
const [uploadingFile, setUploadingFile] = useState(false); const [uploadingFile, setUploadingFile] = useState(false);
const [messageFromWorker, setMessageFromWorker] = useState(null); const [messageFromWorker, setMessageFromWorker] = useState(null);
const [doingWorkProp, setDoingWorkProp] = useState(false); const [doingWorkProp, setDoingWorkProp] = useState(false);
const [doingWorkProgress, setDoingWorkProgress] = useState(0);
// Initialize the worker outside of any effects // Initialize the worker outside of any effects
const worker = useMemo( const numCores = navigator.hardwareConcurrency || 4;
() => new Worker(new URL("../../powWorker", import.meta.url)),
const workers = useMemo(
() => Array(numCores).fill(null).map(() => new Worker(new URL("../../powWorker", import.meta.url))),
[] []
); );
useEffect(() => { useEffect(() => {
workers.forEach((worker) => {
worker.onmessage = (event) => { worker.onmessage = (event) => {
setMessageFromWorker(event.data); if (event.data.status === 'progress') {
console.log(`Worker progress: Checked ${event.data.currentNonce} nonces.`);
setDoingWorkProgress(event.data.currentNonce);
} else if (event.data.found) {
setMessageFromWorker(event.data.event);
// Terminate all workers once a solution is found
workers.forEach(w => w.terminate());
}
}; };
});
const handleDifficultyChange = (event: Event) => { const handleDifficultyChange = (event: Event) => {
const customEvent = event as CustomEvent; const customEvent = event as CustomEvent;
@ -47,6 +61,8 @@ const NewThreadCard: React.FC = () => {
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
workers.forEach((worker, index) => {
worker.postMessage({ worker.postMessage({
unsigned: { unsigned: {
kind: 1, kind: 1,
@ -56,9 +72,13 @@ const NewThreadCard: React.FC = () => {
pubkey: getPublicKey(sk), pubkey: getPublicKey(sk),
}, },
difficulty, difficulty,
nonceStart: index, // Each worker starts from its index
nonceStep: numCores // Each worker increments by the total number of workers
});
}); });
}; };
useEffect(() => { useEffect(() => {
setDoingWorkProp(false); setDoingWorkProp(false);
if (messageFromWorker) { if (messageFromWorker) {
@ -71,9 +91,6 @@ const NewThreadCard: React.FC = () => {
setSk(generatePrivateKey()); setSk(generatePrivateKey());
setMessageFromWorker(null); setMessageFromWorker(null);
return () => {
worker.terminate();
};
} catch (error) { } catch (error) {
setComment(error + " " + comment); setComment(error + " " + comment);
} }
@ -114,7 +131,7 @@ const NewThreadCard: React.FC = () => {
<input type="hidden" name="MAX_FILE_SIZE" defaultValue={4194304} /> <input type="hidden" name="MAX_FILE_SIZE" defaultValue={4194304} />
<div <div
id="togglePostFormLink" id="togglePostFormLink"
className="text-lg text-neutral-700 text-center mb-2 font-semibold" className="text-lg text-neutral-500 text-center mb-2 font-semibold"
> >
Start a New Thread Start a New Thread
</div> </div>
@ -176,15 +193,16 @@ const NewThreadCard: React.FC = () => {
Submit Submit
</button> </button>
</div> </div>
</div>
</div>
</div>
{doingWorkProp ? ( {doingWorkProp ? (
<div className="flex animate-pulse text-sm text-gray-300"> <div className="flex animate-pulse text-sm text-gray-300">
<CpuChipIcon className="h-4 w-4 ml-auto" /> <CpuChipIcon className="h-4 w-4 ml-auto" />
<span>Generating Proof-of-Work...</span> <span>Generating Proof-of-Work:</span>
<span>iteration {doingWorkProgress}</span>
</div> </div>
) : null} ) : null}
</div>
</div>
</div>
<div id="postFormError" className="text-red-500" /> <div id="postFormError" className="text-red-500" />
</form> </form>
); );

View File

@ -6,7 +6,7 @@ import { useEffect } from 'react';
import { uniqBy } from '../../utils/utils'; import { uniqBy } from '../../utils/utils';
import { DocumentTextIcon, FolderPlusIcon } from '@heroicons/react/24/outline'; import { DocumentTextIcon, FolderPlusIcon } from '@heroicons/react/24/outline';
import { generatePrivateKey, getPublicKey, finishEvent } from 'nostr-tools'; import { generatePrivateKey, getPublicKey, finishEvent } from 'nostr-tools';
import { minePow } from '../../utils/mine'; import { getPow } from '../../utils/mine';
import { publish } from '../../utils/relays'; import { publish } from '../../utils/relays';
import ThreadPost from './ThreadPost'; import ThreadPost from './ThreadPost';
import ReplyCard from './ReplyCard'; import ReplyCard from './ReplyCard';
@ -23,6 +23,7 @@ const Thread = () => {
const [postType, setPostType] = useState(""); const [postType, setPostType] = useState("");
const [hasRun, setHasRun] = useState(false); const [hasRun, setHasRun] = useState(false);
const [preOPEvents, setPreOPEvents] = useState(['']); const [preOPEvents, setPreOPEvents] = useState(['']);
const [sortByPoW, setSortByPoW] = useState(false);
// Define your callback function for subGlobalFeed // Define your callback function for subGlobalFeed
const onEvent = (event: Event, relay: string) => { const onEvent = (event: Event, relay: string) => {
@ -78,6 +79,17 @@ const Thread = () => {
) )
.sort((a, b) => (b.created_at as any) - (a.created_at as any)); .sort((a, b) => (b.created_at as any) - (a.created_at as any));
const toggleSort = () => {
setSortByPoW(prev => !prev);
};
const eventsSortedByTime = [...uniqEvents].slice(1).sort((a, b) => a.created_at - b.created_at);
// Events sorted by PoW (assuming `getPow` returns a numerical representation of the PoW)
const eventsSortedByPow = [...uniqEvents].slice(1).sort((a, b) => getPow(b.id) - getPow(a.id));
const displayedEvents = sortByPoW ? eventsSortedByPow : eventsSortedByTime;
if (!uniqEvents[0]) { if (!uniqEvents[0]) {
return ( return (
<> <>
@ -132,12 +144,30 @@ const Thread = () => {
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2"> <div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
<ThreadPost OPEvent={uniqEvents[0]} state={showForm} type={postType} /> <ThreadPost OPEvent={uniqEvents[0]} state={showForm} type={postType} />
</div> </div>
<div className="flex items-center justify-center w-full py-4">
<label htmlFor="toggleB" className="flex items-center cursor-pointer">
<div className="relative">
<input
id="toggleB"
type="checkbox"
className="sr-only"
checked={sortByPoW}
onChange={toggleSort}
/>
<div className="block bg-gray-600 w-10 h-6 rounded-full"></div>
<div
className={`dot absolute left-1 top-1 bg-white w-4 h-4 rounded-full transition ${sortByPoW ? 'transform translate-x-full bg-blue-400' : ''
}`}
></div>
</div>
<div className={`ml-3 text-neutral-500 font-medium ${sortByPoW ? 'text-neutral-500' : ''}`}>
{sortByPoW ? 'Sort by PoW' : 'Sort by age'}
</div>
</label>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
<div className="col-span-full h-0.5 bg-neutral-900"></div> {/* This is the white line separator */} <div className="col-span-full h-0.5 bg-neutral-900"></div> {/* This is the white line separator */}
{uniqEvents {displayedEvents.map((event, index) => (
.slice(1)
.filter(event => event.kind === 1)
.sort((a, b) => a.created_at - b.created_at).map((event, index) => (
<ReplyCard key={index} event={event} metadata={getMetadataEvent(event)} replyCount={countReplies(event)} repliedTo={repliedList(event)} /> <ReplyCard key={index} event={event} metadata={getMetadataEvent(event)} replyCount={countReplies(event)} repliedTo={repliedList(event)} />
))} ))}
</div> </div>

View File

@ -21,13 +21,29 @@ const ThreadPost = ({ OPEvent, state, type }: { OPEvent: NostrEvent, state: Bool
const [messageFromWorker, setMessageFromWorker] = useState(null); const [messageFromWorker, setMessageFromWorker] = useState(null);
const [doingWorkProp, setDoingWorkProp] = useState(false); const [doingWorkProp, setDoingWorkProp] = useState(false);
const [doingWorkProgress, setDoingWorkProgress] = useState(0);
// Initialize the worker outside of any effects // Initialize the worker outside of any effects
const worker = useMemo(() => new Worker(new URL('../../powWorker', import.meta.url)), []); const numCores = navigator.hardwareConcurrency || 4;
const workers = useMemo(
() => Array(numCores).fill(null).map(() => new Worker(new URL("../../powWorker", import.meta.url))),
[]
);
useEffect(() => { useEffect(() => {
workers.forEach((worker) => {
worker.onmessage = (event) => { worker.onmessage = (event) => {
setMessageFromWorker(event.data); if (event.data.status === 'progress') {
console.log(`Worker progress: Checked ${event.data.currentNonce} nonces.`);
setDoingWorkProgress(event.data.currentNonce);
} else if (event.data.found) {
setMessageFromWorker(event.data.event);
// Terminate all workers once a solution is found
workers.forEach(w => w.terminate());
}
}; };
});
const handleDifficultyChange = (event: Event) => { const handleDifficultyChange = (event: Event) => {
const customEvent = event as CustomEvent; const customEvent = event as CustomEvent;
@ -45,6 +61,7 @@ const ThreadPost = ({ OPEvent, state, type }: { OPEvent: NostrEvent, state: Bool
event.preventDefault(); event.preventDefault();
let id = decodeResult.data as string let id = decodeResult.data as string
workers.forEach((worker, index) => {
let tags = []; let tags = [];
let modifiedComment = comment + " " + file; let modifiedComment = comment + " " + file;
if (type === 'r') { if (type === 'r') {
@ -64,12 +81,16 @@ const ThreadPost = ({ OPEvent, state, type }: { OPEvent: NostrEvent, state: Bool
content: modifiedComment, content: modifiedComment,
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
pubkey: getPublicKey(sk), pubkey: getPublicKey(sk),
}, difficulty },
difficulty,
nonceStart: index, // Each worker starts from its index
nonceStep: numCores // Each worker increments by the total number of workers
}); });
} catch (error) { } catch (error) {
setComment(comment + " " + error); setComment(comment + " " + error);
} }
});
}; };
useEffect(() => { useEffect(() => {
@ -83,10 +104,6 @@ const ThreadPost = ({ OPEvent, state, type }: { OPEvent: NostrEvent, state: Bool
setFile(""); setFile("");
setSk(generatePrivateKey()) setSk(generatePrivateKey())
setMessageFromWorker(null); setMessageFromWorker(null);
return () => {
worker.terminate();
};
} catch (error) { } catch (error) {
setComment(error + ' ' + comment); setComment(error + ' ' + comment);
} }
@ -191,15 +208,16 @@ const ThreadPost = ({ OPEvent, state, type }: { OPEvent: NostrEvent, state: Bool
Submit Submit
</button> </button>
</div> </div>
</div>
</div>
</div>
{doingWorkProp ? ( {doingWorkProp ? (
<div className="flex animate-pulse text-sm text-gray-300"> <div className="flex animate-pulse text-sm text-gray-300">
<CpuChipIcon className="h-4 w-4 ml-auto" /> <CpuChipIcon className="h-4 w-4 ml-auto" />
<span>Generating Proof-of-Work...</span> <span>Generating Proof-of-Work:</span>
<span>iteration {doingWorkProgress}</span>
</div> </div>
) : null} ) : null}
</div>
</div>
</div>
</form>)} </form>)}
</> </>
); );

View File

@ -7,9 +7,9 @@ const ctx: Worker = self as any;
ctx.addEventListener('message', (event) => { ctx.addEventListener('message', (event) => {
console.log("Received message in worker:", event.data); console.log("Received message in worker:", event.data);
const { unsigned, difficulty } = event.data; const { unsigned, difficulty, nonceStart, nonceStep } = event.data;
const result = minePow(unsigned, difficulty); const result = minePow(unsigned, difficulty, nonceStart, nonceStep);
console.log("Mining result:", result); console.log("Mining result:", result);
// Post the mined event back to the main thread // Post the mined event back to the main thread
@ -43,32 +43,32 @@ export function getPow(hex: string): number {
* *
* Adapted from Snort: https://git.v0l.io/Kieran/snort/src/commit/4df6c19248184218c4c03728d61e94dae5f2d90c/packages/system/src/pow-util.ts#L14-L36 * Adapted from Snort: https://git.v0l.io/Kieran/snort/src/commit/4df6c19248184218c4c03728d61e94dae5f2d90c/packages/system/src/pow-util.ts#L14-L36
*/ */
export function minePow<K extends number>(unsigned: UnsignedEvent<K>, difficulty: number): Omit<Event<K>, 'sig'> { export function minePow<K extends number>(unsigned: UnsignedEvent<K>, difficulty: number, nonceStart: number, nonceStep: number): { found: boolean, event?: Omit<Event<K>, 'sig'> } {
let count = 0 let nonce = nonceStart;
const event = unsigned as Omit<Event<K>, 'sig'> const event = unsigned as Omit<Event<K>, 'sig'>
const tag = ['nonce', count.toString(), difficulty.toString()] const tag = ['nonce', nonce.toString(), difficulty.toString()]
event.tags.push(tag) event.tags.push(tag);
// We use a while loop that might run indefinitely until a solution is found.
// Consider adding a breaking condition if you want to limit the number of nonces each worker checks.
while (true) { while (true) {
const now = Math.floor(new Date().getTime() / 1000) tag[1] = (nonce).toString();
if (now !== event.created_at) { event.id = getEventHash(event);
count = 0
event.created_at = now
}
tag[1] = (++count).toString()
event.id = getEventHash(event)
if (getPow(event.id) >= difficulty) { if (getPow(event.id) >= difficulty) {
break return { found: true, event: event };
}
} }
return event nonce += nonceStep;
if (nonce % (nonceStep * 10000) === 0) {
ctx.postMessage({ status: 'progress', currentNonce: nonce });
}
}
return { found: false };
} }
export default ctx; export default ctx;