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

If your val already knows who the visitor is, Secure SSO hands that identity to the widget so they never see a second login. There are no endpoints to build and nothing to call at runtime: you compute three values server-side and pass them in the widget config.

Val Town ships zero-config login with std/oauth, so the visitor can sign in with the Val Town account they already have. Swap that for whatever your app uses; the FastComments half does not change.

Build the payload on the server

The API secret signs the payload and must never reach browser code. Install the SDK from npm, which works on Val Town's Deno runtime as-is:

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() returns { userDataJSONBase64, verificationHash, timestamp }. Those three values are all that reach the browser. The secret signs them and is then dropped, so nothing in the page lets a reader forge a different user.

Pass it to the widget

Widget konfiguracija s 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 adds GET /auth/login, GET /auth/callback and POST /auth/logout for you. Note that logout is a POST, while the widget navigates to logoutURL with a GET, so point logoutURL at a small route of your own that submits the POST.

When the visitor is logged out, pass sso with only a loginURL. The widget then shows a login prompt instead of an anonymous comment box.

Things that go wrong

timestamp is epoch milliseconds, must not be in the future, and must not be more than two days old. Generate it on the server in the same request that computes the hash. Generating it in the browser is the classic failure: the value differs from the one that was hashed and every comment is rejected.

Never set isAdmin or isModerator from the identity provider. Signing in with a Val Town account says nothing about who should moderate your site.

See the SSO guide for the full field list, group-gated threads, and badges.

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.