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, RecordCache } 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 /** Cache for fetched AT Protocol records. */ 34 recordCache: RecordCache; 35} 36 37const AtProtoContext = createContext<AtProtoContextValue | undefined>( 38 undefined, 39); 40 41/** 42 * Context provider that supplies AT Protocol infrastructure to all child components. 43 * 44 * This provider initializes and shares: 45 * - Service resolver for DID and PDS endpoint resolution 46 * - DID cache for identity resolution 47 * - Blob cache for efficient media handling 48 * 49 * All AT Protocol components (`BlueskyPost`, `LeafletDocument`, etc.) must be wrapped 50 * in this provider to function correctly. 51 * 52 * @example 53 * ```tsx 54 * import { AtProtoProvider, BlueskyPost } from 'atproto-ui'; 55 * 56 * function App() { 57 * return ( 58 * <AtProtoProvider> 59 * <BlueskyPost did="did:plc:example" rkey="3k2aexample" /> 60 * </AtProtoProvider> 61 * ); 62 * } 63 * ``` 64 * 65 * @example 66 * ```tsx 67 * // Using a custom PLC directory 68 * <AtProtoProvider plcDirectory="https://custom-plc.example.com"> 69 * <YourComponents /> 70 * </AtProtoProvider> 71 * ``` 72 * 73 * @param children - Child components to render within the provider. 74 * @param plcDirectory - Optional PLC directory override (defaults to https://plc.directory). 75 * @returns Provider component that enables AT Protocol functionality. 76 */ 77export function AtProtoProvider({ 78 children, 79 plcDirectory, 80}: AtProtoProviderProps) { 81 const normalizedPlc = useMemo( 82 () => 83 normalizeBaseUrl( 84 plcDirectory && plcDirectory.trim() 85 ? plcDirectory 86 : "https://plc.directory", 87 ), 88 [plcDirectory], 89 ); 90 const resolver = useMemo( 91 () => new ServiceResolver({ plcDirectory: normalizedPlc }), 92 [normalizedPlc], 93 ); 94 const cachesRef = useRef<{ 95 didCache: DidCache; 96 blobCache: BlobCache; 97 recordCache: RecordCache; 98 } | null>(null); 99 if (!cachesRef.current) { 100 cachesRef.current = { 101 didCache: new DidCache(), 102 blobCache: new BlobCache(), 103 recordCache: new RecordCache(), 104 }; 105 } 106 107 const value = useMemo<AtProtoContextValue>( 108 () => ({ 109 resolver, 110 plcDirectory: normalizedPlc, 111 didCache: cachesRef.current!.didCache, 112 blobCache: cachesRef.current!.blobCache, 113 recordCache: cachesRef.current!.recordCache, 114 }), 115 [resolver, normalizedPlc], 116 ); 117 118 return ( 119 <AtProtoContext.Provider value={value}> 120 {children} 121 </AtProtoContext.Provider> 122 ); 123} 124 125/** 126 * Hook that accesses the AT Protocol context provided by `AtProtoProvider`. 127 * 128 * This hook exposes the service resolver, DID cache, blob cache, and record cache 129 * for building custom AT Protocol functionality. 130 * 131 * @throws {Error} When called outside of an `AtProtoProvider`. 132 * @returns {AtProtoContextValue} Object containing resolver, caches, and PLC directory URL. 133 * 134 * @example 135 * ```tsx 136 * import { useAtProto } from 'atproto-ui'; 137 * 138 * function MyCustomComponent() { 139 * const { resolver, didCache, blobCache, recordCache } = useAtProto(); 140 * // Use the resolver and caches for custom AT Protocol operations 141 * } 142 * ``` 143 */ 144export function useAtProto() { 145 const ctx = useContext(AtProtoContext); 146 if (!ctx) throw new Error("useAtProto must be used within AtProtoProvider"); 147 return ctx; 148}