让套餐限制和定价规则远离你的公开 bundle

客户端 JavaScript 中的套餐限制、配额阈值和功能开关规则,读起来就是一份商业模式的规格说明。如何让它们不可读,以及哪些内容无论如何仍然可以被观察到。

套餐限制、配额阈值和功能开关规则通常最终会进入客户端 bundle,竞争对手或客户可以直接从文件中读出它们。

在纯 JavaScript 中,你的定价表读起来就是一份规格说明

当定价表、配额映射和功能开关列表以纯 JavaScript 的形式存在时,它们读起来就是你商业模式的文档。任何人都可以打开 bundle,看到你的各个层级在哪里分界、什么会触发升级推销,以及哪些开关控制着哪些功能。

编码智能体在这个 bundle 上过一遍,就能把零散的值整理成一份有条理的规格说明。这份说明可以被照抄、在支持工单里被拿来据理力争,或者在你察觉之前就被用来绕过某个限制。

编码后的值,重写后的结构

在任何正数的 complexity 目标下,字符串字面量都会被编码,因此表格和阈值在你提供的文件中不再是可读的文本。它们周围的结构也会被重写。仔细研读输出的阅读者能恢复出一个等价的计算过程,并且必须从中重建出你的规则原本的含义。

轮换是让恢复出来的副本无法一直有用的关键。每次发布都使用新的种子,意味着别人上一次重建出来的东西,描述的是一个你已经不再提供的构建。

在存放规则的模块上提高目标

在预设阶梯上把整个 bundle 提高一级,或者当预设的粒度不合适时直接设置目标:

{
  "preset": "medium",
  "complexity": 40
}

若只想标记存放规则的那个模块,就使用指令。指令在 Pro 构建上生效。

/* @afterpack preset=extreme */
export function resolveQuota(plan: Plan, usage: Usage): number {
  return TIER_LIMITS[plan.tier] - usage.consumed;
}
/* @afterpack end */

strings.leaks.max 把“没有可读字面量残留”变成一道构建关口,而不是靠肉眼检查的东西。失败时会给出 DIAG_SURVIVING_READABLE_LITERAL,并指向漏网的那个字面量。

有字面量残留时让构建失败

在 CI 中用 --strings.leaks.max=0 构建,并把非零退出视为构建失败。然后打开 Protection Map,检查规则模块的着色是否达到了你为它设定的目标。

无论你发布什么,都仍然可以被观察到的内容

轮换构建会让别人抓取的规则副本在下一次发布时过时,而这正是值得拥有的部分。这里讨论的范围是那些只为在 bundle 内部被读取而存在的值;你的 UI 渲染出来的价格或你的 API 返回的限制,无论 bundle 长什么样都可以被观察到,所以请把页面显示的任何内容都视为公开的,并在服务端强制执行每一项限制。

下一步

  • 威胁模型:静态阅读者能恢复什么、不能恢复什么。
  • 预设:完整的阶梯,以及每一级的复杂度目标。
  • Protection Map:确认规则模块得到了你所配置的处理。