FastComments.com

Val Town ऐप्स में लाइव कमेंटिंग जोड़ें

Val Town Deno पर TypeScript चलाता है, इसलिए एक val एक वास्तविक सर्वर है। इससे यह FastComments के लिए उपयुक्त बनता है: विजेट पेज पर एक script टैग होता है, और जो कुछ भी सीक्रेट की आवश्यकता रखता है, जैसे Secure SSO या वेबहुक सत्यापित करना, वह उसी val में सर्वर-साइड पर चल सकता है।

यह गाइड एक HTTP val में कमेंट विजेट जोड़ना, इंडेक्स पेज पर कमेंट काउंट दिखाना, उपयोगकर्ताओं को उनके मौजूदा Val Town खाते से साइन इन कराना, और कमेंट वेबहुक प्राप्त करना शामिल करती है।

इसे आज़माने के लिए आपको खाते की आवश्यकता नहीं है। उदाहरण tenantId: "demo" का उपयोग करते हैं, जो एक साझा सैंडबॉक्स है, और चरण 2 में अपने स्वयं के पर स्विच करना बताया गया है।

इंडेक्स पेज पर कमेंट काउंट Internal Link

अनुक्रमणिका (index) पेज पर, प्रत्येक पंक्ति के लिए एक comment-count विजेट रेंडर न करें। इसका मतलब है प्रत्येक पोस्ट के लिए एक अनुरोध। बल्क काउंट का उपयोग करें, जो पूरे पेज के लिए एक ही अनुरोध लेता है।

प्रत्येक पंक्ति को उसके थ्रेड द्वारा उपयोग किए जाने वाले urlId से चिह्नित करें, फिर बल्क विजेट को एक बार लोड करें:

अनुक्रमणिका पर बल्क टिप्पणी गणना
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 से मेल खाना चाहिए। यदि विजेट स्लग का उपयोग करता है, तो मार्कर स्लग का उपयोग करता है। बेमेल होने पर ऐसे थ्रेड पर शून्य दिखाई देगा जिसमें टिप्पणियाँ मौजूद हैं।

स्क्रिप्ट window.FastCommentsBulkCountConfig के लिए पोलिंग करती है, इसलिए यह मायने नहीं रखता कि आपने कॉन्फ़िग को स्क्रिप्ट टैग से पहले या बाद में सेट किया है।

std/oauth के साथ सुरक्षित SSO Internal Link

यदि आपका val पहले से जानता है कि विज़िटर कौन है, तो Secure SSO उस पहचान को विजेट को सौंप देता है ताकि उन्हें दूसरा लॉगिन कभी न दिखे। बनाने के लिए कोई endpoints नहीं हैं और runtime पर कुछ भी कॉल करने की ज़रूरत नहीं है: आप तीन मान server-side पर compute करते हैं और उन्हें widget config में पास कर देते हैं।

Val Town std/oauth के साथ zero-config लॉगिन देता है, ताकि विज़िटर उसी Val Town खाते से साइन इन कर सके जो उनके पास पहले से है। इसे अपने ऐप द्वारा उपयोग किए जाने वाले किसी भी विकल्प से बदल दें; FastComments वाला हिस्सा नहीं बदलता।

Payload को server पर बनाएँ

API secret payload पर हस्ताक्षर करता है और इसे कभी भी browser कोड तक नहीं पहुँचना चाहिए। SDK को npm से इंस्टॉल करें, जो Val Town के 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 एक ही व्यक्ति के लिए स्थिर होना चाहिए, अन्यथा उन्हें हर लॉगिन पर एक नई comment पहचान मिल जाती है।
6 const id = `vt-${user.id}`;
7
8 return new SecureSSOPayloadBuilder(Deno.env.get("FASTCOMMENTS_API_SECRET"), {
9 id,
10 // email आवश्यक है और अद्वितीय होना चाहिए।
11 email: user.email ?? `${id}@users.noreply.val.town`,
12 // username आवश्यक है और ईमेल नहीं हो सकता।
13 username: user.username ?? id,
14 displayName: user.username ?? undefined,
15 avatar: user.links.profileImageUrl ?? undefined,
16 }).getPayload();
17}
18

getPayload() { userDataJSONBase64, verificationHash, timestamp } लौटाता है। यही तीन मान browser तक पहुँचते हैं। Secret इन पर हस्ताक्षर करता है और फिर हटा दिया जाता है, इसलिए पेज में कुछ भी ऐसा नहीं होता जिससे कोई पाठक किसी भिन्न उपयोगकर्ता का forge कर सके।

इसे विजेट को पास करें

SSO के साथ Widget config
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 // ...इस config के साथ विजेट render करें
17});
18
19export default oauthMiddleware(app.fetch);
20

