The cloudflare worker code used for bluesky-cdn.madebydanny.uk
worker.js
edited
1const PLC_DIRECTORY = "https://plc.directory";
2
3export default {
4 async fetch(request, env, ctx) {
5 const url = new URL(request.url);
6 const cache = caches.default;
7
8 // Regex to extract Type, DID, and CID
9 const pathRegex = /^\/img\/([^/]+)\/plain\/(did:[^/]+)\/([^@/]+)(?:@([^/]+))?$/;
10 const match = url.pathname.match(pathRegex);
11
12 if (!match) return new Response("400: Invalid Path", { status: 400 });
13 const [_, type, did, cid] = match;
14
15 // 1. TRY OFFICIAL CDN FIRST (Best for resized/transformed images)
16 const bskyCdnUrl = `https://cdn.bsky.app${url.pathname}`;
17 let response = await fetch(bskyCdnUrl, { cf: { cacheTtl: 3600 } });
18
19 // If CDN works (200 OK), return it immediately
20 if (response.ok) {
21 const cdnRes = new Response(response.body, response);
22 cdnRes.headers.set("X-Proxy-Source", "Bluesky-CDN");
23 return cdnRes;
24 }
25
26 // 2. GLOBAL CACHE CHECK (For the Fallback)
27 let cachedResponse = await cache.match(request);
28 if (cachedResponse) return cachedResponse;
29
30 // 3. PDS FALLBACK LOGIC
31 try {
32 const pdsUrl = await resolvePds(did);
33 if (!pdsUrl) throw new Error("PDS not found for this DID");
34
35 // A. Try the CID from the URL directly on the PDS (Works if it's the original)
36 let blobRes = await fetchBlob(pdsUrl, did, cid);
37
38 // B. TRANSFORMED IMAGE FIX: If 404, the CID is a thumbnail.
39 // We look up the 'Original' CID from the user's Profile Record.
40 if (blobRes.status === 404 && (type === 'avatar' || type === 'banner')) {
41 console.log(`Thumbnail CID not found. Fetching original for ${type}...`);
42 const originalCid = await findOriginalCidFromProfile(pdsUrl, did, type);
43
44 if (originalCid && originalCid !== cid) {
45 blobRes = await fetchBlob(pdsUrl, did, originalCid);
46 }
47 }
48
49 if (blobRes.ok) {
50 const finalRes = new Response(blobRes.body, blobRes);
51 finalRes.headers.set("Cache-Control", "public, s-maxage=604800");
52 finalRes.headers.set("X-Proxy-Source", "PDS-Discovery");
53
54 ctx.waitUntil(cache.put(request, finalRes.clone()));
55 return finalRes;
56 }
57
58 return new Response(`404: Asset not found on CDN or PDS.`, { status: 404 });
59
60 } catch (err) {
61 return new Response(`502: Failover Error: ${err.message}`, { status: 502 });
62 }
63 }
64};
65
66/** * Helpers
67 **/
68
69async function fetchBlob(pdsUrl, did, cid) {
70 return fetch(`${pdsUrl}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`);
71}
72
73async function resolvePds(did) {
74 const res = await fetch(`${PLC_DIRECTORY}/${did}`, { cf: { cacheTtl: 3600 } });
75 if (!res.ok) return null;
76 const doc = await res.json();
77 const pds = doc.service?.find(s => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer");
78 return pds?.serviceEndpoint;
79}
80
81async function findOriginalCidFromProfile(pdsUrl, did, type) {
82 // Collection: app.bsky.actor.profile, Key: self
83 const recordUrl = `${pdsUrl}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=app.bsky.actor.profile&rkey=self`;
84 const res = await fetch(recordUrl);
85 if (!res.ok) return null;
86 const data = await res.json();
87
88 // Navigate the JSON to find the blob reference for 'avatar' or 'banner'
89 return data.value?.[type]?.ref?.$link;
90}