FastComments.com

Dodajte live komentarisanje u Val Town aplikacije

Val Town извршава TypeScript на Deno, тако да је вал прави сервер. То га чини добрим избором за FastComments: виџет је script ознака на страници, а све што захтева тајну, као што су Secure SSO или верификација вебхука, може да се изврши на серверу у истом валу.

Овај водич обухвата додавање виџета за коментаре у HTTP вал, приказ броја коментара на индекс страници, пријављивање корисника помоћу постојећег Val Town налога, и примање вебхукова за коментаре.

Не треба вам налог да бисте пробали. Примери користе tenantId: "demo", заједничку песочницу, а корак 2 објашњава како да пређете на ваш сопствени.

Broj komentara na indeksnoj stranici Internal Link

На индекс страници, немојте да рендерујете један widget за број коментара по реду. То је један захтев по посту. Користите групни број, који захтева један захтев за целу страницу.

Означите сваки ред urlId‑ом који користи његова тема, а затим учитајте групни widget једном:

Групни број коментара на индекс страници
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

Скрипта проналази сваки елемент .fast-comments-count на страници и попуњава његов број.

data-fast-comments-url-id мора да се поклапа са urlId‑ом који користи widget за коментаре поста. Ако widget користи slug, маркер користи slug. Неусаглашеност приказује нулу на теми која има коментаре.

Скрипта проверава window.FastCommentsBulkCountConfig, па није битно да ли постављате конфигурацију пре или после script таг‑а.

Sigurni SSO sa std/oauth Internal Link

Ако ваш val већ зна ко је посетилац, Secure SSO предаје тај идентитет виџету тако да посетилац никада не види другу пријаву. Нема крајњих тачака за изградњу и нема ничега за позивати у току рада: израчунавате три вредности на серверу и прослеђујете их у конфигурацију виџета.

Val Town нуди пријаву без конфигурације преко std/oauth, тако да посетилац може да се пријави Val Town налогом који већ има. Замените то оним што ваша апликација користи; FastComments део се не мења.

Изградите payload на серверу

API тајна потписује payload и никада не сме да доспе до кода у прегледачу. Инсталирајте SDK са npm-а, који ради на Val Town Deno runtime-у какав јесте:

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() враћа { userDataJSONBase64, verificationHash, timestamp }. Те три вредности су све што доспева до прегледача. Тајна их потписује и затим се одбацује, тако да ништа на страни не омогућава читаоцу да фалсификује другог корисника.

Проследите га виџету

Конфигурација виџета са 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 додаје GET /auth/login, GET /auth/callback и POST /auth/logout уместо вас. Имајте у виду да је одјава POST, док виџет одлази на logoutURL са GET, па усмерите logoutURL на малу руту коју сами правите и која шаље тај POST.

Када је посетилац одјављен, проследите sso са само loginURL. Виџет тада приказује позив за пријаву уместо анонимног поља за коментаре.

Ствари које крену наопако

timestamp је у епохи милисекундама, не сме бити у будућности и не сме бити старији од два дана. Генеришите га на серверу у истом захтеву у коме се рачуна хеш. Генерисање у прегледачу је класична грешка: вредност се разликује од оне која је хеширана и сваки коментар бива одбијен.

Никада не постављајте isAdmin или isModerator од добављача идентитета. Пријава Val Town налогом не говори ништа о томе ко би требало да модерира ваш сајт.

Погледајте SSO водич за потпуну листу поља, теме ограничене групама и значке.

Primanje webhook-ova Internal Link

A val је природни прималац вебхука: има стабилну URL адресу, може да верификује потпис, и има уграђену SQLite и blob складиште.

FastComments потписује ${timestamp}.${body} тајном вашег налога за API и шаље два заглавља:

Заглавља вебхука
Copy CopyRun External Link
1
2X-FastComments-Timestamp: 1789004710 unix seconds, not milliseconds
3X-FastComments-Signature: sha256=<hex>
4

Метод преноси догађај: PUT за креиран или ажуриран коментар, DELETE за обрисан.

Провера испоруке
Copy CopyRun External Link
1
2import { createHmac, timingSafeEqual } from "node:crypto";
3
4async function receive(c) {
5 // Тачни бајтови који су стигли. НЕ користите c.req.json() и поново серијализујте.
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 // Одбаци застареле испоруке како захваћени захтев не би могао бити поново реплициран касније.
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 // ...обради JSON.parse(rawBody)
28 return Response.json({ received: true });
29}
30
31app.put("/", receive);
32app.delete("/", receive);
33

Две ствари које гризу

Провери сирове бајтове. Парсирање JSON-а и поновно серијализовање мења редослед кључева и размаке, па хеш буде различит и свака испорука не успе без очигледног разлога. Ово је уобичајен разлог зашто прималац вебхука "само не ради".

Упореди у константном времену. Обичан === над потписом открива колико бајтова се поклапа, што је довољно за фалсификовање једног бајта појединачно.

Обрада догађаја

Одговори брзо. FastComments поново покушава на не‑2xx одговор, а крајња тачка која стално не успева се на крају аутоматски онемогућава, па изврши прави посао након одговора уместо унутар.

Учини да рад буде идемпотентан по ID-ју коментара. Поновни покушај се поново потписује са свежим временским жигом, а исти ID коментара се поново пошиље приликом измене и брисања, тако да нема стабилног атрибута за дедаупликацију.

Primer Vals Internal Link

Четири јавна vals-а које можете ремиксовати, сваки покрива по један део овог водича.

Блог са коментарима (уживо) је Markdown блог са нитима коментара испод сваког поста и укупним бројевима коментара на индексу. Ради чим га ремиксујете, а једна променљива окружења усмерава га на ваш налог.

SSO демо (уживо) пријављује посетиоца преко његовог Val Town налога и предаје тај идентитет виџету, тако да нема друге пријаве.

Пријемник webhook-ова (уживо) верификује HMAC потпис на свакој достави и чува догађаје у SQLite. Има дугме које потписује тестни payload и доставља га самом себи, тако да можете видети успешну верификацију пре него што конфигуришете стварни webhook.

Agent skills (уживо) је библиотека FastComments agent skills-ова који покривају виџет, SSO, REST API, модерацију и миграцију са Disqus-а. Ремиксујте га и Val Town-ов агент, Townie, аутоматски преузима skills-ове из skills/, тако да ваш агент зна како да повеже коментаре без тога да лепите документацију у ћаскање.

Исти skills-ови се инсталирају било где другде са npx skills add fastcomments/skills.