diff --git a/CHANGELOG.md b/CHANGELOG.md index d3df1e3..b9b9849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ verbatim as the GitHub Release notes — so what you write here is what ships. ### Fixed +- RSS and Atom feeds now advertise the WebSub hub inside the feed body + (``), not just in the HTTP `Link` header. Readers like + FreshRSS discover the hub from the XML, so they can now subscribe and receive + an instant push when a new email arrives instead of waiting up to the cache + `max-age` (30 min) to refresh. - Subscription-confirmation detection now recognises a confirm email whose CTA button carries the subscribe/subscription hint only in its visible text (e.g. "Yes, subscribe me to this mailing list.") over an opaque tracking-redirect diff --git a/src/infrastructure/feed-generator.test.ts b/src/infrastructure/feed-generator.test.ts index db887ca..fb98e77 100644 --- a/src/infrastructure/feed-generator.test.ts +++ b/src/infrastructure/feed-generator.test.ts @@ -130,6 +130,17 @@ describe("generateRssFeed", () => { expect(result).toContain(`${BASE_URL}/rss/${FEED_ID}`); }); + it("advertises the WebSub hub in the RSS body", () => { + const result = generateRssFeed( + mockFeedConfig, + mockEmails, + BASE_URL, + FEED_ID, + ); + expect(result).toContain('rel="hub"'); + expect(result).toContain(`${BASE_URL}/hub`); + }); + it("includes email entries as elements", () => { const result = generateRssFeed( mockFeedConfig, @@ -280,6 +291,17 @@ describe("generateAtomFeed", () => { expect(result).toContain(`${BASE_URL}/atom/${FEED_ID}`); }); + it("advertises the WebSub hub in the Atom body", () => { + const result = generateAtomFeed( + mockFeedConfig, + mockEmails, + BASE_URL, + FEED_ID, + ); + expect(result).toContain('rel="hub"'); + expect(result).toContain(`${BASE_URL}/hub`); + }); + it("includes rss alternate link", () => { const result = generateAtomFeed( mockFeedConfig, diff --git a/src/infrastructure/feed-generator.ts b/src/infrastructure/feed-generator.ts index 5575578..6e616f1 100644 --- a/src/infrastructure/feed-generator.ts +++ b/src/infrastructure/feed-generator.ts @@ -35,6 +35,10 @@ function buildFeed( // Public "website" for this feed: its own read URL (never the inbound address // or an auth-gated admin path, so the feed output leaks neither). link: `${baseUrl}/rss/${feedId}`, + // WebSub hub advertised in the feed body (). Readers like + // FreshRSS discover the hub here, not from the HTTP Link header, so without it + // they never subscribe and only refresh on cache expiry. + hub: `${baseUrl}/hub`, language: feedConfig.language, updated: new Date(), generator: "kill-the-news",