@ fluid-app/ droplet- sdk (英语)
Fluid倒计时器的签字核查和租户解决 (原始内容存档于2018-10-21). webhoks. 零运行时间依赖关系.
流体符号每次调用 HMAC-SHA256 结束 {timestamp}.{body} 发送
登记本身的核查标志 与它。 此软件包验证
,并解决从登记中申请属于哪个租户的问题
而不是从任何有效载荷.
安装
在npmjs上公开,所以没有 .npmrc,没有符号,也没有配置
CI 或 Docker 文件中 :
pnpm add @fluid-app/droplet-sdk
正在验证召回
import { withFluidCallback } from "@fluid-app/droplet-sdk/next";
import { createPrismaCallbackStore } from "@fluid-app/droplet-sdk/prisma";
const callbackStore = createPrismaCallbackStore(
prisma.fluidCallbackRegistration,
);
export const POST = withFluidCallback(
{
definitions: ["cart_item_added"],
store: callbackStore,
resolvePrincipal: async ({ registration }) =>
registration.dri
? prisma.company.findFirst({
where: { dropletInstallationUuid: registration.dri, active: true },
})
: null,
},
async ({ payload, principal, definition }) => {
// `principal` is the verified tenant. `definition` is the one the
// registration was issued for — not a field the caller declared.
return { success: true };
},
);
店需要一张桌子 普里斯玛号拆船 包裹在
@fluid-app/droplet-sdk/schema/callback-registrations.prisma; 符号是
存储为 a 摘要从来没有在清晰.
在您执行前: 充斥着 order 商店
校验与请求中显示的代号相匹配, 并与摘要相匹配 水滴已经存储。 商店之前的安装没有,所以每个 一旦开始核查,就会拒绝回电.
backfillCallbackTokens 充满了它。 流体返回 verification_token 联 合 国
因此,现有的登记可以是:
未经重新登记和重新安装而通过:
import { backfillCallbackTokens } from "@fluid-app/droplet-sdk";
const result = await backfillCallbackTokens({
client, // anything with listCallbacks({ page, per_page })
store, // your CallbackTokenStore
dri: installation.dropletInstallationUuid,
dropletUrl: process.env.FLUID_DROPLET_URL!,
// Every callback url this droplet registers. Matched exactly: the listing is
// company-scoped and includes other droplets' registrations, so this is what
// decides which tokens are yours to adopt.
ownUrls: callbacks.map((c) => `${process.env.FLUID_DROPLET_URL}${c.url}`),
});
运行到每个活动安装 在此之前 部署被包裹的路线。 A 类 部分回填意味着它错过的设施开始被拒绝.
"被"不是"没有扔",一个计数不覆盖. 两个
一个定义的有价登记,另一个定义的无价登记
stored: 2 带空 missingToken - 看起来很完整,没有.
请检查实际采用的定义:
// `definition_name` is the field Fluid uses, and what droplet configs carry.
const expected = callbacks
.filter((c) => c.enabled !== false)
.map((c) => c.definition_name);
if (result.missingToken.length > 0) {
// These registrations exist but the listing returned no token for them, so
// they cannot be adopted. They need re-registering, not a retry.
throw new Error(
`${dri}: no token listed for ${result.missingToken.join(", ")}`,
);
}
const covered = new Set(result.adopted.map((r) => r.definitionName));
const uncovered = expected.filter((d) => !covered.has(d));
if (uncovered.length > 0) {
throw new Error(`${dri}: no stored token for ${uncovered.join(", ")}`);
}
要求每次安装都要如此,并将故障作为阻塞器处理 部署,以后不能修复.
还有一个限度, 是关于什么留在后面,而不是什么支票
看到了吗. adopted 反映当前列表,所以 stale 行不能满足
检查,但店里只有高涨, 所以老字号仍然在那里,它
将仍然验证一个符号 Fluid 不再为已注册的
已替换。 任何事物都不会因为它而被拒绝;它是一个超过历史的证书
登记.
CallbackTokenStore 曝光情况 deleteForInstallation,所以的方式是确定
安装完全持有其目前的注册是清关
按此顺序再次填充该安装:
await store.deleteForInstallation(dri);
const result = await backfillCallbackTokens({ ...opts, dri });
// then assert coverage as above
当定义重新注册时就这么做。 一个简单的回填就够了 否则.
三个最有可能咬你的东西
resolvePrincipal 不得读取有效载荷. 仅证明有效签名
哪些登记 签了名——不是请求对象. 带走房客
从 body.company.id 或者说 x-fluid-shop 让房客A的地契持有人
b. 解决 registration.dri 返回时
null 如果它不解决; null 被视作验证失败.
失败的路线 回答200 每一个结果. 在取出时召回
路径不能拒绝 — Fluid 屏蔽了响应中的存储前端请求, 所以
401是辆破车 给那些路线一个 onAuthFailure 返回他们的
中性形状 :
onAuthFailure: () => Response.json({ tax_total: 0, lines: [] }),
这意味着状态告诉你什么. 警报 [fluid-callback:…] rejected 日志行,从不显示状态代码.
一个网瘾 resolve 必须返回 null 用于"未知",并只扔给
"无法发现". 包装器对两者不同 因为Fluid
否: 4xx被记录为永久拒绝,永远不再交付,而
5xx和1个429重审. 这么说吧 null 答案 401, 和一个抛出的错误答案 503
通过 onResolveError。 。 。 .
退后是安静和昂贵的。 A 类 resolve 吞下自己的东西
数据库出错和返回 null 变成一堵永久的墙
否认--如果其中一人是-- droplet.installed那家公司没有排行,
他们的网游将不再解决 唯一的线索是401 让
错误传播 :
resolve: async ({ dri }) => {
const company = await db.company.findUnique({ where: { dri } }); // may throw
if (!company?.webhookVerificationToken) return null; // settled: no such tenant
return { secret: company.webhookVerificationToken, principal: company.id };
},
读取状态和接收调节事件
import { createFluidInstallationClient } from "@fluid-app/droplet-sdk";
const client = createFluidInstallationClient({ installToken, baseUrl });
const result = await client.getInstallationStatus();
if (result.ok) console.log(result.status.droplet_installation_status);
import {
createInMemoryReconcileDedupe,
withFluidReconcileEvents,
} from "@fluid-app/droplet-sdk/next";
export const POST = withFluidReconcileEvents(
async (event) => {
await handleReconcile(event);
},
{ secret: webhookSecret, dedupe: createInMemoryReconcileDedupe() },
);
使用液滴本身的网球秘诀. dedupe 需要; 没有默认
存储。 上面的明晰内存是每个过程,适合一个
单例。 执行原子的共享商店
claim, (中文(简体) ), complete 财务报告和审定财务报表 release 需要(例如Redis) SET NX PX
与原子符号检查以完成和释放).
索赔方法可以同步或按承诺:
type ReconcileClaim =
| { status: "claimed"; token: string }
| { status: "in_flight" }
| { status: "done" };
claim(id: string, ttlMs: number): ReconcileClaim | Promise<ReconcileClaim>;
complete(id: string, token: string, ttlMs: number): void | Promise<void>;
release(id: string, token: string): void | Promise<void>;
claim 必须在解剖学上保留一个带有新标志的可用ID。 内容 ttlMs
只有飞行中租赁: Math.min(inFlightTtlMs, dedupeTtlMs)。 。 。 .
包装 inFlightTtlMs 默认为 6万 ms, 并且必须是正和有限度的; 保持大约 10 分钟, 宽度为 Fluid
重试,或者一个坠毁的实例的租赁 能够超过每次重试. dedupeTtlMs 必须
同时也是积极和有限的.
活动
索赔返回 in_flight:包装器响应503(被Fluid记录为永久拒绝) { error: "in flight" }
不叫处理器, 所以流体复试以后。 完成交付返回
done: 包装器响应 200 { ok: true, duplicate: true }。 。 。 .
一个扔出的权利要求返回一个可重试的500,而不运行处理器.
执行 complete 财务报告和审定财务报表 release 并行 claim在处理器回复后,
complete(id, token, ttlMs) 接收 dedupeTtlMs 完成的保留
完成(默认为86 400 000毫秒)。 扔下来
或非正确反应, release(id, token) 释放权利要求,以便重试可以运行.
两种操作都必须在解剖学上检查该符号是否仍然拥有一个未过期
索赔;否则,他们什么都不做。 期满后,新的交货人可以声称其身份
并且旧的处理者无法完成或解除索赔.
仅用事件编号记录完成和释放失败并保存
处理器的响应(或一个被扔出处理器的500个).
明晰的内存商店执行所有三项业务. 它也限制租赁
由它自己负责 inFlightTtlMs 选项(默认为60,000毫秒)。 对于更长的处理器,设置
inFlightTtlMs 在包装和 createInMemoryReconcileDedupe。 。 。 .
在签名验证和JSON解析后,未知 reconcile.* 事件类型
返回 200 { ok: true, ignored: true } 没有要求或呼叫处理者.
只记录事件名称 。 不对齐或缺失的事件名称仍然返回
无效有效载荷400发.
只接受原子储存: seen编号 :mark 不支持存储,因为
它们不能排除同时交付一项事件编号。 共享原子存储
(例如,Redis或具有独特标识的数据库行)
多层部署; createInMemoryReconcileDedupe() 每个进程和
只适合一个案例。 保持处理器同位素,包括之后的重试
TTL过期或定界入室驱逐.
签名验证前的正文限制为 1 048 576 字节; 配置
maxBodyBytes 更改此限制.
活性探测器
流体发送签名 GET 在你的清单 health.path (默认) /api/health每一分钟,只有2xx表示健康.
// app/api/health/route.ts
import { withFluidLiveness } from "@fluid-app/droplet-sdk/next";
export const GET = withFluidLiveness({
check: async () => void (await db.$queryRaw`SELECT 1`),
});
秘密默认为 FLUID_PROBE_SIGNING_SECRET。 。 。 . check 仅运行有效签名的探测器,以 checkTimeoutMs (2000年违约),并回答503, false 或暂停。 超时时中止 AbortSignal 转至 check交给你的司机 检查从不堆积:并行的探测器共享一个检查,而一个仍在运行的计时出站检查使得探测器回答503而不启动另一个. 未签名的请求得到平板 200;被拒绝的签名最多每分钟记录一次。 Middleware 绝不能重定向或认证此路径 .
出口
| 子路径 | 目录 |
|---|---|
. | verifySignature, (中文(简体) ), verifyLivenessProbe, (中文(简体) ), tokenDigest, (中文(简体) ), backfillCallbackTokens, (中文(简体) ), describeError, (中文(简体) ), createFluidInstallationClient,状态计划/类型,健康助手,存储类型 |
/next | withFluidCallback, (中文(简体) ), withFluidWebhook, (中文(简体) ), withFluidLiveness, (中文(简体) ), withFluidReconcileEvents, (中文(简体) ), createInMemoryReconcileDedupe,调节计划/类型 |
/prisma | createPrismaCallbackStore |
/drizzle | createDrizzleCallbackStore ——结构打字,取出 eq 已注射, 因此此软件包从不依赖于 drizzle-orm |
许可证
UNLICENSED - 故意的。 - 故意的。 这个软件包在 npmjs 上公开,以便
独立滴盘重置器可以在没有注册符的情况下安装,即
唯一原因就是它被发表。 公共服务不是
权利:不给予使用、复制、修改或重新分配的权限.