@nitida/asset-client 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +7 -0
- package/README.md +9 -4
- package/dist/index.cjs +67 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +183 -42
- package/dist/index.d.ts +183 -42
- package/dist/index.js +61 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access.ts +21 -1
- package/src/index.ts +37 -30
- package/src/transform.ts +202 -32
package/AGENTS.md
CHANGED
|
@@ -47,6 +47,13 @@ schemas, not against a hand-kept list.
|
|
|
47
47
|
*guesses* — the server keys the original off the **uploaded filename**, which the mime does not
|
|
48
48
|
determine — and with neither it emits the `-o.bin` sentinel and 404s forever.
|
|
49
49
|
|
|
50
|
+
The ladder itself — **generated from `TRANSFORM_WIDTHS`, do not edit by hand.**
|
|
51
|
+
`bun run gen:docs` rewrites it; `--check` fails the build if it drifts:
|
|
52
|
+
|
|
53
|
+
<!-- BEGIN GENERATED: transform-widths · bun run gen:docs -->
|
|
54
|
+
`96, 128, 160, 180, 240, 256, 320, 400, 480, 600, 640, 800, 960, 1080, 1200, 1280, 1440, 1600, 1920, 2560, 3840` — 21 widths.
|
|
55
|
+
<!-- END GENERATED: transform-widths -->
|
|
56
|
+
|
|
50
57
|
## Existence checks
|
|
51
58
|
|
|
52
59
|
Use `hasPreset(asset, preset)` against `dto.presets` (the compact code string). It is on every
|
package/README.md
CHANGED
|
@@ -43,10 +43,15 @@ getTransformUrl({ sha }, { format: "webp", width: 640 });
|
|
|
43
43
|
getTransformSrcSet({ sha }, [640, 960, 1280], { format: "webp" });
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
⚠️ **Widths must be on the unsigned ladder** `TRANSFORM_WIDTHS`
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
⚠️ **Widths must be on the unsigned ladder** `TRANSFORM_WIDTHS` — generated from the source, do not
|
|
47
|
+
edit by hand:
|
|
48
|
+
|
|
49
|
+
<!-- BEGIN GENERATED: transform-widths · bun run gen:docs -->
|
|
50
|
+
`96, 128, 160, 180, 240, 256, 320, 400, 480, 600, 640, 800, 960, 1080, 1200, 1280, 1440, 1600, 1920, 2560, 3840` — 21 widths.
|
|
51
|
+
<!-- END GENERATED: transform-widths -->
|
|
52
|
+
|
|
53
|
+
Anything else is **HTTP 400** at the edge — it is a DoS guard, not a bug. Import the `TransformWidth`
|
|
54
|
+
type and an off-ladder number becomes a compile error instead of a runtime 400.
|
|
50
55
|
|
|
51
56
|
## Video — the stored variant, not a transform
|
|
52
57
|
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
MAX_SIGNED_TRANSFORM_TTL_SECONDS: () => MAX_SIGNED_TRANSFORM_TTL_SECONDS,
|
|
24
|
+
MAX_SIGNED_URL_TTL_SECONDS: () => MAX_SIGNED_URL_TTL_SECONDS,
|
|
25
|
+
MIN_SIGNED_TRANSFORM_TTL_SECONDS: () => MIN_SIGNED_TRANSFORM_TTL_SECONDS,
|
|
23
26
|
PRESET_EXT: () => PRESET_EXT,
|
|
24
27
|
PRESET_LONG: () => PRESET_LONG,
|
|
25
28
|
PRESET_MAX_DIM: () => PRESET_MAX_DIM,
|
|
@@ -34,6 +37,7 @@ __export(index_exports, {
|
|
|
34
37
|
configureSlotResolver: () => configureSlotResolver,
|
|
35
38
|
contrastRatio: () => contrastRatio,
|
|
36
39
|
deriveAccessKey: () => deriveAccessKey,
|
|
40
|
+
deriveTransformKid: () => deriveTransformKid,
|
|
37
41
|
extractAssetSha: () => extractAssetSha,
|
|
38
42
|
getAmbientGradient: () => getAmbientGradient,
|
|
39
43
|
getAssetDimensions: () => getAssetDimensions,
|
|
@@ -66,7 +70,8 @@ __export(index_exports, {
|
|
|
66
70
|
setTenantId: () => setTenantId,
|
|
67
71
|
signAccessUrl: () => signAccessUrl,
|
|
68
72
|
signTransformUrl: () => signTransformUrl,
|
|
69
|
-
toRequestablePresets: () => toRequestablePresets
|
|
73
|
+
toRequestablePresets: () => toRequestablePresets,
|
|
74
|
+
transformMessage: () => transformMessage
|
|
70
75
|
});
|
|
71
76
|
module.exports = __toCommonJS(index_exports);
|
|
72
77
|
|
|
@@ -97,12 +102,18 @@ function accessMessage(tenantPrefix, exp, resourcePath) {
|
|
|
97
102
|
${exp}
|
|
98
103
|
${resourcePath.replace(/^\/+/, "")}`;
|
|
99
104
|
}
|
|
105
|
+
var MAX_SIGNED_URL_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
100
106
|
async function signAccessUrl(publicUrl, signingKey, opts) {
|
|
101
107
|
if (!Number.isFinite(opts.expiresInSeconds) || opts.expiresInSeconds <= 0) {
|
|
102
108
|
throw new Error(
|
|
103
109
|
"signAccessUrl: `expiresInSeconds` must be a positive number \u2014 a signed URL without an expiry is a public URL the moment it is forwarded."
|
|
104
110
|
);
|
|
105
111
|
}
|
|
112
|
+
if (opts.expiresInSeconds > MAX_SIGNED_URL_TTL_SECONDS) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`signAccessUrl: \`expiresInSeconds\` may not exceed ${MAX_SIGNED_URL_TTL_SECONDS} (7 days). Both verifiers refuse a longer one, so this would build a URL that 401s. An expiry that never arrives is not an expiry.`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
106
117
|
const u = new URL(publicUrl);
|
|
107
118
|
const segments = u.pathname.split("/").filter(Boolean);
|
|
108
119
|
if (segments[0] === "a" && segments[2] && /^[vrt]$/.test(segments[2])) {
|
|
@@ -258,6 +269,7 @@ var TRANSFORM_WIDTHS = [
|
|
|
258
269
|
96,
|
|
259
270
|
128,
|
|
260
271
|
160,
|
|
272
|
+
180,
|
|
261
273
|
240,
|
|
262
274
|
256,
|
|
263
275
|
320,
|
|
@@ -361,7 +373,7 @@ function getTransformUrl(asset, opts) {
|
|
|
361
373
|
);
|
|
362
374
|
return buildTransformUrl(asset, opts);
|
|
363
375
|
}
|
|
364
|
-
function getSignedTransformUrl(asset, opts, signingKey) {
|
|
376
|
+
function getSignedTransformUrl(asset, opts, signingKey, signOpts) {
|
|
365
377
|
assertSha(asset, "getSignedTransformUrl");
|
|
366
378
|
assertPublic(
|
|
367
379
|
asset,
|
|
@@ -370,9 +382,34 @@ function getSignedTransformUrl(asset, opts, signingKey) {
|
|
|
370
382
|
);
|
|
371
383
|
const url = buildTransformUrl(asset, opts);
|
|
372
384
|
if (!url) return null;
|
|
373
|
-
return signTransformUrl(url, signingKey);
|
|
374
|
-
}
|
|
375
|
-
|
|
385
|
+
return signTransformUrl(url, signingKey, signOpts);
|
|
386
|
+
}
|
|
387
|
+
var TRANSFORM_SIG_DOMAIN = "nitida/transform/v2";
|
|
388
|
+
var KID_INFO = "nitida/kid/v1";
|
|
389
|
+
var MAX_SIGNED_TRANSFORM_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
390
|
+
var MIN_SIGNED_TRANSFORM_TTL_SECONDS = 120;
|
|
391
|
+
async function deriveTransformKid(signingKey) {
|
|
392
|
+
return (await hmacSha256Hex(signingKey, KID_INFO)).slice(0, 8);
|
|
393
|
+
}
|
|
394
|
+
function transformMessage(args) {
|
|
395
|
+
return `${TRANSFORM_SIG_DOMAIN}
|
|
396
|
+
${args.tenantPrefix}
|
|
397
|
+
${args.exp}
|
|
398
|
+
${args.canonicalDsl}/${args.filename}`;
|
|
399
|
+
}
|
|
400
|
+
async function signTransformUrl(unsignedUrl, signingKey, opts) {
|
|
401
|
+
const ttl = Math.floor(opts.expiresInSeconds);
|
|
402
|
+
if (!Number.isFinite(ttl) || ttl < MIN_SIGNED_TRANSFORM_TTL_SECONDS || ttl > MAX_SIGNED_TRANSFORM_TTL_SECONDS) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`signTransformUrl: \`expiresInSeconds\` must be between ${MIN_SIGNED_TRANSFORM_TTL_SECONDS} and ${MAX_SIGNED_TRANSFORM_TTL_SECONDS}. The platform refuses a longer one on every verify, so a bigger number here just builds a URL that 401s.`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
const tid = opts.tenantId ?? getTenantId();
|
|
408
|
+
if (tid == null) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
"signTransformUrl: no tenant is configured. Call setTenantId(id) (or construct a NitidaClient with `tenantId`) \u2014 the tenant is part of what the signature covers, so this cannot be guessed."
|
|
411
|
+
);
|
|
412
|
+
}
|
|
376
413
|
const u = new URL(unsignedUrl);
|
|
377
414
|
const parts = u.pathname.split("/").filter(Boolean);
|
|
378
415
|
if (parts[0] !== "t" || parts.length < 3) {
|
|
@@ -380,8 +417,19 @@ async function signTransformUrl(unsignedUrl, signingKey) {
|
|
|
380
417
|
}
|
|
381
418
|
const filename = parts[parts.length - 1];
|
|
382
419
|
const dsl = parts.slice(1, -1).join("/");
|
|
383
|
-
const
|
|
384
|
-
const
|
|
420
|
+
const now = opts.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
421
|
+
const exp = Math.floor((now + ttl) / 60) * 60;
|
|
422
|
+
const sig = await hmacSha256Hex(
|
|
423
|
+
signingKey,
|
|
424
|
+
transformMessage({
|
|
425
|
+
tenantPrefix: tid.toString(36),
|
|
426
|
+
exp,
|
|
427
|
+
canonicalDsl: dsl,
|
|
428
|
+
filename
|
|
429
|
+
})
|
|
430
|
+
);
|
|
431
|
+
u.searchParams.set("kid", await deriveTransformKid(signingKey));
|
|
432
|
+
u.searchParams.set("exp", String(exp));
|
|
385
433
|
u.searchParams.set("sig", sig);
|
|
386
434
|
return u.toString();
|
|
387
435
|
}
|
|
@@ -508,16 +556,16 @@ function materializeResolution(dto, overridePreset) {
|
|
|
508
556
|
|
|
509
557
|
// src/index.ts
|
|
510
558
|
var REQUESTABLE_PRESETS = [
|
|
511
|
-
"
|
|
512
|
-
"sm",
|
|
513
|
-
"md",
|
|
559
|
+
"aiproxy",
|
|
514
560
|
"lg",
|
|
515
|
-
"
|
|
561
|
+
"md",
|
|
516
562
|
"original",
|
|
517
563
|
"poster",
|
|
564
|
+
"probe",
|
|
565
|
+
"sm",
|
|
566
|
+
"thumb",
|
|
518
567
|
"video",
|
|
519
|
-
"
|
|
520
|
-
"probe"
|
|
568
|
+
"xl"
|
|
521
569
|
];
|
|
522
570
|
var isRequestablePreset = (v) => REQUESTABLE_PRESETS.includes(v);
|
|
523
571
|
var toRequestablePresets = (input) => {
|
|
@@ -756,6 +804,9 @@ function getAssetDimensions(asset) {
|
|
|
756
804
|
}
|
|
757
805
|
// Annotate the CommonJS export names for ESM import in node:
|
|
758
806
|
0 && (module.exports = {
|
|
807
|
+
MAX_SIGNED_TRANSFORM_TTL_SECONDS,
|
|
808
|
+
MAX_SIGNED_URL_TTL_SECONDS,
|
|
809
|
+
MIN_SIGNED_TRANSFORM_TTL_SECONDS,
|
|
759
810
|
PRESET_EXT,
|
|
760
811
|
PRESET_LONG,
|
|
761
812
|
PRESET_MAX_DIM,
|
|
@@ -770,6 +821,7 @@ function getAssetDimensions(asset) {
|
|
|
770
821
|
configureSlotResolver,
|
|
771
822
|
contrastRatio,
|
|
772
823
|
deriveAccessKey,
|
|
824
|
+
deriveTransformKid,
|
|
773
825
|
extractAssetSha,
|
|
774
826
|
getAmbientGradient,
|
|
775
827
|
getAssetDimensions,
|
|
@@ -802,6 +854,7 @@ function getAssetDimensions(asset) {
|
|
|
802
854
|
setTenantId,
|
|
803
855
|
signAccessUrl,
|
|
804
856
|
signTransformUrl,
|
|
805
|
-
toRequestablePresets
|
|
857
|
+
toRequestablePresets,
|
|
858
|
+
transformMessage
|
|
806
859
|
});
|
|
807
860
|
//# sourceMappingURL=index.cjs.map
|