FastComments.com

Dodajte komentarisanje u realnom vremenu u aplikacije Val Town

Val Town pokreće TypeScript na Deno, pa je val pravi server. To ga čini dobrim izborom za FastComments: widget je script tag na stranici, a sve što zahteva tajnu, poput Secure SSO ili verifikacije webhook‑a, može se izvršavati na serveru u istom val‑u.

Ovaj vodič pokriva dodavanje widgeta za komentare u HTTP val, prikazivanje broja komentara na indeksnoj stranici, prijavljivanje korisnika pomoću Val Town naloga koji već imaju, i primanje webhook‑ova za komentare.

Ne morate imati nalog da biste ga isprobali. Primeri koriste tenantId: "demo", zajednički sandbox, a korak 2 objašnjava kako preći na svoj.

Broj komentara na indeksnoj stranici Internal Link

Na indeksnoj stranici, ne prikazujte po jedan widget za brojanje komentara po redu. To je jedan zahtev po objavi. Koristite grupno brojanje, koje zahteva jedan zahtev za celu stranicu.

Označite svaki red sa urlId koji njegova nit koristi, zatim učitajte bulk widget jednom:

Grupno brojanje komentara 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 da se podudara sa urlId koji widget za komentare objave koristi. Ako widget koristi slug, marker koristi slug. Nepodudaranje prikazuje nulu na niti koja ima komentare.

Skript periodično proverava window.FastCommentsBulkCountConfig, pa nije važno da li konfiguraciju postavite pre ili posle <script> taga.

Sigurni SSO sa std/oauth Internal Link

Ako vaš val već zna ko je posetilac, Secure SSO tu identitetu predaje vidžetu tako da nikad ne vidi drugi login. Nema endpointa koje treba napraviti ni čega što se poziva u runtime-u: tri vrednosti izračunate na serverskoj strani i prosleđujete ih u konfiguraciji vidžeta.

Val Town nudi login bez konfiguracije putem std/oauth, tako da se posetilac može prijaviti Val Town nalogom koji već ima. Zamenite to bilo čime što vaša aplikacija koristi; FastComments deo se ne menja.

Izgradite payload na serveru

API tajna potpisuje payload i nikada ne sme dospeti do koda u pregledaču. Instalirajte SDK sa npm-a, koji radi na Val Town Deno runtime-u bez izmena:

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 vrednosti su sve što dospeva do pregledača. Tajna ih potpisuje i zatim se odbacuje, tako da ništa na stranici ne omogućava čitaocu da lažno predstavi drugog korisnika.

Prosledite ga vidžetu

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 umesto vas. Imajte na umu da je logout POST, dok vidžet navigira na logoutURL GET zahtevom, pa usmerite logoutURL na malu sopstvenu rutu koja šalje POST.

Kada je posetilac odjavljen, prosledite sso samo sa loginURL. Vidžet tada prikazuje upit za prijavu umesto anonimnog polja za komentare.

Stvari koje mogu poći naopako

timestamp je epoch milisekunde, ne sme biti u budućnosti i ne sme biti stariji od dva dana. Generišite ga na serveru u istom zahtevu u kojem se računa hash. Generisanje u pregledaču je klasična greška: vrednost se razlikuje od one koja je heširana i svaki komentar se odbija.

Nikada ne postavljajte isAdmin ili isModerator na osnovu identity provajdera. Prijava Val Town nalogom ne govori ništa o tome ko treba da moderira vaš sajt.

Pogledajte SSO vodič za kompletnu listu polja, teme ograničene grupama i bedževe.

Primanje webhook-ova Internal Link

A val je prirodni webhook prijemnik: ima stabilan URL, može verifikovati potpis i ima ugrađenu SQLite i blob skladište.

FastComments potpisuje ${timestamp}.${body} tajnim ključem API-ja vašeg naloga i šalje dva zaglavlja:

Zaglavlja webhook-a
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 kreiran ili ažuriran komentar, DELETE za obrisan komentar.

Verifikacija isporuke
Copy CopyRun External Link
1
2import { createHmac, timingSafeEqual } from "node:crypto";
3
4async function receive(c) {
5 // Tačni bajtovi koji su stigli. Nemojte koristiti c.req.json() i ponovo serijalizovati.
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 // Odbaci zastarele isporuke kako uhvaćeni zahtev ne bi mogao biti ponovo reprodukovan kasnije.
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 // ...obradi JSON.parse(rawBody)
28 return Response.json({ received: true });
29}
30
31app.put("/", receive);
32app.delete("/", receive);
33

Dve stvari koje mogu da zagrizu

Verifikujte sirove bajtove. Parsiranje JSON-a i njegovo ponovo serijalizovanje menja redosled ključeva i razmake, pa se hash razlikuje i svaka isporuka ne uspeva bez očiglednog uzroka. Ovo je uobičajen razlog da webhook prijemnik „jednostavno ne radi“.

Uporedite u konstantnom vremenu. Obična === operacija na potpisu otkriva koliko bajtova se podudara, što je dovoljno da se falsifikuje po jedan bajt odjednom.

Obrada događaja

Odgovorite brzo. FastComments ponovo pokušava na ne‑2xx odgovoru, a krajnja tačka koja stalno ne uspeva se na kraju automatski onemogućava, zato obavite stvarni rad nakon odgovora, a ne inline.

Učinite da rad bude idempotentan po ID‑u komentara. Ponovni pokušaj se ponovo potpisuje svežim timestamp‑om, a isti ID komentara dolazi ponovo pri izmeni i brisanju, pa nema stabilnog podatka za deduplikaciju.

Primer Vals Internal Link

Četiri javna vala koja možete remixovati, svako pokriva jedan deo ovog vodiča.

Blog sa komentarima (uživo) je Markdown blog sa nitima ispod svakog posta i grupnim brojem komentara na indeksu. Radi odmah kada ga remixujete, a jedna promenljiva okruženja usmerava ga na vaš sopstveni nalog.

SSO demo (uživo) prijavljuje posetioca pomoću njihovog Val Town naloga i predaje taj identitet widgetu, tako da nema drugog prijavljivanja.

Webhook prijemnik (uživo) verifikuje HMAC potpis na svakoj isporuci i čuva događaje u SQLite. Ima dugme koje potpisuje testni payload i šalje ga samom sebi, tako da možete videti da verifikacija uspe pre podešavanja pravog webhook‑a.

Veštine agenta (uživo) je biblioteka FastComments veština agenta koja pokriva widget, SSO, REST API, moderaciju i migraciju sa Disqus‑a. Remixujte je i agent Val Town‑a, Townie, automatski preuzima veštine iz skills/, tako da vaš agent zna kako da postavi komentare bez da vi lepite dokumentaciju u ćaskanje.

Iste veštine se instaliraju bilo gde drugde pomoću npx skills add fastcomments/skills.