oauthMiddleware आपके लिए GET /auth/login, GET /auth/callback और POST /auth/logout जोड़ देता है। ध्यान दें कि logout एक POST है, जबकि विजेट logoutURL पर GET के साथ नेविगेट करता है, इसलिए logoutURL को अपने द्वारा बनाए गए एक छोटे route की ओर इंगित करें जो वह POST सबमिट करता है।

जब विज़िटर लॉग आउट हो, तो केवल loginURL के साथ sso पास करें। तब विजेट एक अनाम comment बॉक्स के बजाय लॉगिन प्रॉम्प्ट दिखाता है।

जो चीज़ें गलत हो जाती हैं

timestamp epoch milliseconds में होना चाहिए, भविष्य में नहीं होना चाहिए, और दो दिन से अधिक पुराना नहीं होना चाहिए। इसे उसी request में server पर generate करें जो hash compute करती है। इसे browser में generate करना क्लासिक विफलता है: मान उससे भिन्न होता है जिस पर hash बना था और हर comment अस्वीकार कर दिया जाता है।

कभी भी identity provider से isAdmin या isModerator सेट न करें। Val Town खाते से साइन इन करने का इस बात से कोई लेना-देना नहीं है कि आपकी साइट कौन moderate करेगा।

पूरी field सूची, group-gated threads, और badges के लिए SSO गाइड देखें।

वेबहुक प्राप्त करना Internal Link

एक val एक स्वाभाविक webhook रिसीवर है: इसका एक स्थिर URL होता है, यह सिग्नेचर सत्यापित कर सकता है, और इसमें SQLite और blob storage इनबिल्ट होते हैं।

FastComments आपके खाते के API secret से ${timestamp}.${body} पर सिग्न करके दो हेडर भेजता है:

Webhook हेडर
Copy CopyRun External Link
1
2X-FastComments-Timestamp: 1789004710 unix सेकंड, मिलीसेकंड नहीं
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 को पार्स करके दोबारा सीरियलाइज़ करने से key क्रम और व्हाइटस्पेस बदल जाता है, जिससे हैश अलग हो जाता है और हर डिलीवरी बिना स्पष्ट कारण के विफल हो जाती है। यही आम तौर पर वह कारण है जिससे webhook रिसीवर "बस काम नहीं करता"।

कॉन्स्टेंट टाइम में तुलना करें। सिग्नेचर पर साधारण === यह लीक कर देता है कि कितने बाइट्स मैच हुए, जो एक-एक बाइट करके फोर्ज करने के लिए पर्याप्त है।

इवेंट हैंडल करना

जल्दी जवाब दें। FastComments नॉन-2xx पर रिट्राई करता है, और लगातार विफल होने वाला एंडपॉइंट अंततः स्वचालित रूप से डिसेबल कर दिया जाता है, इसलिए असली काम जवाब देने के बाद करें, इनलाइन नहीं।

उस काम को कमेंट id पर idempotent बनाएं। रिट्राई एक नए टाइमस्टैम्प के साथ दोबारा सिग्न किया जाता है, और एडिट और डिलीट पर वही कमेंट id फिर से आता है, इसलिए डिडुप्लिकेट करने के लिए कुछ भी स्थिर नहीं होता।

उदाहरण Vals Internal Link

चार सार्वजनिक vals जिन्हें आप remix कर सकते हैं, जिनमें से प्रत्येक इस गाइड के एक हिस्से को कवर करता है।

Blog with comments (live) एक Markdown ब्लॉग है जिसमें हर पोस्ट के नीचे एक थ्रेड और इंडेक्स पर बल्क कमेंट काउंट हैं। यह remix करते ही काम करने लगता है, और एक environment variable इसे आपके अपने खाते से जोड़ देता है।

SSO demo (live) विज़िटर को उनके Val Town खाते से साइन इन करता है और उस पहचान को विजेट को सौंप देता है, ताकि दूसरा लॉगिन करने की ज़रूरत न पड़े।

Webhook receiver (live) हर डिलीवरी पर HMAC सिग्नेचर को सत्यापित करता है और इवेंट्स को SQLite में सहेजता है। इसमें एक बटन है जो एक टेस्ट पेलोड पर सिग्न करके उसे स्वयं को डिलीवर करता है, ताकि आप असली webhook कॉन्फ़िगर करने से पहले सत्यापन सफल होते देख सकें।

Agent skills (live) FastComments एजेंट स्किल्स की एक लाइब्रेरी है जो विजेट, SSO, REST API, मॉडरेशन, और Disqus से माइग्रेशन को कवर करती है। इसे remix करें और Val Town का एजेंट, Townie, skills/ से स्किल्स को स्वतः उठा लेता है, ताकि आपका एजेंट बिना डॉक्यूमेंटेशन चैट में पेस्ट किए कमेंट्स सेट अप करना जानता हो।

वही स्किल्स कहीं और भी npx skills add fastcomments/skills के साथ इंस्टॉल हो जाती हैं।