From f2a069a1554fae9cc85bc17d54a674ca02cdd801 Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Sun, 22 Jan 2023 16:45:44 -0500 Subject: [PATCH 1/7] nip42 initial --- src/Feed/EventPublisher.ts | 29 +++++++++++++++ src/Nostr/Auth.ts | 19 ++++++++++ src/Nostr/Connection.ts | 74 ++++++++++++++++++++++++++++++++------ src/Nostr/EventKind.ts | 3 +- 4 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 src/Nostr/Auth.ts diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index 125cf17b..a3a8b5fa 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -7,6 +7,8 @@ import { RootState } from "State/Store"; import { HexKey, RawEvent, u256, UserMetadata } from "Nostr"; import { bech32ToHex } from "Util" import { DefaultRelays, HashtagRegex } from "Const"; +import { useEffect } from "react"; +import { NIP42AuthChallenge, NIP42AuthResponse } from "Nostr/Auth"; declare global { interface Window { @@ -76,6 +78,33 @@ export default function useEventPublisher() { ev.Content = content; } + const nip42Auth = async (challenge: string, relay:string): Promise => { + if(pubKey) { + const ev = NEvent.ForPubKey(pubKey); + ev.Kind = EventKind.Auth; + ev.Content = ""; + ev.Tags.push(new Tag(["relay", relay], 0)); + ev.Tags.push(new Tag(["challenge", challenge], 1)); + return await signEvent(ev); + } + return Promise.reject(); + } + + useEffect(() =>{ + const nip42AuthEvent = async (event: NIP42AuthChallenge) => { + if(event.challenge && event.relay) { + const signedEvent = await nip42Auth(event.challenge, event.relay); + const response = new NIP42AuthResponse(event.challenge, signedEvent); + window.dispatchEvent(response); + } + } + window.addEventListener("nip42auth", nip42AuthEvent) + + return () => { + window.removeEventListener("nip42auth", nip42AuthEvent) + } + }, []) + return { broadcast: (ev: NEvent | undefined) => { if (ev) { diff --git a/src/Nostr/Auth.ts b/src/Nostr/Auth.ts new file mode 100644 index 00000000..c9a62c9f --- /dev/null +++ b/src/Nostr/Auth.ts @@ -0,0 +1,19 @@ +import {default as NEvent} from "./Event" + +export class NIP42AuthChallenge extends Event { + challenge?:string + relay?:string + constructor(challenge:string, relay:string) { + super("nip42auth"); + this.challenge = challenge; + this.relay = relay; + } +} + +export class NIP42AuthResponse extends Event { + event?: NEvent + constructor(challenge: string, event: NEvent) { + super(`nip42response:${challenge}`); + this.event = event; + } +} \ No newline at end of file diff --git a/src/Nostr/Connection.ts b/src/Nostr/Connection.ts index 49819622..f0b3dff0 100644 --- a/src/Nostr/Connection.ts +++ b/src/Nostr/Connection.ts @@ -48,6 +48,8 @@ export default class Connection { IsClosed: boolean; ReconnectTimer: ReturnType | null; EventsCallback: Map void>; + Authed: boolean; + AwaitingAuth: boolean; constructor(addr: string, options: RelaySettings) { this.Address = addr; @@ -72,6 +74,8 @@ export default class Connection { this.IsClosed = false; this.ReconnectTimer = null; this.EventsCallback = new Map(); + this.Authed = false; + this.AwaitingAuth = false; this.Connect(); } @@ -119,17 +123,12 @@ export default class Connection { OnOpen(e: Event) { this.ConnectTimeout = DefaultConnectTimeout; console.log(`[${this.Address}] Open!`); - - // send pending - for (let p of this.Pending) { - this._SendJson(p); - } - this.Pending = []; - - for (let [_, s] of this.Subscriptions) { - this._SendSubscription(s); - } - this._UpdateState(); + setTimeout(() => { + if(this.AwaitingAuth) { + return + } + this._InitSubscriptions(); + }, 500) } OnClose(e: CloseEvent) { @@ -152,6 +151,12 @@ export default class Connection { let msg = JSON.parse(e.data); let tag = msg[0]; switch (tag) { + case "AUTH": { + this._OnAuth(msg[1]) + this.Stats.EventsReceived++; + this._UpdateState(); + break; + } case "EVENT": { this._OnEvent(msg[1], msg[2]); this.Stats.EventsReceived++; @@ -297,6 +302,19 @@ export default class Connection { } } + _InitSubscriptions() { + // send pending + for (let p of this.Pending) { + this._SendJson(p); + } + this.Pending = []; + + for (let [_, s] of this.Subscriptions) { + this._SendSubscription(s); + } + this._UpdateState(); + } + _SendSubscription(sub: Subscriptions) { let req = ["REQ", sub.Id, sub.ToObject()]; if (sub.OrSubs.length > 0) { @@ -332,6 +350,40 @@ export default class Connection { } } + async _OnAuth(challenge: string, timeout: number = 5000):Promise { + const challengeEvent = new NIP42AuthChallenge(challenge, this.Address) + + const authCallback = (e:NIP42AuthResponse):Promise => { + return new Promise((resolve,reject) => { + if(!e.event) { + return Promise.reject('no event'); + } + + let t = setTimeout(() => { + window.removeEventListener(`nip42response:${challenge}`, authCallback); + this.AwaitingAuth = false; + reject('timeout'); + }, timeout); + + this.EventsCallback.set(e.event.Id, () => { + clearTimeout(t); + this.AwaitingAuth = false; + window.removeEventListener(`nip42response:${challenge}`, authCallback) + resolve(); + }); + + let req = ["AUTH", e.event.ToObject()]; + this._SendJson(req); + this.Stats.EventsSent++; + this._UpdateState(); + }) + } + + this.AwaitingAuth = true; + window.addEventListener(`nip42response:${challenge}`, authCallback) + window.dispatchEvent(challengeEvent) + } + _OnEnd(subId: string) { let sub = this.Subscriptions.get(subId); if (sub) { diff --git a/src/Nostr/EventKind.ts b/src/Nostr/EventKind.ts index d12012b9..5b4cfbdb 100644 --- a/src/Nostr/EventKind.ts +++ b/src/Nostr/EventKind.ts @@ -7,7 +7,8 @@ const enum EventKind { DirectMessage = 4, // NIP-04 Deletion = 5, // NIP-09 Repost = 6, // NIP-18 - Reaction = 7 // NIP-25 + Reaction = 7, // NIP-25 + Auth = 22242 // NIP-42 }; export default EventKind; \ No newline at end of file -- 2.45.2 From daf903c254d007fd040a7eb97f7bb1f776937964 Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Sun, 22 Jan 2023 20:45:53 -0500 Subject: [PATCH 2/7] nip42 support --- src/Feed/EventPublisher.ts | 16 +----------- src/Nostr/Connection.ts | 50 +++++++++++++++++++++++--------------- src/Pages/Layout.tsx | 18 +++++++++++++- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index a3a8b5fa..bbd7b08b 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -90,22 +90,8 @@ export default function useEventPublisher() { return Promise.reject(); } - useEffect(() =>{ - const nip42AuthEvent = async (event: NIP42AuthChallenge) => { - if(event.challenge && event.relay) { - const signedEvent = await nip42Auth(event.challenge, event.relay); - const response = new NIP42AuthResponse(event.challenge, signedEvent); - window.dispatchEvent(response); - } - } - window.addEventListener("nip42auth", nip42AuthEvent) - - return () => { - window.removeEventListener("nip42auth", nip42AuthEvent) - } - }, []) - return { + nip42Auth: nip42Auth, broadcast: (ev: NEvent | undefined) => { if (ev) { console.debug("Sending event: ", ev); diff --git a/src/Nostr/Connection.ts b/src/Nostr/Connection.ts index f0b3dff0..3b2e645c 100644 --- a/src/Nostr/Connection.ts +++ b/src/Nostr/Connection.ts @@ -47,9 +47,9 @@ export default class Connection { LastState: Readonly; IsClosed: boolean; ReconnectTimer: ReturnType | null; - EventsCallback: Map void>; + EventsCallback: Map void>; + AwaitingAuth: Map; Authed: boolean; - AwaitingAuth: boolean; constructor(addr: string, options: RelaySettings) { this.Address = addr; @@ -74,8 +74,8 @@ export default class Connection { this.IsClosed = false; this.ReconnectTimer = null; this.EventsCallback = new Map(); + this.AwaitingAuth = new Map(); this.Authed = false; - this.AwaitingAuth = false; this.Connect(); } @@ -124,11 +124,10 @@ export default class Connection { this.ConnectTimeout = DefaultConnectTimeout; console.log(`[${this.Address}] Open!`); setTimeout(() => { - if(this.AwaitingAuth) { - return + if(this.Authed || this.AwaitingAuth.size === 0) { + this._InitSubscriptions(); } - this._InitSubscriptions(); - }, 500) + }, 150) } OnClose(e: CloseEvent) { @@ -152,7 +151,7 @@ export default class Connection { let tag = msg[0]; switch (tag) { case "AUTH": { - this._OnAuth(msg[1]) + this._OnAuthAsync(msg[1]) this.Stats.EventsReceived++; this._UpdateState(); break; @@ -174,7 +173,7 @@ export default class Connection { if (this.EventsCallback.has(id)) { let cb = this.EventsCallback.get(id)!; this.EventsCallback.delete(id); - cb(); + cb(msg); } break; } @@ -316,6 +315,11 @@ export default class Connection { } _SendSubscription(sub: Subscriptions) { + if(!this.Authed && this.AwaitingAuth.size > 0) { + this.Pending.push(sub); + return; + } + let req = ["REQ", sub.Id, sub.ToObject()]; if (sub.OrSubs.length > 0) { req = [ @@ -350,25 +354,33 @@ export default class Connection { } } - async _OnAuth(challenge: string, timeout: number = 5000):Promise { + async _OnAuthAsync(challenge: string) { const challengeEvent = new NIP42AuthChallenge(challenge, this.Address) + const authCleanup = () => { + this.AwaitingAuth.delete(challenge) + } + const authCallback = (e:NIP42AuthResponse):Promise => { - return new Promise((resolve,reject) => { + window.removeEventListener(`nip42response:${challenge}`, authCallback) + return new Promise((resolve,_) => { if(!e.event) { + authCleanup(); return Promise.reject('no event'); } let t = setTimeout(() => { - window.removeEventListener(`nip42response:${challenge}`, authCallback); - this.AwaitingAuth = false; - reject('timeout'); - }, timeout); + authCleanup(); + resolve(); + }, 10_000); - this.EventsCallback.set(e.event.Id, () => { + this.EventsCallback.set(e.event.Id, (msg:any[]) => { clearTimeout(t); - this.AwaitingAuth = false; - window.removeEventListener(`nip42response:${challenge}`, authCallback) + authCleanup(); + if(msg.length > 3 && msg[2] === true) { + this.Authed = true; + this._InitSubscriptions(); + } resolve(); }); @@ -379,7 +391,7 @@ export default class Connection { }) } - this.AwaitingAuth = true; + this.AwaitingAuth.set(challenge, true) window.addEventListener(`nip42response:${challenge}`, authCallback) window.dispatchEvent(challengeEvent) } diff --git a/src/Pages/Layout.tsx b/src/Pages/Layout.tsx index dffdc48a..2eb5b18f 100644 --- a/src/Pages/Layout.tsx +++ b/src/Pages/Layout.tsx @@ -6,13 +6,15 @@ import { faBell, faMessage } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { RootState } from "State/Store"; -import { init, setPreferences, UserPreferences } from "State/Login"; +import { init, UserPreferences } from "State/Login"; import { HexKey, RawEvent, TaggedRawEvent } from "Nostr"; import { RelaySettings } from "Nostr/Connection"; import { System } from "Nostr/System" import ProfileImage from "Element/ProfileImage"; import useLoginFeed from "Feed/LoginFeed"; import { totalUnread } from "Pages/MessagesPage"; +import useEventPublisher from "Feed/EventPublisher"; +import { NIP42AuthChallenge, NIP42AuthResponse } from "Nostr/Auth"; export default function Layout() { const dispatch = useDispatch(); @@ -24,10 +26,20 @@ export default function Layout() { const readNotifications = useSelector(s => s.login.readNotifications); const dms = useSelector(s => s.login.dms); const prefs = useSelector(s => s.login.preferences); + const pub = useEventPublisher(); useLoginFeed(); useEffect(() => { if (relays) { + const nip42AuthEvent = async (event: NIP42AuthChallenge) => { + if(event.challenge && event.relay) { + const signedEvent = await pub.nip42Auth(event.challenge, event.relay); + const response = new NIP42AuthResponse(event.challenge, signedEvent); + window.dispatchEvent(response); + } + } + window.addEventListener("nip42auth", nip42AuthEvent) + for (let [k, v] of Object.entries(relays)) { System.ConnectToRelay(k, v); } @@ -36,6 +48,10 @@ export default function Layout() { System.DisconnectRelay(k); } } + + return () => { + window.removeEventListener("nip42auth", nip42AuthEvent) + } } }, [relays]); -- 2.45.2 From ef7d66d303e1bcdf91f25c733973b603636d3ed2 Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Sun, 22 Jan 2023 20:47:52 -0500 Subject: [PATCH 3/7] move function --- src/Feed/EventPublisher.ts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index bbd7b08b..580a7d13 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -78,20 +78,18 @@ export default function useEventPublisher() { ev.Content = content; } - const nip42Auth = async (challenge: string, relay:string): Promise => { - if(pubKey) { - const ev = NEvent.ForPubKey(pubKey); - ev.Kind = EventKind.Auth; - ev.Content = ""; - ev.Tags.push(new Tag(["relay", relay], 0)); - ev.Tags.push(new Tag(["challenge", challenge], 1)); - return await signEvent(ev); - } - return Promise.reject(); - } - return { - nip42Auth: nip42Auth, + nip42Auth: async (challenge: string, relay:string): Promise => { + if(pubKey) { + const ev = NEvent.ForPubKey(pubKey); + ev.Kind = EventKind.Auth; + ev.Content = ""; + ev.Tags.push(new Tag(["relay", relay], 0)); + ev.Tags.push(new Tag(["challenge", challenge], 1)); + return await signEvent(ev); + } + return Promise.reject(); + }, broadcast: (ev: NEvent | undefined) => { if (ev) { console.debug("Sending event: ", ev); -- 2.45.2 From e69b8011e9680c388cfa5090a2ba697139be109f Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Mon, 23 Jan 2023 13:52:32 -0500 Subject: [PATCH 4/7] remove timeout on connection open --- src/Nostr/Connection.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Nostr/Connection.ts b/src/Nostr/Connection.ts index 3b2e645c..161cd80b 100644 --- a/src/Nostr/Connection.ts +++ b/src/Nostr/Connection.ts @@ -122,12 +122,8 @@ export default class Connection { OnOpen(e: Event) { this.ConnectTimeout = DefaultConnectTimeout; + this._InitSubscriptions(); console.log(`[${this.Address}] Open!`); - setTimeout(() => { - if(this.Authed || this.AwaitingAuth.size === 0) { - this._InitSubscriptions(); - } - }, 150) } OnClose(e: CloseEvent) { @@ -332,6 +328,10 @@ export default class Connection { } _SendJson(obj: any) { + if(!this.Authed && this.AwaitingAuth.size > 0) { + this.Pending.push(obj); + return; + } if (this.Socket?.readyState !== WebSocket.OPEN) { this.Pending.push(obj); return; @@ -439,4 +439,4 @@ export default class Connection { } return ev; } -} \ No newline at end of file +} -- 2.45.2 From a71e3f47c7745a66f2f2c601353bb74faa9e57b2 Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Mon, 23 Jan 2023 21:52:52 -0500 Subject: [PATCH 5/7] could not send auth --- src/Nostr/Connection.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Nostr/Connection.ts b/src/Nostr/Connection.ts index 161cd80b..b0d6b516 100644 --- a/src/Nostr/Connection.ts +++ b/src/Nostr/Connection.ts @@ -328,10 +328,6 @@ export default class Connection { } _SendJson(obj: any) { - if(!this.Authed && this.AwaitingAuth.size > 0) { - this.Pending.push(obj); - return; - } if (this.Socket?.readyState !== WebSocket.OPEN) { this.Pending.push(obj); return; -- 2.45.2 From bf5953500bf42574f432f41a6b38a09b24e06a14 Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Wed, 25 Jan 2023 08:37:35 -0500 Subject: [PATCH 6/7] remove listeners, set auth function onto system --- src/Feed/EventPublisher.ts | 5 +-- src/Nostr/Auth.ts | 19 ----------- src/Nostr/Connection.ts | 65 +++++++++++++++++--------------------- src/Nostr/System.ts | 4 +++ src/Pages/Layout.tsx | 20 +++--------- 5 files changed, 39 insertions(+), 74 deletions(-) delete mode 100644 src/Nostr/Auth.ts diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index 580a7d13..b5a835a0 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -7,8 +7,6 @@ import { RootState } from "State/Store"; import { HexKey, RawEvent, u256, UserMetadata } from "Nostr"; import { bech32ToHex } from "Util" import { DefaultRelays, HashtagRegex } from "Const"; -import { useEffect } from "react"; -import { NIP42AuthChallenge, NIP42AuthResponse } from "Nostr/Auth"; declare global { interface Window { @@ -79,7 +77,7 @@ export default function useEventPublisher() { } return { - nip42Auth: async (challenge: string, relay:string): Promise => { + nip42Auth: async (challenge: string, relay:string) => { if(pubKey) { const ev = NEvent.ForPubKey(pubKey); ev.Kind = EventKind.Auth; @@ -88,7 +86,6 @@ export default function useEventPublisher() { ev.Tags.push(new Tag(["challenge", challenge], 1)); return await signEvent(ev); } - return Promise.reject(); }, broadcast: (ev: NEvent | undefined) => { if (ev) { diff --git a/src/Nostr/Auth.ts b/src/Nostr/Auth.ts deleted file mode 100644 index c9a62c9f..00000000 --- a/src/Nostr/Auth.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {default as NEvent} from "./Event" - -export class NIP42AuthChallenge extends Event { - challenge?:string - relay?:string - constructor(challenge:string, relay:string) { - super("nip42auth"); - this.challenge = challenge; - this.relay = relay; - } -} - -export class NIP42AuthResponse extends Event { - event?: NEvent - constructor(challenge: string, event: NEvent) { - super(`nip42response:${challenge}`); - this.event = event; - } -} \ No newline at end of file diff --git a/src/Nostr/Connection.ts b/src/Nostr/Connection.ts index b0d6b516..c288fc49 100644 --- a/src/Nostr/Connection.ts +++ b/src/Nostr/Connection.ts @@ -7,6 +7,7 @@ import { DefaultConnectTimeout } from "Const"; import { ConnectionStats } from "Nostr/ConnectionStats"; import { RawEvent, TaggedRawEvent, u256 } from "Nostr"; import { RelayInfo } from "./RelayInfo"; +import { System } from "./System"; export type CustomHook = (state: Readonly) => void; @@ -350,46 +351,38 @@ export default class Connection { } } - async _OnAuthAsync(challenge: string) { - const challengeEvent = new NIP42AuthChallenge(challenge, this.Address) - + async _OnAuthAsync(challenge: string): Promise { const authCleanup = () => { this.AwaitingAuth.delete(challenge) } - - const authCallback = (e:NIP42AuthResponse):Promise => { - window.removeEventListener(`nip42response:${challenge}`, authCallback) - return new Promise((resolve,_) => { - if(!e.event) { - authCleanup(); - return Promise.reject('no event'); - } - - let t = setTimeout(() => { - authCleanup(); - resolve(); - }, 10_000); - - this.EventsCallback.set(e.event.Id, (msg:any[]) => { - clearTimeout(t); - authCleanup(); - if(msg.length > 3 && msg[2] === true) { - this.Authed = true; - this._InitSubscriptions(); - } - resolve(); - }); - - let req = ["AUTH", e.event.ToObject()]; - this._SendJson(req); - this.Stats.EventsSent++; - this._UpdateState(); - }) - } - this.AwaitingAuth.set(challenge, true) - window.addEventListener(`nip42response:${challenge}`, authCallback) - window.dispatchEvent(challengeEvent) + const authEvent = await System.nip42Auth(challenge, this.Address) + return new Promise((resolve,_) => { + if(!authEvent) { + authCleanup(); + return Promise.reject('no event'); + } + + let t = setTimeout(() => { + authCleanup(); + resolve(); + }, 10_000); + + this.EventsCallback.set(authEvent.Id, (msg:any[]) => { + clearTimeout(t); + authCleanup(); + if(msg.length > 3 && msg[2] === true) { + this.Authed = true; + this._InitSubscriptions(); + } + resolve(); + }); + + let req = ["AUTH", authEvent.ToObject()]; + this._SendJson(req); + this.Stats.EventsSent++; + this._UpdateState(); + }) } _OnEnd(subId: string) { diff --git a/src/Nostr/System.ts b/src/Nostr/System.ts index bdc81e73..ce726716 100644 --- a/src/Nostr/System.ts +++ b/src/Nostr/System.ts @@ -211,6 +211,10 @@ export class NostrSystem { setTimeout(() => this._FetchMetadata(), 500); } + + async nip42Auth(challenge: string, relay:string): Promise { + return + } } export const System = new NostrSystem(); \ No newline at end of file diff --git a/src/Pages/Layout.tsx b/src/Pages/Layout.tsx index 2eb5b18f..cd905e7a 100644 --- a/src/Pages/Layout.tsx +++ b/src/Pages/Layout.tsx @@ -1,5 +1,5 @@ import "./Layout.css"; -import { useEffect } from "react" +import { useEffect, useMemo } from "react" import { useDispatch, useSelector } from "react-redux"; import { Outlet, useNavigate } from "react-router-dom"; import { faBell, faMessage } from "@fortawesome/free-solid-svg-icons"; @@ -14,7 +14,6 @@ import ProfileImage from "Element/ProfileImage"; import useLoginFeed from "Feed/LoginFeed"; import { totalUnread } from "Pages/MessagesPage"; import useEventPublisher from "Feed/EventPublisher"; -import { NIP42AuthChallenge, NIP42AuthResponse } from "Nostr/Auth"; export default function Layout() { const dispatch = useDispatch(); @@ -29,17 +28,12 @@ export default function Layout() { const pub = useEventPublisher(); useLoginFeed(); + useMemo(() => { + System.nip42Auth = pub.nip42Auth + },[pub]) + useEffect(() => { if (relays) { - const nip42AuthEvent = async (event: NIP42AuthChallenge) => { - if(event.challenge && event.relay) { - const signedEvent = await pub.nip42Auth(event.challenge, event.relay); - const response = new NIP42AuthResponse(event.challenge, signedEvent); - window.dispatchEvent(response); - } - } - window.addEventListener("nip42auth", nip42AuthEvent) - for (let [k, v] of Object.entries(relays)) { System.ConnectToRelay(k, v); } @@ -48,10 +42,6 @@ export default function Layout() { System.DisconnectRelay(k); } } - - return () => { - window.removeEventListener("nip42auth", nip42AuthEvent) - } } }, [relays]); -- 2.45.2 From 5ea6ee90a4a97abf4a3e65762792de0a00c8b81e Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 25 Jan 2023 13:54:45 +0000 Subject: [PATCH 7/7] chore: cleanup warnings --- src/Element/Nip5Service.tsx | 10 +++++----- src/Element/NoteCreator.css | 9 ++------- src/Element/NoteFooter.tsx | 4 ++-- src/Element/NoteReaction.tsx | 16 ++-------------- src/Element/Relay.tsx | 6 +++--- src/Element/Timeline.tsx | 12 ++++++------ src/Feed/ProfileFeed.ts | 2 +- src/Feed/Subscription.ts | 6 ++++-- src/Feed/ThreadFeed.ts | 2 +- src/Feed/TimelineFeed.ts | 24 +++++++++++++----------- src/Nostr/Connection.ts | 4 ++-- src/Pages/NewUserPage.tsx | 2 +- src/Pages/settings/Profile.tsx | 2 -- src/State/Login.ts | 5 ++--- src/Util.ts | 4 ++-- src/index.css | 10 +++------- 16 files changed, 49 insertions(+), 69 deletions(-) diff --git a/src/Element/Nip5Service.tsx b/src/Element/Nip5Service.tsx index f437b301..0ab9dfca 100644 --- a/src/Element/Nip5Service.tsx +++ b/src/Element/Nip5Service.tsx @@ -33,7 +33,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { const pubkey = useSelector(s => s.login.publicKey); const user = useProfile(pubkey); const publisher = useEventPublisher(); - const svc = new ServiceProvider(props.service); + const svc = useMemo(() => new ServiceProvider(props.service), [props.service]); const [serviceConfig, setServiceConfig] = useState(); const [error, setError] = useState(); const [handle, setHandle] = useState(""); @@ -43,7 +43,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { const [showInvoice, setShowInvoice] = useState(false); const [registerStatus, setRegisterStatus] = useState(); - const domainConfig = useMemo(() => serviceConfig?.domains.find(a => a.name === domain), [domain]); + const domainConfig = useMemo(() => serviceConfig?.domains.find(a => a.name === domain), [domain, serviceConfig]); useEffect(() => { svc.GetConfig() @@ -58,7 +58,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { } }) .catch(console.error) - }, [props]); + }, [props, svc]); useEffect(() => { setError(undefined); @@ -89,7 +89,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { .catch(console.error); }); } - }, [handle, domain]); + }, [handle, domain, domainConfig, svc]); useEffect(() => { if (registerResponse && showInvoice) { @@ -111,7 +111,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { }, 2_000); return () => clearInterval(t); } - }, [registerResponse, showInvoice]) + }, [registerResponse, showInvoice, svc]) function mapError(e: ServiceErrorCode, t: string | null): string | undefined { let whyMap = new Map([ diff --git a/src/Element/NoteCreator.css b/src/Element/NoteCreator.css index c415376d..9dce9434 100644 --- a/src/Element/NoteCreator.css +++ b/src/Element/NoteCreator.css @@ -16,13 +16,8 @@ min-height: 40px; background-color: var(--note-bg); border-radius: 10px 10px 0 0; - max-width: -webkit-fill-available; - max-width: -moz-available; - max-width: fill-available; - min-width: 100%; - min-width: -webkit-fill-available; - min-width: -moz-available; - min-width: fill-available; + max-width: stretch; + min-width: stretch; } .note-creator .actions { diff --git a/src/Element/NoteFooter.tsx b/src/Element/NoteFooter.tsx index b8ddf1ac..37a1e89d 100644 --- a/src/Element/NoteFooter.tsx +++ b/src/Element/NoteFooter.tsx @@ -31,8 +31,8 @@ export default function NoteFooter(props: NoteFooterProps) { const [reply, setReply] = useState(false); const [tip, setTip] = useState(false); const isMine = ev.RootPubKey === login; - const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related]); - const reposts = useMemo(() => getReactions(related, ev.Id, EventKind.Repost), [related]); + const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related, ev]); + const reposts = useMemo(() => getReactions(related, ev.Id, EventKind.Repost), [related, ev]); const groupReactions = useMemo(() => { return reactions?.reduce((acc, { content }) => { let r = normalizeReaction(content); diff --git a/src/Element/NoteReaction.tsx b/src/Element/NoteReaction.tsx index f7d75122..dc5bd62f 100644 --- a/src/Element/NoteReaction.tsx +++ b/src/Element/NoteReaction.tsx @@ -16,7 +16,8 @@ export interface NoteReactionProps { root?: TaggedRawEvent } export default function NoteReaction(props: NoteReactionProps) { - const ev = useMemo(() => props["data-ev"] || new NEvent(props.data), [props.data, props["data-ev"]]) + const { ["data-ev"]: dataEv, data } = props; + const ev = useMemo(() => dataEv || new NEvent(data), [data, dataEv]) const refEvent = useMemo(() => { if (ev) { @@ -32,19 +33,6 @@ export default function NoteReaction(props: NoteReactionProps) { return null; } - function mapReaction(c: string) { - switch (c) { - case "+": return "❤️"; - case "-": return "👎"; - default: { - if (c.length === 0) { - return "❤️"; - } - return c; - } - } - } - /** * Some clients embed the reposted note in the content */ diff --git a/src/Element/Relay.tsx b/src/Element/Relay.tsx index 92b63fc4..4a00c1db 100644 --- a/src/Element/Relay.tsx +++ b/src/Element/Relay.tsx @@ -1,11 +1,11 @@ import "./Relay.css" -import { faPlug, faTrash, faSquareCheck, faSquareXmark, faWifi, faPlugCircleXmark, faGear } from "@fortawesome/free-solid-svg-icons"; +import { faPlug, faSquareCheck, faSquareXmark, faWifi, faPlugCircleXmark, faGear } from "@fortawesome/free-solid-svg-icons"; import useRelayState from "Feed/RelayState"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { removeRelay, setRelays } from "State/Login"; +import { setRelays } from "State/Login"; import { RootState } from "State/Store"; import { RelaySettings } from "Nostr/Connection"; import { useNavigate } from "react-router-dom"; diff --git a/src/Element/Timeline.tsx b/src/Element/Timeline.tsx index a907ca23..c837c399 100644 --- a/src/Element/Timeline.tsx +++ b/src/Element/Timeline.tsx @@ -1,5 +1,5 @@ import "./Timeline.css"; -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; import useTimelineFeed, { TimelineSubject } from "Feed/TimelineFeed"; import { TaggedRawEvent } from "Nostr"; import EventKind from "Nostr/EventKind"; @@ -23,17 +23,17 @@ export default function Timeline({ subject, postsOnly = false, method }: Timelin method }); - const filterPosts = (notes: TaggedRawEvent[]) => { - return [...notes].sort((a, b) => b.created_at - a.created_at)?.filter(a => postsOnly ? !a.tags.some(b => b[0] === "e") : true); - } + const filterPosts = useCallback((nts: TaggedRawEvent[]) => { + return [...nts].sort((a, b) => b.created_at - a.created_at)?.filter(a => postsOnly ? !a.tags.some(b => b[0] === "e") : true); + }, [postsOnly]); const mainFeed = useMemo(() => { return filterPosts(main.notes); - }, [main]); + }, [main, filterPosts]); const latestFeed = useMemo(() => { return filterPosts(latest.notes).filter(a => !mainFeed.some(b => b.id === a.id)); - }, [latest]); + }, [latest, mainFeed, filterPosts]); function eventElement(e: TaggedRawEvent) { switch (e.kind) { diff --git a/src/Feed/ProfileFeed.ts b/src/Feed/ProfileFeed.ts index a4c4028d..b439f814 100644 --- a/src/Feed/ProfileFeed.ts +++ b/src/Feed/ProfileFeed.ts @@ -1,5 +1,5 @@ import { useLiveQuery } from "dexie-react-hooks"; -import { useEffect, useMemo } from "react"; +import { useEffect } from "react"; import { db } from "Db"; import { MetadataCache } from "Db/User"; import { HexKey } from "Nostr"; diff --git a/src/Feed/Subscription.ts b/src/Feed/Subscription.ts index 052201e2..08966653 100644 --- a/src/Feed/Subscription.ts +++ b/src/Feed/Subscription.ts @@ -38,7 +38,8 @@ function notesReducer(state: NoteStore, arg: ReducerArg) { if (!Array.isArray(evs)) { evs = [evs]; } - evs = evs.filter(a => !state.notes.some(b => b.id === a.id)); + let existingIds = new Set(state.notes.map(a => a.id)); + evs = evs.filter(a => !existingIds.has(a.id)); if (evs.length === 0) { return state; } @@ -83,7 +84,7 @@ export default function useSubscription(sub: Subscriptions | null, options?: Use setSubDebounced(sub); }); } - }, [sub]); + }, [sub, options]); useEffect(() => { if (sub) { @@ -115,6 +116,7 @@ export default function useSubscription(sub: Subscriptions | null, options?: Use }; } }, [subDebounce]); + useEffect(() => { return debounce(DebounceMs, () => { setDebounceOutput(s => s += 1); diff --git a/src/Feed/ThreadFeed.ts b/src/Feed/ThreadFeed.ts index 81921197..aef2c024 100644 --- a/src/Feed/ThreadFeed.ts +++ b/src/Feed/ThreadFeed.ts @@ -36,7 +36,7 @@ export default function useThreadFeed(id: u256) { thisSub.AddSubscription(subRelated); return thisSub; - }, [trackingEvents]); + }, [trackingEvents, pref, id]); const main = useSubscription(sub, { leaveOpen: true }); diff --git a/src/Feed/TimelineFeed.ts b/src/Feed/TimelineFeed.ts index abfd05d1..fe259421 100644 --- a/src/Feed/TimelineFeed.ts +++ b/src/Feed/TimelineFeed.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { u256 } from "Nostr"; import EventKind from "Nostr/EventKind"; import { Subscriptions } from "Nostr/Subscriptions"; @@ -19,15 +19,15 @@ export interface TimelineSubject { export default function useTimelineFeed(subject: TimelineSubject, options: TimelineFeedOptions) { const now = unixNow(); - const [window, setWindow] = useState(60 * 60); + const [window] = useState(60 * 60); const [until, setUntil] = useState(now); const [since, setSince] = useState(now - window); const [trackingEvents, setTrackingEvent] = useState([]); const [trackingParentEvents, setTrackingParentEvents] = useState([]); const pref = useSelector(s => s.login.preferences); - function createSub() { - if (subject.type !== "global" && subject.items.length == 0) { + const createSub = useCallback(() => { + if (subject.type !== "global" && subject.items.length === 0) { return null; } @@ -49,7 +49,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel } } return sub; - } + }, [subject.type, subject.items]); const sub = useMemo(() => { let sub = createSub(); @@ -78,7 +78,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel } } return sub; - }, [subject.type, subject.items, until, since, window]); + }, [until, since, options.method, pref, createSub]); const main = useSubscription(sub, { leaveOpen: true }); @@ -90,7 +90,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel subLatest.Since = Math.floor(new Date().getTime() / 1000); } return subLatest; - }, [subject.type, subject.items]); + }, [pref, createSub]); const latest = useSubscription(subRealtime, { leaveOpen: true }); @@ -103,7 +103,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel sub.ETags = new Set(trackingEvents); } return sub ?? null; - }, [trackingEvents]); + }, [trackingEvents, pref, subject.type]); const others = useSubscription(subNext, { leaveOpen: true }); @@ -115,7 +115,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel return parents; } return null; - }, [trackingParentEvents]); + }, [trackingParentEvents, subject.type]); const parent = useSubscription(subParents); @@ -123,8 +123,10 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel if (main.store.notes.length > 0) { setTrackingEvent(s => { let ids = main.store.notes.map(a => a.id); - let temp = new Set([...s, ...ids]); - return Array.from(temp); + if(ids.some(a => !s.includes(a))) { + return Array.from(new Set([...s, ...ids])); + } + return s; }); let reposts = main.store.notes .filter(a => a.kind === EventKind.Repost && a.content === "") diff --git a/src/Nostr/Connection.ts b/src/Nostr/Connection.ts index 49819622..092b49f0 100644 --- a/src/Nostr/Connection.ts +++ b/src/Nostr/Connection.ts @@ -59,7 +59,7 @@ export default class Connection { this.Stats = new ConnectionStats(); this.StateHooks = new Map(); this.HasStateChange = true; - this.CurrentState = { + this.CurrentState = { connected: false, disconnects: 0, avgLatency: 0, @@ -67,7 +67,7 @@ export default class Connection { received: 0, send: 0 } - }; + } as StateSnapshot; this.LastState = Object.freeze({ ...this.CurrentState }); this.IsClosed = false; this.ReconnectTimer = null; diff --git a/src/Pages/NewUserPage.tsx b/src/Pages/NewUserPage.tsx index b3792b57..f268856f 100644 --- a/src/Pages/NewUserPage.tsx +++ b/src/Pages/NewUserPage.tsx @@ -24,7 +24,7 @@ export default function NewUserPage() { const sortedTwitterFollows = useMemo(() => { return follows.map(a => bech32ToHex(a)) .sort((a, b) => currentFollows.includes(a) ? 1 : -1); - }, [follows]); + }, [follows, currentFollows]); async function loadFollows() { setFollows([]); diff --git a/src/Pages/settings/Profile.tsx b/src/Pages/settings/Profile.tsx index f8ab59e0..482340da 100644 --- a/src/Pages/settings/Profile.tsx +++ b/src/Pages/settings/Profile.tsx @@ -31,7 +31,6 @@ export default function ProfileSettings() { const [about, setAbout] = useState(); const [website, setWebsite] = useState(); const [nip05, setNip05] = useState(); - const [lud06, setLud06] = useState(); const [lud16, setLud16] = useState(); const avatarPicture = (picture?.length ?? 0) === 0 ? Nostrich : picture @@ -45,7 +44,6 @@ export default function ProfileSettings() { setAbout(user.about); setWebsite(user.website); setNip05(user.nip05); - setLud06(user.lud06); setLud16(user.lud16); } }, [user]); diff --git a/src/State/Login.ts b/src/State/Login.ts index 23c0a4b6..ca009db4 100644 --- a/src/State/Login.ts +++ b/src/State/Login.ts @@ -1,9 +1,8 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import * as secp from '@noble/secp256k1'; import { DefaultRelays } from 'Const'; -import { HexKey, RawEvent, TaggedRawEvent } from 'Nostr'; +import { HexKey, TaggedRawEvent } from 'Nostr'; import { RelaySettings } from 'Nostr/Connection'; -import { useDispatch } from 'react-redux'; const PrivateKeyItem = "secret"; const PublicKeyItem = "pubkey"; @@ -182,7 +181,7 @@ const LoginSlice = createSlice({ let filtered = new Map(); for (let [k, v] of Object.entries(relays)) { if (k.startsWith("wss://") || k.startsWith("ws://")) { - filtered.set(k, v); + filtered.set(k, v as RelaySettings); } } diff --git a/src/Util.ts b/src/Util.ts index 2e68e50b..abd73dde 100644 --- a/src/Util.ts +++ b/src/Util.ts @@ -1,6 +1,6 @@ import * as secp from "@noble/secp256k1"; import { bech32 } from "bech32"; -import { HexKey, RawEvent, TaggedRawEvent, u256 } from "Nostr"; +import { HexKey, TaggedRawEvent, u256 } from "Nostr"; import EventKind from "Nostr/EventKind"; export async function openFile(): Promise { @@ -65,7 +65,7 @@ export function eventLink(hex: u256) { * @param {string} hex */ export function hexToBech32(hrp: string, hex: string) { - if (typeof hex !== "string" || hex.length === 0 || hex.length % 2 != 0) { + if (typeof hex !== "string" || hex.length === 0 || hex.length % 2 !== 0) { return ""; } diff --git a/src/index.css b/src/index.css index d4328f8a..8556f0db 100644 --- a/src/index.css +++ b/src/index.css @@ -228,16 +228,12 @@ textarea:placeholder { .w-max { width: 100%; - width: -moz-available; - width: -webkit-fill-available; - width: fill-available; + width: stretch; } .w-max-w { max-width: 100%; - max-width: -moz-available; - max-width: -webkit-fill-available; - max-width: fill-available; + max-width: stretch; } a { @@ -267,7 +263,7 @@ div.form-group>div:nth-child(1) { div.form-group>div:nth-child(2) { display: flex; flex-grow: 1; - justify-content: end; + justify-content: flex-end; } div.form-group>div:nth-child(2) input { -- 2.45.2