FastComments.com

Προσθήκη ζωντανής σχολιασμού στις εφαρμογές Val Town

Val Town εκτελεί TypeScript στο Deno, έτσι ένα val είναι ένας πραγματικός διακομιστής. Αυτό το καθιστά κατάλληλο για το FastComments: το widget είναι μια ετικέτα script στη σελίδα, και οτιδήποτε χρειάζεται ένα μυστικό, όπως Secure SSO ή η επαλήθευση ενός webhook, μπορεί να εκτελεστεί στο διακομιστή στο ίδιο val.

Αυτός ο οδηγός καλύπτει την προσθήκη του widget σχολίων σε ένα HTTP val, την εμφάνιση του αριθμού σχολίων σε μια σελίδα ευρετηρίου, τη σύνδεση χρηστών με τον λογαριασμό Val Town που ήδη έχουν, και τη λήψη webhook σχολίων.

Δεν χρειάζεστε λογαριασμό για να το δοκιμάσετε. Τα παραδείγματα χρησιμοποιούν tenantId: "demo", ένα κοινόχρηστο sandbox, και το Βήμα 2 καλύπτει τη μετάβαση στο δικό σας.

Πλήθος σχολίων σε σελίδα ευρετηρίου 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

Το script εντοπίζει κάθε στοιχείο .fast-comments-count στη σελίδα και γεμίζει τη μέτρησή του.

data-fast-comments-url-id πρέπει να ταιριάζει με το urlId που χρησιμοποιεί το widget σχολίων της ανάρτησης. Εάν το widget χρησιμοποιεί το slug, ο δείκτης χρησιμοποιεί το slug. Μια ασυμφωνία εμφανίζει μηδέν σε ένα νήμα που έχει σχόλια.

Το script ελέγχει περιοδικά το window.FastCommentsBulkCountConfig, επομένως δεν έχει σημασία αν ορίσετε τη διαμόρφωση πριν ή μετά το στοιχείο script.

Ασφαλής SSO με std/oauth Internal Link

Αν το val σας γνωρίζει ήδη ποιος είναι ο επισκέπτης, το Secure SSO μεταφέρει αυτή την ταυτότητα στο widget ώστε να μη δει ποτέ δεύτερη σύνδεση. Δεν υπάρχουν endpoints προς κατασκευή και τίποτα προς κλήση κατά την εκτέλεση: υπολογίζετε τρεις τιμές server-side και τις περνάτε στη διαμόρφωση του widget.

Το Val Town προσφέρει σύνδεση χωρίς διαμόρφωση με το std/oauth, ώστε ο επισκέπτης να συνδεθεί με τον λογαριασμό Val Town που ήδη έχει. Αντικαταστήστε το με ό,τι χρησιμοποιεί η εφαρμογή σας· το κομμάτι του FastComments δεν αλλάζει.

Κατασκευάστε το payload στον server

Το API secret υπογράφει το payload και δεν πρέπει ποτέ να φτάσει σε κώδικα browser. Εγκαταστήστε το SDK από το npm, που λειτουργεί ως έχει στο Deno runtime του Val Town:

sso.ts
Copy CopyRun External Link
1
2import { SecureSSOPayloadBuilder } from "npm:fastcomments-sdk/server";
3
4export function buildSSOPayload(user) {
5 // Το id πρέπει να είναι σταθερό για το ίδιο άτομο, αλλιώς παίρνει νέα ταυτότητα σχολίων σε κάθε σύνδεση.
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 είναι υποχρεωτικό και δεν μπορεί να είναι 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 }. Αυτές οι τρεις τιμές είναι το μόνο που φτάνει στον browser. Το secret τις υπογράφει και στη συνέχεια απορρίπτεται, ώστε τίποτα στη σελίδα να μην επιτρέπει σε έναν αναγνώστη να πλαστογραφήσει διαφορετικό χρήστη.

Περάστε το στο widget

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 // ...αποδώστε το widget με αυτή τη διαμόρφωση
17});
18
19export default oauthMiddleware(app.fetch);
20

Το oauthMiddleware προσθέτει για εσάς τα GET /auth/login, GET /auth/callback και POST /auth/logout. Σημειώστε ότι το logout είναι POST, ενώ το widget πλοηγείται στο logoutURL με GET, οπότε δείξτε το logoutURL σε ένα μικρό δικό σας route που υποβάλλει το POST.

Όταν ο επισκέπτης είναι αποσυνδεδεμένος, περάστε το sso με μόνο ένα loginURL. Το widget τότε εμφανίζει προτροπή σύνδεσης αντί για ανώνυμο πλαίσιο σχολίων.

Πράγματα που πηγαίνουν στραβά

