FastComments.com

Dodajte live komentiranje u Val Town aplikacije

Val Town pokreće TypeScript na Deno-u, pa je val pravi poslužitelj. To ga čini dobrim izborom za FastComments: widget je script tag na stranici, a sve što treba tajnu, poput Secure SSO ili provjere webhooka, može se izvoditi na poslužiteljskoj strani u istom valu.

Ovaj vodič pokriva dodavanje widgeta za komentare u HTTP val, prikaz broja komentara na indeksnoj stranici, prijavu korisnika pomoću Val Town računa koji već imaju i primanje webhookova za komentare.

Ne trebate račun za probu. Primjeri koriste tenantId: "demo", zajednički sandbox, a korak 2 opisuje prebacivanje na vlastiti.

Broj komentara na indeksnoj stranici Internal Link

Na indeksnoj stranici, nemojte prikazivati jedan widget za broj komentara po retku. To je jedan zahtjev po objavi. Koristite grupni broj, koji uzima jedan zahtjev za cijelu stranicu.

Označite svaki redak s urlId koji njegova nit koristi, zatim učitajte grupni widget jednom:

Broj komentara u grupi na indeksnoj stranici
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

Skript pronalazi svaki element .fast-comments-count na stranici i popunjava njegov broj.

data-fast-comments-url-id mora odgovarati urlId koji widget za komentare objave koristi. Ako widget koristi slug, marker koristi slug. Neslaganje prikazuje nulu na niti koja ima komentare.

Skript provjerava window.FastCommentsBulkCountConfig, pa nije važno postaviti li konfiguraciju prije ili nakon skript taga.

Sigurni SSO s std/oauth Internal Link

Ako vaš val već zna tko je posjetitelj, Secure SSO predaje taj identitet widgetu tako da posjetitelj nikada ne vidi drugu prijavu. Nema endpointa koje treba izgraditi ni ničega što treba pozvati u runtimeu: tri vrijednosti izračunate su na strani servera i prosljeđuju se u konfiguraciji widgeta.

Val Town nudi prijavu bez konfiguracije putem std/oauth, tako da se posjetitelj može prijaviti Val Town računom koji već ima. Zamijenite to onim što koristi vaša aplikacija; FastComments dio se ne mijenja.

Izgradnja payloada na serveru

API tajna potpisuje payload i nikada ne smije dospjeti u kod preglednika. Instalirajte SDK s npm-a, koji radi na Val Town Deno runtimeu bez izmjena:

sso.ts
Copy CopyRun External Link
1
2import { SecureSSOPayloadBuilder } from "npm:fastcomments-sdk/server";
3
4export function buildSSOPayload(user) {
5 // id must be stable for the same person, or they get a new comment identity on every login.
6 const id = `vt-${user.id}`;
7
8 return new SecureSSOPayloadBuilder(Deno.env.get("FASTCOMMENTS_API_SECRET"), {
9 id,
10 // email is required and must be unique.
11 email: user.email ?? `${id}@users.noreply.val.town`,
12 // username is required and cannot be an email.
13 username: user.username ?? id,
14 displayName: user.username ?? undefined,
15 avatar: user.links.profileImageUrl ?? undefined,
16 }).getPayload();
17}
18

getPayload() vraća { userDataJSONBase64, verificationHash, timestamp }. Te tri vrijednosti su sve što dospijeva u preglednik. Tajna ih potpisuje i zatim se odbacuje, tako da ništa na stranici ne omogućuje čitatelju da krivotvori drugog korisnika.

Prosljeđivanje widgetu

Widget config with 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 the widget with this config
17});
18
19export default oauthMiddleware(app.fetch);
20

oauthMiddleware dodaje GET /auth/login, GET /auth/callback i POST /auth/logout umjesto vas. Imajte na umu da je odjava POST, dok widget navigira na logoutURL GET zahtjevom, pa usmjerite logoutURL na malu vlastitu rutu koja šalje POST zahtjev.

Kada je posjetitelj odjavljen, proslijedite sso samo s loginURL. Widget tada prikazuje upit za prijavu umjesto anonimnog okvira za komentare.

Stvari koje mogu poći po krivom

timestamp je u epoch milisekundama, ne smije biti u budućnosti i ne smije biti stariji od dva dana. Generirajte ga na serveru u istom zahtjevu u kojem se računa hash. Generiranje u pregledniku klasična je greška: vrijednost se razlikuje od one koja je potpisana i svaki komentar se odbacuje.

Nikada ne postavljajte isAdmin ili isModerator na temelju davatelja identiteta. Prijava Val Town računom ne govori ništa o tome tko treba moderirati vašu stranicu.

Pogledajte SSO vodič za potpuni popis polja, threadove ograničene grupama i bedževe.

Primanje webhookova Internal Link

A val je prirodni primatelj webhooka: ima stabilan URL, može provjeriti potpis i ima ugrađenu SQLite i blob pohranu.

FastComments potpisuje ${timestamp}.${body} tajnom API-ja vašeg računa i šalje dva zaglavlja:

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

Metoda nosi događaj: PUT za kreirani ili ažurirani komentar, DELETE za izbrisani.

Provjera isporuke
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

Dvije stvari koje grizu

Provjerite sirove bajtove. Parsiranje JSON-a i ponovno serijaliziranje mijenja redoslijed ključeva i razmake, pa se hash razlikuje i svaka isporuka ne uspijeva bez očiglednog uzroka. Ovo je uobičajeni razlog da primatelj webhooka „just doesn't work“.

Usporedite u konstantnom vremenu. Obični === na potpis otkriva koliko bajtova se podudara, što je dovoljno za falsificiranje jednog bajta po jedan.

Obrada događaja

Odgovorite brzo. FastComments ponavlja pokušaje kod ne‑2xx odgovora, a krajnja točka koja stalno ne uspijeva na kraju se automatski onemogućuje, stoga stvarni rad obavite nakon odgovora, a ne inline.

Učinite da rad bude idempotentan po ID‑u komentara. Ponovni pokušaj se ponovno potpisuje s novim vremenskim žigom, a isti ID komentara ponovno dolazi pri uređivanju i brisanju, pa nema ničeg stabilnog na čemu se može deduplicirati.

Primjeri Vals Internal Link

Četiri javna val-a koja možete remixati, svako pokriva jedan dio ovog vodiča.

Blog with comments (live) je Markdown blog s nitima ispod svakog posta i grupiranim brojem komentara na indeksu. Radi odmah nakon što ga remixate, a jedna varijabla okruženja usmjerava ga na vaš vlastiti račun.

SSO demo (live) prijavljuje posjetitelja s njihovim Val Town računom i predaje taj identitet widgetu, tako da nema drugog prijavljivanja.

Webhook receiver (live) provjerava HMAC potpis na svakoj isporuci i pohranjuje događaje u SQLite. Ima gumb koji potpisuje testni payload i isporučuje ga samom sebi, tako da možete vidjeti da je provjera uspješna prije konfiguriranja pravog webhooka.

Agent skills (live) je biblioteka FastComments agenata vještina koje pokrivaju widget, SSO, REST API, moderaciju i migraciju s Disqus‑a. Remixajte ga i Val Townov agent, Townie, automatski preuzima vještine iz skills/, tako da vaš agent zna kako postaviti komentare bez da vi lijepite dokumentaciju u chat.

Iste vještine instaliraju se bilo gdje drugdje pomoću npx skills add fastcomments/skills.