From bde06dd3e4e35f5a042d9c90c45a86f017cae07a Mon Sep 17 00:00:00 2001 From: Julien Herr Date: Thu, 21 May 2026 23:46:38 +0200 Subject: [PATCH] fix(websub): add missing WebSub Link header to Atom feed The RSS feed already advertised hub and self via the Link response header, but the Atom feed was missing it entirely. Subscribers using Atom had no way to discover the hub for real-time push notifications. Co-Authored-By: Claude Sonnet 4.6 --- src/routes/atom.test.ts | 8 ++++++++ src/routes/atom.ts | 6 ++++++ 2 files changed, 14 insertions(+) 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) {