FastComments.com

Tilføj live-kommentarer til Val Town-apps

Val Town kører TypeScript på Deno, så en val er en rigtig server. Det gør den velegnet til FastComments: widget'en er et script‑tag på siden, og alt der har brug for en hemmelighed, som Secure SSO eller verifikation af en webhook, kan køre server‑side i den samme val.

Denne guide dækker, hvordan du tilføjer kommentarswidget'en til en HTTP‑val, viser kommentarantal på en indeks‑side, logger brugere ind med den Val Town‑konto, de allerede har, og modtager kommentar‑webhooks.

Du behøver ikke en konto for at prøve det. Eksemplerne bruger tenantId: "demo", en delt sandbox, og trin 2 dækker, hvordan du skifter til din egen.

Kommentarantal på en indeks-side Internal Link

På en indeksside skal du ikke gengive én comment-count-widget pr. række. Det er én anmodning pr. opslag. Brug bulk-tællingen, som tager en enkelt anmodning for hele siden.

Markér hver række med den urlId, dens tråd bruger, og indlæs derefter bulk-widgetten én gang:

Bulk-kommentartællinger på en indeksside
Copy CopyRun External Link
1
2<ul>
3 {posts.map((post) => (
4 <li>
5 <a href={post.slug}>{post.title}</a>{" "}
6 <span class="fast-comments-count" data-fast-comments-url-id={post.slug}></span>
7 </li>
8 ))}
9</ul>
10
11<script
12 dangerouslySetInnerHTML={{
13 __html: `window.FastCommentsBulkCountConfig = ${
14 JSON.stringify({ tenantId: TENANT_ID })
15 };`,
16 }}
17/>
18<script src="https://cdn.fastcomments.com/js/embed-widget-comment-count-bulk.min.js"></script>
19

Scriptet finder hvert .fast-comments-count-element på siden og udfylder dets tælling.

data-fast-comments-url-id skal matche den urlId, som opslagets kommentarwidget bruger. Hvis widgetten bruger slug'en, bruger markøren slug'en. En uoverensstemmelse viser nul på en tråd, der har kommentarer.

Scriptet poller efter window.FastCommentsBulkCountConfig, så det er ligegyldigt, om du angiver konfigurationen før eller efter script-tagget.

Sikre SSO med std/oauth Internal Link

Hvis din val allerede ved, hvem den besøgende er, giver Secure SSO denne identitet videre til widget'en, så de aldrig ser en anden login. Der er ingen endpoints at bygge og intet at kalde ved runtime: du beregner tre værdier server-side og sender dem med i widget-konfigurationen.

Val Town leverer zero-config login med std/oauth, så den besøgende kan logge ind med den Val Town-konto, de allerede har. Udskift det med, hvad din app nu bruger; FastComments-delen ændres ikke.

Byg payload'en på serveren

API-secret'en signerer payload'en og må aldrig nå browser-koden. Installer SDK'en fra npm, som virker på Val Towns Deno-runtime som den er:

sso.ts
Copy CopyRun External Link
1
2import { SecureSSOPayloadBuilder } from "npm:fastcomments-sdk/server";
3
4export function buildSSOPayload(user) {
5 // id skal være stabilt for den samme person, ellers får de en ny kommentar-identitet ved hvert login.
6 const id = `vt-${user.id}`;
7
8 return new SecureSSOPayloadBuilder(Deno.env.get("FASTCOMMENTS_API_SECRET"), {
9 id,
10 // email er påkrævet og skal være unik.
11 email: user.email ?? `${id}@users.noreply.val.town`,
12 // username er påkrævet og må ikke være en email.
13 username: user.username ?? id,
14 displayName: user.username ?? undefined,
15 avatar: user.links.profileImageUrl ?? undefined,
16 }).getPayload();
17}
18

getPayload() returnerer { userDataJSONBase64, verificationHash, timestamp }. Det er de tre værdier, der når browseren. Secret'en signerer dem og smides derefter væk, så intet på siden gør det muligt for en læser at forfalske en anden bruger.

Send det til widget'en

Widget-konfiguration med SSO
Copy CopyRun External Link
1
2import { getOAuthUserData, oauthMiddleware } from "https://esm.town/v/std/oauth/middleware.ts";
3
4app.get("/", async (c) => {
5 const session = await getOAuthUserData(c.req.raw);
6 const user = session?.user;
7
8 const config = {
9 tenantId: TENANT_ID,
10 urlId: "my-thread",
11 ...(user
12 ? { sso: { ...buildSSOPayload(user), logoutURL: "/logout" } }
13 : { sso: { loginURL: "/auth/login" } }),
14 };
15
16 // ...render widget'en med denne konfiguration
17});
18
19export default oauthMiddleware(app.fetch);
20

