From 86fd61a869c8820307aac0ddf92fbd039820699b Mon Sep 17 00:00:00 2001 From: smolgrrr Date: Sat, 28 Oct 2023 22:30:21 +1100 Subject: [PATCH 1/2] got something work i guess --- client/src/App.tsx | 1 + client/src/components/Home.tsx | 16 ++++++++++++++++ client/src/components/Modals/TextModal.tsx | 10 +++++----- client/src/custom.d.ts | 15 +++++++++++++++ client/src/myWorker.ts | 11 +++++++++++ client/tsconfig.json | 3 ++- 6 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 client/src/custom.d.ts create mode 100644 client/src/myWorker.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index 0a7a4e6..3f2fef7 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,6 +5,7 @@ import Settings from './components/Settings'; import SwipeableViews from 'react-swipeable-views'; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import Thread from './components/Thread/Thread'; +import { useState, useEffect } from 'react'; function App() { const [index, setIndex] = React.useState(1); diff --git a/client/src/components/Home.tsx b/client/src/components/Home.tsx index 89547d2..7eab46e 100644 --- a/client/src/components/Home.tsx +++ b/client/src/components/Home.tsx @@ -41,10 +41,26 @@ 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/Modals/TextModal.tsx b/client/src/components/Modals/TextModal.tsx index 3fe93dd..6110058 100644 --- a/client/src/components/Modals/TextModal.tsx +++ b/client/src/components/Modals/TextModal.tsx @@ -17,11 +17,11 @@ const ContentPreview = ({ key, comment }: { key: string, comment: string }) => { }; useEffect(() => { - const findUrl = comment.match(/\bhttps?:\/\/\S+/gi); - if (findUrl && findUrl.length > 0) { - setUrl(findUrl[0]) - setFinalComment(finalComment.replace(findUrl[0], '').trim()) - } + // const findUrl = comment.match(/\bhttps?:\/\/\S+/gi); + // if (findUrl && findUrl.length > 0) { + // setUrl(findUrl[0]) + // setFinalComment(finalComment.replace(findUrl[0], '').trim()) + // } const match = comment.match(/\bnostr:([a-z0-9]+)/i); const nostrQuoteID = match && match[1]; diff --git a/client/src/custom.d.ts b/client/src/custom.d.ts new file mode 100644 index 0000000..a0f6808 --- /dev/null +++ b/client/src/custom.d.ts @@ -0,0 +1,15 @@ +// Make TypeScript treat *.worker.ts as a module +declare module 'worker-loader!*' { + class WebpackWorker extends Worker { + constructor(); + } + + export = WebpackWorker; + } + + // Extend the default Worker type with the properties used in the worker + interface Worker { + new (stringUrl: string, options?: WorkerOptions): Worker; + postMessage: (message: any) => void; + } + \ No newline at end of file diff --git a/client/src/myWorker.ts b/client/src/myWorker.ts new file mode 100644 index 0000000..ae51552 --- /dev/null +++ b/client/src/myWorker.ts @@ -0,0 +1,11 @@ +// eslint-disable-next-line no-restricted-globals +const ctx: Worker = self as any; + +// Respond to message from parent thread +ctx.addEventListener('message', (event) => { + setTimeout(() => { + ctx.postMessage('Hello from Worker after 2 seconds'); + }, 2000); +}); + +export default ctx; diff --git a/client/tsconfig.json b/client/tsconfig.json index a273b0c..ffe6f2c 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -4,7 +4,8 @@ "lib": [ "dom", "dom.iterable", - "esnext" + "esnext", + "webworker" ], "allowJs": true, "skipLibCheck": true, From 3d724b417c8c560818f550e5ce80dd8ba7295e60 Mon Sep 17 00:00:00 2001 From: smolgrrr Date: Sun, 29 Oct 2023 14:09:46 +1100 Subject: [PATCH 2/2] it works! --- client/src/components/Home.tsx | 16 ---- .../src/components/PostCard/NewThreadCard.tsx | 62 +++++++++++----- client/src/myWorker.ts | 11 --- client/src/powWorker.ts | 74 +++++++++++++++++++ 4 files changed, 116 insertions(+), 47 deletions(-) delete mode 100644 client/src/myWorker.ts create mode 100644 client/src/powWorker.ts 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;