feat: syndication webhook on incremental builds

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
This commit is contained in:
Ricardo
2026-03-04 17:33:18 +01:00
parent c766a981c1
commit b811b43bc2

View File

@@ -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";