Add NostrContext to manage connection to Nostr relay
This commit is contained in:
parent
fc0de1bc49
commit
b9a0c1f0f6
4 changed files with 77 additions and 38 deletions
|
@ -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]);
|
||||
}
|
||||
|
||||
|
|
48
src/contexts/nostr-context.tsx
Normal file
48
src/contexts/nostr-context.tsx
Normal file
|
@ -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<NostrContextType | undefined>(undefined);
|
||||
|
||||
interface NostrProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const NostrProvider: React.FC<NostrProviderProps> = ({ children }) => {
|
||||
const instance = useInstance();
|
||||
const [relay, setRelay] = useState<NRelay1>();
|
||||
|
||||
const url = instance.nostr?.relay;
|
||||
const pubkey = instance.nostr?.pubkey;
|
||||
|
||||
useEffect(() => {
|
||||
if (url) {
|
||||
setRelay(new NRelay1(url));
|
||||
}
|
||||
return () => {
|
||||
relay?.close();
|
||||
};
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<NostrContext.Provider value={{ relay, pubkey, signer }}>
|
||||
{children}
|
||||
</NostrContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useNostr = () => {
|
||||
const context = useContext(NostrContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useNostr must be used within a NostrProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
|
@ -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<string, NostrSigner> {
|
|||
}
|
||||
}
|
||||
|
||||
#dataSchema() {
|
||||
#dataSchema(): z.ZodType<`nsec1${string}`[]> {
|
||||
return n.json().pipe(n.bech32('nsec').array());
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = () => {
|
|||
<Provider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StatProvider>
|
||||
<SoapboxHead>
|
||||
<SoapboxLoad>
|
||||
<SoapboxMount />
|
||||
</SoapboxLoad>
|
||||
</SoapboxHead>
|
||||
<NostrProvider>
|
||||
<SoapboxHead>
|
||||
<SoapboxLoad>
|
||||
<SoapboxMount />
|
||||
</SoapboxLoad>
|
||||
</SoapboxHead>
|
||||
</NostrProvider>
|
||||
</StatProvider>
|
||||
</QueryClientProvider>
|
||||
</Provider>
|
||||
|
|
Loading…
Reference in a new issue