fix(stash): avoid nip44 getter entirely — check login type + window.nostr directly
The NUser extension signer's .nip44 getter calls .nostr which throws "Browser extension not available" when window.nostr is absent. A try/catch around ?.nip44 doesn't work because esbuild optimises it away. Fix: check login type + window.nostr directly (no getter, no throw) to determine nip44 availability, and wrap all actual signer accesses at call-time in their own try/catch. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
4c4e7159ba
commit
73f51662af
1 changed files with 30 additions and 8 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useNostr } from '@nostrify/react';
|
import { useNostr } from '@nostrify/react';
|
||||||
|
import { useNostrLogin } from '@nostrify/react/login';
|
||||||
import { useCurrentUser } from './useCurrentUser';
|
import { useCurrentUser } from './useCurrentUser';
|
||||||
import { useNostrPublish } from './useNostrPublish';
|
import { useNostrPublish } from './useNostrPublish';
|
||||||
|
|
||||||
|
|
@ -47,14 +48,26 @@ const STASH_D_TAG = 'skatespots-stash';
|
||||||
export function useStash() {
|
export function useStash() {
|
||||||
const { nostr } = useNostr();
|
const { nostr } = useNostr();
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
|
const { logins } = useNostrLogin();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { mutateAsync: createEvent } = useNostrPublish();
|
const { mutateAsync: createEvent } = useNostrPublish();
|
||||||
|
|
||||||
// Safely check if nip44 is available without throwing (some signers throw
|
// Determine if nip44 is safely available WITHOUT touching any throwing getter.
|
||||||
// "Browser extension not available" when the getter is accessed with no login)
|
// Extension signers throw "Browser extension not available" from their .nostr
|
||||||
const hasNip44 = (() => {
|
// getter when window.nostr isn't present yet (page load race, or no extension).
|
||||||
try { return !!user?.signer?.nip44; } catch { return false; }
|
// We check the login type instead: nsec and bunker have nip44 built-in always;
|
||||||
})();
|
// extension logins only have it when window.nostr is present; amber never has it.
|
||||||
|
const hasNip44 = useMemo(() => {
|
||||||
|
if (!user?.pubkey) return false;
|
||||||
|
const login = logins.find((l) => l.pubkey === user.pubkey);
|
||||||
|
if (!login) return false;
|
||||||
|
// nsec and bunker: nip44 is always available (pure JS, no external dep)
|
||||||
|
if (login.type === 'nsec' || login.type === 'bunker') return true;
|
||||||
|
// extension: safe to check window.nostr directly (no getter, no throw)
|
||||||
|
if (login.type === 'extension') return !!(globalThis as { nostr?: { nip44?: unknown } }).nostr?.nip44;
|
||||||
|
// amber / unknown: no nip44 support
|
||||||
|
return false;
|
||||||
|
}, [user?.pubkey, logins]);
|
||||||
|
|
||||||
// Fetch and decrypt stash
|
// Fetch and decrypt stash
|
||||||
const { data: stash, isLoading, error } = useQuery({
|
const { data: stash, isLoading, error } = useQuery({
|
||||||
|
|
@ -81,8 +94,12 @@ export function useStash() {
|
||||||
const event = events[0];
|
const event = events[0];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Decrypt the content (encrypted to self)
|
// Decrypt the content (encrypted to self) — access nip44 inside try/catch
|
||||||
const decrypted = await user.signer!.nip44!.decrypt(user.pubkey, event.content);
|
// to guard against extension signers throwing when window.nostr is gone
|
||||||
|
let nip44Dec: typeof user.signer.nip44;
|
||||||
|
try { nip44Dec = user.signer?.nip44; } catch { return { items: [], updatedAt: 0 }; }
|
||||||
|
if (!nip44Dec) return { items: [], updatedAt: 0 };
|
||||||
|
const decrypted = await nip44Dec.decrypt(user.pubkey, event.content);
|
||||||
const stashData = JSON.parse(decrypted) as Stash;
|
const stashData = JSON.parse(decrypted) as Stash;
|
||||||
return stashData;
|
return stashData;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -100,8 +117,13 @@ export function useStash() {
|
||||||
throw new Error('Encryption not available');
|
throw new Error('Encryption not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Access nip44 inside a try/catch at call time (signer is guaranteed by hasNip44 above)
|
||||||
|
let nip44Enc: typeof user.signer.nip44;
|
||||||
|
try { nip44Enc = user.signer?.nip44; } catch { throw new Error('Encryption not available'); }
|
||||||
|
if (!nip44Enc) throw new Error('Encryption not available');
|
||||||
|
|
||||||
// Encrypt to self
|
// Encrypt to self
|
||||||
const encrypted = await user.signer!.nip44!.encrypt(user.pubkey, JSON.stringify(newStash));
|
const encrypted = await nip44Enc.encrypt(user.pubkey, JSON.stringify(newStash));
|
||||||
|
|
||||||
// Publish as replaceable event
|
// Publish as replaceable event
|
||||||
await createEvent({
|
await createEvent({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue