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 { type NostrEvent } from '@soapbox/nspec';
|
||||||
import { NiceRelay } from 'nostr-machina';
|
import { useEffect } from 'react';
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
|
|
||||||
import { signer } from 'soapbox/features/nostr/sign';
|
import { useNostr } from 'soapbox/contexts/nostr-context';
|
||||||
import { useInstance } from 'soapbox/hooks';
|
|
||||||
import { connectRequestSchema, nwcRequestSchema } from 'soapbox/schemas/nostr';
|
import { connectRequestSchema, nwcRequestSchema } from 'soapbox/schemas/nostr';
|
||||||
import { jsonSchema } from 'soapbox/schemas/utils';
|
import { jsonSchema } from 'soapbox/schemas/utils';
|
||||||
|
|
||||||
function useSignerStream() {
|
function useSignerStream() {
|
||||||
const instance = useInstance();
|
const { relay, pubkey, signer } = useNostr();
|
||||||
|
|
||||||
const relayUrl = instance.nostr?.relay;
|
|
||||||
const pubkey = instance.nostr?.pubkey;
|
|
||||||
|
|
||||||
const relay = useMemo(() => {
|
|
||||||
if (relayUrl && signer) {
|
|
||||||
return new NiceRelay(relayUrl);
|
|
||||||
}
|
|
||||||
}, [relayUrl, !!signer]);
|
|
||||||
|
|
||||||
async function handleConnectEvent(event: NostrEvent) {
|
async function handleConnectEvent(event: NostrEvent) {
|
||||||
if (!relay || !pubkey || !signer) return;
|
if (!relay || !pubkey || !signer) return;
|
||||||
|
@ -42,7 +31,7 @@ function useSignerStream() {
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
});
|
});
|
||||||
|
|
||||||
relay.send(['EVENT', respEvent]);
|
relay.event(respEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleWalletEvent(event: NostrEvent) {
|
async function handleWalletEvent(event: NostrEvent) {
|
||||||
|
@ -61,28 +50,26 @@ function useSignerStream() {
|
||||||
await window.webln?.sendPayment(reqMsg.data.params.invoice);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!relay || !pubkey) return;
|
if (!relay || !pubkey) return;
|
||||||
const sub = relay.req([{ kinds: [24133, 23194], authors: [pubkey], limit: 0 }]);
|
|
||||||
|
|
||||||
const readEvents = async () => {
|
(async() => {
|
||||||
for await (const event of sub) {
|
for await (const msg of relay.req([{ kinds: [24133, 23194], authors: [pubkey], limit: 0 }])) {
|
||||||
switch (event.kind) {
|
if (msg[0] === 'EVENT') handleEvent(msg[2]);
|
||||||
case 24133:
|
|
||||||
await handleConnectEvent(event);
|
|
||||||
break;
|
|
||||||
case 23194:
|
|
||||||
await handleWalletEvent(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
})();
|
||||||
|
|
||||||
readEvents();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
relay?.close();
|
|
||||||
};
|
|
||||||
}, [relay, pubkey]);
|
}, [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 { NSchema as n, NostrSigner, NSecSigner } from '@soapbox/nspec';
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { lockStorageKey } from 'soapbox/utils/storage';
|
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());
|
return n.json().pipe(n.bech32('nsec').array());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import { NostrProvider } from 'soapbox/contexts/nostr-context';
|
||||||
import { StatProvider } from 'soapbox/contexts/stat-context';
|
import { StatProvider } from 'soapbox/contexts/stat-context';
|
||||||
import { createGlobals } from 'soapbox/globals';
|
import { createGlobals } from 'soapbox/globals';
|
||||||
import { queryClient } from 'soapbox/queries/client';
|
import { queryClient } from 'soapbox/queries/client';
|
||||||
|
@ -29,11 +30,13 @@ const Soapbox: React.FC = () => {
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<StatProvider>
|
<StatProvider>
|
||||||
<SoapboxHead>
|
<NostrProvider>
|
||||||
<SoapboxLoad>
|
<SoapboxHead>
|
||||||
<SoapboxMount />
|
<SoapboxLoad>
|
||||||
</SoapboxLoad>
|
<SoapboxMount />
|
||||||
</SoapboxHead>
|
</SoapboxLoad>
|
||||||
|
</SoapboxHead>
|
||||||
|
</NostrProvider>
|
||||||
</StatProvider>
|
</StatProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
Loading…
Reference in a new issue