在发送之前用预检拦下不合格的提交
在填写人还在页面上时,就向表单本身索要判定。预检检查请求结构、Origin 和严格模式架构,不存储任何记录,也从不保证真实提交一定被接受。
本文由机器翻译。 阅读英文原文

一次被拒绝的提交,代价是这个产品唯一无法挽回的东西:一个人本来还在页面上,然后不在了。FormFlow 把每一次拒绝都保留成你可以处理的一条记录,表单的健康信号也会统计这些拒绝。预检是这套判定中唯一在“发送”之前运行的部分。它随 v1.0.0 一同到来。
预检回答什么
向 /api/v1/f/{token}/preflight 发送一个 POST,请求体是表单本来会发送的那些回答,格式为 JSON,路径中带上表单的公开令牌。该接口返回接收接口会对这些回答作出的判定,但并不真的作出。传入页面自身的 Origin 请求头,就会按提交接口完全相同的方式检查。契约写在 API 参考里。
响应有四个键:ok、errors、checked 和 policy。errors 是一个以字段名为键、以句子列表为值的对象;即使为空也仍是对象,好让生成的调用方能索引它。checked 指出实际问了什么——结构、origin 和架构;在托管页面上 origin 缺席,因为 FormFlow 提供的页面不受你的允许来源名单约束。
在填写人还在页面上时调用
一次 fetch 就够了。下面是这次调用的形状;把它挂到你页面本来就信任的事件上,比如离开某个字段。
const endpoint = 'https://formflow.digital/api/v1/f/YOUR_FORM_TOKEN/preflight'
async function preflight(answers) {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(answers),
})
// A 429 or an outage is not a verdict. Say nothing rather than something wrong.
if (!response.ok) return null
const { ok, errors, checked, policy } = await response.json()
return ok ? [] : Object.entries(errors)
}把返回的句子显示在它所指的那个字段旁边。不要在你自己的代码里重述规则:这个接口的意义在于,填写人读到的句子就是接口本来会使用的句子,用的是表单的语言,而不是你的 JavaScript 的语言。
读懂响应
键 | 它包含什么 |
|---|---|
ok | 仅当没有任何东西拒绝这些回答时为 true |
errors | 每个字段名一条,每条都是句子列表 |
checked | 问了什么:结构、origin、架构 |
policy.revision | 判定所用的策略版本 |
policy.fingerprint | 标识那份确切策略的指纹 |
errors 里有两个键不是字段。origin 承载允许来源名单的拒绝,而 submission 承载属于整个表单而不是某一条回答的拒绝,比如表单已关闭。把 policy 块记进日志:版本与指纹合起来标识判定所用的确切策略,这正是当这里通过、那里却被拒绝时你需要的东西。
它不检查什么
它只问决策中真正是表单配置纯函数的那一半。它不运行垃圾评分,不索取 Turnstile 挑战,也完全不看文件字段——预检不携带任何字节,所以已声明的图片既不检查也不承诺。它不存记录、不消耗配额、不投递任何东西。
但它确实在限流之内。预检与提交接口共用 token|IP 桶和整个团队的请求桶,所以一阵突发可能返回 429 与 Retry-After。429 是缺席的判定,不是拒绝;页面应当保持沉默,而不是编造一个错误。
它放在哪里
FormFlow 为被拒绝的提交提供的其他每一种机制,都在一个人被拒绝之后才运行:拒绝台账、健康信号、拒绝面、找回收件箱。只有这一个在之前运行。把它当作两次判定中的第一次,而绝不是唯一一次。
通过不是承诺。由 POST 决定,而它在决定时会带上预检没有问的一切。
如果两次调用之间改了表单,答案也会跟着变。响应里的策略版本才是判定你的那一份。
文件字段不被检查,所以绝不要因为一次预检就告诉填写人他的图片已被接受。
errors以字段名为键,但origin和submission不是字段;把它们显示在表单顶部。任何持有表单公开令牌的人都能调用它。它不能证明调用者是谁,也不是秘密。