@crabcrop/url (英语)
构建和签名 蟹类 来自TypeScript或JavaScript,在节点或浏览器中的URL. 无依赖关系:结点标志和封条 node:crypto,浏览器捆绑则获得相同功能的内置 TypeScript 执行(参见 浏览器和捆绑器) (中文(简体) ). 签名方案在 spec/url-signing.md签名由峡谷支撑 spec/test-vectors.json,这个软件包作为测试运行,解释者的判决和打字的构建者由共享语法公司在 spec/explain-verdicts.json 财务报告和审定财务报表 spec/operation-vectors.json游乐场的套房 和服务器板条箱的差别测试也一样.
使用量
import { buildSealedURL, buildURL, buildSrcSet, purgeGroupSignature, purgeSignature, seal, sign, unseal, uploadSignature, verifyUrlSignature } from "@crabcrop/url";
// A signed URL. The operation string is passed verbatim and may
// contain several segments and preset: references.
const url = buildURL(
"https://images.example.com",
"your-secret",
"crop:1200x630:smart/filters:format(webp)",
"https://example.com/photo.jpg",
);
// An expiring URL (unix seconds), emitted as a leading exp: segment.
const expiring = buildURL(
"https://images.example.com",
"your-secret",
"300x200",
"https://example.com/photo.jpg",
{ expires: 1893456000 },
);
// A responsive srcset: one {width}x0 URL per width.
const srcset = buildSrcSet(
"https://images.example.com",
"your-secret",
"https://example.com/photo.jpg",
[400, 800, 1200],
);
// The raw signature for a remainder, if you assemble URLs yourself.
const signature = sign("your-secret", "300x200/https://example.com/photo.jpg");
// Verify a signed URL's signature locally, in constant time
// (unsigned or malformed URLs return null).
const ok = verifyUrlSignature("your-secret", url);
// Purge signatures for the server's /purge and /purge-group endpoints
// are domain-separated under a derived subkey; the helpers sign the
// right strings so callers never assemble them by hand.
const purge = purgeSignature("your-secret", "300x200/https://example.com/photo.jpg");
const group = purgeGroupSignature("your-secret", "thumb");
// Upload signatures for the server's PUT /upload/<signature>/<name>
// endpoint live in a third domain, under their own derived subkey.
// Pass an expiry (unix seconds) for a token that stops working
// then; its path is /upload/<signature>/exp:<expiry>/<name>.
const upload = uploadSignature("your-secret", "heroes/photo.jpg");
const expiringUpload = uploadSignature("your-secret", "heroes/photo.jpg", 1893456000);
// Sealed URLs (issue #255): the whole remainder encrypted into an
// opaque token in the signature slot, for public links whose recipe
// should stay unreadable. The server serves the sealed and plain
// forms of one render from the same cache entry.
const sealedUrl = buildSealedURL(
"https://images.example.com",
"your-secret",
"300x200",
"https://example.com/photo.jpg",
{ name: "photo.jpg" },
);
const remainder = unseal("your-secret", seal("your-secret", "300x200/https://example.com/photo.jpg"));
过 null 作为产生文字的秘密 unsafe 签名段,用于运行于 CRABCROP_ALLOW_UNSAFE 或以预设模式.
建造者投放操作或源 永远无法验证 : ?, (中文(简体) ), #,白空间,控制和非ASCII字符,以及 " < > \ ^ ` { | },浏览器重写于 <img src> 或者说 fetch() 请求离开页面( e)\ 变成 /其余的都是编码的 将源代码编码为第一(x{y}.jpg 作为 x%7By%7D.jpg);错误名称为编码。 全部规则在 spec/url-signing.md。 。 。 .
类型操作
操作字符串是 spec,通过它逐字保持主API. 对于那些更愿意建造它的召唤者来说, ops 财务报告和审定财务报表 filter 被键入的构建器可以验证每个反对服务器语法的论据并返回词条拼写,所以被键入和手写的形式会标出字节-等同的URL(测试套接字会以此来比对词条). 数字和名称也是空想:一个十进制参数是服务器打印它的方式所拼写出来的,对于每个参数,但演化内核的64位值是最短的十进制,可以读回相同的32位浮点数(Cops.focalPoint(0.1 + 0.2, 0.4, 2) 实值 focalpoint:0.3x0.4:2),一个类名被拼写为修饰(ops.cropObjects(100, 100, [" person "]) 实值 crop:100x100:objects(person):1) (中文(简体) ). 例如:
import { buildURL, filter, joinOps, ops } from "@crabcrop/url";
const operations = joinOps(
ops.crop(1200, 630, "smart"),
ops.filters(filter.format("webp"), filter.quality(80)),
);
// operations === "crop:1200x630:smart/filters:format(webp):quality(80)"
const url = buildURL("https://images.example.com", "your-secret", operations, "https://example.com/photo.jpg");
服务器拒绝的值( A)filter.quality(0), (中文(简体) ), filter.brighten(256), (中文(简体) ), ops.crop(0, 100))在构建时抛出,而不是在边缘刻出一个失败的 URL 。 关闭的套件作为类型导出( Q)ImageFormat, (中文(简体) ), PerFormatQualityFormat, (中文(简体) ), CropAlign, (中文(简体) ), Anchor, (中文(简体) ), BlendMode, (中文(简体) ), MaskShape, (中文(简体) ), CurveChannel, (中文(简体) ), StudioMode, (中文(简体) ), StudioShadow, (中文(简体) ), Dimension),选项对象也是如此(FormatOptions 页:1 filter.format, (中文(简体) ), StudioOptions 页:1 filter.studio) (中文(简体) ). MAX_DIMENSION 是服务器的每轴像素盖,16383. 大小、盒子、区域、边框、半径和像素建构器对照它(如 ops.cropRect 财务报告和审定财务报表 ops.focal,没有封顶) 框架整合 输入他们自己的支票. FILTER_DESCRIPTIONS 是解释者使用的过滤元数据表.
import { filter, MAX_DIMENSION, ops, type FormatOptions } from "@crabcrop/url";
const jpeg: FormatOptions = { progressive: true, subsampling: 420 };
filter.format("jpeg", jpeg); // "format(jpeg,progressive,subsampling:420)"
ops.size(Math.min(requestedWidth, MAX_DIMENSION), 0); // clamp rather than throw
浏览器和捆绑器
包通过一个内部分解器导入密码, #crypto,哪个 package.json 按条件分列的地图 :
| 条件 | 后端 | 谁解决它 |
|---|---|---|
browser | dist/crypto/portable.js: SHA-256, HMAC-SHA-256, 和 AES-256-GCM 平面类型脚本 | 客户端从读取的捆绑器构建 imports 条件(Vite、Webpack 5、esbuild、Rollup) |
node | dist/crypto/node.js数字 : node:crypto (开放SSL) | 节点和服务器构建目标 |
default | 便携式后端 | 任何其他运行时间, 如边缘工人不兼容节点 |
软件包的其余部分使用Node Globals(无) Buffer,没有 process),因此客户端捆绑从不拉入已建节点. 每个函数在后端保持同步; WebCrypto 的 crypto.subtle 是同步的,无法保留 API 。 两个后端都产生相同的字节: 测试套件检查可移植字节与 node:crypto 数以千计的随机输入,并运行每一个光谱矢量 tests/browser-bundles 与Vite建立框架集成,并运行没有节点环球的捆绑.
便携式后端速度较慢:每个签名约5 μs,对接2 μs左右 node:crypto (22号口),并用约四分之一毫秒来封存2个KB剩余部分,最大允许. 密封需要 crypto.getRandomValues,每个浏览器和边缘运行时间都可以提供。 它的AES回合通过密钥依赖字节来索引S-box,一个缓存刻度观察者原则上可以测量;节点为此原因和速度保持了OpenSSL.
浏览器代码中的秘密是公开的:任何装入页面的人都可以读取. 浏览器构建的存在使得客户端代码可以完全导入包:构建未签名并预设的URL,解释URL,并签名该秘密属于使用该页面的人的地方(一个游乐场式的工具). 只在服务器代码中以部署密钥签名 框架整合 绝不在浏览器中签名.
运行时间
已发布的软件包是编译的 JavaScript ()dist/)为节点 22. 出处为TypeScript并直接运行于22.18节点或更新(或23.6或更新)上,其中类型的剥取默认进行,这也是测试如何运行的:
npm test
npm test 正在建设 dist/ (第一个) #crypto 导入解析处)和类型检查 test/exports.test.ts,它用它自己的名字导入包, 对照已建的申报, 所以在公共指数中缺失的类型在这里失效。 然后它运行 套房两次: node:crypto,然后再次在 --conditions=browser 在便携式后端.
在此寄存器中, 框架加载器和 MCP 服务器解析 @crabcrop/url 至这些源,所以一次构建软件包(npm run build在运行测试之前; 独立安装来自 npm 发布后, 或来自寄存器 "@crabcrop/url": "file:../typescript" 如此 repo 中的 MCP 软件包.