FastComments.com

Val Town Uygulamalarına Canlı Yorum Ekle

Val Town Deno üzerinde TypeScript çalıştırır, bu yüzden bir val gerçek bir sunucudur. Bu, FastComments için iyi bir uyum sağlar: widget sayfada bir script etiketi olarak bulunur ve Secure SSO gibi bir gizli bilgiye ya da bir webhook doğrulamaya ihtiyaç duyan her şey aynı val içinde sunucu tarafında çalıştırılabilir.

Bu kılavuz, yorum widget'ını bir HTTP val'ına eklemeyi, bir indeks sayfasında yorum sayılarını göstermeyi, kullanıcıları zaten sahip oldukları Val Town hesabı ile oturum açtırmayı ve yorum webhook'larını almayı kapsar.

Bunu denemek için bir hesaba ihtiyacınız yok. Örnekler tenantId: "demo" adlı paylaşılan bir sandbox kullanır ve Adım 2, kendi sandbox'ınıza geçişi kapsar.

İndeks Sayfasında Yorum Sayıları Internal Link

Bir dizin sayfasında her satır için ayrı bir yorum sayacı widget'ı oluşturmayın. Bu, her gönderi için bir istek demektir. Tüm sayfa için tek bir istek alan toplu sayacı (bulk count) kullanın.

Her satırı, ilgili thread'in kullandığı urlId ile işaretleyin, ardından toplu widget'ı bir kez yükleyin:

Bir dizin sayfasında toplu yorum sayaçları
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

Betik, sayfadaki her .fast-comments-count öğesini bulur ve sayacını doldurur.

data-fast-comments-url-id, ilgili gönderinin yorum widget'ının kullandığı urlId ile eşleşmelidir. Widget slug kullanıyorsa, işaretleyici de slug kullanır. Bir uyuşmazlık, yorumları olan bir thread'de sıfır gösterilmesine neden olur.

Betik, window.FastCommentsBulkCountConfig için yoklama yapar, bu nedenle yapılandırmayı script etiketinden önce veya sonra ayarlamanız fark etmez.

std/oauth ile Güvenli SSO Internal Link

Val'iniz ziyaretçinin kim olduğunu zaten biliyorsa, Secure SSO bu kimliği widget'a aktarır ve ziyaretçi ikinci bir giriş ekranı görmez. Oluşturulacak bir uç nokta yok ve çalışma zamanında çağrılacak bir şey yok: üç değeri sunucu tarafında hesaplar ve widget yapılandırmasına geçirirsiniz.

Val Town, std/oauth ile sıfır yapılandırmalı giriş sunar, böylece ziyaretçi zaten sahip olduğu Val Town hesabıyla oturum açabilir. Bunu uygulamanızın kullandığı herhangi bir şeyle değiştirebilirsiniz; FastComments tarafı değişmez.

Payload'ı sunucuda oluşturun

API gizli anahtarı payload'ı imzalar ve asla tarayıcı koduna ulaşmamalıdır. SDK'yı npm'den yükleyin; Val Town'un Deno çalışma zamanında olduğu gibi çalışır:

sso.ts
Copy CopyRun External Link
1
2import { SecureSSOPayloadBuilder } from "npm:fastcomments-sdk/server";
3
4export function buildSSOPayload(user) {
5 // id aynı kişi için sabit olmalıdır, aksi halde her girişte yeni bir yorum kimliği alır.
6 const id = `vt-${user.id}`;
7
8 return new SecureSSOPayloadBuilder(Deno.env.get("FASTCOMMENTS_API_SECRET"), {
9 id,
10 // email zorunludur ve benzersiz olmalıdır.
11 email: user.email ?? `${id}@users.noreply.val.town`,
12 // username zorunludur ve bir e-posta olamaz.
13 username: user.username ?? id,
14 displayName: user.username ?? undefined,
15 avatar: user.links.profileImageUrl ?? undefined,
16 }).getPayload();
17}
18

getPayload(), { userDataJSONBase64, verificationHash, timestamp } değerlerini döndürür. Tarayıcıya ulaşan yalnızca bu üç değerdir. Gizli anahtar bunları imzalar ve ardından atılır, böylece sayfadaki hiçbir şey bir okuyucunun farklı bir kullanıcı taklit etmesini sağlamaz.

Widget'a geçirin

SSO ile widget yapılandırması
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 // ...widget'ı bu yapılandırmayla render edin
17});
18
19export default oauthMiddleware(app.fetch);
20

