diff --git a/client/src/components/Home.tsx b/client/src/components/Home.tsx index 7eab46e..89547d2 100644 --- a/client/src/components/Home.tsx +++ b/client/src/components/Home.tsx @@ -41,26 +41,10 @@ const Home = () => { return uniqEvents.filter(e => e.tags.some(tag => tag[0] === 'e' && tag[1] === event.id)).length; } - const [messageFromWorker, setMessageFromWorker] = useState(''); - - useEffect(() => { - const worker = new Worker(new URL('../myWorker', import.meta.url)); - - worker.onmessage = (event) => { - setMessageFromWorker(event.data); - }; - - worker.postMessage('Start worker'); - return () => { - worker.terminate(); - }; - }, []); - return (
-

Message from worker: {messageFromWorker}

{filteredAndSortedEvents.map((event, index) => ( ))} diff --git a/client/src/components/PostCard/NewThreadCard.tsx b/client/src/components/PostCard/NewThreadCard.tsx index 5049344..32d334e 100644 --- a/client/src/components/PostCard/NewThreadCard.tsx +++ b/client/src/components/PostCard/NewThreadCard.tsx @@ -1,6 +1,6 @@ import CardContainer from './CardContainer'; import { ArrowUpTrayIcon, CpuChipIcon } from '@heroicons/react/24/outline'; -import { useState } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { generatePrivateKey, getPublicKey, finishEvent } from 'nostr-tools'; import { minePow } from '../../utils/mine'; import { publish } from '../../utils/relays'; @@ -11,30 +11,51 @@ const difficulty = 20 const NewThreadCard: React.FC = () => { const [comment, setComment] = useState(""); const [file, setFile] = useState(""); + const [sk, setSk] = useState(generatePrivateKey()); + + const [messageFromWorker, setMessageFromWorker] = useState(null); + // Initialize the worker outside of any effects + const worker = useMemo(() => new Worker(new URL('../../powWorker', import.meta.url)), []); + + useEffect(() => { + worker.onmessage = (event) => { + setMessageFromWorker(event.data); + }; + }, []); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); - let sk = generatePrivateKey(); - - try { - const event = minePow({ - kind: 1, - tags: [], - content: comment + " " + file, - created_at: Math.floor(Date.now() / 1000), - pubkey: getPublicKey(sk), - }, difficulty); - - const signedEvent = finishEvent(event, sk); - await publish(signedEvent); - - setComment("") - setFile("") - } catch (error) { - setComment(comment + " " + error); - } + worker.postMessage({ + unsigned: { + kind: 1, + tags: [], + content: comment + " " + file, + created_at: Math.floor(Date.now() / 1000), + pubkey: getPublicKey(sk), + }, + difficulty + }); }; + useEffect(() => { + if (messageFromWorker) { + try { + const signedEvent = finishEvent(messageFromWorker, sk); + publish(signedEvent); + + setComment(""); + setFile(""); + setSk(generatePrivateKey()) + + return () => { + worker.terminate(); + }; + } catch (error) { + setComment(error + ' ' + comment); + } + } +}, [messageFromWorker]); + async function attachFile(file_input: File | null) { try { if (file_input) { @@ -55,6 +76,7 @@ const NewThreadCard: React.FC = () => { return ( <> + {/*

Message from worker: {messageFromWorker}

*/}
{ - setTimeout(() => { - ctx.postMessage('Hello from Worker after 2 seconds'); - }, 2000); -}); - -export default ctx; diff --git a/client/src/powWorker.ts b/client/src/powWorker.ts new file mode 100644 index 0000000..54c7169 --- /dev/null +++ b/client/src/powWorker.ts @@ -0,0 +1,74 @@ +import { type UnsignedEvent, type Event, getEventHash } from 'nostr-tools' + +// eslint-disable-next-line no-restricted-globals +const ctx: Worker = self as any; + +// Respond to message from parent thread +ctx.addEventListener('message', (event) => { + console.log("Received message in worker:", event.data); + + const { unsigned, difficulty } = event.data; + + const result = minePow(unsigned, difficulty); + console.log("Mining result:", result); + + // Post the mined event back to the main thread + ctx.postMessage(result); +}); + +ctx.onerror = function(e) { + console.error("Worker error:", e); +}; + +/** Get POW difficulty from a Nostr hex ID. */ +export function getPow(hex: string): number { + let count = 0 + + for (let i = 0; i < hex.length; i++) { + const nibble = parseInt(hex[i], 16) + if (nibble === 0) { + count += 4 + } else { + count += Math.clz32(nibble) - 28 + break + } + } + + return count +} + +/** + * Mine an event with the desired POW. This function mutates the event. + * Note that this operation is synchronous and should be run in a worker context to avoid blocking the main thread. + * + * Adapted from Snort: https://git.v0l.io/Kieran/snort/src/commit/4df6c19248184218c4c03728d61e94dae5f2d90c/packages/system/src/pow-util.ts#L14-L36 + */ +export function minePow(unsigned: UnsignedEvent, difficulty: number): Omit, 'sig'> { + let count = 0 + + const event = unsigned as Omit, 'sig'> + const tag = ['nonce', count.toString(), difficulty.toString()] + + event.tags.push(tag) + + while (true) { + const now = Math.floor(new Date().getTime() / 1000) + + if (now !== event.created_at) { + count = 0 + event.created_at = now + } + + tag[1] = (++count).toString() + + event.id = getEventHash(event) + + if (getPow(event.id) >= difficulty) { + break + } + } + + return event +} + +export default ctx;