/** * Standalone React context for Nostr so HMR on `NostrProvider/index.tsx` does not recreate * `createContext()` (which breaks `useNostr` in providers like InterestListProvider after Fast Refresh). */ import type { TAccountPointer, TDraftEvent, TProfile, TPublishOptions, TRelayList } from '@/types' import { Event, VerifiedEvent } from 'nostr-tools' import { createContext, useContext } from 'react' export type TNostrContext = { isInitialized: boolean /** True while replaceable events + relay list are loading from cache/relays after login or account switch. */ isAccountSessionHydrating: boolean pubkey: string | null profile: TProfile | null profileEvent: Event | null relayList: TRelayList | null cacheRelayListEvent: Event | null /** Kind 10243 (HTTPS index relays); null if none, undefined while not loaded. */ httpRelayListEvent: Event | null | undefined followListEvent: Event | null muteListEvent: Event | null bookmarkListEvent: Event | null interestListEvent: Event | null favoriteRelaysEvent: Event | null blockedRelaysEvent: Event | null userEmojiListEvent: Event | null rssFeedListEvent: Event | null account: TAccountPointer | null accounts: TAccountPointer[] nsec: string | null ncryptsec: string | null /** True when the session can sign (not read-only npub fallback). */ canSignEvents: boolean /** Anonymous write session: fresh key per publish/sign/auth. */ isAnonSession: boolean /** Stable identity features (profile, follow, mute, lists). False in anon write mode. */ canManageIdentity: boolean /** Returns the new session pubkey on success, or `null` if logout / switch failed. */ switchAccount: (account: TAccountPointer | null) => Promise /** View an account read-only (notifications, relays) without matching the browser extension. */ viewAccountAsReadOnly: (account: TAccountPointer) => Promise /** Reconnect NIP-07 when the extension pubkey matches the stored preferred account. */ retryNip07SignerForPreferredAccount: () => Promise /** Sign in with whichever pubkey the browser extension exposes now. */ adoptExtensionNip07Identity: () => Promise /** True while the login modal must stay open for an in-flight NIP-07 authorize. */ isNip07LoginInFlight: boolean nsecLogin: (nsec: string, password?: string, needSetup?: boolean) => Promise ncryptsecLogin: (ncryptsec: string) => Promise nip07Login: () => Promise bunkerLogin: (bunker: string) => Promise nostrConnectionLogin: (clientSecretKey: Uint8Array, connectionString: string) => Promise npubLogin(npub: string): Promise removeAccount: (account: TAccountPointer) => void /** Remove locally stored nsec/ncryptsec; account becomes read-only npub until remote login. */ discardLocalPrivateKey: () => void publish: (draftEvent: TDraftEvent, options?: TPublishOptions) => Promise attemptDelete: (targetEvent: Event) => Promise signHttpAuth: (url: string, method: string) => Promise signEvent: (draftEvent: TDraftEvent) => Promise nip04Encrypt: (pubkey: string, plainText: string) => Promise nip04Decrypt: (pubkey: string, cipherText: string) => Promise startLogin: () => void checkLogin: (cb?: () => T | Promise) => Promise updateRelayListEvent: (relayListEvent: Event) => Promise updateCacheRelayListEvent: (cacheRelayListEvent: Event) => Promise updateHttpRelayListEvent: (httpRelayListEvent: Event) => Promise updateProfileEvent: (profileEvent: Event) => Promise updateFollowListEvent: (followListEvent: Event) => Promise updateMuteListEvent: (muteListEvent: Event, privateTags: string[][]) => Promise updateBookmarkListEvent: (bookmarkListEvent: Event) => Promise updateInterestListEvent: (interestListEvent: Event) => Promise updateFavoriteRelaysEvent: (favoriteRelaysEvent: Event) => Promise updateBlockedRelaysEvent: (blockedRelaysEvent: Event) => Promise updateRssFeedListEvent: (rssFeedListEvent: Event) => Promise updateUserEmojiListEvent: (userEmojiListEvent: Event) => Promise /** * Re-run the full account network hydrate (relay lists + replaceable merge + prewarm), bypassing the * 24h throttle. Resolves when the hydrate pass finishes. No-op when logged out. */ requestAccountNetworkHydrate: () => Promise } export const NostrContext = createContext(undefined) export function useNostr(): TNostrContext { const context = useContext(NostrContext) if (!context) { throw new Error('useNostr must be used within a NostrProvider') } return context } /** Returns undefined when outside NostrProvider (e.g. embedded notes in createRoot trees). */ export function useNostrOptional(): TNostrContext | undefined { return useContext(NostrContext) }