a tool for shared writing and social publishing
1import { IdResolver } from "@atproto/identity";
2import { NextRequest, NextResponse } from "next/server";
3
4let idResolver = new IdResolver();
5
6/**
7 * Fetches a blob from an AT Protocol PDS given a DID and CID
8 * Returns the Response object or null if the blob couldn't be fetched
9 */
10export async function fetchAtprotoBlob(
11 did: string,
12 cid: string,
13): Promise<Response | null> {
14 if (!did || !cid) return null;
15
16 let identity = await idResolver.did.resolve(did);
17 let service = identity?.service?.find((f) => f.id === "#atproto_pds");
18 if (!service) return null;
19
20 const response = await fetch(
21 `${service.serviceEndpoint}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`,
22 {
23 headers: {
24 "Accept-Encoding": "gzip, deflate, br, zstd",
25 },
26 },
27 );
28
29 if (!response.ok) return null;
30
31 return response;
32}
33
34export async function GET(req: NextRequest) {
35 const url = new URL(req.url);
36 const params = {
37 did: url.searchParams.get("did") ?? "",
38 cid: url.searchParams.get("cid") ?? "",
39 };
40
41 const response = await fetchAtprotoBlob(params.did, params.cid);
42 if (!response) return new NextResponse(null, { status: 404 });
43
44 // Clone the response to modify headers
45 const cachedResponse = new Response(response.body, response);
46
47 // Set cache-control header to cache indefinitely
48 cachedResponse.headers.set(
49 "Cache-Control",
50 "public, max-age=31536000, immutable, s-maxage=86400, stale-while-revalidate=604800",
51 );
52 cachedResponse.headers.set(
53 "CDN-Cache-Control",
54 "s-maxage=86400, stale-while-revalidate=86400",
55 );
56
57 return cachedResponse;
58}