You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
103 lines
4.9 KiB
103 lines
4.9 KiB
/** |
|
* 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<string | null> |
|
/** View an account read-only (notifications, relays) without matching the browser extension. */ |
|
viewAccountAsReadOnly: (account: TAccountPointer) => Promise<string | null> |
|
/** Reconnect NIP-07 when the extension pubkey matches the stored preferred account. */ |
|
retryNip07SignerForPreferredAccount: () => Promise<boolean> |
|
/** Sign in with whichever pubkey the browser extension exposes now. */ |
|
adoptExtensionNip07Identity: () => Promise<void> |
|
/** 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<string> |
|
ncryptsecLogin: (ncryptsec: string) => Promise<string> |
|
nip07Login: () => Promise<string | null> |
|
bunkerLogin: (bunker: string) => Promise<string> |
|
nostrConnectionLogin: (clientSecretKey: Uint8Array, connectionString: string) => Promise<string> |
|
npubLogin(npub: string): Promise<string> |
|
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<Event> |
|
attemptDelete: (targetEvent: Event) => Promise<void> |
|
signHttpAuth: (url: string, method: string) => Promise<string> |
|
signEvent: (draftEvent: TDraftEvent) => Promise<VerifiedEvent> |
|
nip04Encrypt: (pubkey: string, plainText: string) => Promise<string> |
|
nip04Decrypt: (pubkey: string, cipherText: string) => Promise<string> |
|
startLogin: () => void |
|
checkLogin: <T>(cb?: () => T | Promise<T>) => Promise<T | void> |
|
updateRelayListEvent: (relayListEvent: Event) => Promise<void> |
|
updateCacheRelayListEvent: (cacheRelayListEvent: Event) => Promise<void> |
|
updateHttpRelayListEvent: (httpRelayListEvent: Event) => Promise<void> |
|
updateProfileEvent: (profileEvent: Event) => Promise<void> |
|
updateFollowListEvent: (followListEvent: Event) => Promise<void> |
|
updateMuteListEvent: (muteListEvent: Event, privateTags: string[][]) => Promise<void> |
|
updateBookmarkListEvent: (bookmarkListEvent: Event) => Promise<void> |
|
updateInterestListEvent: (interestListEvent: Event) => Promise<void> |
|
updateFavoriteRelaysEvent: (favoriteRelaysEvent: Event) => Promise<void> |
|
updateBlockedRelaysEvent: (blockedRelaysEvent: Event) => Promise<void> |
|
updateRssFeedListEvent: (rssFeedListEvent: Event) => Promise<void> |
|
updateUserEmojiListEvent: (userEmojiListEvent: Event) => Promise<void> |
|
/** |
|
* 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<void> |
|
} |
|
|
|
export const NostrContext = createContext<TNostrContext | undefined>(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) |
|
}
|
|
|