@nitida/asset-client 0.17.0 → 0.18.1
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 +26 -0
- package/README.md +6 -3
- package/dist/index.cjs +265 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +266 -85
- package/dist/index.d.ts +266 -85
- package/dist/index.js +258 -99
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access.ts +232 -0
- package/src/index.ts +219 -1
- package/src/transform.ts +44 -5
package/dist/index.js
CHANGED
|
@@ -1,3 +1,81 @@
|
|
|
1
|
+
// src/access.ts
|
|
2
|
+
var ACCESS_KEY_INFO = "nitida/access/v1";
|
|
3
|
+
async function hmac(key, message) {
|
|
4
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
5
|
+
"raw",
|
|
6
|
+
key,
|
|
7
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
8
|
+
false,
|
|
9
|
+
["sign"]
|
|
10
|
+
);
|
|
11
|
+
return new Uint8Array(
|
|
12
|
+
await crypto.subtle.sign(
|
|
13
|
+
"HMAC",
|
|
14
|
+
cryptoKey,
|
|
15
|
+
new TextEncoder().encode(message)
|
|
16
|
+
)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
var toHex = (b) => [...b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
20
|
+
async function deriveAccessKey(signingKey) {
|
|
21
|
+
return hmac(new TextEncoder().encode(signingKey), ACCESS_KEY_INFO);
|
|
22
|
+
}
|
|
23
|
+
function accessMessage(tenantPrefix, exp, resourcePath) {
|
|
24
|
+
return `${tenantPrefix}
|
|
25
|
+
${exp}
|
|
26
|
+
${resourcePath.replace(/^\/+/, "")}`;
|
|
27
|
+
}
|
|
28
|
+
async function signAccessUrl(publicUrl, signingKey, opts) {
|
|
29
|
+
if (!Number.isFinite(opts.expiresInSeconds) || opts.expiresInSeconds <= 0) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"signAccessUrl: `expiresInSeconds` must be a positive number \u2014 a signed URL without an expiry is a public URL the moment it is forwarded."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const u = new URL(publicUrl);
|
|
35
|
+
const segments = u.pathname.split("/").filter(Boolean);
|
|
36
|
+
if (segments[0] === "a" && segments[2] && /^[vrt]$/.test(segments[2])) {
|
|
37
|
+
segments.shift();
|
|
38
|
+
}
|
|
39
|
+
const tenantPrefix = segments.shift();
|
|
40
|
+
if (!tenantPrefix || segments.length === 0) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`signAccessUrl: expected a tenant-prefixed CDN path like /<tenant>/v/<sha>-<preset>.<ext>, got ${u.pathname}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (!/^[vrt]$/.test(segments[0])) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`signAccessUrl: expected /<tenant>/<v|r|t>/\u2026 but the segment after the tenant is "${segments[0]}". ` + (segments[0]?.includes("=") ? `That looks like a transform DSL, so the path is probably /t/<dsl>/<sha>.<ext> \u2014 which has no tenant in it (\`t\` here was read as tenant ${Number.parseInt(tenantPrefix, 36)}). Use getPrivateTransformUrl(asset, opts, signingKey, { expiresInSeconds }) instead.` : `Got ${u.pathname}.`)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const resourcePath = segments.join("/");
|
|
51
|
+
const now = opts.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
52
|
+
const exp = now + Math.floor(opts.expiresInSeconds);
|
|
53
|
+
const sig = toHex(
|
|
54
|
+
await hmac(
|
|
55
|
+
await deriveAccessKey(signingKey),
|
|
56
|
+
accessMessage(tenantPrefix, exp, resourcePath)
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
u.pathname = `/a/${tenantPrefix}/${resourcePath}`;
|
|
60
|
+
u.searchParams.set("exp", String(exp));
|
|
61
|
+
u.searchParams.set("sig", sig);
|
|
62
|
+
return u.toString();
|
|
63
|
+
}
|
|
64
|
+
function assertPublic(asset, fn, escape) {
|
|
65
|
+
if (asset.visibility !== "private") return;
|
|
66
|
+
throw new Error(
|
|
67
|
+
`${fn}: this asset is private, so a public CDN URL for it will answer 404 \u2014 that is the feature, not a missing file. Mint a signed URL on your BACKEND instead: await ${escape}. Never ship the signing key to a browser.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
function assertSha(asset, fn) {
|
|
71
|
+
const sha = asset?.sha;
|
|
72
|
+
if (typeof sha === "string" && /^[0-9a-f]{16,64}$/i.test(sha)) return;
|
|
73
|
+
const hint = asset && typeof asset === "object" && "sha256" in asset ? " The value you passed has `sha256` but not `sha` \u2014 that is the shape `upload()` returns. Use `{ sha: result.sha256.slice(0, 16) }`, or fetch the DTO with `assets.get(id)`." : ` Got ${JSON.stringify(sha)}.`;
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${fn}: no usable \`sha\` on the value you passed, so the URL would contain "undefined" and 404 somewhere else.${hint}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
1
79
|
// src/palette.ts
|
|
2
80
|
function resolveSwatch(palette, ...keys) {
|
|
3
81
|
if (!palette) return null;
|
|
@@ -15,9 +93,9 @@ function pickAmbientBackground(palette) {
|
|
|
15
93
|
function getAmbientGradient(palette, opts = {}) {
|
|
16
94
|
if (!palette) return void 0;
|
|
17
95
|
const fromHex = palette[opts.from ?? "lm"] ?? palette.m ?? palette.d;
|
|
18
|
-
const
|
|
19
|
-
if (!fromHex || !
|
|
20
|
-
return `linear-gradient(${opts.angle ?? "135deg"}, ${fromHex}, ${
|
|
96
|
+
const toHex2 = palette[opts.to ?? "m"] ?? palette.dm ?? palette.d;
|
|
97
|
+
if (!fromHex || !toHex2) return void 0;
|
|
98
|
+
return `linear-gradient(${opts.angle ?? "135deg"}, ${fromHex}, ${toHex2})`;
|
|
21
99
|
}
|
|
22
100
|
function getTextColorForBackground(swatch) {
|
|
23
101
|
if (!swatch) return "#000000";
|
|
@@ -102,102 +180,6 @@ function getPaletteBlurBackground(palette) {
|
|
|
102
180
|
return layers.length > 0 ? `${layers.join(", ")}, ${base}` : base;
|
|
103
181
|
}
|
|
104
182
|
|
|
105
|
-
// src/slots.ts
|
|
106
|
-
var DEFAULT_TTL_MS = 6e4;
|
|
107
|
-
var cache = /* @__PURE__ */ new Map();
|
|
108
|
-
var endpoint = "https://api.nitida.gofuture.space";
|
|
109
|
-
var apiKey = null;
|
|
110
|
-
var tenantCode = null;
|
|
111
|
-
function configureSlotResolver(opts) {
|
|
112
|
-
if (opts.endpoint) endpoint = opts.endpoint.replace(/\/+$/, "");
|
|
113
|
-
if (opts.apiKey !== void 0) apiKey = opts.apiKey;
|
|
114
|
-
if (opts.tenantCode !== void 0) tenantCode = opts.tenantCode;
|
|
115
|
-
}
|
|
116
|
-
function invalidateSlotCache(slotKey) {
|
|
117
|
-
if (slotKey === void 0) cache.clear();
|
|
118
|
-
else
|
|
119
|
-
for (const k of cache.keys())
|
|
120
|
-
if (k.endsWith(`:${slotKey}`)) cache.delete(k);
|
|
121
|
-
}
|
|
122
|
-
var baseHeaders = () => {
|
|
123
|
-
const h = {};
|
|
124
|
-
if (apiKey) h.Authorization = `Bearer ${apiKey}`;
|
|
125
|
-
if (tenantCode) h["X-Tenant-Code"] = tenantCode;
|
|
126
|
-
return h;
|
|
127
|
-
};
|
|
128
|
-
async function fetchSlot(slotKey) {
|
|
129
|
-
const r = await fetch(`${endpoint}/slots/${encodeURIComponent(slotKey)}`, {
|
|
130
|
-
headers: baseHeaders()
|
|
131
|
-
});
|
|
132
|
-
if (r.status === 404) return null;
|
|
133
|
-
if (!r.ok) throw new Error(`slot fetch ${r.status}: ${await r.text()}`);
|
|
134
|
-
return await r.json();
|
|
135
|
-
}
|
|
136
|
-
async function fetchSlotsBulk(slotKeys) {
|
|
137
|
-
if (slotKeys.length === 0) return {};
|
|
138
|
-
const r = await fetch(`${endpoint}/slots/resolve`, {
|
|
139
|
-
method: "POST",
|
|
140
|
-
headers: { ...baseHeaders(), "Content-Type": "application/json" },
|
|
141
|
-
body: JSON.stringify({ keys: slotKeys })
|
|
142
|
-
});
|
|
143
|
-
if (!r.ok) throw new Error(`slots resolve ${r.status}: ${await r.text()}`);
|
|
144
|
-
const body = await r.json();
|
|
145
|
-
return body.resolved;
|
|
146
|
-
}
|
|
147
|
-
async function resolveSlot(slotKey, opts = {}) {
|
|
148
|
-
const ttl = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
149
|
-
const cacheKey = `${tenantCode ?? "_"}:${slotKey}`;
|
|
150
|
-
const now = Date.now();
|
|
151
|
-
let dto;
|
|
152
|
-
const hit = cache.get(cacheKey);
|
|
153
|
-
if (hit && now - hit.fetchedAt < ttl) {
|
|
154
|
-
dto = hit.value;
|
|
155
|
-
} else {
|
|
156
|
-
dto = await fetchSlot(slotKey);
|
|
157
|
-
cache.set(cacheKey, { fetchedAt: now, value: dto });
|
|
158
|
-
}
|
|
159
|
-
return materializeResolution(dto, opts.preset);
|
|
160
|
-
}
|
|
161
|
-
async function resolveSlots(slotKeys, opts = {}) {
|
|
162
|
-
if (slotKeys.length === 0) return {};
|
|
163
|
-
const ttl = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
164
|
-
const now = Date.now();
|
|
165
|
-
const missing = [];
|
|
166
|
-
const out = {};
|
|
167
|
-
for (const k of slotKeys) {
|
|
168
|
-
const cacheKey = `${tenantCode ?? "_"}:${k}`;
|
|
169
|
-
const hit = cache.get(cacheKey);
|
|
170
|
-
if (hit && now - hit.fetchedAt < ttl) {
|
|
171
|
-
out[k] = materializeResolution(hit.value, opts.preset);
|
|
172
|
-
} else {
|
|
173
|
-
missing.push(k);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if (missing.length > 0) {
|
|
177
|
-
const resolved = await fetchSlotsBulk(missing);
|
|
178
|
-
for (const k of missing) {
|
|
179
|
-
const dto = resolved[k] ?? null;
|
|
180
|
-
cache.set(`${tenantCode ?? "_"}:${k}`, { fetchedAt: now, value: dto });
|
|
181
|
-
out[k] = materializeResolution(dto, opts.preset);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return out;
|
|
185
|
-
}
|
|
186
|
-
function defaultPresetFor(asset) {
|
|
187
|
-
if (!asset) return "lg";
|
|
188
|
-
return asset.kind === "video" ? "video" : "lg";
|
|
189
|
-
}
|
|
190
|
-
function materializeResolution(dto, overridePreset) {
|
|
191
|
-
if (!dto) return { slot: null, preset: overridePreset ?? "lg", url: null };
|
|
192
|
-
const effective = overridePreset ?? dto.preset ?? defaultPresetFor(dto.asset);
|
|
193
|
-
const finalPreset = hasPreset(dto.asset, effective) ? effective : defaultPresetFor(dto.asset);
|
|
194
|
-
return {
|
|
195
|
-
slot: dto,
|
|
196
|
-
preset: finalPreset,
|
|
197
|
-
url: getAssetUrl(dto.asset, finalPreset)
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
183
|
// src/transform.ts
|
|
202
184
|
var TRANSFORM_WIDTHS = [
|
|
203
185
|
96,
|
|
@@ -269,12 +251,24 @@ function extForOptions(opts) {
|
|
|
269
251
|
}
|
|
270
252
|
}
|
|
271
253
|
function getVideoTransformUrl(asset, opts) {
|
|
254
|
+
assertSha(asset, "getVideoTransformUrl");
|
|
255
|
+
assertPublic(
|
|
256
|
+
asset,
|
|
257
|
+
"getVideoTransformUrl",
|
|
258
|
+
"getPrivateTransformUrl(asset, opts, signingKey, { expiresInSeconds: 300 })"
|
|
259
|
+
);
|
|
272
260
|
const dsl = serializeTransform(opts);
|
|
273
261
|
if (!dsl) return null;
|
|
274
262
|
const ext = opts.format === "webm" ? "webm" : "mp4";
|
|
275
263
|
return `${getCdnBase()}/t/${dsl}/${asset.sha}.${ext}`;
|
|
276
264
|
}
|
|
277
265
|
function getHlsStreamingUrl(asset, opts = {}) {
|
|
266
|
+
assertSha(asset, "getHlsStreamingUrl");
|
|
267
|
+
assertPublic(
|
|
268
|
+
asset,
|
|
269
|
+
"getHlsStreamingUrl",
|
|
270
|
+
'getPrivateAssetUrl(asset, "hls", signingKey, { expiresInSeconds: 300 }) \u2014 the worker re-signs the playlist children'
|
|
271
|
+
);
|
|
278
272
|
const merged = { ...opts, format: "hls" };
|
|
279
273
|
const dsl = serializeTransform(merged);
|
|
280
274
|
return `${getCdnBase()}/t/${dsl}/${asset.sha}.m3u8`;
|
|
@@ -286,9 +280,21 @@ function buildTransformUrl(asset, opts) {
|
|
|
286
280
|
return `${getCdnBase()}/t/${dsl}/${asset.sha}.${ext}`;
|
|
287
281
|
}
|
|
288
282
|
function getTransformUrl(asset, opts) {
|
|
283
|
+
assertSha(asset, "getTransformUrl");
|
|
284
|
+
assertPublic(
|
|
285
|
+
asset,
|
|
286
|
+
"getTransformUrl",
|
|
287
|
+
"getPrivateTransformUrl(asset, opts, signingKey, { expiresInSeconds: 300 })"
|
|
288
|
+
);
|
|
289
289
|
return buildTransformUrl(asset, opts);
|
|
290
290
|
}
|
|
291
291
|
function getSignedTransformUrl(asset, opts, signingKey) {
|
|
292
|
+
assertSha(asset, "getSignedTransformUrl");
|
|
293
|
+
assertPublic(
|
|
294
|
+
asset,
|
|
295
|
+
"getSignedTransformUrl",
|
|
296
|
+
"getPrivateTransformUrl(asset, opts, signingKey, { expiresInSeconds: 300 })"
|
|
297
|
+
);
|
|
292
298
|
const url = buildTransformUrl(asset, opts);
|
|
293
299
|
if (!url) return null;
|
|
294
300
|
return signTransformUrl(url, signingKey);
|
|
@@ -319,12 +325,114 @@ async function hmacSha256Hex(key, message) {
|
|
|
319
325
|
return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
320
326
|
}
|
|
321
327
|
function getTransformSrcSet(asset, widths, extraOpts = {}) {
|
|
328
|
+
assertSha(asset, "getTransformSrcSet");
|
|
329
|
+
assertPublic(
|
|
330
|
+
asset,
|
|
331
|
+
"getTransformSrcSet",
|
|
332
|
+
"getPrivateTransformUrl(asset, opts, signingKey, { expiresInSeconds: 300 }) per width"
|
|
333
|
+
);
|
|
322
334
|
return widths.map((w) => {
|
|
323
335
|
const url = buildTransformUrl(asset, { ...extraOpts, width: w });
|
|
324
336
|
return url ? `${url} ${w}w` : null;
|
|
325
337
|
}).filter((s) => s != null).join(", ");
|
|
326
338
|
}
|
|
327
339
|
|
|
340
|
+
// src/slots.ts
|
|
341
|
+
var DEFAULT_TTL_MS = 6e4;
|
|
342
|
+
var cache = /* @__PURE__ */ new Map();
|
|
343
|
+
var endpoint = "https://api.nitida.gofuture.space";
|
|
344
|
+
var apiKey = null;
|
|
345
|
+
var tenantCode = null;
|
|
346
|
+
function configureSlotResolver(opts) {
|
|
347
|
+
if (opts.endpoint) endpoint = opts.endpoint.replace(/\/+$/, "");
|
|
348
|
+
if (opts.apiKey !== void 0) apiKey = opts.apiKey;
|
|
349
|
+
if (opts.tenantCode !== void 0) tenantCode = opts.tenantCode;
|
|
350
|
+
}
|
|
351
|
+
function invalidateSlotCache(slotKey) {
|
|
352
|
+
if (slotKey === void 0) cache.clear();
|
|
353
|
+
else
|
|
354
|
+
for (const k of cache.keys())
|
|
355
|
+
if (k.endsWith(`:${slotKey}`)) cache.delete(k);
|
|
356
|
+
}
|
|
357
|
+
var baseHeaders = () => {
|
|
358
|
+
const h = {};
|
|
359
|
+
if (apiKey) h.Authorization = `Bearer ${apiKey}`;
|
|
360
|
+
if (tenantCode) h["X-Tenant-Code"] = tenantCode;
|
|
361
|
+
return h;
|
|
362
|
+
};
|
|
363
|
+
async function fetchSlot(slotKey) {
|
|
364
|
+
const r = await fetch(`${endpoint}/slots/${encodeURIComponent(slotKey)}`, {
|
|
365
|
+
headers: baseHeaders()
|
|
366
|
+
});
|
|
367
|
+
if (r.status === 404) return null;
|
|
368
|
+
if (!r.ok) throw new Error(`slot fetch ${r.status}: ${await r.text()}`);
|
|
369
|
+
return await r.json();
|
|
370
|
+
}
|
|
371
|
+
async function fetchSlotsBulk(slotKeys) {
|
|
372
|
+
if (slotKeys.length === 0) return {};
|
|
373
|
+
const r = await fetch(`${endpoint}/slots/resolve`, {
|
|
374
|
+
method: "POST",
|
|
375
|
+
headers: { ...baseHeaders(), "Content-Type": "application/json" },
|
|
376
|
+
body: JSON.stringify({ keys: slotKeys })
|
|
377
|
+
});
|
|
378
|
+
if (!r.ok) throw new Error(`slots resolve ${r.status}: ${await r.text()}`);
|
|
379
|
+
const body = await r.json();
|
|
380
|
+
return body.resolved;
|
|
381
|
+
}
|
|
382
|
+
async function resolveSlot(slotKey, opts = {}) {
|
|
383
|
+
const ttl = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
384
|
+
const cacheKey = `${tenantCode ?? "_"}:${slotKey}`;
|
|
385
|
+
const now = Date.now();
|
|
386
|
+
let dto;
|
|
387
|
+
const hit = cache.get(cacheKey);
|
|
388
|
+
if (hit && now - hit.fetchedAt < ttl) {
|
|
389
|
+
dto = hit.value;
|
|
390
|
+
} else {
|
|
391
|
+
dto = await fetchSlot(slotKey);
|
|
392
|
+
cache.set(cacheKey, { fetchedAt: now, value: dto });
|
|
393
|
+
}
|
|
394
|
+
return materializeResolution(dto, opts.preset);
|
|
395
|
+
}
|
|
396
|
+
async function resolveSlots(slotKeys, opts = {}) {
|
|
397
|
+
if (slotKeys.length === 0) return {};
|
|
398
|
+
const ttl = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
399
|
+
const now = Date.now();
|
|
400
|
+
const missing = [];
|
|
401
|
+
const out = {};
|
|
402
|
+
for (const k of slotKeys) {
|
|
403
|
+
const cacheKey = `${tenantCode ?? "_"}:${k}`;
|
|
404
|
+
const hit = cache.get(cacheKey);
|
|
405
|
+
if (hit && now - hit.fetchedAt < ttl) {
|
|
406
|
+
out[k] = materializeResolution(hit.value, opts.preset);
|
|
407
|
+
} else {
|
|
408
|
+
missing.push(k);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (missing.length > 0) {
|
|
412
|
+
const resolved = await fetchSlotsBulk(missing);
|
|
413
|
+
for (const k of missing) {
|
|
414
|
+
const dto = resolved[k] ?? null;
|
|
415
|
+
cache.set(`${tenantCode ?? "_"}:${k}`, { fetchedAt: now, value: dto });
|
|
416
|
+
out[k] = materializeResolution(dto, opts.preset);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
function defaultPresetFor(asset) {
|
|
422
|
+
if (!asset) return "lg";
|
|
423
|
+
return asset.kind === "video" ? "video" : "lg";
|
|
424
|
+
}
|
|
425
|
+
function materializeResolution(dto, overridePreset) {
|
|
426
|
+
if (!dto) return { slot: null, preset: overridePreset ?? "lg", url: null };
|
|
427
|
+
const effective = overridePreset ?? dto.preset ?? defaultPresetFor(dto.asset);
|
|
428
|
+
const finalPreset = hasPreset(dto.asset, effective) ? effective : defaultPresetFor(dto.asset);
|
|
429
|
+
return {
|
|
430
|
+
slot: dto,
|
|
431
|
+
preset: finalPreset,
|
|
432
|
+
url: getAssetUrl(dto.asset, finalPreset)
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
328
436
|
// src/index.ts
|
|
329
437
|
var PRESET_SHORT = {
|
|
330
438
|
thumb: "q",
|
|
@@ -451,6 +559,25 @@ function originalExtForMime(mime) {
|
|
|
451
559
|
return (mime ? ORIGINAL_EXT_BY_MIME[mime] : void 0) ?? PRESET_EXT.original;
|
|
452
560
|
}
|
|
453
561
|
function getAssetUrl(asset, preset) {
|
|
562
|
+
assertSha(asset, "getAssetUrl");
|
|
563
|
+
assertPublic(
|
|
564
|
+
asset,
|
|
565
|
+
"getAssetUrl",
|
|
566
|
+
`getPrivateAssetUrl(asset, "${preset}", signingKey, { expiresInSeconds: 300 })`
|
|
567
|
+
);
|
|
568
|
+
const fallback = transformFallbackFor(asset, preset);
|
|
569
|
+
if (fallback) return fallback;
|
|
570
|
+
return buildPublicAssetUrl(asset, preset);
|
|
571
|
+
}
|
|
572
|
+
function transformFallbackFor(asset, preset) {
|
|
573
|
+
if (typeof asset.presets !== "string") return null;
|
|
574
|
+
if (hasPreset({ presets: asset.presets }, preset)) return null;
|
|
575
|
+
const maxDim = PRESET_MAX_DIM[preset];
|
|
576
|
+
if (maxDim == null) return null;
|
|
577
|
+
return `${cdnBaseUrl}/t/format=webp,width=${maxDim}/${asset.sha}.webp`;
|
|
578
|
+
}
|
|
579
|
+
function buildPublicAssetUrl(asset, preset) {
|
|
580
|
+
assertSha(asset, "getPrivateAssetUrl");
|
|
454
581
|
if (preset === "original") {
|
|
455
582
|
const stored = asset.variants?.find((v) => v.preset === "original")?.url;
|
|
456
583
|
if (stored) return stored;
|
|
@@ -464,6 +591,25 @@ function getAssetUrl(asset, preset) {
|
|
|
464
591
|
}
|
|
465
592
|
return `${cdnBaseUrl}/${variantPrefix()}${asset.sha}-${PRESET_SHORT[preset]}.${PRESET_EXT[preset]}`;
|
|
466
593
|
}
|
|
594
|
+
async function getPrivateAssetUrl(asset, preset, signingKey, opts) {
|
|
595
|
+
return signAccessUrl(buildPublicAssetUrl(asset, preset), signingKey, opts);
|
|
596
|
+
}
|
|
597
|
+
async function getPrivateTransformUrl(asset, opts, signingKey, signOpts) {
|
|
598
|
+
const url = buildTransformUrl(asset, opts);
|
|
599
|
+
if (!url) return null;
|
|
600
|
+
const tid = getTenantId();
|
|
601
|
+
if (tid == null) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
"getPrivateTransformUrl: 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."
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
const u = new URL(url);
|
|
607
|
+
return signAccessUrl(
|
|
608
|
+
`${u.origin}/${tid.toString(36)}${u.pathname}`,
|
|
609
|
+
signingKey,
|
|
610
|
+
signOpts
|
|
611
|
+
);
|
|
612
|
+
}
|
|
467
613
|
function hasPreset(asset, preset) {
|
|
468
614
|
if (preset === "mp3") return asset.presets.includes("mp3");
|
|
469
615
|
return stripMultiCharTokens(asset.presets).includes(PRESET_SHORT[preset]);
|
|
@@ -473,6 +619,12 @@ function stripMultiCharTokens(presets) {
|
|
|
473
619
|
}
|
|
474
620
|
var IMAGE_PRESETS = ["thumb", "sm", "md", "lg", "xl"];
|
|
475
621
|
function getAssetSrcSet(asset) {
|
|
622
|
+
assertSha(asset, "getAssetSrcSet");
|
|
623
|
+
assertPublic(
|
|
624
|
+
asset,
|
|
625
|
+
"getAssetSrcSet",
|
|
626
|
+
"getPrivateAssetUrl(asset, preset, signingKey, { expiresInSeconds: 300 }) per preset"
|
|
627
|
+
);
|
|
476
628
|
return IMAGE_PRESETS.filter(
|
|
477
629
|
(p) => hasPreset(asset, p) && PRESET_MAX_DIM[p] != null
|
|
478
630
|
).map((p) => `${getAssetUrl(asset, p)} ${PRESET_MAX_DIM[p]}w`).join(", ");
|
|
@@ -498,10 +650,14 @@ export {
|
|
|
498
650
|
PRESET_MAX_DIM,
|
|
499
651
|
PRESET_SHORT,
|
|
500
652
|
TRANSFORM_WIDTHS,
|
|
653
|
+
accessMessage,
|
|
654
|
+
assertPublic,
|
|
655
|
+
assertSha,
|
|
501
656
|
bestTextContrast,
|
|
502
657
|
computeVariantDimensions,
|
|
503
658
|
configureSlotResolver,
|
|
504
659
|
contrastRatio,
|
|
660
|
+
deriveAccessKey,
|
|
505
661
|
extractAssetSha,
|
|
506
662
|
getAmbientGradient,
|
|
507
663
|
getAssetDimensions,
|
|
@@ -512,6 +668,8 @@ export {
|
|
|
512
668
|
getHlsStreamingUrl,
|
|
513
669
|
getPaletteBlurBackground,
|
|
514
670
|
getPaletteCssVars,
|
|
671
|
+
getPrivateAssetUrl,
|
|
672
|
+
getPrivateTransformUrl,
|
|
515
673
|
getSignedTransformUrl,
|
|
516
674
|
getTenantId,
|
|
517
675
|
getTextColorForBackground,
|
|
@@ -529,6 +687,7 @@ export {
|
|
|
529
687
|
serializeTransform,
|
|
530
688
|
setCdnBase,
|
|
531
689
|
setTenantId,
|
|
690
|
+
signAccessUrl,
|
|
532
691
|
signTransformUrl
|
|
533
692
|
};
|
|
534
693
|
//# sourceMappingURL=index.js.map
|