From 6475948fbb47c85bbc595a7c82d0929cb87effa4 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Mon, 25 Aug 2025 02:43:44 +0000 Subject: [PATCH] add pub favicon --- app/layout.tsx | 7 ------- app/lish/[did]/[publication]/icon.ts | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 file(s) changed, 65 insertion(s)(+), 7 deletion(s)(-) diff --git a/app/layout.tsx b/app/layout.tsx --- a/app/layout.tsx +++ b/app/layout.tsx @@ -14,13 +14,6 @@ title: "Leaflet", description: "tiny interconnected social documents", metadataBase: new URL(`https://leaflet.pub`), - icons: [ - { - type: "apple-touch-icon", - sizes: "180x180", - url: "/apple-touch-icon.png", - }, - ], appleWebApp: { title: "Leaflet", statusBarStyle: "black-translucent", diff --git a/app/lish/[did]/[publication]/icon.ts b/app/lish/[did]/[publication]/icon.ts new file mode 100644 --- /dev/null +++ b/app/lish/[did]/[publication]/icon.ts @@ -0,0 +1,65 @@ +import { NextRequest } from "next/server"; +import { IdResolver } from "@atproto/identity"; +import { AtUri } from "@atproto/syntax"; +import { PubLeafletPublication } from "lexicons/api"; +import { supabaseServerClient } from "supabase/serverClient"; +import sharp from "sharp"; + +let idResolver = new IdResolver(); + +export const size = { + width: 32, + height: 32, +}; + +export const contentType = "image/png"; +export default async function Icon({ + params, +}: { + params: { did: string; publication: string }; +}) { + let did = decodeURIComponent(params.did); + let uri; + if (/^(?!\.$|\.\.S)[A-Za-z0-9._:~-]{1,512}$/.test(params.publication)) { + uri = AtUri.make( + did, + "pub.leaflet.publication", + params.publication, + ).toString(); + } + let { data: publication } = await supabaseServerClient + .from("publications") + .select( + `*, + publication_subscriptions(*), + documents_in_publications(documents(*)) + `, + ) + .eq("identity_did", did) + .or(`name.eq."${params.publication}", uri.eq."${uri}"`) + .single(); + + let record = publication?.record as PubLeafletPublication.Record | null; + if (!record?.icon) return null; + + let identity = await idResolver.did.resolve(did); + let service = identity?.service?.find((f) => f.id === "#atproto_pds"); + console.log(identity); + if (!service) return null; + let cid = (record.icon.ref as unknown as { $link: string })["$link"]; + const response = await fetch( + `${service.serviceEndpoint}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`, + ); + let blob = await response.blob(); + let resizedImage = await sharp(await blob.arrayBuffer()) + .resize({ width: 32, height: 32 }) + .toBuffer(); + console.log("fetched favicon!"); + return new Response(new Uint8Array(resizedImage), { + headers: { + "Content-Type": "image/png", + "CDN-Cache-Control": "s-max-age=86400, stale-while-revalidate=86400", + "Cache-Control": "public, max-age=3600", + }, + }); +} -- tangled.sh