A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import { useBlueskyAppview } from "./useBlueskyAppview";
2import type { ProfileRecord } from "../types/bluesky";
3import { extractCidFromBlob } from "../utils/blob";
4
5/**
6 * Minimal profile fields returned by the Bluesky actor profile endpoint.
7 */
8export interface BlueskyProfileData {
9 /** Actor DID. */
10 did: string;
11 /** Actor handle. */
12 handle: string;
13 /** Display name configured by the actor. */
14 displayName?: string;
15 /** Profile description/bio. */
16 description?: string;
17 /** Avatar blob (CID reference). */
18 avatar?: string;
19 /** Banner image blob (CID reference). */
20 banner?: string;
21 /** Creation timestamp for the profile. */
22 createdAt?: string;
23}
24
25/**
26 * Fetches a Bluesky actor profile for a DID and exposes loading/error state.
27 *
28 * Uses a three-tier fallback strategy:
29 * 1. Try Bluesky appview API (app.bsky.actor.getProfile) - CIDs are extracted from CDN URLs
30 * 2. Fall back to Slingshot getRecord
31 * 3. Finally query the PDS directly
32 *
33 * When using the appview, avatar/banner CDN URLs (e.g., https://cdn.bsky.app/img/avatar/plain/did:plc:xxx/bafkreixxx@jpeg)
34 * are automatically parsed to extract CIDs and convert them to standard Blob format for compatibility.
35 *
36 * @param did - Actor DID whose profile should be retrieved.
37 * @returns {{ data: BlueskyProfileData | undefined; loading: boolean; error: Error | undefined }} Object exposing the profile payload, loading flag, and any error.
38 */
39export function useBlueskyProfile(did: string | undefined) {
40 const { record, loading, error } = useBlueskyAppview<ProfileRecord>({
41 did,
42 collection: "app.bsky.actor.profile",
43 rkey: "self",
44 });
45
46 // Convert ProfileRecord to BlueskyProfileData
47 // Note: avatar and banner are Blob objects in the record (from all sources)
48 // The appview response is converted to ProfileRecord format by extracting CIDs from CDN URLs
49 const data: BlueskyProfileData | undefined = record
50 ? {
51 did: did || "",
52 handle: "",
53 displayName: record.displayName,
54 description: record.description,
55 avatar: extractCidFromBlob(record.avatar),
56 banner: extractCidFromBlob(record.banner),
57 createdAt: record.createdAt,
58 }
59 : undefined;
60
61 return { data, loading, error };
62}