A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1/* eslint-disable react-refresh/only-export-components */ 2import React, { 3 createContext, 4 useContext, 5 useMemo, 6 useRef, 7} from "react"; 8import { ServiceResolver, normalizeBaseUrl } from "../utils/atproto-client"; 9import { BlobCache, DidCache } from "../utils/cache"; 10 11/** 12 * Props for the AT Protocol context provider. 13 */ 14export interface AtProtoProviderProps { 15 /** Child components that will have access to the AT Protocol context. */ 16 children: React.ReactNode; 17 /** Optional custom PLC directory URL. Defaults to https://plc.directory */ 18 plcDirectory?: string; 19} 20 21/** 22 * Internal context value shared across all AT Protocol hooks. 23 */ 24interface AtProtoContextValue { 25 /** Service resolver for DID resolution and PDS endpoint discovery. */ 26 resolver: ServiceResolver; 27 /** Normalized PLC directory base URL. */ 28 plcDirectory: string; 29 /** Cache for DID documents and handle mappings. */ 30 didCache: DidCache; 31 /** Cache for fetched blob data. */ 32 blobCache: BlobCache; 33} 34 35const AtProtoContext = createContext<AtProtoContextValue | undefined>( 36 undefined, 37); 38 39/** 40 * Context provider that supplies AT Protocol infrastructure to all child components. 41 * 42 * This provider initializes and shares: 43 * - Service resolver for DID and PDS endpoint resolution 44 * - DID cache for identity resolution 45 * - Blob cache for efficient media handling 46 * 47 * All AT Protocol components (`BlueskyPost`, `LeafletDocument`, etc.) must be wrapped 48 * in this provider to function correctly. 49 * 50 * @example 51 * ```tsx 52 * import { AtProtoProvider, BlueskyPost } from 'atproto-ui'; 53 * 54 * function App() { 55 * return ( 56 * <AtProtoProvider> 57 * <BlueskyPost did="did:plc:example" rkey="3k2aexample" /> 58 * </AtProtoProvider> 59 * ); 60 * } 61 * ``` 62 * 63 * @example 64 * ```tsx 65 * // Using a custom PLC directory 66 * <AtProtoProvider plcDirectory="https://custom-plc.example.com"> 67 * <YourComponents /> 68 * </AtProtoProvider> 69 * ``` 70 * 71 * @param children - Child components to render within the provider. 72 * @param plcDirectory - Optional PLC directory override (defaults to https://plc.directory). 73 * @returns Provider component that enables AT Protocol functionality. 74 */ 75export function AtProtoProvider({ 76 children, 77 plcDirectory, 78}: AtProtoProviderProps) { 79 const normalizedPlc = useMemo( 80 () => 81 normalizeBaseUrl( 82 plcDirectory && plcDirectory.trim() 83 ? plcDirectory 84 : "https://plc.directory", 85 ), 86 [plcDirectory], 87 ); 88 const resolver = useMemo( 89 () => new ServiceResolver({ plcDirectory: normalizedPlc }), 90 [normalizedPlc], 91 ); 92 const cachesRef = useRef<{ 93 didCache: DidCache; 94 blobCache: BlobCache; 95 } | null>(null); 96 if (!cachesRef.current) { 97 cachesRef.current = { 98 didCache: new DidCache(), 99 blobCache: new BlobCache(), 100 }; 101 } 102 103 const value = useMemo<AtProtoContextValue>( 104 () => ({ 105 resolver, 106 plcDirectory: normalizedPlc, 107 didCache: cachesRef.current!.didCache, 108 blobCache: cachesRef.current!.blobCache, 109 }), 110 [resolver, normalizedPlc], 111 ); 112 113 return ( 114 <AtProtoContext.Provider value={value}> 115 {children} 116 </AtProtoContext.Provider> 117 ); 118} 119 120/** 121 * Hook that accesses the AT Protocol context provided by `AtProtoProvider`. 122 * 123 * This hook exposes the service resolver, DID cache, and blob cache for building 124 * custom AT Protocol functionality. 125 * 126 * @throws {Error} When called outside of an `AtProtoProvider`. 127 * @returns {AtProtoContextValue} Object containing resolver, caches, and PLC directory URL. 128 * 129 * @example 130 * ```tsx 131 * import { useAtProto } from 'atproto-ui'; 132 * 133 * function MyCustomComponent() { 134 * const { resolver, didCache, blobCache } = useAtProto(); 135 * // Use the resolver and caches for custom AT Protocol operations 136 * } 137 * ``` 138 */ 139export function useAtProto() { 140 const ctx = useContext(AtProtoContext); 141 if (!ctx) throw new Error("useAtProto must be used within AtProtoProvider"); 142 return ctx; 143}