Wait for the signer before loading the UI (fix Ditto race condition)

environments/review-main-yi2y9f/deployments/4697^2
Alex Gleason 3 months ago
parent bbdc224010
commit 0b1446c655
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7

@ -8,7 +8,10 @@ const secretStorageKey = 'soapbox:nip46:secret';
sessionStorage.setItem(secretStorageKey, crypto.randomUUID()); sessionStorage.setItem(secretStorageKey, crypto.randomUUID());
function useSignerStream() { function useSignerStream() {
const { relay, signer } = useNostr(); const [isSubscribed, setIsSubscribed] = useState(false);
const [isSubscribing, setIsSubscribing] = useState(true);
const { relay, signer, hasNostr } = useNostr();
const [pubkey, setPubkey] = useState<string | undefined>(undefined); const [pubkey, setPubkey] = useState<string | undefined>(undefined);
const authStorageKey = `soapbox:nostr:auth:${pubkey}`; const authStorageKey = `soapbox:nostr:auth:${pubkey}`;
@ -16,7 +19,7 @@ function useSignerStream() {
useEffect(() => { useEffect(() => {
let isCancelled = false; let isCancelled = false;
if (signer) { if (signer && hasNostr) {
signer.getPublicKey().then((newPubkey) => { signer.getPublicKey().then((newPubkey) => {
if (!isCancelled) { if (!isCancelled) {
setPubkey(newPubkey); setPubkey(newPubkey);
@ -27,7 +30,7 @@ function useSignerStream() {
return () => { return () => {
isCancelled = true; isCancelled = true;
}; };
}, [signer]); }, [signer, hasNostr]);
useEffect(() => { useEffect(() => {
if (!relay || !signer || !pubkey) return; if (!relay || !signer || !pubkey) return;
@ -39,6 +42,10 @@ function useSignerStream() {
localStorage.setItem(authStorageKey, authorizedPubkey); localStorage.setItem(authStorageKey, authorizedPubkey);
sessionStorage.setItem(secretStorageKey, crypto.randomUUID()); sessionStorage.setItem(secretStorageKey, crypto.randomUUID());
}, },
onSubscribed() {
setIsSubscribed(true);
setIsSubscribing(false);
},
authorizedPubkey: localStorage.getItem(authStorageKey) ?? undefined, authorizedPubkey: localStorage.getItem(authStorageKey) ?? undefined,
getSecret: () => sessionStorage.getItem(secretStorageKey)!, getSecret: () => sessionStorage.getItem(secretStorageKey)!,
}); });
@ -47,6 +54,11 @@ function useSignerStream() {
connect.close(); connect.close();
}; };
}, [relay, signer, pubkey]); }, [relay, signer, pubkey]);
return {
isSubscribed,
isSubscribing,
};
} }
export { useSignerStream }; export { useSignerStream };

@ -8,6 +8,8 @@ import { useInstance } from 'soapbox/hooks/useInstance';
interface NostrContextType { interface NostrContextType {
relay?: NRelay; relay?: NRelay;
signer?: NostrSigner; signer?: NostrSigner;
hasNostr: boolean;
isRelayOpen: boolean;
} }
const NostrContext = createContext<NostrContextType | undefined>(undefined); const NostrContext = createContext<NostrContextType | undefined>(undefined);
@ -18,7 +20,10 @@ interface NostrProviderProps {
export const NostrProvider: React.FC<NostrProviderProps> = ({ children }) => { export const NostrProvider: React.FC<NostrProviderProps> = ({ children }) => {
const instance = useInstance(); const instance = useInstance();
const hasNostr = !!instance.nostr;
const [relay, setRelay] = useState<NRelay1>(); const [relay, setRelay] = useState<NRelay1>();
const [isRelayOpen, setIsRelayOpen] = useState(false);
const { account } = useOwnAccount(); const { account } = useOwnAccount();
@ -30,17 +35,24 @@ export const NostrProvider: React.FC<NostrProviderProps> = ({ children }) => {
[accountPubkey], [accountPubkey],
); );
const handleRelayOpen = () => {
setIsRelayOpen(true);
};
useEffect(() => { useEffect(() => {
if (url) { if (url) {
setRelay(new NRelay1(url)); const relay = new NRelay1(url);
relay.socket.underlyingWebsocket.addEventListener('open', handleRelayOpen);
setRelay(relay);
} }
return () => { return () => {
relay?.socket.underlyingWebsocket.removeEventListener('open', handleRelayOpen);
relay?.close(); relay?.close();
}; };
}, [url]); }, [url]);
return ( return (
<NostrContext.Provider value={{ relay, signer }}> <NostrContext.Provider value={{ relay, signer, isRelayOpen, hasNostr }}>
{children} {children}
</NostrContext.Provider> </NostrContext.Provider>
); );

@ -5,6 +5,7 @@ interface NConnectOpts {
signer: NostrSigner; signer: NostrSigner;
authorizedPubkey: string | undefined; authorizedPubkey: string | undefined;
onAuthorize(pubkey: string): void; onAuthorize(pubkey: string): void;
onSubscribed(): void;
getSecret(): string; getSecret(): string;
} }
@ -14,6 +15,7 @@ export class NConnect {
private signer: NostrSigner; private signer: NostrSigner;
private authorizedPubkey: string | undefined; private authorizedPubkey: string | undefined;
private onAuthorize: (pubkey: string) => void; private onAuthorize: (pubkey: string) => void;
private onSubscribed: () => void;
private getSecret: () => string; private getSecret: () => string;
private controller = new AbortController(); private controller = new AbortController();
@ -23,6 +25,7 @@ export class NConnect {
this.signer = opts.signer; this.signer = opts.signer;
this.authorizedPubkey = opts.authorizedPubkey; this.authorizedPubkey = opts.authorizedPubkey;
this.onAuthorize = opts.onAuthorize; this.onAuthorize = opts.onAuthorize;
this.onSubscribed = opts.onSubscribed;
this.getSecret = opts.getSecret; this.getSecret = opts.getSecret;
this.open(); this.open();
@ -32,7 +35,10 @@ export class NConnect {
const pubkey = await this.signer.getPublicKey(); const pubkey = await this.signer.getPublicKey();
const signal = this.controller.signal; const signal = this.controller.signal;
for await (const msg of this.relay.req([{ kinds: [24133], '#p': [pubkey] }], { signal })) { const sub = this.relay.req([{ kinds: [24133], '#p': [pubkey] }], { signal });
this.onSubscribed();
for await (const msg of sub) {
if (msg[0] === 'EVENT') { if (msg[0] === 'EVENT') {
const event = msg[2]; const event = msg[2];
this.handleEvent(event); this.handleEvent(event);

@ -6,6 +6,7 @@ import { fetchMe } from 'soapbox/actions/me';
import { loadSoapboxConfig } from 'soapbox/actions/soapbox'; import { loadSoapboxConfig } from 'soapbox/actions/soapbox';
import { useSignerStream } from 'soapbox/api/hooks/nostr/useSignerStream'; import { useSignerStream } from 'soapbox/api/hooks/nostr/useSignerStream';
import LoadingScreen from 'soapbox/components/loading-screen'; import LoadingScreen from 'soapbox/components/loading-screen';
import { useNostr } from 'soapbox/contexts/nostr-context';
import { import {
useAppSelector, useAppSelector,
useAppDispatch, useAppDispatch,
@ -44,7 +45,8 @@ const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
const [localeLoading, setLocaleLoading] = useState(true); const [localeLoading, setLocaleLoading] = useState(true);
const [isLoaded, setIsLoaded] = useState(false); const [isLoaded, setIsLoaded] = useState(false);
useSignerStream(); const { hasNostr, isRelayOpen } = useNostr();
const { isSubscribed } = useSignerStream();
/** Whether to display a loading indicator. */ /** Whether to display a loading indicator. */
const showLoading = [ const showLoading = [
@ -53,6 +55,7 @@ const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
!isLoaded, !isLoaded,
localeLoading, localeLoading,
swUpdating, swUpdating,
hasNostr && (!isRelayOpen || !isSubscribed),
].some(Boolean); ].some(Boolean);
// Load the user's locale // Load the user's locale

Loading…
Cancel
Save