this repo has no description
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: use cdn.bsky.app for OG banner on Bluesky-hosted accounts

Bluesky's link-preview crawler (cardyb.bsky.app) only proxies og:images
from its own allowlisted domains. When the project's PDS is on Bluesky
(bsky.social / *.bsky.network), use cdn.bsky.app directly for og:image
so Bluesky can actually retrieve and display the embed card image.
Custom-PDS accounts continue to use our own banner proxy endpoint.

Also adds @png/@jpeg format suffix to bskyCdnBannerUrl (required by
Bluesky's CDN for correct content-type negotiation).

Made-with: Cursor

+39 -6
+24 -2
lib/avatar.ts
··· 13 13 * project pages and used as the OpenGraph / Twitter card image when 14 14 * the page URL is shared. 15 15 */ 16 - export function bskyCdnBannerUrl(did: string, cid: string): string { 17 - return `https://cdn.bsky.app/img/banner/plain/${did}/${cid}`; 16 + export function bskyCdnBannerUrl( 17 + did: string, 18 + cid: string, 19 + mime = "jpeg", 20 + ): string { 21 + const fmt = mime === "image/png" ? "png" : "jpeg"; 22 + return `https://cdn.bsky.app/img/banner/plain/${did}/${cid}@${fmt}`; 23 + } 24 + 25 + /** 26 + * Returns true when a PDS URL belongs to Bluesky's own infrastructure 27 + * (bsky.social or *.bsky.network). Banners on Bluesky-hosted accounts can be 28 + * served straight from cdn.bsky.app, which is on Bluesky's own domain 29 + * allowlist for link-preview images. Custom-PDS banners must go through our 30 + * own proxy instead. 31 + */ 32 + export function isBlueskyPds(pdsUrl: string | null | undefined): boolean { 33 + if (!pdsUrl) return false; 34 + try { 35 + const host = new URL(pdsUrl).hostname; 36 + return host === "bsky.social" || host.endsWith(".bsky.network"); 37 + } catch { 38 + return false; 39 + } 18 40 }
+15 -4
routes/explore/[handle].tsx
··· 18 18 getProfileByHandle, 19 19 type ProfileRow, 20 20 } from "../../lib/registry.ts"; 21 + import { bskyCdnBannerUrl, isBlueskyPds } from "../../lib/avatar.ts"; 21 22 import { 22 23 getOwnReview, 23 24 getReviewSummary, ··· 84 85 const pageTitle = `${profile.name} on Atmosphere Account`; 85 86 const pageDescription = profile.description || 86 87 messages.detail.missingProfile; 88 + // Bluesky's link-preview crawler only proxies images from its own 89 + // allowlisted domains. For accounts hosted on Bluesky's PDS, use 90 + // cdn.bsky.app directly (always accessible). For custom-PDS accounts, 91 + // fall back to our own proxy which fetches the blob from their PDS. 87 92 const ogImageUrl = profile.bannerCid 88 - ? new URL( 89 - `/api/registry/banner/${encodeURIComponent(profile.did)}`, 90 - ctx.url.origin, 91 - ).href 93 + ? isBlueskyPds(profile.pdsUrl) 94 + ? bskyCdnBannerUrl( 95 + profile.did, 96 + profile.bannerCid, 97 + profile.bannerMime ?? undefined, 98 + ) 99 + : new URL( 100 + `/api/registry/banner/${encodeURIComponent(profile.did)}`, 101 + ctx.url.origin, 102 + ).href 92 103 : undefined; 93 104 ctx.state.pageMeta = { 94 105 title: pageTitle,