
语言 🇨🇳 简体中文
Val Town 安装
投票
为 Val Town 应用添加实时评论
Val Town 在 Deno 上运行 TypeScript,因此 val 是一个真实的服务器。这使它非常适合 FastComments:小部件是页面上的 script 标签,任何需要密钥的功能,如 Secure SSO 或验证 webhook,都可以在同一个 val 的服务器端运行。
本指南涵盖了将评论小部件添加到 HTTP val、在索引页面显示评论计数、使用用户已有的 Val Town 账户登录以及接收评论 webhook。
您无需账户即可尝试。示例使用 tenantId: "demo",这是一个共享的沙盒环境,第 2 步将介绍如何切换到您自己的。
步骤 1:添加小部件 
该组件是一个 script 标签和一个容器元素,因此可以嵌入到你现有的 val 渲染内容中。此示例使用 Hono JSX,这也是 Val Town 的 HTTP 模板所使用的技术。
Run 
在上线之前先确定 urlId
urlId 决定评论归属于哪个讨论串。如果不设置,它默认使用当前页面 URL 的清理版本,而这恰恰是 Val Town 上会变化的东西:在你认领子域名之前,val 会一直使用较长的 *.web.val.run 主机名,分支会有各自的 URL,重命名页面会改变路径。每一种变化都会悄悄变成一个独立的、空的讨论串,其症状表现为“我的评论消失了”。
请像上面那样,将其设置为你可控的稳定值,例如文章的 slug 或数据库 id。同时也要传入 url,这样通知邮件和审核工具才能链接回真实页面。
在无 JavaScript 的情况下保留评论
FastComments 会在服务端渲染完整的讨论串,val 可以将其放入 <noscript> 块中:
Run 
请对参数进行 URL 编码。服务端版本支持匿名和登录评论、SSO 以及嵌套回复。
步骤 2:使用您自己的账户 
tenantId: "demo" 是一个共享的公共沙盒。它无需注册即可使用,这也是示例使用它的原因,但其他使用 FastComments 的人会写入相同的线程,任何人都可以对其进行审核。在发布任何重要内容之前请切换。
您的租户 ID 位于 API secret page。
租户 ID 是公开的,应该放在浏览器代码中。API 密钥不是公开的,且本页的内容不需要它。
从环境变量读取
Val Town 的 vals 在免费层是公开的,因此它们的源代码是全局可读的。请将任何敏感信息保存在环境变量中,并使用 Deno.env.get 读取:
Run 
这在 Val Town 上尤为重要,原因有二:重新混合一个 val 会复制环境变量的键,但不会复制其值。 保存在环境变量中的密钥不会随你的 val 进入他人的账户,而写入文件的密钥会。
回退到 "demo" 可以让在设置自己租户之前重新混合该 val 的任何人都能正常使用。
EU 账户
一个账户、其数据以及密钥都位于同一地区。如果您的账户是在 eu.fastcomments.com 创建的,则每个小部件配置也需要 region: "eu",并且脚本会从 cdn-eu.fastcomments.com 加载。否则保持默认即可。
索引页面上的评论计数 
在索引页面上,不要为每一行渲染一个 comment-count 小部件。这会对每篇文章产生一次请求。使用批量计数,它只需要对整页发起一次请求。
为每一行标记其线程使用的 urlId,然后一次性加载批量小部件:
Run 
脚本会查找页面上所有 .fast-comments-count 元素并填充其计数。
data-fast-comments-url-id 必须与帖子评论小部件使用的 urlId 相匹配。如果小部件使用 slug,则标记也使用 slug。若不匹配,则会在已有评论的线程上显示为零。
脚本会轮询 window.FastCommentsBulkCountConfig,因此在脚本标签之前或之后设置配置都没有关系。
使用 std/oauth 实现安全单点登录 
如果你的 val 已经知道访客的身份,Secure SSO 会将该身份传递给评论组件,这样访客就无需再次登录。无需构建任何端点,运行时也无需调用任何东西:你只需在服务端计算三个值,并将它们传入组件配置即可。
Val Town 通过 std/oauth 提供零配置登录,因此访客可以使用他们已有的 Val Town 账户登录。你可以将其替换为你应用所使用的任何登录方式;FastComments 这部分不会改变。
在服务端构建 payload
API 密钥用于对 payload 进行签名,绝不能出现在浏览器代码中。从 npm 安装 SDK,它可以直接在 Val Town 的 Deno 运行时上使用:
Run 
getPayload() 返回 { userDataJSONBase64, verificationHash, timestamp }。这三个值就是传递给浏览器的全部内容。密钥对它们进行签名后即被丢弃,因此页面中的任何内容都无法让阅读者伪造其他用户。
将其传递给组件
Run 
oauthMiddleware 会为你添加 GET /auth/login、GET /auth/callback 和 POST /auth/logout。请注意,登出是一个 POST 请求,而组件通过 GET 导航到 logoutURL,因此请将 logoutURL 指向你自己的一个小路由,由它来提交 POST 请求。
当访客未登录时,传入仅包含 loginURL 的 sso。此时组件会显示登录提示,而不是匿名评论框。
常见问题
timestamp 是以毫秒为单位的 epoch 时间,不能是未来时间,也不能超过两天前。请在计算哈希的同一请求中于服务端生成它。在浏览器中生成它是典型的失败原因:该值与被哈希的值不一致,导致所有评论都被拒绝。
切勿根据身份提供商设置 isAdmin 或 isModerator。使用 Val Town 账户登录并不能说明谁应该管理你的网站。
完整的字段列表、基于群组的评论线程以及徽章,请参阅 SSO 指南。
接收 Webhook 
val 是天然的 webhook 接收器:它拥有稳定的 URL,可以验证签名,并且内置了 SQLite 和 blob 存储。
FastComments 使用您账户的 API secret 对 ${timestamp}.${body} 进行签名,并发送两个请求头:
Run 
HTTP 方法承载了事件类型:PUT 表示评论被创建或更新,DELETE 表示评论被删除。
Run 
两个容易踩的坑
验证原始字节。 解析 JSON 再重新序列化会改变键的顺序和空白字符,因此哈希值会不同,导致每次送达都失败且没有明显原因。这是 webhook 接收器“就是无法工作”的常见原因。
以恒定时间进行比较。 对签名使用普通的 === 会泄露有多少字节匹配,这足以让攻击者逐字节伪造签名。
处理事件
快速响应。FastComments 会在收到非 2xx 响应时重试,而持续失败的端点最终会被自动禁用,因此应在响应之后再执行实际工作,而不是内联处理。
确保该工作基于评论 id 是幂等的。重试会使用新的时间戳重新签名,并且同一条评论 id 在编辑和删除时会再次到达,因此没有稳定的字段可用于去重。
示例 Vals 
四个公开的 val 供你 remix,每个对应本指南的一部分。
带评论的博客(在线演示)是一个 Markdown 博客,每篇文章下都有一个评论线程,索引页上显示批量评论计数。remix 后即可立即使用,只需一个环境变量即可指向你自己的账户。
SSO 演示(在线演示)使用访客的 Val Town 账户登录,并将该身份传递给组件,因此无需二次登录。
Webhook 接收器(在线演示)会验证每次投递的 HMAC 签名,并将事件存储在 SQLite 中。它有一个按钮,可以对测试载荷进行签名并投递给自己,因此你可以在配置真实 webhook 之前看到验证成功的过程。
Agent 技能(在线演示)是一个 FastComments agent 技能库,涵盖组件、SSO、REST API、审核以及从 Disqus 迁移。remix 后,Val Town 的 agent Townie 会自动从 skills/ 中获取这些技能,因此你的 agent 无需你将文档粘贴到聊天中就知道如何接入评论。
同样的技能也可以通过 npx skills add fastcomments/skills 安装到其他任何地方。
域错误 
一旦关闭 demo 租户,小部件可能会因授权错误而拒绝加载。这是因为 FastComments 不知道它应该允许在该域上使用您的账户。
Val Town 在这里值得再次关注,因为一个 val 可以通过多个主机名访问:
- 每个 HTTP val 都有一个较长的默认端点,
-- .web.val.run。 - 声明自定义子域名会添加
.val.run。 - 自定义域名 会再添加一个。
- 分支会拥有自己的 URL。
添加您实际提供小部件服务的所有主机名。如果在设置完成后再声明子域名,请也将其添加,否则小部件将在旧 URL 上工作,而在新 URL 上失败。