oauthMiddleware tilføjer GET /auth/login, GET /auth/callback og POST /auth/logout for dig. Bemærk, at logout er en POST, mens widget'en navigerer til logoutURL med en GET, så peg logoutURL på en lille route hos dig selv, som sender POST'en.

Når den besøgende er logget ud, sendes sso med kun en loginURL. Widget'en viser så en login-prompt i stedet for en anonym kommentarboks.

Ting der går galt

timestamp er i epoch-millisekunder, må ikke ligge i fremtiden og må ikke være mere end to dage gammel. Generér den på serveren i samme request, som beregner hashen. At generere den i browseren er den klassiske fejl: værdien afviger fra den, der blev hashet, og hver kommentar afvises.

Sæt aldrig isAdmin eller isModerator ud fra identitetsudbyderen. At logge ind med en Val Town-konto siger intet om, hvem der bør moderere dit site.

Se SSO-guiden for den fulde feltliste, gruppebegrænsede tråde og badges.

Modtag webhooks Internal Link

En val er en naturlig webhook-modtager: den har en stabil URL, den kan verificere en signatur, og den har SQLite og blob-lagring indbygget.

FastComments signerer ${timestamp}.${body} med din kontos API-hemmelighed og sender to headers:

Webhook-overskrifter
Copy CopyRun External Link
1
2X-FastComments-Timestamp: 1789004710 unix seconds, not milliseconds
3X-FastComments-Signature: sha256=<hex>
4

Metoden bærer hændelsen: PUT for en oprettet eller opdateret kommentar, DELETE for en slettet.

Verificering af en levering
Copy CopyRun External Link
1
2import { createHmac, timingSafeEqual } from "node:crypto";
3
4async function receive(c) {
5 // The exact bytes that arrived. Do NOT use c.req.json() and re-serialize.
6 const rawBody = await c.req.raw.text();
7 const timestamp = c.req.raw.headers.get("X-FastComments-Timestamp");
8 const signature = c.req.raw.headers.get("X-FastComments-Signature");
9
10 if (!timestamp || !signature) return new Response("Missing headers", { status: 400 });
11
12 // Reject stale deliveries so a captured request cannot be replayed later.
13 if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) {
14 return new Response("Timestamp outside window", { status: 400 });
15 }
16
17 const expected = "sha256=" + createHmac("sha256", Deno.env.get("FASTCOMMENTS_API_SECRET"))
18 .update(`${timestamp}.${rawBody}`)
19 .digest("hex");
20
21 const a = new TextEncoder().encode(signature);
22 const b = new TextEncoder().encode(expected);
23 if (a.length !== b.length || !timingSafeEqual(a, b)) {
24 return new Response("Signature mismatch", { status: 401 });
25 }
26
27 // ...handle JSON.parse(rawBody)
28 return Response.json({ received: true });
29}
30
31app.put("/", receive);
32app.delete("/", receive);
33

To ting der bider

Verificer de rå bytes. Parsing af JSON og gen-serialisering ændrer nøgleordren og mellemrum, så hash'en er forskellig, og hver levering fejler uden en åbenlys årsag. Dette er den sædvanlige grund til, at en webhook-modtager "bare ikke virker".

Sammenlign i konstant tid. En simpel === på signaturen lækker hvor mange bytes der matchede, hvilket er nok til at forfalske en byte ad gangen.

Håndtering af hændelser

Svar hurtigt. FastComments forsøger igen ved et svar, der ikke er 2xx, og et endpoint, der fortsat fejler, deaktiveres til sidst automatisk, så udfør reelt arbejde efter at have svaret i stedet for inline.

Gør arbejdet idempotent på kommentar-id'et. Et retry bliver gen-signeret med en frisk tidsstempel, og det samme kommentar-id ankommer igen ved redigering og sletning, så der er intet stabilt at deduplere på.

Eksempel Vals Internal Link

Fire offentlige vals, du kan remixe, som hver især dækker en del af denne guide.

Blog med kommentarer (live) er en Markdown-blog med en tråd under hvert opslag og samlede kommentartællinger på indekssiden. Den virker, så snart du remixer den, og én miljøvariabel peger den på din egen konto.

SSO-demo (live) logger besøgende ind med deres Val Town-konto og giver denne identitet videre til widgetten, så der ikke er behov for et andet login.

Webhook-modtager (live) verificerer HMAC-signaturen ved hver levering og gemmer events i SQLite. Den har en knap, der signerer en test-payload og leverer den til sig selv, så du kan se verifikationen lykkes, før du konfigurerer en rigtig webhook.

Agent skills (live) er et bibliotek af FastComments agent skills, der dækker widgetten, SSO, REST API'et, moderation og migrering væk fra Disqus. Remix den, og Val Towns agent, Townie, henter automatisk skills fra skills/, så din agent ved, hvordan man sætter kommentarer op, uden at du skal indsætte dokumentation i chatten.

De samme skills kan installeres andre steder med npx skills add fastcomments/skills.