diff --git a/src/routes/atom.test.ts b/src/routes/atom.test.ts index 959cb43..07dafc8 100644 --- a/src/routes/atom.test.ts +++ b/src/routes/atom.test.ts @@ -119,6 +119,14 @@ describe("Atom Feed Route", () => { const body = await res.text(); expect(body).toContain(`/atom/${FEED_ID}`); }); + + it("Link header advertises hub and self for WebSub discovery", async () => { + const res = await testApp.request(`/${FEED_ID}`, {}, mockEnv); + const link = res.headers.get("Link") ?? ""; + expect(link).toContain(`rel="hub"`); + expect(link).toContain(`/atom/${FEED_ID}`); + expect(link).toContain(`rel="self"`); + }); }); describe("fallback config when no config in KV", () => { diff --git a/src/routes/atom.ts b/src/routes/atom.ts index c05bea5..993976f 100644 --- a/src/routes/atom.ts +++ b/src/routes/atom.ts @@ -51,11 +51,17 @@ export async function handle(c: Context): Promise { const baseUrl = `https://${env.DOMAIN}`; const atomXml = generateAtomFeed(feedConfig, emailsData, baseUrl, feedId); + const linkHeader = [ + `<${baseUrl}/hub>; rel="hub"`, + `<${baseUrl}/atom/${feedId}>; rel="self"`, + ].join(", "); + return new Response(atomXml, { status: 200, headers: { "Content-Type": "application/atom+xml", "Cache-Control": "max-age=1800", + Link: linkHeader, }, }); } catch (error) {