Hook up DVM request
This commit is contained in:
parent
338d0a7b3e
commit
37cc1e88f6
3 changed files with 163 additions and 3 deletions
59
src/features/nostr/hooks/useNostrReq.ts
Normal file
59
src/features/nostr/hooks/useNostrReq.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { NostrEvent, NostrFilter } from '@soapbox/nspec';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useNostr } from 'soapbox/contexts/nostr-context';
|
||||
|
||||
/** Streams events from the relay for the given filters. */
|
||||
export function useNostrReq(filters: NostrFilter[]): { events: NostrEvent[]; eose: boolean; closed: boolean } {
|
||||
const { relay } = useNostr();
|
||||
|
||||
const [events, setEvents] = useState<NostrEvent[]>([]);
|
||||
const [closed, setClosed] = useState(false);
|
||||
const [eose, setEose] = useState(false);
|
||||
|
||||
const controller = useRef<AbortController>(new AbortController());
|
||||
const signal = controller.current.signal;
|
||||
const value = useValue(filters);
|
||||
|
||||
useEffect(() => {
|
||||
if (relay && value.length) {
|
||||
(async () => {
|
||||
for await (const msg of relay.req(value, { signal })) {
|
||||
if (msg[0] === 'EVENT') {
|
||||
setEvents((prev) => [msg[2], ...prev]);
|
||||
} else if (msg[0] === 'EOSE') {
|
||||
setEose(true);
|
||||
} else if (msg[0] === 'CLOSED') {
|
||||
setClosed(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
return () => {
|
||||
controller.current.abort();
|
||||
controller.current = new AbortController();
|
||||
setEose(false);
|
||||
setClosed(false);
|
||||
};
|
||||
}, [relay, value]);
|
||||
|
||||
return {
|
||||
events,
|
||||
eose,
|
||||
closed,
|
||||
};
|
||||
}
|
||||
|
||||
/** Preserves the memory reference of a value across re-renders. */
|
||||
function useValue<T>(value: T): T {
|
||||
const ref = useRef<T>(value);
|
||||
|
||||
if (!isEqual(ref.current, value)) {
|
||||
ref.current = value;
|
||||
}
|
||||
|
||||
return ref.current;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import { NRelay, NostrEvent, NostrSigner } from '@soapbox/nspec';
|
||||
|
||||
interface DittoSignupRequestOpts {
|
||||
dvm: string;
|
||||
url: string;
|
||||
relay: NRelay;
|
||||
signer: NostrSigner;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export class DittoSignup {
|
||||
|
||||
static async request(opts: DittoSignupRequestOpts): Promise<NostrEvent> {
|
||||
const { dvm, url, relay, signer, signal } = opts;
|
||||
|
||||
const pubkey = await signer.getPublicKey();
|
||||
const event = await signer.signEvent({
|
||||
kind: 5951,
|
||||
content: '',
|
||||
tags: [
|
||||
['i', url, 'text'],
|
||||
['p', dvm],
|
||||
],
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
|
||||
const subscription = relay.req(
|
||||
[{ kinds: [7000, 6951], authors: [dvm], '#p': [pubkey], '#e': [event.id] }],
|
||||
{ signal },
|
||||
);
|
||||
|
||||
await relay.event(event, { signal });
|
||||
|
||||
for await (const msg of subscription) {
|
||||
if (msg[0] === 'EVENT') {
|
||||
return msg[2];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('DittoSignup: no response');
|
||||
}
|
||||
|
||||
static async check(opts: Omit<DittoSignupRequestOpts, 'url'>): Promise<NostrEvent | undefined> {
|
||||
const { dvm, relay, signer, signal } = opts;
|
||||
|
||||
const pubkey = await signer.getPublicKey();
|
||||
const [event] = await relay.query(
|
||||
[{ kinds: [7000, 6951], authors: [dvm], '#p': [pubkey] }],
|
||||
{ signal },
|
||||
);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import { NSchema as n } from '@soapbox/nspec';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useAccount } from 'soapbox/api/hooks';
|
||||
import { Avatar, Text, Stack, Emoji, Button, Tooltip, Modal } from 'soapbox/components/ui';
|
||||
import { useNostr } from 'soapbox/contexts/nostr-context';
|
||||
import { useNostrReq } from 'soapbox/features/nostr/hooks/useNostrReq';
|
||||
import ModalLoading from 'soapbox/features/ui/components/modal-loading';
|
||||
import { useInstance } from 'soapbox/hooks';
|
||||
|
||||
|
@ -16,9 +18,41 @@ interface IAccountStep {
|
|||
}
|
||||
|
||||
const AccountStep: React.FC<IAccountStep> = ({ accountId, setStep, onClose }) => {
|
||||
const { relay, signer } = useNostr();
|
||||
const { account } = useAccount(accountId);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const instance = useInstance();
|
||||
|
||||
const { events } = useNostrReq((instance.nostr && account?.nostr) ? [{
|
||||
kinds: [7000, 6951],
|
||||
authors: [instance.nostr.pubkey],
|
||||
'#p': [account.nostr.pubkey],
|
||||
}] : []);
|
||||
|
||||
const success = events.find((event) => event.kind === 6951);
|
||||
const feedback = events.find((event) => event.kind === 7000);
|
||||
|
||||
const handleJoin = async () => {
|
||||
if (!relay || !signer || !instance.nostr) return;
|
||||
setSubmitting(true);
|
||||
|
||||
const event = await signer.signEvent({
|
||||
kind: 5951,
|
||||
content: '',
|
||||
tags: [
|
||||
['i', instance.nostr.relay, 'text'],
|
||||
['p', instance.nostr.pubkey, 'text'],
|
||||
],
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
|
||||
await relay.event(event);
|
||||
|
||||
setSubmitting(false);
|
||||
setSubmitted(true);
|
||||
};
|
||||
|
||||
const username = useMemo(
|
||||
() => n.bech32().safeParse(account?.acct).success ? account?.acct.slice(0, 13) : account?.acct,
|
||||
[account?.acct],
|
||||
|
@ -63,11 +97,23 @@ const AccountStep: React.FC<IAccountStep> = ({ accountId, setStep, onClose }) =>
|
|||
<Emoji className='h-16 w-16' emoji='🫂' />
|
||||
|
||||
<Text align='center' className='max-w-72'>
|
||||
You need an account on {instance.title} to continue.
|
||||
{(success || feedback) ? (
|
||||
JSON.stringify(success || feedback, null, 2)
|
||||
) : (
|
||||
<>You need an account on {instance.title} to continue.</>
|
||||
)}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Button theme='accent' size='lg' block>Join</Button>
|
||||
<Button
|
||||
theme='accent'
|
||||
size='lg'
|
||||
onClick={handleJoin}
|
||||
disabled={submitting || submitted}
|
||||
block
|
||||
>
|
||||
Join
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
|
Loading…
Reference in a new issue