my website at ewancroft.uk
6
fork

Configure Feed

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

feat: dynamic well-known routes and fediverse metatag

- Add /.well-known/atproto-did server route driven by PUBLIC_ATPROTO_DID
- Add /.well-known/webfinger server route; proxies AP instance response,
injects site-domain alias (acct:user@sitedomain)
- Remove static/.well-known/atproto-did
- Remove hardcoded fediverse:creator from app.html; inject conditionally
via MetaTags.svelte using PUBLIC_AP_INSTANCE_URL + PUBLIC_AP_USERNAME
- Remove webfinger redirect from vercel.json; add Content-Type header rule
- Document PUBLIC_AP_INSTANCE_URL and PUBLIC_AP_USERNAME in .env.example

+87 -7
+5
.env.example
··· 2 2 # You can find this in your Bluesky profile settings or at https://bsky.app 3 3 PUBLIC_ATPROTO_DID=did:plc:your-did-here 4 4 5 + # ActivityPub / Fediverse (optional) 6 + # Used for the webfinger endpoint and fediverse:creator metatag 7 + # PUBLIC_AP_INSTANCE_URL=https://ap.example.com 8 + # PUBLIC_AP_USERNAME=yourname 9 + 5 10 # Ko-fi Supporters (optional) 6 11 # PDS and DID are resolved automatically from PUBLIC_ATPROTO_DID above. 7 12 # The only secrets needed are the Ko-fi verification token and a PDS app password.
-1
src/app.html
··· 11 11 <meta charset="utf-8" /> 12 12 <meta name="viewport" content="width=device-width, initial-scale=1" /> 13 13 <meta name="theme-color" content="#10b981" /> 14 - <meta name="fediverse:creator" content="ewan@ap.ewancroft.uk" /> 15 14 %sveltekit.head% 16 15 </head> 17 16 <body data-sveltekit-preload-data="hover">
+15
src/lib/components/seo/MetaTags.svelte
··· 1 1 <script lang="ts"> 2 2 import type { SiteMetadata } from '$lib/helper/siteMeta'; 3 + import { 4 + PUBLIC_AP_INSTANCE_URL, 5 + PUBLIC_AP_USERNAME 6 + } from '$env/static/public'; 3 7 4 8 interface Props { 5 9 meta: SiteMetadata; ··· 7 11 } 8 12 9 13 let { meta, siteMeta }: Props = $props(); 14 + 15 + const instanceDomain = PUBLIC_AP_INSTANCE_URL 16 + ? new URL(PUBLIC_AP_INSTANCE_URL).hostname 17 + : null; 18 + const fediverseCreator = 19 + PUBLIC_AP_USERNAME && instanceDomain ? `${PUBLIC_AP_USERNAME}@${instanceDomain}` : null; 10 20 11 21 // Merge with defaults 12 22 const finalMeta = $derived({ ··· 37 47 {/if} 38 48 {#if finalMeta.imageHeight} 39 49 <meta property="og:image:height" content={finalMeta.imageHeight.toString()} /> 50 + {/if} 51 + 52 + <!-- Fediverse --> 53 + {#if fediverseCreator} 54 + <meta name="fediverse:creator" content={fediverseCreator} /> 40 55 {/if} 41 56 42 57 <!-- Twitter -->
+9
src/routes/.well-known/atproto-did/+server.ts
··· 1 + import type { RequestHandler } from './$types'; 2 + import { PUBLIC_ATPROTO_DID } from '$env/static/public'; 3 + 4 + export const prerender = false; 5 + 6 + export const GET: RequestHandler = ({ setHeaders }) => { 7 + setHeaders({ 'Content-Type': 'text/plain' }); 8 + return new Response(PUBLIC_ATPROTO_DID); 9 + };
+49
src/routes/.well-known/webfinger/+server.ts
··· 1 + import { json, error } from '@sveltejs/kit'; 2 + import type { RequestHandler } from './$types'; 3 + import { PUBLIC_AP_INSTANCE_URL, PUBLIC_AP_USERNAME, PUBLIC_SITE_URL } from '$env/static/public'; 4 + 5 + export const prerender = false; 6 + 7 + function knownResources() { 8 + const instanceDomain = new URL(PUBLIC_AP_INSTANCE_URL).hostname; 9 + const siteDomain = new URL(PUBLIC_SITE_URL).hostname; 10 + return new Set([ 11 + `acct:${PUBLIC_AP_USERNAME}@${instanceDomain}`, 12 + `acct:${PUBLIC_AP_USERNAME}@${siteDomain}`, 13 + `${PUBLIC_AP_INSTANCE_URL}/@${PUBLIC_AP_USERNAME}` 14 + ]); 15 + } 16 + 17 + export const GET: RequestHandler = async ({ url, setHeaders, fetch }) => { 18 + const resource = url.searchParams.get('resource'); 19 + 20 + if (!resource) { 21 + throw error(400, 'Missing resource parameter'); 22 + } 23 + 24 + if (!knownResources().has(resource)) { 25 + throw error(404, 'Resource not found'); 26 + } 27 + 28 + // Proxy the canonical webfinger from the AP instance so we don't need to 29 + // hardcode internal user IDs — the instance always returns the correct links. 30 + const instanceUrl = `${PUBLIC_AP_INSTANCE_URL}/.well-known/webfinger?resource=acct:${PUBLIC_AP_USERNAME}@${new URL(PUBLIC_AP_INSTANCE_URL).hostname}`; 31 + const upstream = await fetch(instanceUrl); 32 + 33 + if (!upstream.ok) { 34 + throw error(502, 'Failed to fetch webfinger from AP instance'); 35 + } 36 + 37 + const webfinger = await upstream.json(); 38 + 39 + // Inject the site-domain alias so Fediverse servers can resolve @user@sitedomain 40 + const siteDomain = new URL(PUBLIC_SITE_URL).hostname; 41 + const siteAlias = `acct:${PUBLIC_AP_USERNAME}@${siteDomain}`; 42 + const aliases: string[] = webfinger.aliases ?? []; 43 + if (!aliases.includes(siteAlias)) { 44 + webfinger.aliases = [siteAlias, ...aliases]; 45 + } 46 + 47 + setHeaders({ 'Content-Type': 'application/jrd+json' }); 48 + return json(webfinger); 49 + };
-1
static/.well-known/atproto-did
··· 1 - did:plc:ofrbh253gwicbkc5nktqepol
+9 -5
vercel.json
··· 29 29 ] 30 30 }, 31 31 { 32 + "source": "/.well-known/webfinger", 33 + "headers": [ 34 + { 35 + "key": "Content-Type", 36 + "value": "application/jrd+json" 37 + } 38 + ] 39 + }, 40 + { 32 41 "source": "/favicon.ico", 33 42 "headers": [ 34 43 { ··· 49 58 ], 50 59 "rewrites": [], 51 60 "redirects": [ 52 - { 53 - "source": "/.well-known/webfinger", 54 - "destination": "https://ap.ewancroft.uk/.well-known/webfinger", 55 - "permanent": false 56 - }, 57 61 { 58 62 "source": "/.well-known/nodeinfo", 59 63 "destination": "https://ap.ewancroft.uk/.well-known/nodeinfo",