A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.

refactor some more

+2 -19
lib/components/BlueskyPost.tsx
···
import { BLUESKY_PROFILE_COLLECTION } from "./BlueskyProfile";
import { getAvatarCid } from "../utils/profile";
import { formatDidForLabel } from "../utils/at-uri";
-
import type { BlobWithCdn } from "../hooks/useBlueskyAppview";
+
import { isBlobWithCdn } from "../utils/blob";
/**
* Props for rendering a single Bluesky post with optional customization hooks.
···
collection: BLUESKY_PROFILE_COLLECTION,
rkey: "self",
});
-
// Check if the avatar has a CDN URL from the appview (preferred)
const avatar = profile?.avatar;
const avatarCdnUrl = isBlobWithCdn(avatar) ? avatar.cdnUrl : undefined;
-
const avatarCid = !avatarCdnUrl ? getAvatarCid(profile) : undefined;
+
const avatarCid = avatarCdnUrl ? undefined : getAvatarCid(profile);
const Comp: React.ComponentType<BlueskyPostRendererInjectedProps> = useMemo(
() => renderer ?? ((props) => <BlueskyPostRenderer {...props} />),
···
error?: Error;
}> = (props) => {
const { url: avatarUrlFromBlob } = useBlob(repoIdentifier, avatarCid);
-
// Use CDN URL from appview if available, otherwise use blob URL
const avatarUrl = avatarCdnUrl || avatarUrlFromBlob;
return (
<Comp
···
/>
);
};
-
-
/**
-
* Type guard to check if a blob has a CDN URL from appview.
-
*/
-
function isBlobWithCdn(value: unknown): value is BlobWithCdn {
-
if (typeof value !== "object" || value === null) return false;
-
const obj = value as Record<string, unknown>;
-
return (
-
obj.$type === "blob" &&
-
typeof obj.cdnUrl === "string" &&
-
typeof obj.ref === "object" &&
-
obj.ref !== null &&
-
typeof (obj.ref as { $link?: unknown }).$link === "string"
-
);
-
}
export default BlueskyPost;
+2 -19
lib/components/BlueskyProfile.tsx
···
import { getAvatarCid } from "../utils/profile";
import { useDidResolution } from "../hooks/useDidResolution";
import { formatDidForLabel } from "../utils/at-uri";
-
import type { BlobWithCdn } from "../hooks/useBlueskyAppview";
+
import { isBlobWithCdn } from "../utils/blob";
/**
* Props used to render a Bluesky actor profile record.
···
// Check if the avatar has a CDN URL from the appview (preferred)
const avatar = props.record?.avatar;
const avatarCdnUrl = isBlobWithCdn(avatar) ? avatar.cdnUrl : undefined;
-
const avatarCid = !avatarCdnUrl ? getAvatarCid(props.record) : undefined;
+
const avatarCid = avatarCdnUrl ? undefined : getAvatarCid(props.record);
const { url: avatarUrlFromBlob } = useBlob(repoIdentifier, avatarCid);
-
-
// Use CDN URL from appview if available, otherwise use blob URL
const avatarUrl = avatarCdnUrl || avatarUrlFromBlob;
return (
···
/>
);
};
-
-
/**
-
* Type guard to check if a blob has a CDN URL from appview.
-
*/
-
function isBlobWithCdn(value: unknown): value is BlobWithCdn {
-
if (typeof value !== "object" || value === null) return false;
-
const obj = value as Record<string, unknown>;
-
return (
-
obj.$type === "blob" &&
-
typeof obj.cdnUrl === "string" &&
-
typeof obj.ref === "object" &&
-
obj.ref !== null &&
-
typeof (obj.ref as { $link?: unknown }).$link === "string"
-
);
-
}
export default BlueskyProfile;
+3 -11
lib/hooks/useAtProtoRecord.ts
···
collection,
rkey,
}: AtProtoRecordKey): AtProtoRecordState<T> {
-
// Determine if this is a Bluesky collection that should use the appview
const isBlueskyCollection = collection?.startsWith("app.bsky.");
-
// Use the three-tier fallback for Bluesky collections
+
// Always call all hooks (React rules) - conditionally use results
const blueskyResult = useBlueskyAppview<T>({
did: isBlueskyCollection ? handleOrDid : undefined,
collection: isBlueskyCollection ? collection : undefined,
rkey: isBlueskyCollection ? rkey : undefined,
});
+
const {
did,
error: didError,
···
if (cancelled) return;
setState((prev) => ({ ...prev, ...next }));
};
-
-
// If using Bluesky appview, skip the manual fetch logic
-
if (isBlueskyCollection) {
-
return () => {
-
cancelled = true;
-
};
-
}
if (!handleOrDid || !collection || !rkey) {
assignState({
···
resolvingEndpoint,
didError,
endpointError,
-
isBlueskyCollection,
]);
-
// Return Bluesky appview result if it's a Bluesky collection
+
// Return Bluesky result for app.bsky.* collections
if (isBlueskyCollection) {
return {
record: blueskyResult.record,
+4 -4
lib/hooks/useBlueskyAppview.ts
···
params: { actor: did },
});
-
if (!res.ok) throw new Error("Appview profile request failed");
+
if (!res.ok) throw new Error(`Appview ${endpoint} request failed for ${did}`);
// The appview returns avatar/banner as CDN URLs like:
// https://cdn.bsky.app/img/avatar/plain/{did}/{cid}@jpeg
···
params: { uri: atUri, depth: 0 },
});
-
if (!res.ok) throw new Error("Appview post thread request failed");
+
if (!res.ok) throw new Error(`Appview ${endpoint} request failed for ${atUri}`);
const post = res.data.thread?.post;
if (!post?.record) return undefined;
···
rkey: string,
): Promise<T | undefined> {
const res = await callGetRecord<T>(SLINGSHOT_BASE_URL, did, collection, rkey);
-
if (!res.ok) throw new Error("Slingshot getRecord failed");
+
if (!res.ok) throw new Error(`Slingshot getRecord failed for ${did}/${collection}/${rkey}`);
return res.data.value;
}
···
pdsEndpoint: string,
): Promise<T | undefined> {
const res = await callGetRecord<T>(pdsEndpoint, did, collection, rkey);
-
if (!res.ok) throw new Error("PDS getRecord failed");
+
if (!res.ok) throw new Error(`PDS getRecord failed for ${did}/${collection}/${rkey} at ${pdsEndpoint}`);
return res.data.value;
}
+3 -22
lib/hooks/useBlueskyProfile.ts
···
import { useBlueskyAppview } from "./useBlueskyAppview";
import type { ProfileRecord } from "../types/bluesky";
+
import { extractCidFromBlob } from "../utils/blob";
/**
* Minimal profile fields returned by the Bluesky actor profile endpoint.
···
handle: "",
displayName: record.displayName,
description: record.description,
-
avatar: extractCidFromProfileBlob(record.avatar),
-
banner: extractCidFromProfileBlob(record.banner),
+
avatar: extractCidFromBlob(record.avatar),
+
banner: extractCidFromBlob(record.banner),
createdAt: record.createdAt,
}
: undefined;
return { data, loading, error };
-
}
-
-
/**
-
* Helper to extract CID from profile blob (avatar or banner).
-
*/
-
function extractCidFromProfileBlob(blob: unknown): string | undefined {
-
if (typeof blob !== "object" || blob === null) return undefined;
-
-
const blobObj = blob as {
-
ref?: { $link?: string };
-
cid?: string;
-
};
-
-
if (typeof blobObj.cid === "string") return blobObj.cid;
-
if (typeof blobObj.ref === "object" && blobObj.ref !== null) {
-
const link = blobObj.ref.$link;
-
if (typeof link === "string") return link;
-
}
-
-
return undefined;
}
+1
lib/index.ts
···
// Utilities
export * from "./utils/at-uri";
export * from "./utils/atproto-client";
+
export * from "./utils/blob";
export * from "./utils/profile";
+2 -39
lib/renderers/BlueskyPostRenderer.tsx
···
import { useDidResolution } from "../hooks/useDidResolution";
import { useBlob } from "../hooks/useBlob";
import { BlueskyIcon } from "../components/BlueskyIcon";
-
import type { BlobWithCdn } from "../hooks/useBlueskyAppview";
+
import { isBlobWithCdn, extractCidFromBlob } from "../utils/blob";
export interface BlueskyPostRendererProps {
record: FeedPostRecord;
···
}
const PostImage: React.FC<PostImageProps> = ({ image, did, scheme }) => {
-
// Check if the image has a CDN URL from the appview (preferred)
const imageBlob = image.image;
const cdnUrl = isBlobWithCdn(imageBlob) ? imageBlob.cdnUrl : undefined;
-
const cid = !cdnUrl ? extractCidFromImageBlob(imageBlob) : undefined;
+
const cid = cdnUrl ? undefined : extractCidFromBlob(imageBlob);
const { url: urlFromBlob, loading, error } = useBlob(did, cid);
-
// Use CDN URL from appview if available, otherwise use blob URL
const url = cdnUrl || urlFromBlob;
const alt = image.alt?.trim() || "Bluesky attachment";
const palette =
···
</figure>
);
};
-
-
/**
-
* Type guard to check if a blob has a CDN URL from appview.
-
*/
-
function isBlobWithCdn(value: unknown): value is BlobWithCdn {
-
if (typeof value !== "object" || value === null) return false;
-
const obj = value as Record<string, unknown>;
-
return (
-
obj.$type === "blob" &&
-
typeof obj.cdnUrl === "string" &&
-
typeof obj.ref === "object" &&
-
obj.ref !== null &&
-
typeof (obj.ref as { $link?: unknown }).$link === "string"
-
);
-
}
-
-
/**
-
* Helper to extract CID from image blob.
-
*/
-
function extractCidFromImageBlob(blob: unknown): string | undefined {
-
if (typeof blob !== "object" || blob === null) return undefined;
-
-
const blobObj = blob as {
-
ref?: { $link?: string };
-
cid?: string;
-
};
-
-
if (typeof blobObj.cid === "string") return blobObj.cid;
-
if (typeof blobObj.ref === "object" && blobObj.ref !== null) {
-
const link = blobObj.ref.$link;
-
if (typeof link === "string") return link;
-
}
-
-
return undefined;
-
}
const imagesBase = {
container: {