From b811b43bc26b5d4bfe9db41d68ee3856503086e6 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Wed, 4 Mar 2026 17:33:18 +0100 Subject: [PATCH] feat: syndication webhook on incremental builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an eleventy.after hook that triggers syndication immediately after incremental rebuilds, cutting latency from ~2 min (poller) to ~5 sec. Uses built-in crypto for HS256 JWT — no new dependencies. Confab-Link: http://localhost:8080/sessions/d116ad5b-ef8a-424e-9ebe-76c06bef1df6 --- eleventy.config.js | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/eleventy.config.js b/eleventy.config.js index 2225dab..ba8a9b4 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -9,7 +9,7 @@ import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight"; import { minify } from "html-minifier-terser"; import registerUnfurlShortcode, { getCachedCard, prefetchUrl } from "./lib/unfurl-shortcode.js"; import matter from "gray-matter"; -import { createHash } from "crypto"; +import { createHash, createHmac } from "crypto"; import { execFileSync } from "child_process"; import { readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync, copyFileSync } from "fs"; import { resolve, dirname } from "path"; @@ -1096,6 +1096,41 @@ export default function (eleventyConfig) { } + // Syndication webhook — trigger after incremental rebuilds (new posts are now live) + // Cuts syndication latency from ~2 min (poller) to ~5 sec (immediate trigger) + if (incremental) { + const syndicateUrl = process.env.SYNDICATE_WEBHOOK_URL; + if (syndicateUrl) { + try { + const secretFile = process.env.SYNDICATE_SECRET_FILE || "/app/data/config/.secret"; + const secret = readFileSync(secretFile, "utf-8").trim(); + + // Build a minimal HS256 JWT using built-in crypto (no jsonwebtoken dependency) + const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url"); + const now = Math.floor(Date.now() / 1000); + const payload = Buffer.from(JSON.stringify({ + me: siteUrl, + scope: "update", + iat: now, + exp: now + 300, // 5 minutes + })).toString("base64url"); + const signature = createHmac("sha256", secret) + .update(`${header}.${payload}`) + .digest("base64url"); + const token = `${header}.${payload}.${signature}`; + + const res = await fetch(`${syndicateUrl}?token=${token}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + signal: AbortSignal.timeout(30000), + }); + console.log(`[syndicate-hook] Triggered syndication: ${res.status}`); + } catch (err) { + console.error(`[syndicate-hook] Failed:`, err.message); + } + } + } + // WebSub hub notification — skip on incremental rebuilds if (incremental) return; const hubUrl = "https://websubhub.com/hub";