在 Cloudflare Worker 中混淆 JavaScript

用 @afterpack/wasm 在你自己的 Cloudflare Worker 中运行 AfterPack,传入 Pro 密钥即可在 AfterPack 云端构建,并按轮换窗口缓存每个结果。

AfterPack 可以通过两种方式从 Cloudflare Worker 运行,用的是同一个包、同一个调用。不传密钥时,Free 引擎在你的 Worker 中运行,是一个从 npm 安装的 WebAssembly 模块。传入你的 Pro 密钥后,构建会在 AfterPack 的云端以 Pro 引擎运行。

本页介绍这两种方式,然后说明如何缓存结果,让每个资源在每个轮换窗口内只构建一次。

AfterPack,在你自己的 Worker 中

@afterpack/wasm 是编译成 WebAssembly 的 AfterPack。它与 CLI 和框架插件运行的是同一个 Rust 引擎,只是面向 wasm32 构建,而不是面向你机器的架构。

npm install @afterpack/wasm
import { obfuscate } from "@afterpack/wasm";

export default {
  async fetch(request) {
    const source = await request.text();
    const result = await obfuscate({ path: "app.js", source }, { preset: "hard" });
    return new Response(result.bytes, {
      headers: { "content-type": "application/javascript" },
    });
  },
};

集成就是这么多。不需要 init 调用,不需要 nodejs_compat 标志,也不需要为 .wasm 文件编写 wrangler 规则。该模块没有任何 WebAssembly 导入,因此会在你第一次调用 obfuscate 时同步实例化,而包的 exports 映射会自动为你的打包工具提供适用于 Worker 的入口。同样的导入在 Node 中也能用,那里会由另一个入口从磁盘读取二进制文件。

其接口与 @afterpack/core 完全相同——obfuscate、obfuscateAll、version,全部是异步的,遇到无法发布的构建都会抛出异常。配置是配置页面中记录的同一个对象,包括 preset 和 seed。

除非无法使用,否则请用 `@afterpack/core`

@afterpack/core 附带原生二进制文件,速度更快。当宿主无法加载原生二进制时再选用 @afterpack/wasm:Worker、沙箱化的 CI 镜像、边缘函数。

它的代价

引擎二进制文件已经编译好,随包附带。

字节体积
原始 .wasm4,573,3214.36 MiB
gzip 压缩后1,307,7721.25 MiB

Cloudflare 对 Worker 的体积限制是免费套餐压缩后 3 MB、付费套餐 10 MB,因此引擎在两者中都放得下。免费套餐放不下的是工作量:它把每次调用的 CPU 限制在 10 ms,而在笔记本电脑的一个核心上混淆一个 185 KB 的 bundle,在默认的 light 预设下需要不到 0.3 秒,在 medium 下约需 1 秒。超出演示范围的用途都需要 Workers Paid。

在每个请求上都混淆同一份资源的 Worker,每次都要付出这份 CPU 开销。如果你并不需要每个请求都换一种新形态,就按轮换窗口缓存结果,或者在构建中完成这项工作并提供其输出,以便控制费用。

你托管的包能做什么、不能做什么

你安装的 .wasm 执行的是 Free 保护。Pro 变换只在 AfterPack 的基础设施上运行,因此这个包里没有任何可以解锁的东西:没有可以绕过的密钥检查,也没有能把托管副本变成 Pro 构建的开关。key 的作用是把工作路由到云端;它不会在本地启用任何东西。

发布的包带有 npm provenance 记录——包版本、构建它的工作流,以及产物摘要——因此你可以把自己安装的字节与产生它们的那次构建对应起来。

Pro,从你自己的 Worker 中

在同一个 obfuscate() 调用的配置里传入你的 key(例如来自 Worker secret),构建就会在 AfterPack 的云端以 Pro 引擎运行:

import { obfuscate } from "@afterpack/wasm";

export default {
  async fetch(request, env) {
    const source = await request.text();
    const result = await obfuscate(
      { path: "app.js", source },
      { preset: "hard", key: env.AFTERPACK_KEY },
    );
    return new Response(result.bytes, {
      headers: { "content-type": "application/javascript" },
    });
  },
};

包会替你向 POST /v1/obfuscate/batch 发起请求,因此 Worker 永远不需要手写 API 调用。对 Worker 来说过大的文件会自动转到容器中处理。Pro 引擎永远不会进入你的 Worker:它只在 AfterPack 的基础设施上运行,这就是上文所说的层级边界。

每次调用都是一次计量的 Pro 构建(Pro 的费用),外加一次在 Cloudflare 网络内部完成的快速请求。你可以在每个请求上都构建,但每次都会计费,所以更好的做法是按轮换窗口缓存结果。

你的源代码会离开你的 Worker,在 AfterPack 的云端处理:在内存中处理,不会持久化,详见隐私与数据处理。如果无法连接云端,调用会失败即中止:它会抛出异常,不会发布任何未受保护的内容。云端 API 参考是完整的线路契约,部署模式解释了引擎换了运行位置之后会有什么变化。

Pro 永远不会以由你托管的 wasm 形式发布

声称能在你自己的基础设施上运行 Pro 变换的包不是我们的。在安装任何使用这个名字的东西之前,请检查 npm provenance 记录。

按轮换窗口缓存结果

从当前时间窗口派生 seed,并用包含该窗口的键缓存结果。这样每个位置在每个窗口内只构建一次资源,并一直提供到窗口结束,Free 和 Pro 都是如此:

import { obfuscate } from "@afterpack/wasm";
import source from "./pricing.client.js";

const WINDOW_SECONDS = 600;

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    if (url.pathname !== "/pricing.js") {
      return new Response("Not found", { status: 404 });
    }

    const now = Math.floor(Date.now() / 1000);
    const slot = Math.floor(now / WINDOW_SECONDS);
    const secondsLeft = (slot + 1) * WINDOW_SECONDS - now;
    const cacheKey = new Request(`${url.origin}/pricing.js?slot=${slot}`);

    let response = await caches.default.match(cacheKey);
    if (!response) {
      const result = await obfuscate(
        { path: "pricing.js", source },
        { preset: "hard", seed: `pricing.js:${slot}`, key: env.AFTERPACK_KEY },
      );
      response = new Response(result.bytes, {
        headers: {
          "content-type": "application/javascript",
          "cache-control": `public, max-age=${secondsLeft}`,
        },
      });
      ctx.waitUntil(caches.default.put(cacheKey, response.clone()));
    }

    response = new Response(response.body, response);
    response.headers.set("cache-control", `public, max-age=${secondsLeft}`);
    return response;
  },
};

如果没有设置 AFTERPACK_KEY secret,key 就是 undefined,同一个 Worker 会在你的 Worker 内以 Free 构建。源文件通过 wrangler 的 Text 规则以字符串形式导入,做法与完整演练相同。

窗口同时出现在 seed 和缓存键里,因此运行同一部署的每个位置在一个窗口内都提供相同的字节,新窗口也永远不会读到旧条目。返回的 cache-control 会倒数到窗口结束,因此浏览器不会把副本保留到窗口之后。Cache API 按数据中心存储条目,在 dashboard 编辑器中不起作用,所以请用 wrangler dev 或已部署的路由来测试。

如何选择

你想要使用
在 Worker 中混淆,任何东西都不离开你的账户@afterpack/wasm,即本页
在普通的 Node 构建中混淆@afterpack/core 或 CLI
Pro 加固和按区域的指令,从 Worker 中使用带上 key 的同一个调用,它会在 AfterPack 的云端构建
每个窗口换一种新形态,而不必为每位访客构建一次轮换窗口缓存

下一步