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 { subGlobalFeed } from "../utils/subscriptions";
import { uniqBy } from "../utils/utils";
import PWAInstallPopup from "./Modals/PWACheckModal";
// import PWAInstallPopup from "./Modals/PWACheckModal"; // Removed as it's not being used
const Home = () => {
// State declarations
const [events, setEvents] = useState<Event[]>([]);
const [filterDifficulty, setFilterDifficulty] = useState(
localStorage.getItem("filterDifficulty") || "20"
);
const [inBrowser, setInBrowser] = useState(false);
const [filterDifficulty, setFilterDifficulty] = useState(localStorage.getItem("filterDifficulty") || "20");
const [sortByPoW, setSortByPoW] = useState(true);
// const [inBrowser, setInBrowser] = useState(false); // Removed as it's not being used
// Function to handle new events
const onEvent = (event: Event) => {
setEvents((prevEvents) => [...prevEvents, event]);
};
useEffect(() => {
// Subscribe to the global feed
subGlobalFeed(onEvent);
// If you eventually need a cleanup function, put it here
// Event listener to handle difficulty changes
const handleDifficultyChange = (event: any) => {
const customEvent = event as CustomEvent;
const { difficulty, filterDifficulty } = customEvent.detail;
const { filterDifficulty } = event.detail;
setFilterDifficulty(filterDifficulty);
};
// if ((window.navigator as any).standalone || window.matchMedia('(display-mode: standalone)').matches) {
// console.log('App is running in standalone mode.');
// } else {
// console.log('App is running in a browser.');
// setInBrowser(true)
// }
// Attach event listener
window.addEventListener("difficultyChanged", handleDifficultyChange);
// Cleanup listener on component unmount
return () => {
window.removeEventListener("difficultyChanged", handleDifficultyChange);
};
}, []);
// Get unique events based on id
const uniqEvents = events.length > 0 ? uniqBy(events, "id") : [];
const filteredAndSortedEvents = uniqEvents
.filter(
(event) =>
getPow(event.id) > Number(filterDifficulty) &&
event.kind === 1 &&
!event.tags.some((tag) => tag[0] === "e")
// Filter and sort events
const filteredEvents = uniqEvents
.filter((event) =>
getPow(event.id) > Number(filterDifficulty) &&
event.kind === 1 &&
!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 metadataEvent = uniqEvents.find(
(e) => e.pubkey === event.pubkey && e.kind === 0
);
if (metadataEvent) {
return metadataEvent;
}
return null;
return uniqEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null;
};
// Count replies for an event
const countReplies = (event: Event) => {
return uniqEvents.filter((e) =>
e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id)
).length;
return uniqEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id)).length;
};
// Render the component
return (
<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">
<NewThreadCard />
</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">
{filteredAndSortedEvents.map((event) => (
{displayedEvents.map((event) => (
<PostCard
key={event.id}
event={event}

View File

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

View File

@ -21,16 +21,30 @@ const NewThreadCard: React.FC = () => {
const [uploadingFile, setUploadingFile] = useState(false);
const [messageFromWorker, setMessageFromWorker] = useState(null);
const [doingWorkProp, setDoingWorkProp] = useState(false);
const [doingWorkProgress, setDoingWorkProgress] = useState(0);
// 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(() => {
worker.onmessage = (event) => {
setMessageFromWorker(event.data);
};
workers.forEach((worker) => {
worker.onmessage = (event) => {
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 customEvent = event as CustomEvent;
@ -47,18 +61,24 @@ const NewThreadCard: React.FC = () => {
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
worker.postMessage({
unsigned: {
kind: 1,
tags: [],
content: comment + " " + file,
created_at: Math.floor(Date.now() / 1000),
pubkey: getPublicKey(sk),
},
difficulty,
workers.forEach((worker, index) => {
worker.postMessage({
unsigned: {
kind: 1,
tags: [],
content: comment + " " + file,
created_at: Math.floor(Date.now() / 1000),
pubkey: getPublicKey(sk),
},
difficulty,
nonceStart: index, // Each worker starts from its index
nonceStep: numCores // Each worker increments by the total number of workers
});
});
};
useEffect(() => {
setDoingWorkProp(false);
if (messageFromWorker) {
@ -71,9 +91,6 @@ const NewThreadCard: React.FC = () => {
setSk(generatePrivateKey());
setMessageFromWorker(null);
return () => {
worker.terminate();
};
} catch (error) {
setComment(error + " " + comment);
}
@ -114,7 +131,7 @@ const NewThreadCard: React.FC = () => {
<input type="hidden" name="MAX_FILE_SIZE" defaultValue={4194304} />
<div
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
</div>
@ -176,15 +193,16 @@ const NewThreadCard: React.FC = () => {
Submit
</button>
</div>
{doingWorkProp ? (
<div className="flex animate-pulse text-sm text-gray-300">
<CpuChipIcon className="h-4 w-4 ml-auto" />
<span>Generating Proof-of-Work...</span>
</div>
) : null}
</div>
</div>
</div>
{doingWorkProp ? (
<div className="flex animate-pulse text-sm text-gray-300">
<CpuChipIcon className="h-4 w-4 ml-auto" />
<span>Generating Proof-of-Work:</span>
<span>iteration {doingWorkProgress}</span>
</div>
) : null}
<div id="postFormError" className="text-red-500" />
</form>
);

View File

@ -6,7 +6,7 @@ import { useEffect } from 'react';
import { uniqBy } from '../../utils/utils';
import { DocumentTextIcon, FolderPlusIcon } from '@heroicons/react/24/outline';
import { generatePrivateKey, getPublicKey, finishEvent } from 'nostr-tools';
import { minePow } from '../../utils/mine';
import { getPow } from '../../utils/mine';
import { publish } from '../../utils/relays';
import ThreadPost from './ThreadPost';
import ReplyCard from './ReplyCard';
@ -23,6 +23,7 @@ const Thread = () => {
const [postType, setPostType] = useState("");
const [hasRun, setHasRun] = useState(false);
const [preOPEvents, setPreOPEvents] = useState(['']);
const [sortByPoW, setSortByPoW] = useState(false);
// Define your callback function for subGlobalFeed
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));
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]) {
return (
<>
@ -132,12 +144,30 @@ const Thread = () => {
<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} />
</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="col-span-full h-0.5 bg-neutral-900"></div> {/* This is the white line separator */}
{uniqEvents
.slice(1)
.filter(event => event.kind === 1)
.sort((a, b) => a.created_at - b.created_at).map((event, index) => (
{displayedEvents.map((event, index) => (
<ReplyCard key={index} event={event} metadata={getMetadataEvent(event)} replyCount={countReplies(event)} repliedTo={repliedList(event)} />
))}
</div>

View File

@ -21,13 +21,29 @@ const ThreadPost = ({ OPEvent, state, type }: { OPEvent: NostrEvent, state: Bool
const [messageFromWorker, setMessageFromWorker] = useState(null);
const [doingWorkProp, setDoingWorkProp] = useState(false);
const [doingWorkProgress, setDoingWorkProgress] = useState(0);
// 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(() => {
worker.onmessage = (event) => {
setMessageFromWorker(event.data);
};
workers.forEach((worker) => {
worker.onmessage = (event) => {
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 customEvent = event as CustomEvent;
@ -45,31 +61,36 @@ const ThreadPost = ({ OPEvent, state, type }: { OPEvent: NostrEvent, state: Bool
event.preventDefault();
let id = decodeResult.data as string
let tags = [];
let modifiedComment = comment + " " + file;
if (type === 'r') {
tags.push(["e", id as string])
tags.push(["p", OPEvent.pubkey])
} else if (type === 'q') {
tags.push(["q", id as string])
tags.push(["p", OPEvent.pubkey])
modifiedComment += ' nostr:' + nip19.noteEncode(id);
}
workers.forEach((worker, index) => {
let tags = [];
let modifiedComment = comment + " " + file;
if (type === 'r') {
tags.push(["e", id as string])
tags.push(["p", OPEvent.pubkey])
} else if (type === 'q') {
tags.push(["q", id as string])
tags.push(["p", OPEvent.pubkey])
modifiedComment += ' nostr:' + nip19.noteEncode(id);
}
try {
worker.postMessage({
unsigned: {
kind: 1,
tags,
content: modifiedComment,
created_at: Math.floor(Date.now() / 1000),
pubkey: getPublicKey(sk),
}, difficulty
});
try {
worker.postMessage({
unsigned: {
kind: 1,
tags,
content: modifiedComment,
created_at: Math.floor(Date.now() / 1000),
pubkey: getPublicKey(sk),
},
difficulty,
nonceStart: index, // Each worker starts from its index
nonceStep: numCores // Each worker increments by the total number of workers
});
} catch (error) {
setComment(comment + " " + error);
}
} catch (error) {
setComment(comment + " " + error);
}
});
};
useEffect(() => {
@ -83,10 +104,6 @@ const ThreadPost = ({ OPEvent, state, type }: { OPEvent: NostrEvent, state: Bool
setFile("");
setSk(generatePrivateKey())
setMessageFromWorker(null);
return () => {
worker.terminate();
};
} catch (error) {
setComment(error + ' ' + comment);
}
@ -191,15 +208,16 @@ const ThreadPost = ({ OPEvent, state, type }: { OPEvent: NostrEvent, state: Bool
Submit
</button>
</div>
{doingWorkProp ? (
<div className="flex animate-pulse text-sm text-gray-300">
<CpuChipIcon className="h-4 w-4 ml-auto" />
<span>Generating Proof-of-Work...</span>
</div>
) : null}
</div>
</div>
</div>
{doingWorkProp ? (
<div className="flex animate-pulse text-sm text-gray-300">
<CpuChipIcon className="h-4 w-4 ml-auto" />
<span>Generating Proof-of-Work:</span>
<span>iteration {doingWorkProgress}</span>
</div>
) : null}
</form>)}
</>
);

View File

@ -7,9 +7,9 @@ const ctx: Worker = self as any;
ctx.addEventListener('message', (event) => {
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);
// 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
*/
export function minePow<K extends number>(unsigned: UnsignedEvent<K>, difficulty: number): Omit<Event<K>, 'sig'> {
let count = 0
export function minePow<K extends number>(unsigned: UnsignedEvent<K>, difficulty: number, nonceStart: number, nonceStep: number): { found: boolean, event?: Omit<Event<K>, 'sig'> } {
let nonce = nonceStart;
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) {
const now = Math.floor(new Date().getTime() / 1000)
tag[1] = (nonce).toString();
if (now !== event.created_at) {
count = 0
event.created_at = now
}
tag[1] = (++count).toString()
event.id = getEventHash(event)
event.id = getEventHash(event);
if (getPow(event.id) >= difficulty) {
break
return { found: true, event: event };
}
nonce += nonceStep;
if (nonce % (nonceStep * 10000) === 0) {
ctx.postMessage({ status: 'progress', currentNonce: nonce });
}
}
return event
return { found: false };
}
export default ctx;