Το timestamp είναι σε epoch milliseconds, δεν πρέπει να είναι στο μέλλον και δεν πρέπει να είναι παλαιότερο από δύο ημέρες. Δημιουργήστε το στον server στο ίδιο request που υπολογίζει το hash. Η δημιουργία του στον browser είναι η κλασική αποτυχία: η τιμή διαφέρει από αυτή που υπογράφηκε και κάθε σχόλιο απορρίπτεται.

Ποτέ μην ορίζετε isAdmin ή isModerator από τον πάροχο ταυτότητας. Η σύνδεση με λογαριασμό Val Town δεν λέει τίποτα για το ποιος πρέπει να συντονίζει τον ιστότοπό σας.

Δείτε τον οδηγό SSO για την πλήρη λίστα πεδίων, τα threads με περιορισμό ομάδας και τα badges.

Λήψη webhooks Internal Link

A val είναι ένας φυσικός δέκτης webhook: διαθέτει σταθερό URL, μπορεί να επαληθεύσει μια υπογραφή και έχει ενσωματωμένη αποθήκευση SQLite και blob.

FastComments υπογράφει ${timestamp}.${body} με το μυστικό API του λογαριασμού σας και στέλνει δύο κεφαλίδες:

Κεφαλίδες Webhook
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 // 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

Δύο πράγματα που προκαλούν προβλήματα

Επαληθεύστε τα ακατέργαστα byte. Η ανάλυση του JSON και η επανα-σειριοποίησή του αλλάζει τη σειρά των κλειδιών και τα κενά, έτσι το hash διαφέρει και κάθε παράδοση αποτυγχάνει χωρίς προφανή λόγο. Αυτό είναι ο συνηθισμένος λόγος που ένας δέκτης webhook «απλώς δεν λειτουργεί».

Συγκρίνετε σε σταθερό χρόνο. Ένα απλό === στην υπογραφή διαρρέει πόσα byte ταιριάζουν, κάτι που αρκεί για να παραχθεί ένα byte τη φορά.

Διαχείριση συμβάντων

Απαντήστε γρήγορα. Το FastComments κάνει επανεγγραφές σε μη-2xx απαντήσεις, και ένα endpoint που συνεχίζει να αποτυγχάνει απενεργοποιείται αυτόματα μετά από κάποιο χρόνο, επομένως εκτελέστε την πραγματική εργασία μετά την απάντηση αντί εντός της απάντησης.

Κάντε αυτή τη λειτουργία αμετάβλητη (idempotent) με βάση το αναγνωριστικό του σχολίου. Μια επανεγγραφή υπογράφεται ξανά με νέο χρονικό σήμα, και το ίδιο αναγνωριστικό σχολίου φτάνει ξανά σε επεξεργασία και διαγραφή, οπότε δεν υπάρχει κάτι σταθερό για απο-διπλοεγγραφή.

Παραδείγματα Vals Internal Link

Τέσσερα δημόσια vals που μπορείτε να κάνετε remix, το καθένα καλύπτει ένα κομμάτι αυτού του οδηγού.

Blog with comments (live) είναι ένα blog σε Markdown με ένα νήμα σχολίων κάτω από κάθε ανάρτηση και μαζικά μετρητικά σχολίων στην ευρετήρια σελίδα. Λειτουργεί αμέσως μετά το remix, και μία μεταβλητή περιβάλλοντος το δείχνει στον δικό σας λογαριασμό.

SSO demo (live) συνδέει τον επισκέπτη με τον λογαριασμό Val Town του και μεταφέρει αυτήν την ταυτότητα στο widget, ώστε να μην χρειάζεται δεύτερη σύνδεση.

Webhook receiver (live) επαληθεύει την υπογραφή HMAC σε κάθε παράδοση και αποθηκεύει τα γεγονότα σε SQLite. Διαθέτει ένα κουμπί που υπογράφει ένα δοκιμαστικό payload και το παραδίδει στον εαυτό του, ώστε να δείτε την επαλήθευση να succeeds πριν ρυθμίσετε ένα πραγματικό webhook.

Agent skills (live) είναι μια βιβλιοθήκη δεξιοτήτων agent του FastComments που καλύπτουν το widget, το SSO, το REST API, τον συντονισμό (moderation) και τη μετάβαση από το Disqus. Κάντε το remix και ο agent του Val Town, Townie, τις ανακαλύπτει αυτόματα από το skills/, ώστε ο agent σας να ξέρει πώς να συνδέσει τα σχόλια χωρίς να επικολλάτε τεκμηρίωση στη συνομιλία.

Οι ίδιες δεξιότητες εγκαθίστανται οπουδήποτε αλλού με npx skills add fastcomments/skills.