From b9a0c1f0f6fdf10000d1ccd0e1b087d30bfe1678 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Mar 2024 20:03:18 -0600 Subject: [PATCH] Add NostrContext to manage connection to Nostr relay --- src/api/hooks/nostr/useSignerStream.ts | 51 ++++++++++---------------- src/contexts/nostr-context.tsx | 48 ++++++++++++++++++++++++ src/features/nostr/NKeyStorage.ts | 3 +- src/init/soapbox.tsx | 13 ++++--- 4 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 src/contexts/nostr-context.tsx diff --git a/src/api/hooks/nostr/useSignerStream.ts b/src/api/hooks/nostr/useSignerStream.ts index b6c2c8a2f0..460cfd9d6f 100644 --- a/src/api/hooks/nostr/useSignerStream.ts +++ b/src/api/hooks/nostr/useSignerStream.ts @@ -1,23 +1,12 @@ import { type NostrEvent } from '@soapbox/nspec'; -import { NiceRelay } from 'nostr-machina'; -import { useEffect, useMemo } from 'react'; +import { useEffect } from 'react'; -import { signer } from 'soapbox/features/nostr/sign'; -import { useInstance } from 'soapbox/hooks'; +import { useNostr } from 'soapbox/contexts/nostr-context'; import { connectRequestSchema, nwcRequestSchema } from 'soapbox/schemas/nostr'; import { jsonSchema } from 'soapbox/schemas/utils'; function useSignerStream() { - const instance = useInstance(); - - const relayUrl = instance.nostr?.relay; - const pubkey = instance.nostr?.pubkey; - - const relay = useMemo(() => { - if (relayUrl && signer) { - return new NiceRelay(relayUrl); - } - }, [relayUrl, !!signer]); + const { relay, pubkey, signer } = useNostr(); async function handleConnectEvent(event: NostrEvent) { if (!relay || !pubkey || !signer) return; @@ -42,7 +31,7 @@ function useSignerStream() { created_at: Math.floor(Date.now() / 1000), }); - relay.send(['EVENT', respEvent]); + relay.event(respEvent); } async function handleWalletEvent(event: NostrEvent) { @@ -61,28 +50,26 @@ function useSignerStream() { await window.webln?.sendPayment(reqMsg.data.params.invoice); } + async function handleEvent(event: NostrEvent) { + switch (event.kind) { + case 24133: + await handleConnectEvent(event); + break; + case 23194: + await handleWalletEvent(event); + break; + } + } + useEffect(() => { if (!relay || !pubkey) return; - const sub = relay.req([{ kinds: [24133, 23194], authors: [pubkey], limit: 0 }]); - const readEvents = async () => { - for await (const event of sub) { - switch (event.kind) { - case 24133: - await handleConnectEvent(event); - break; - case 23194: - await handleWalletEvent(event); - break; - } + (async() => { + for await (const msg of relay.req([{ kinds: [24133, 23194], authors: [pubkey], limit: 0 }])) { + if (msg[0] === 'EVENT') handleEvent(msg[2]); } - }; + })(); - readEvents(); - - return () => { - relay?.close(); - }; }, [relay, pubkey]); } diff --git a/src/contexts/nostr-context.tsx b/src/contexts/nostr-context.tsx new file mode 100644 index 0000000000..de02a0a854 --- /dev/null +++ b/src/contexts/nostr-context.tsx @@ -0,0 +1,48 @@ +import { NRelay, NRelay1, NostrSigner } from '@soapbox/nspec'; +import React, { createContext, useContext, useState, useEffect } from 'react'; + +import { signer } from 'soapbox/features/nostr/sign'; +import { useInstance } from 'soapbox/hooks/useInstance'; + +interface NostrContextType { + relay?: NRelay; + pubkey?: string; + signer?: NostrSigner; +} + +const NostrContext = createContext(undefined); + +interface NostrProviderProps { + children: React.ReactNode; +} + +export const NostrProvider: React.FC = ({ children }) => { + const instance = useInstance(); + const [relay, setRelay] = useState(); + + const url = instance.nostr?.relay; + const pubkey = instance.nostr?.pubkey; + + useEffect(() => { + if (url) { + setRelay(new NRelay1(url)); + } + return () => { + relay?.close(); + }; + }, [url]); + + return ( + + {children} + + ); +}; + +export const useNostr = () => { + const context = useContext(NostrContext); + if (context === undefined) { + throw new Error('useNostr must be used within a NostrProvider'); + } + return context; +}; diff --git a/src/features/nostr/NKeyStorage.ts b/src/features/nostr/NKeyStorage.ts index 1b0ebd1702..d5ed117b99 100644 --- a/src/features/nostr/NKeyStorage.ts +++ b/src/features/nostr/NKeyStorage.ts @@ -1,5 +1,6 @@ import { NSchema as n, NostrSigner, NSecSigner } from '@soapbox/nspec'; import { getPublicKey, nip19 } from 'nostr-tools'; +import { z } from 'zod'; import { lockStorageKey } from 'soapbox/utils/storage'; @@ -34,7 +35,7 @@ export class NKeyStorage implements ReadonlyMap { } } - #dataSchema() { + #dataSchema(): z.ZodType<`nsec1${string}`[]> { return n.json().pipe(n.bech32('nsec').array()); } diff --git a/src/init/soapbox.tsx b/src/init/soapbox.tsx index e8b657d282..2b3cce2283 100644 --- a/src/init/soapbox.tsx +++ b/src/init/soapbox.tsx @@ -2,6 +2,7 @@ import { QueryClientProvider } from '@tanstack/react-query'; import React from 'react'; import { Provider } from 'react-redux'; +import { NostrProvider } from 'soapbox/contexts/nostr-context'; import { StatProvider } from 'soapbox/contexts/stat-context'; import { createGlobals } from 'soapbox/globals'; import { queryClient } from 'soapbox/queries/client'; @@ -29,11 +30,13 @@ const Soapbox: React.FC = () => { - - - - - + + + + + + +