From 6fe9a27041b709ff449ab56bc2f4ae24ed2aedf1 Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 4 May 2023 14:16:58 +0100 Subject: [PATCH] feat: rebroadcast --- packages/app/src/Element/NoteCreator.tsx | 68 ++++++++++++++++- packages/app/src/Element/NoteFooter.tsx | 35 ++++++++- packages/app/src/Element/ReBroadcaster.tsx | 87 ++++++++++++++++++++++ packages/app/src/Element/messages.ts | 1 + packages/app/src/State/NoteCreator.ts | 6 ++ packages/app/src/State/ReBroadcast.ts | 34 +++++++++ packages/app/src/State/Store.ts | 2 + packages/app/src/System/index.ts | 16 +++- 8 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 packages/app/src/Element/ReBroadcaster.tsx create mode 100644 packages/app/src/State/ReBroadcast.ts diff --git a/packages/app/src/Element/NoteCreator.tsx b/packages/app/src/Element/NoteCreator.tsx index b6c9ad6f..9c49a168 100644 --- a/packages/app/src/Element/NoteCreator.tsx +++ b/packages/app/src/Element/NoteCreator.tsx @@ -18,6 +18,7 @@ import { setActive, setPreview, setShowAdvanced, + setSelectedCustomRelays, setZapForward, setSensitive, reset, @@ -34,6 +35,7 @@ import { EventBuilder } from "System"; import { Menu, MenuItem } from "@szhsin/react-menu"; import { LoginStore } from "Login"; import { getCurrentSubscription } from "Subscription"; +import useLogin from "Hooks/useLogin"; interface NotePreviewProps { note: TaggedRawEvent; @@ -55,11 +57,25 @@ export function NoteCreator() { const { formatMessage } = useIntl(); const publisher = useEventPublisher(); const uploader = useFileUpload(); - const { note, zapForward, sensitive, pollOptions, replyTo, otherEvents, preview, active, show, showAdvanced, error } = - useSelector((s: RootState) => s.noteCreator); + const { + note, + zapForward, + sensitive, + pollOptions, + replyTo, + otherEvents, + preview, + active, + show, + showAdvanced, + selectedCustomRelays, + error, + } = useSelector((s: RootState) => s.noteCreator); const [uploadInProgress, setUploadInProgress] = useState(false); const dispatch = useDispatch(); const sub = getCurrentSubscription(LoginStore.allSubscriptions()); + const login = useLogin(); + const relays = login.relays; async function sendNote() { if (note && publisher) { @@ -96,10 +112,12 @@ export function NoteCreator() { return eb; }; const ev = replyTo ? await publisher.reply(replyTo, note, hk) : await publisher.note(note, hk); - publisher.broadcast(ev); + if (selectedCustomRelays) publisher.broadcastAll(ev, selectedCustomRelays); + else publisher.broadcast(ev); dispatch(reset()); for (const oe of otherEvents) { - publisher.broadcast(oe); + if (selectedCustomRelays) publisher.broadcastAll(oe, selectedCustomRelays); + else publisher.broadcast(oe); } dispatch(reset()); } @@ -233,6 +251,41 @@ export function NoteCreator() { } } + function renderRelayCustomisation() { + return ( +
+ {Object.keys(relays.item || {}) + .filter(el => relays.item[el].write) + .map((r, i, a) => ( +
+
+
{r}
+
+
+ + dispatch( + setSelectedCustomRelays( + // set false if all relays selected + e.target.checked && selectedCustomRelays && selectedCustomRelays.length == a.length - 1 + ? false + : // otherwise return selectedCustomRelays with target relay added / removed + a.filter(el => + el === r ? e.target.checked : !selectedCustomRelays || selectedCustomRelays.includes(el) + ) + ) + ) + } + /> +
+
+ ))} +
+ ); + } + function listAccounts() { return LoginStore.getSessions().map(a => ( +

+ +

+

+ +

+ {renderRelayCustomisation()}

diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx index 22096dee..50cdc9bb 100644 --- a/packages/app/src/Element/NoteFooter.tsx +++ b/packages/app/src/Element/NoteFooter.tsx @@ -12,12 +12,18 @@ import { formatShort } from "Number"; import useEventPublisher from "Feed/EventPublisher"; import { bech32ToHex, delay, normalizeReaction, unwrap } from "Util"; import { NoteCreator } from "Element/NoteCreator"; +import { ReBroadcaster } from "Element/ReBroadcaster"; import Reactions from "Element/Reactions"; import SendSats from "Element/SendSats"; import { ParsedZap, ZapsSummary } from "Element/Zap"; import { useUserProfile } from "Hooks/useUserProfile"; import { RootState } from "State/Store"; import { setReplyTo, setShow, reset } from "State/NoteCreator"; +import { + setNote as setReBroadcastNote, + setShow as setReBroadcastShow, + reset as resetReBroadcast, +} from "State/ReBroadcast"; import useModeration from "Hooks/useModeration"; import { SnortPubKey, TranslateHost } from "Const"; import { LNURL } from "LNURL"; @@ -70,8 +76,11 @@ export default function NoteFooter(props: NoteFooterProps) { const interactionCache = useInteractionCache(publicKey, ev.id); const publisher = useEventPublisher(); const showNoteCreatorModal = useSelector((s: RootState) => s.noteCreator.show); + const showReBroadcastModal = useSelector((s: RootState) => s.reBroadcast.show); + const reBroadcastNote = useSelector((s: RootState) => s.reBroadcast.note); const replyTo = useSelector((s: RootState) => s.noteCreator.replyTo); const willRenderNoteCreator = showNoteCreatorModal && replyTo?.id === ev.id; + const willRenderReBroadcast = showReBroadcastModal && reBroadcastNote && reBroadcastNote?.id === ev.id; const [tip, setTip] = useState(false); const [zapping, setZapping] = useState(false); const walletState = useWallet(); @@ -361,10 +370,18 @@ export default function NoteFooter(props: NoteFooterProps) {
)} - block(ev.pubkey)}> - - - + {ev.pubkey === publicKey && ( + + + + + )} + {ev.pubkey !== publicKey && ( + block(ev.pubkey)}> + + + + )} translate()}> @@ -394,6 +411,15 @@ export default function NoteFooter(props: NoteFooterProps) { dispatch(setShow(!showNoteCreatorModal)); }; + const handleReBroadcastButtonClick = () => { + if (reBroadcastNote?.id !== ev.id) { + dispatch(resetReBroadcast()); + } + + dispatch(setReBroadcastNote(ev)); + dispatch(setReBroadcastShow(!showReBroadcastModal)); + }; + return ( <>
@@ -415,6 +441,7 @@ export default function NoteFooter(props: NoteFooterProps) {
{willRenderNoteCreator && } + {willRenderReBroadcast && } s.reBroadcast); + const dispatch = useDispatch(); + + async function sendReBroadcast() { + if (note && publisher) { + if (selectedCustomRelays) publisher.broadcastAll(note, selectedCustomRelays); + else publisher.broadcast(note); + dispatch(reset()); + } + } + + function cancel() { + dispatch(reset()); + } + + function onSubmit(ev: React.MouseEvent) { + ev.stopPropagation(); + sendReBroadcast().catch(console.warn); + } + + const login = useLogin(); + const relays = login.relays; + + function renderRelayCustomisation() { + return ( +
+ {Object.keys(relays.item || {}) + .filter(el => relays.item[el].write) + .map((r, i, a) => ( +
+
+
{r}
+
+
+ + dispatch( + setSelectedCustomRelays( + // set false if all relays selected + e.target.checked && selectedCustomRelays && selectedCustomRelays.length == a.length - 1 + ? false + : // otherwise return selectedCustomRelays with target relay added / removed + a.filter(el => + el === r ? e.target.checked : !selectedCustomRelays || selectedCustomRelays.includes(el) + ) + ) + ) + } + /> +
+
+ ))} +
+ ); + } + + return ( + <> + {show && ( + dispatch(setShow(false))}> + {renderRelayCustomisation()} +
+ + +
+
+ )} + + ); +} diff --git a/packages/app/src/Element/messages.ts b/packages/app/src/Element/messages.ts index eeee0682..2cae9eb5 100644 --- a/packages/app/src/Element/messages.ts +++ b/packages/app/src/Element/messages.ts @@ -104,4 +104,5 @@ export default defineMessages({ ConfirmUnbookmark: { defaultMessage: "Are you sure you want to remove this note from bookmarks?" }, ConfirmUnpin: { defaultMessage: "Are you sure you want to unpin this note?" }, ReactionsLink: { defaultMessage: "{n} Reactions" }, + ReBroadcast: { defaultMessage: "Broadcast Again" }, }); diff --git a/packages/app/src/State/NoteCreator.ts b/packages/app/src/State/NoteCreator.ts index 123966ff..43aaee19 100644 --- a/packages/app/src/State/NoteCreator.ts +++ b/packages/app/src/State/NoteCreator.ts @@ -9,6 +9,7 @@ interface NoteCreatorStore { preview?: RawEvent; replyTo?: TaggedRawEvent; showAdvanced: boolean; + selectedCustomRelays: false | Array; zapForward: string; sensitive: string; pollOptions?: Array; @@ -21,6 +22,7 @@ const InitState: NoteCreatorStore = { error: "", active: false, showAdvanced: false, + selectedCustomRelays: false, zapForward: "", sensitive: "", otherEvents: [], @@ -51,6 +53,9 @@ const NoteCreatorSlice = createSlice({ setShowAdvanced: (state, action: PayloadAction) => { state.showAdvanced = action.payload; }, + setSelectedCustomRelays: (state, action: PayloadAction>) => { + state.selectedCustomRelays = action.payload; + }, setZapForward: (state, action: PayloadAction) => { state.zapForward = action.payload; }, @@ -75,6 +80,7 @@ export const { setPreview, setReplyTo, setShowAdvanced, + setSelectedCustomRelays, setZapForward, setSensitive, setPollOptions, diff --git a/packages/app/src/State/ReBroadcast.ts b/packages/app/src/State/ReBroadcast.ts new file mode 100644 index 00000000..acb1ddec --- /dev/null +++ b/packages/app/src/State/ReBroadcast.ts @@ -0,0 +1,34 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { RawEvent } from "@snort/nostr"; + +interface ReBroadcastStore { + show: boolean; + selectedCustomRelays: false | Array; + note?: RawEvent; +} + +const InitState: ReBroadcastStore = { + show: false, + selectedCustomRelays: false, +}; + +const ReBroadcastSlice = createSlice({ + name: "ReBroadcast", + initialState: InitState, + reducers: { + setShow: (state, action: PayloadAction) => { + state.show = action.payload; + }, + setNote: (state, action: PayloadAction) => { + state.note = action.payload; + }, + setSelectedCustomRelays: (state, action: PayloadAction>) => { + state.selectedCustomRelays = action.payload; + }, + reset: () => InitState, + }, +}); + +export const { setShow, setNote, setSelectedCustomRelays, reset } = ReBroadcastSlice.actions; + +export const reducer = ReBroadcastSlice.reducer; diff --git a/packages/app/src/State/Store.ts b/packages/app/src/State/Store.ts index 24132725..57bec805 100644 --- a/packages/app/src/State/Store.ts +++ b/packages/app/src/State/Store.ts @@ -1,9 +1,11 @@ import { configureStore } from "@reduxjs/toolkit"; import { reducer as NoteCreatorReducer } from "State/NoteCreator"; +import { reducer as ReBroadcastReducer } from "State/ReBroadcast"; const store = configureStore({ reducer: { noteCreator: NoteCreatorReducer, + reBroadcast: ReBroadcastReducer, }, }); diff --git a/packages/app/src/System/index.ts b/packages/app/src/System/index.ts index b4546173..42a43aef 100644 --- a/packages/app/src/System/index.ts +++ b/packages/app/src/System/index.ts @@ -267,10 +267,18 @@ export class NostrSystem { * Write an event to a relay then disconnect */ async WriteOnceToRelay(address: string, ev: RawEvent) { - const c = new Connection(address, { write: true, read: false }, this.HandleAuth, true); - await c.Connect(); - await c.SendAsync(ev); - c.Close(); + return new Promise((resolve, reject) => { + const c = new Connection(address, { write: true, read: false }, this.HandleAuth, true); + + const t = setTimeout(reject, 5_000); + c.OnConnected = async () => { + clearTimeout(t); + await c.SendAsync(ev); + c.Close(); + resolve(); + }; + c.Connect(); + }); } #changed() {