From 75a6a349004383dca55056dee1322f1843546044 Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 30 Dec 2022 23:35:02 +0000 Subject: [PATCH] Optimize subs and many other things --- package.json | 1 - public/index.html | 2 +- src/element/Modal.css | 11 ++++ src/element/Modal.js | 18 ++++++ src/element/Note.js | 22 ++----- src/feed/EventPublisher.js | 6 +- src/feed/LoginFeed.js | 40 +++++++++---- src/feed/ProfileFeed.js | 8 +-- src/feed/Subscription.js | 51 ++++++++++++++++ src/feed/ThreadFeed.js | 115 +++++++++++++++---------------------- src/feed/TimelineFeed.js | 41 ++++--------- src/feed/UsersFeed.js | 14 ++--- src/index.css | 3 +- src/index.js | 32 +++++------ src/nostr/Connection.js | 54 +++++++++++++---- src/pages/EventPage.js | 2 +- src/pages/Layout.js | 15 ++--- src/pages/Notifications.js | 6 ++ src/pages/ProfilePage.js | 11 ++-- src/state/Login.js | 25 +++++++- src/state/Store.js | 4 +- src/state/Thread.js | 26 --------- yarn.lock | 31 +--------- 23 files changed, 291 insertions(+), 247 deletions(-) create mode 100644 src/element/Modal.css create mode 100644 src/element/Modal.js create mode 100644 src/feed/Subscription.js create mode 100644 src/pages/Notifications.js delete mode 100644 src/state/Thread.js diff --git a/package.json b/package.json index 8a36b8de..727a7912 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "qr-code-styling": "^1.6.0-rc.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-modal": "^3.16.1", "react-redux": "^8.0.5", "react-router-dom": "^6.5.0", "react-scripts": "5.0.1", diff --git a/public/index.html b/public/index.html index 554bed67..c5af8453 100644 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,7 @@ + content="default-src 'self'; child-src 'none'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src wss://* 'self'; img-src * data:; font-src https://fonts.gstatic.com;" /> diff --git a/src/element/Modal.css b/src/element/Modal.css new file mode 100644 index 00000000..b49b1e1a --- /dev/null +++ b/src/element/Modal.css @@ -0,0 +1,11 @@ +.modal { + width: 100vw; + height: 100vh; + position: absolute; + top: 0; + left: 0; + background-color: rgba(0,0,0, 0.8); + display: flex; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/src/element/Modal.js b/src/element/Modal.js new file mode 100644 index 00000000..04df6a89 --- /dev/null +++ b/src/element/Modal.js @@ -0,0 +1,18 @@ +import "./Modal.css"; +import { useEffect } from "react" + +export default function Modal(props) { + const onClose = props.onClose || (() => {}); + + useEffect(() => { + window.scrollTo(0, 0); + document.body.classList.add("scroll-lock"); + return () => document.body.classList.remove("scroll-lock"); + }, []); + + return ( +
onClose(e)}> + {props.children} +
+ ) +} \ No newline at end of file diff --git a/src/element/Note.js b/src/element/Note.js index 64df3541..74107930 100644 --- a/src/element/Note.js +++ b/src/element/Note.js @@ -1,5 +1,5 @@ import "./Note.css"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useSelector } from "react-redux"; import moment from "moment"; import { Link, useNavigate } from "react-router-dom"; @@ -11,9 +11,9 @@ import ProfileImage from "./ProfileImage"; import useEventPublisher from "../feed/EventPublisher"; import { NoteCreator } from "./NoteCreator"; -const UrlRegex = /((?:http|ftp|https):\/\/(?:[\w+?\.\w+])+(?:[a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?)/; -const FileExtensionRegex = /\.([\w]+)$/; -const MentionRegex = /(#\[\d+\])/g; +const UrlRegex = /((?:http|ftp|https):\/\/(?:[\w+?\.\w+])+(?:[a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?)/i; +const FileExtensionRegex = /\.([\w]+)$/i; +const MentionRegex = /(#\[\d+\])/gi; export default function Note(props) { const navigate = useNavigate(); @@ -21,22 +21,10 @@ export default function Note(props) { const dataEvent = props["data-ev"]; const reactions = props.reactions; const publisher = useEventPublisher(); - const [sig, setSig] = useState(false); const [showReply, setShowReply] = useState(false); const users = useSelector(s => s.users?.users); const ev = dataEvent ?? Event.FromObject(data); - useEffect(() => { - if (sig === false) { - verifyEvent(); - } - }, []); - - async function verifyEvent() { - let res = await ev.Verify(); - setSig(res); - } - function goToEvent(e, id) { if (!window.location.pathname.startsWith("/e/")) { e.stopPropagation(); @@ -66,7 +54,7 @@ export default function Note(props) { return urlBody.map(a => { if (a.startsWith("http")) { let url = new URL(a); - let ext = url.pathname.match(FileExtensionRegex); + let ext = url.pathname.toLowerCase().match(FileExtensionRegex); if (ext) { switch (ext[1]) { case "gif": diff --git a/src/feed/EventPublisher.js b/src/feed/EventPublisher.js index eb99602f..224a07b3 100644 --- a/src/feed/EventPublisher.js +++ b/src/feed/EventPublisher.js @@ -1,12 +1,10 @@ -import { useContext } from "react"; import { useSelector } from "react-redux"; -import { NostrContext } from ".."; +import { System } from ".."; import Event from "../nostr/Event"; import EventKind from "../nostr/EventKind"; import Tag from "../nostr/Tag"; export default function useEventPublisher() { - const system = useContext(NostrContext); const pubKey = useSelector(s => s.login.publicKey); const privKey = useSelector(s => s.login.privateKey); const nip07 = useSelector(s => s.login.nip07); @@ -33,7 +31,7 @@ export default function useEventPublisher() { return { broadcast: (ev) => { console.debug("Sending event: ", ev); - system.BroadcastEvent(ev); + System.BroadcastEvent(ev); }, metadata: async (obj) => { let ev = Event.ForPubKey(pubKey); diff --git a/src/feed/LoginFeed.js b/src/feed/LoginFeed.js index 62f96d31..0496730a 100644 --- a/src/feed/LoginFeed.js +++ b/src/feed/LoginFeed.js @@ -1,37 +1,53 @@ import { useContext, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { NostrContext } from ".."; +import { System } from ".."; import Event from "../nostr/Event"; import EventKind from "../nostr/EventKind"; import { Subscriptions } from "../nostr/Subscriptions"; -import { setFollows, setRelays } from "../state/Login"; +import { addNotifications, setFollows, setRelays } from "../state/Login"; /** * Managed loading data for the current logged in user */ export default function useLoginFeed() { const dispatch = useDispatch(); - const system = useContext(NostrContext); const pubKey = useSelector(s => s.login.publicKey); useEffect(() => { - if (system && pubKey) { + if (pubKey) { let sub = new Subscriptions(); + sub.Id = "login"; sub.Authors.add(pubKey); sub.Kinds.add(EventKind.ContactList); + + let notifications = new Subscriptions(); + notifications.Kinds.add(EventKind.TextNote); + notifications.Kinds.add(EventKind.Reaction); + notifications.PTags.add(pubKey); + sub.AddSubscription(notifications); + sub.OnEvent = (e) => { let ev = Event.FromObject(e); - if (ev.Content !== "") { - let relays = JSON.parse(ev.Content); - dispatch(setRelays(relays)); + switch (ev.Kind) { + case EventKind.ContactList: { + if (ev.Content !== "") { + let relays = JSON.parse(ev.Content); + dispatch(setRelays(relays)); + } + let pTags = ev.Tags.filter(a => a.Key === "p").map(a => a.PubKey); + dispatch(setFollows(pTags)); + break; + } + default: { + dispatch(addNotifications(ev.ToObject())); + break; + } } - let pTags = ev.Tags.filter(a => a.Key === "p").map(a => a.PubKey); - dispatch(setFollows(pTags)); } - system.AddSubscription(sub); - return () => system.RemoveSubscription(sub.Id); + System.AddSubscription(sub); + return () => System.RemoveSubscription(sub.Id); } - }, [system, pubKey]); + }, [pubKey]); return {}; } \ No newline at end of file diff --git a/src/feed/ProfileFeed.js b/src/feed/ProfileFeed.js index 60bf77cd..237b85b0 100644 --- a/src/feed/ProfileFeed.js +++ b/src/feed/ProfileFeed.js @@ -1,19 +1,17 @@ -import { useContext, useEffect } from "react"; +import { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { NostrContext } from ".."; import { addPubKey } from "../state/Users"; export default function useProfile(pubKey) { const dispatch = useDispatch(); - const system = useContext(NostrContext); const user = useSelector(s => s.users.users[pubKey]); const pubKeys = useSelector(s => s.users.pubKeys); useEffect(() => { - if (system && pubKey !== "" && !pubKeys.includes(pubKey)) { + if (pubKey !== "" && !pubKeys.includes(pubKey)) { dispatch(addPubKey(pubKey)); } - }, [system]); + }, [pubKey]); return user; } \ No newline at end of file diff --git a/src/feed/Subscription.js b/src/feed/Subscription.js new file mode 100644 index 00000000..46e9bb0a --- /dev/null +++ b/src/feed/Subscription.js @@ -0,0 +1,51 @@ +import { useEffect, useMemo, useState } from "react"; +import { System } from ".."; +import { Subscriptions } from "../nostr/Subscriptions"; + +/** + * + * @param {Subscriptions} sub + * @param {any} opt + * @returns + */ +export default function useSubscription(sub, opt) { + const [notes, setNotes] = useState([]); + + const options = { + leaveOpen: false, + ...opt + }; + + useEffect(() => { + if (sub) { + sub.OnEvent = (e) => { + setNotes(n => { + if (Array.isArray(n) && !n.some(a => a.id === e.id)) { + return [ + ...n, + e + ] + } else { + return n; + } + }); + }; + + if (!options.leaveOpen) { + sub.OnEnd = (c) => { + c.RemoveSubscription(sub.Id); + if (sub.IsFinished()) { + System.RemoveSubscription(sub.Id); + } + }; + } + + System.AddSubscription(sub); + return () => { + System.RemoveSubscription(sub.Id); + }; + } + }, [sub]); + + return { notes, sub }; +} \ No newline at end of file diff --git a/src/feed/ThreadFeed.js b/src/feed/ThreadFeed.js index afbd3bb3..2de35d8e 100644 --- a/src/feed/ThreadFeed.js +++ b/src/feed/ThreadFeed.js @@ -1,79 +1,56 @@ -import { useContext, useEffect, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { NostrContext } from ".."; -import Event from "../nostr/Event"; +import { useMemo } from "react"; +import EventKind from "../nostr/EventKind"; import { Subscriptions } from "../nostr/Subscriptions"; -import { addNote, reset } from "../state/Thread"; -import { addPubKey } from "../state/Users"; +import useSubscription from "./Subscription"; export default function useThreadFeed(id) { - const dispatch = useDispatch(); - const system = useContext(NostrContext); - const notes = useSelector(s => s.thread.notes); - const [thisLoaded, setThisLoaded] = useState(false); + const sub = useMemo(() => { + const thisSub = new Subscriptions(); + thisSub.Id = "thread"; + thisSub.Ids.add(id); - // track profiles - useEffect(() => { - let keys = []; - for (let n of notes) { - if (n.pubkey) { - keys.push(n.pubkey); + // get replies to this event + const subRelated = new Subscriptions(); + subRelated.Kinds.add(EventKind.Reaction); + subRelated.Kinds.add(EventKind.TextNote); + subRelated.ETags = thisSub.Ids; + thisSub.AddSubscription(subRelated); + + return thisSub; + }, [id]); + + const main = useSubscription(sub, { leaveOpen: true }); + + const relatedThisSub = useMemo(() => { + let thisNote = main.notes.find(a => a.id === id); + + if (thisNote) { + let otherSubs = new Subscriptions(); + otherSubs.Id = "thread-related"; + for (let e of thisNote.tags.filter(a => a[0] === "e")) { + otherSubs.Ids.add(e[1]); } - for (let t of n.tags) { - if (t[0] === "p" && t[1]) { - keys.push(t[1]); - } + // no #e skip related + if (otherSubs.Ids.size === 0) { + return null; } + + let relatedSubs = new Subscriptions(); + relatedSubs.Kinds.add(EventKind.Reaction); + relatedSubs.Kinds.add(EventKind.TextNote); + relatedSubs.ETags = otherSubs.Ids; + + otherSubs.AddSubscription(relatedSubs); + return otherSubs; } + }, [main.notes]); - dispatch(addPubKey(keys)); - }, [notes]); + const others = useSubscription(relatedThisSub, { leaveOpen: true }); - useEffect(() => { - if (system) { - let sub = new Subscriptions(); - let thisNote = notes.find(a => a.id === id); - if (thisNote && !thisLoaded) { - console.debug(notes); - setThisLoaded(true); - - let thisNote = Event.FromObject(notes[0]); - let thread = thisNote.GetThread(); - if (thread !== null) { - if (thread.ReplyTo) { - sub.Ids.add(thread.ReplyTo.Event); - } - if (thread.Root) { - sub.Ids.add(thread.Root.Event); - } - for (let m of thread.Mentions) { - sub.Ids.add(m.Event); - } - } else { - // this event is a root note, no other notes need to be loaded - return; - } - } else if (notes.length === 0) { - sub.Ids.add(id); - } else { - return; - } - - // get replies to this event - let subRelated = new Subscriptions(); - subRelated.ETags = sub.Ids; - sub.AddSubscription(subRelated); - - sub.OnEvent = (e) => { - dispatch(addNote(e)); - }; - system.AddSubscription(sub); - } - }, [system, notes]); - - useEffect(() => { - dispatch(reset()); - }, []); - - return { notes }; + return { + notes: [ + ...main.notes, + ...others.notes + ] + }; } \ No newline at end of file diff --git a/src/feed/TimelineFeed.js b/src/feed/TimelineFeed.js index a8945242..efc890a1 100644 --- a/src/feed/TimelineFeed.js +++ b/src/feed/TimelineFeed.js @@ -1,38 +1,21 @@ -import { useContext, useEffect, useState } from "react"; -import { NostrContext } from ".."; +import { useCallback, useMemo } from "react"; import EventKind from "../nostr/EventKind"; import { Subscriptions } from "../nostr/Subscriptions"; +import useSubscription from "./Subscription"; export default function useTimelineFeed(pubKeys) { - const system = useContext(NostrContext); - const [notes, setNotes] = useState([]); + const sub = useMemo(() => { + let sub = new Subscriptions(); + sub.Id = "timeline"; + sub.Authors = new Set(pubKeys); + sub.Kinds.add(EventKind.TextNote); + sub.Limit = 10; - useEffect(() => { - if (system && pubKeys.length > 0) { - const sub = new Subscriptions(); - sub.Authors = new Set(pubKeys); - sub.Kinds.add(EventKind.TextNote); - sub.Limit = 10; + return sub; + }, [pubKeys]); - sub.OnEvent = (e) => { - setNotes(n => { - if (Array.isArray(n) && !n.some(a => a.id === e.id)) { - return [ - ...n, - e - ] - } else { - return n; - } - }); - }; - - system.AddSubscription(sub); - return () => { - system.RemoveSubscription(sub.Id); - }; - } - }, [system, pubKeys]); + const { notes } = useSubscription(sub, { leaveOpen: true }); + return { notes }; } \ No newline at end of file diff --git a/src/feed/UsersFeed.js b/src/feed/UsersFeed.js index 1e40e500..5d8bedcf 100644 --- a/src/feed/UsersFeed.js +++ b/src/feed/UsersFeed.js @@ -1,6 +1,6 @@ -import { useContext, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { NostrContext } from ".."; +import { System } from ".."; import Event from "../nostr/Event"; import EventKind from "../nostr/EventKind"; import { Subscriptions } from "../nostr/Subscriptions"; @@ -8,7 +8,6 @@ import { setUserData } from "../state/Users"; export default function useUsersCache() { const dispatch = useDispatch(); - const system = useContext(NostrContext); const pKeys = useSelector(s => s.users.pubKeys); const users = useSelector(s => s.users.users); const [loading, setLoading] = useState(false); @@ -35,15 +34,16 @@ export default function useUsersCache() { if (needProfiles.length === 0) { return; } - console.debug("Need profiles: ", needProfiles); + let sub = new Subscriptions(); + sub.Id = "profiles"; sub.Authors = new Set(needProfiles); sub.Kinds.add(EventKind.SetMetadata); sub.OnEvent = (ev) => { dispatch(setUserData(mapEventToProfile(ev))); }; - let events = await system.RequestSubscription(sub); + let events = await System.RequestSubscription(sub); let profiles = events .filter(a => a.kind === EventKind.SetMetadata) .map(mapEventToProfile); @@ -61,14 +61,14 @@ export default function useUsersCache() { } useEffect(() => { - if (system && pKeys.length > 0 && !loading) { + if (pKeys.length > 0 && !loading) { setLoading(true); getUsers() .catch(console.error) .then(() => setLoading(false)); } - }, [system, pKeys, loading]); + }, [pKeys, loading]); return { users }; } \ No newline at end of file diff --git a/src/index.css b/src/index.css index 1f4c9098..ce1f65b1 100644 --- a/src/index.css +++ b/src/index.css @@ -145,8 +145,9 @@ div.form-group > div:first-child { margin-top: 5vh; } -.ReactModal__Body--open { +body.scroll-lock { overflow: hidden; + height: 100vh; } .mr10 { diff --git a/src/index.js b/src/index.js index 2931746c..51335a19 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,3 @@ - import './index.css'; import React from 'react'; @@ -17,26 +16,25 @@ import LoginPage from './pages/Login'; import ProfilePage from './pages/ProfilePage'; import RootPage from './pages/Root'; import Store from "./state/Store"; +import NotificationsPage from './pages/Notifications'; -const System = new NostrSystem(); -export const NostrContext = React.createContext(); +export const System = new NostrSystem(); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - - - - - } /> - } /> - } /> - } /> - - - - - + + + + + } /> + } /> + } /> + } /> + } /> + + + + ); diff --git a/src/nostr/Connection.js b/src/nostr/Connection.js index 8288fe23..621cae25 100644 --- a/src/nostr/Connection.js +++ b/src/nostr/Connection.js @@ -1,5 +1,6 @@ import { Subscriptions } from "./Subscriptions"; import Event from "./Event"; +import * as secp from "@noble/secp256k1"; const DefaultConnectTimeout = 1000; @@ -16,15 +17,11 @@ export default class Connection { } Connect() { - try { - this.Socket = new WebSocket(this.Address); - this.Socket.onopen = (e) => this.OnOpen(e); - this.Socket.onmessage = (e) => this.OnMessage(e); - this.Socket.onerror = (e) => this.OnError(e); - this.Socket.onclose = (e) => this.OnClose(e); - } catch (e) { - console.warn(`[${this.Address}] Connect failed!`); - } + this.Socket = new WebSocket(this.Address); + this.Socket.onopen = (e) => this.OnOpen(e); + this.Socket.onmessage = (e) => this.OnMessage(e); + this.Socket.onerror = (e) => this.OnError(e); + this.Socket.onclose = (e) => this.OnClose(e); } OnOpen(e) { @@ -82,6 +79,9 @@ export default class Connection { * @param {Event} e */ SendEvent(e) { + if (!this.Write) { + return; + } let req = ["EVENT", e.ToObject()]; this._SendJson(req); } @@ -91,11 +91,20 @@ export default class Connection { * @param {Subscriptions | Array} sub Subscriptions object */ AddSubscription(sub) { + if (!this.Read) { + return; + } + let subObj = sub.ToObject(); if (Object.keys(subObj).length === 0) { debugger; throw "CANNOT SEND EMPTY SUB - FIX ME"; } + + if (this.Subscriptions[sub.Id]) { + return; + } + let req = ["REQ", sub.Id, subObj]; if (sub.OrSubs.length > 0) { req = [ @@ -133,7 +142,13 @@ export default class Connection { _OnEvent(subId, ev) { if (this.Subscriptions[subId]) { - this.Subscriptions[subId].OnEvent(ev); + this._VerifySig(ev) + .then((e) => { + if (this.Subscriptions[subId]) { + this.Subscriptions[subId].OnEvent(e); + } + }) + .catch(console.error); } else { console.warn(`No subscription for event! ${subId}`); } @@ -148,4 +163,23 @@ export default class Connection { console.warn(`No subscription for end! ${subId}`); } } + + async _VerifySig(ev) { + let payload = [ + 0, + ev.pubkey, + ev.created_at, + ev.kind, + ev.tags, + ev.content + ]; + + let payloadData = new TextEncoder().encode(JSON.stringify(payload)); + let data = await secp.utils.sha256(payloadData); + let hash = secp.utils.bytesToHex(data); + if (!await secp.schnorr.verify(ev.sig, hash, ev.pubkey)) { + throw "Sig verify failed"; + } + return ev; + } } \ No newline at end of file diff --git a/src/pages/EventPage.js b/src/pages/EventPage.js index bfccaa06..187b3e88 100644 --- a/src/pages/EventPage.js +++ b/src/pages/EventPage.js @@ -5,7 +5,7 @@ import useThreadFeed from "../feed/ThreadFeed"; export default function EventPage() { const params = useParams(); const id = params.id; - + const { notes } = useThreadFeed(id); return ; } \ No newline at end of file diff --git a/src/pages/Layout.js b/src/pages/Layout.js index 8e791e26..60a3d734 100644 --- a/src/pages/Layout.js +++ b/src/pages/Layout.js @@ -1,10 +1,10 @@ import "./Layout.css"; -import { useContext, useEffect } from "react" +import { useEffect } from "react" import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { faBell } from "@fortawesome/free-solid-svg-icons"; -import { NostrContext } from ".." +import { System } from ".." import ProfileImage from "../element/ProfileImage"; import { init } from "../state/Login"; import useLoginFeed from "../feed/LoginFeed"; @@ -13,20 +13,20 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; export default function Layout(props) { const dispatch = useDispatch(); - const system = useContext(NostrContext); const navigate = useNavigate(); const key = useSelector(s => s.login.publicKey); const relays = useSelector(s => s.login.relays); + const notifications = useSelector(s => s.login.notifications); useUsersCache(); useLoginFeed(); useEffect(() => { - if (system && relays) { + if (relays) { for (let [k, v] of Object.entries(relays)) { - system.ConnectToRelay(k, v); + System.ConnectToRelay(k, v); } } - }, [relays, system]); + }, [relays]); useEffect(() => { dispatch(init()); @@ -35,8 +35,9 @@ export default function Layout(props) { function accountHeader() { return ( <> -
+
navigate("/notifications")}> + {notifications?.length ?? 0}
diff --git a/src/pages/Notifications.js b/src/pages/Notifications.js new file mode 100644 index 00000000..d27c1ed8 --- /dev/null +++ b/src/pages/Notifications.js @@ -0,0 +1,6 @@ +export default function NotificationsPage() { + return ( + <> + + ) +} \ No newline at end of file diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index af4f5ce1..72efae2b 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -13,7 +13,7 @@ import useEventPublisher from "../feed/EventPublisher"; import useTimelineFeed from "../feed/TimelineFeed"; import Note from "../element/Note"; import QRCodeStyling from "qr-code-styling"; -import ReactModal from "react-modal"; +import Modal from "../element/Modal"; export default function ProfilePage() { const dispatch = useDispatch(); @@ -65,7 +65,7 @@ export default function ProfilePage() { useEffect(() => { if (qrRef.current && showLnQr) { let qr = new QRCodeStyling({ - data: "", + data: {lud16}, type: "canvas" }); qrRef.current.innerHTML = ""; @@ -143,9 +143,10 @@ export default function ProfilePage() {
  ⚡️ {lud16}
: null} {showLnQr === true ? - setShowLnQr(false)} overlayClassName="modal" className="modal-content" preventScroll={true}> -
QR
-
: null} + setShowLnQr(false)}> +

{lud16}

+
+
: null} ) } diff --git a/src/state/Login.js b/src/state/Login.js index 125927d1..7d902597 100644 --- a/src/state/Login.js +++ b/src/state/Login.js @@ -31,6 +31,11 @@ const LoginSlice = createSlice({ * Login keys are managed by extension */ nip07: false, + + /** + * Notifications for this login session + */ + notifications: [] }, reducers: { init: (state) => { @@ -40,7 +45,6 @@ const LoginSlice = createSlice({ state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(state.privateKey, true)); } state.relays = { - "wss://beta.nostr.v0l.io": { read: true, write: true }, "wss://nostr.v0l.io": { read: true, write: true }, "wss://relay.damus.io": { read: true, write: true }, "wss://nostr-pub.wellorder.net": { read: true, write: true } @@ -48,7 +52,7 @@ const LoginSlice = createSlice({ // check nip07 pub key let nip07PubKey = window.localStorage.getItem(Nip07PublicKeyItem); - if(nip07PubKey && !state.privateKey) { + if (nip07PubKey && !state.privateKey) { state.publicKey = nip07PubKey; state.nip07 = true; } @@ -72,6 +76,21 @@ const LoginSlice = createSlice({ setFollows: (state, action) => { state.follows = action.payload; }, + addNotifications: (state, action) => { + let n = action.payload; + if (!Array.isArray(n)) { + n = [n]; + } + + for (let x in n) { + if (!state.notifications.some(a => a.id === x.id)) { + state.notifications.push(x); + } + } + state.notifications = [ + ...state.notifications + ]; + }, logout: (state) => { state.privateKey = null; window.localStorage.removeItem(PrivateKeyItem); @@ -79,5 +98,5 @@ const LoginSlice = createSlice({ } }); -export const { init, setPrivateKey, setPublicKey, setNip07PubKey, setRelays, setFollows, logout } = LoginSlice.actions; +export const { init, setPrivateKey, setPublicKey, setNip07PubKey, setRelays, setFollows, addNotifications, logout } = LoginSlice.actions; export const reducer = LoginSlice.reducer; \ No newline at end of file diff --git a/src/state/Store.js b/src/state/Store.js index 64bab662..ca83e460 100644 --- a/src/state/Store.js +++ b/src/state/Store.js @@ -1,13 +1,11 @@ import { configureStore } from "@reduxjs/toolkit"; import { reducer as UsersReducer } from "./Users"; import { reducer as LoginReducer } from "./Login"; -import { reducer as ThreadReducer } from "./Thread"; const Store = configureStore({ reducer: { users: UsersReducer, - login: LoginReducer, - thread: ThreadReducer + login: LoginReducer } }); diff --git a/src/state/Thread.js b/src/state/Thread.js deleted file mode 100644 index 2f6504b9..00000000 --- a/src/state/Thread.js +++ /dev/null @@ -1,26 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit' - -const ThreadSlice = createSlice({ - name: "Thread", - initialState: { - notes: [], - }, - reducers: { - setNotes: (state, action) => { - state.notes = action.payload; - }, - addNote: (state, action) => { - if (!state.notes.some(n => n.id === action.payload.id)) { - let tmp = new Set(state.notes); - tmp.add(action.payload); - state.notes = Array.from(tmp); - } - }, - reset: (state) => { - state.notes = []; - } - } -}); - -export const { setNotes, addNote, reset } = ThreadSlice.actions; -export const reducer = ThreadSlice.reducer; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 50c5a571..0ca22cec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4124,11 +4124,6 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -exenv@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" - integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -5843,7 +5838,7 @@ lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7035,7 +7030,7 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -7200,21 +7195,6 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-lifecycles-compat@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - -react-modal@^3.16.1: - version "3.16.1" - resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.16.1.tgz#34018528fc206561b1a5467fc3beeaddafb39b2b" - integrity sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg== - dependencies: - exenv "^1.2.0" - prop-types "^15.7.2" - react-lifecycles-compat "^3.0.0" - warning "^4.0.3" - react-redux@^8.0.5: version "8.0.5" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd" @@ -8511,13 +8491,6 @@ walker@^1.0.7: dependencies: makeerror "1.0.12" -warning@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" - integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== - dependencies: - loose-envify "^1.0.0" - watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"