The cloudflare worker code used for bluesky-cdn.madebydanny.uk
worker.js edited
90 lines 3.4 kB view raw
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}