oauthMiddleware, sizin için GET /auth/login, GET /auth/callback ve POST /auth/logout uç noktalarını ekler. Çıkış işleminin bir POST olduğunu, ancak widget'ın logoutURL adresine GET ile gittiğini unutmayın; bu nedenle logoutURL adresini, POST isteğini gönderen kendi küçük bir rotanıza yönlendirin.

Ziyaretçi oturum açmamışken, yalnızca bir loginURL içeren sso değerini geçirin. Widget daha sonra anonim yorum kutusu yerine bir giriş istemi gösterir.

Ters gidebilecek şeyler

timestamp epoch milisaniyesi olmalıdır, gelecekte olmamalı ve iki günden eski olmamalıdır. Hash'i hesaplayan aynı istekte sunucu tarafında oluşturun. Bunu tarayıcıda oluşturmak klasik hatadır: değer, hash'lenenden farklı olur ve her yorum reddedilir.

Kimlik sağlayıcıdan asla isAdmin veya isModerator ayarlamayın. Bir Val Town hesabıyla oturum açmak, sitenizi kimin yönetmesi gerektiği hakkında hiçbir şey söylemez.

Alan listesinin tamamı, grup korumalı konular ve rozetler için SSO rehberine bakın.

Webhooks Alma Internal Link

A val, doğal bir webhook alıcısıdır: sabit bir URL'ye sahiptir, bir imzayı doğrulayabilir ve SQLite ile blob depolama yerleşiktir.

FastComments, ${timestamp}.${body} ifadesini hesabınızın API gizli anahtarıyla imzalar ve iki başlık gönderir:

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

Yöntem olayı taşır: oluşturulan veya güncellenen bir yorum için PUT, silinen bir yorum için DELETE.

Teslimatı doğrulama
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

Sorun yaratan iki şey

Ham baytları doğrulayın. JSON'u ayrıştırıp yeniden serileştirmek, anahtar sırasını ve boşlukları değiştirir, bu yüzden hash farklı olur ve her teslimat belirgin bir neden olmadan başarısız olur. Bu, bir webhook alıcısının “sadece çalışmıyor” olmasının yaygın nedenidir.

Sabit zamanlı karşılaştırma yapın. İmza üzerinde basit bir === ifadesi, kaç baytın eşleştiğini sızdırır ve bu, tek tek bir baytı taklit etmek için yeterlidir.

Olayları işleme

Hızlı yanıt verin. FastComments, 2xx olmayan bir yanıt alındığında yeniden deneme yapar ve sürekli başarısız olan bir uç nokta sonunda otomatik olarak devre dışı bırakılır, bu yüzden gerçek işi yanıt verdikten sonra yapın, satır içinde değil.

Bu işlemi yorum kimliği üzerinde idempotent (tekrarlanabilir) hâle getirin. Bir yeniden deneme, yeni bir zaman damgası ile yeniden imzalanır ve aynı yorum kimliği düzenleme ve silme sırasında tekrar gelir, bu yüzden tutarlı bir şekilde yinelenenleri ayıklamak için sabit bir şey yoktur.

Örnek Vals Internal Link

Bu rehberin her bir parçasını kapsayan, remixleyebileceğiniz dört herkese açık val.

Blog with comments (canlı), her gönderinin altında bir yorum dizisi ve ana sayfada toplu yorum sayıları bulunan bir Markdown blogudur. Remixlediğiniz anda çalışır ve tek bir ortam değişkeni onu kendi hesabınıza yönlendirir.

SSO demo (canlı), ziyaretçiyi Val Town hesabıyla oturum açar ve bu kimliği widget'a aktarır; böylece ikinci bir giriş gerekmez.

Webhook receiver (canlı), her teslimatta HMAC imzasını doğrular ve olayları SQLite'ta saklar. Bir test yükünü imzalayıp kendine teslim eden bir düğmesi vardır; böylece gerçek bir webhook yapılandırmadan önce doğrulamanın başarılı olduğunu izleyebilirsiniz.

Agent skills (canlı), widget, SSO, REST API, moderasyon ve Disqus'tan geçiş konularını kapsayan FastComments agent skill'lerinden oluşan bir kütüphanedir. Remixlediğinizde Val Town'un agent'ı Townie, skill'leri skills/ klasöründen otomatik olarak alır; böylece agent'ınız, dokümantasyonu sohbete yapıştırmanıza gerek kalmadan yorumları nasıl kuracağını bilir.

Aynı skill'ler başka her yerde npx skills add fastcomments/skills ile kurulabilir.