mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
feat(websub): add hub route (subscribe/unsubscribe)
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { Hono } from "hono";
|
||||
import { Env } from "../types";
|
||||
import {
|
||||
verifyAndStoreSubscription,
|
||||
verifyAndDeleteSubscription,
|
||||
} from "../utils/websub";
|
||||
|
||||
const DEFAULT_LEASE_SECONDS = 86400;
|
||||
const MAX_LEASE_SECONDS = 30 * 24 * 3600; // 30 days
|
||||
|
||||
export const hubRouter = new Hono();
|
||||
|
||||
hubRouter.post("/", async (c) => {
|
||||
const env = c.env as unknown as Env;
|
||||
let form: FormData;
|
||||
try {
|
||||
form = await c.req.formData();
|
||||
} catch {
|
||||
return c.text(
|
||||
"Bad Request: expected application/x-www-form-urlencoded",
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
const mode = form.get("hub.mode");
|
||||
const topic = form.get("hub.topic");
|
||||
const callbackUrl = form.get("hub.callback");
|
||||
|
||||
if (!mode || !topic || !callbackUrl) {
|
||||
return c.text(
|
||||
"Bad Request: hub.mode, hub.topic and hub.callback are required",
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
if (mode !== "subscribe" && mode !== "unsubscribe") {
|
||||
return c.text(
|
||||
"Bad Request: hub.mode must be subscribe or unsubscribe",
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
// Validate that topic matches a known RSS feed on this hub
|
||||
const topicPattern = new RegExp(
|
||||
`^https://${env.DOMAIN.replace(".", "\\.")}/rss/([^/]+)$`,
|
||||
);
|
||||
const match = topic.match(topicPattern);
|
||||
if (!match) {
|
||||
return c.text(
|
||||
"Bad Request: hub.topic must be an RSS feed URL on this hub",
|
||||
400,
|
||||
);
|
||||
}
|
||||
const feedId = match[1];
|
||||
|
||||
const secret = form.get("hub.secret") ?? undefined;
|
||||
const rawLease = parseInt(form.get("hub.lease_seconds") ?? "", 10);
|
||||
const leaseSeconds = isNaN(rawLease)
|
||||
? DEFAULT_LEASE_SECONDS
|
||||
: Math.min(Math.max(rawLease, 1), MAX_LEASE_SECONDS);
|
||||
|
||||
// Return 202 immediately; verification is async
|
||||
if (mode === "subscribe") {
|
||||
c.executionCtx.waitUntil(
|
||||
verifyAndStoreSubscription(
|
||||
feedId,
|
||||
callbackUrl as string,
|
||||
secret as string | undefined,
|
||||
leaseSeconds,
|
||||
env,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
c.executionCtx.waitUntil(
|
||||
verifyAndDeleteSubscription(feedId, callbackUrl as string, env),
|
||||
);
|
||||
}
|
||||
|
||||
return c.text("Accepted", 202);
|
||||
});
|
||||
Reference in New Issue
Block a user