feat(cms-images): forward dpr + format=auto to RustyCMS
- Accept dpr query param (clamped 1..4) and include it in cache key so each density gets its own cached file. - Accept format=auto and negotiate locally from the client's Accept header (avif > webp > jpeg) before building the cache key — concrete formats in the cache avoid cross-client leaks.
This commit is contained in:
@@ -29,6 +29,7 @@ export interface ImageTransformParams {
|
|||||||
ar?: string;
|
ar?: string;
|
||||||
fx?: number;
|
fx?: number;
|
||||||
fy?: number;
|
fy?: number;
|
||||||
|
dpr?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const memoryCache = new Map<string, CachedImage>();
|
const memoryCache = new Map<string, CachedImage>();
|
||||||
@@ -60,6 +61,7 @@ export function buildCacheKey(src: string, params: ImageTransformParams): string
|
|||||||
if (params.ar) parts.push(`ar=${params.ar}`);
|
if (params.ar) parts.push(`ar=${params.ar}`);
|
||||||
if (params.fx != null) parts.push(`fx=${roundFocal(params.fx)}`);
|
if (params.fx != null) parts.push(`fx=${roundFocal(params.fx)}`);
|
||||||
if (params.fy != null) parts.push(`fy=${roundFocal(params.fy)}`);
|
if (params.fy != null) parts.push(`fy=${roundFocal(params.fy)}`);
|
||||||
|
if (params.dpr != null) parts.push(`dpr=${roundFocal(params.dpr)}`);
|
||||||
return createHash('sha256').update(parts.join('|')).digest('hex').slice(0, 16);
|
return createHash('sha256').update(parts.join('|')).digest('hex').slice(0, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,19 @@ function resolveSourceUrl(src: string): string {
|
|||||||
return `${getCmsBaseUrl()}/api/assets/${encodeURIComponent(src)}`;
|
return `${getCmsBaseUrl()}/api/assets/${encodeURIComponent(src)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ url }) => {
|
/**
|
||||||
|
* Pick output format based on client Accept header.
|
||||||
|
* Priority: avif > webp > jpeg. Used when `format=auto` is requested so the
|
||||||
|
* cache key binds to a concrete format (no cross-client leaks).
|
||||||
|
*/
|
||||||
|
function negotiateFormat(accept: string | null): 'avif' | 'webp' | 'jpeg' {
|
||||||
|
const a = (accept ?? '').toLowerCase();
|
||||||
|
if (a.includes('image/avif')) return 'avif';
|
||||||
|
if (a.includes('image/webp')) return 'webp';
|
||||||
|
return 'jpeg';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ url, request }) => {
|
||||||
const src = url.searchParams.get('src');
|
const src = url.searchParams.get('src');
|
||||||
if (!src) {
|
if (!src) {
|
||||||
return new Response('Missing "src" parameter', { status: 400 });
|
return new Response('Missing "src" parameter', { status: 400 });
|
||||||
@@ -46,6 +58,7 @@ export const GET: RequestHandler = async ({ url }) => {
|
|||||||
const ar = url.searchParams.get('ar');
|
const ar = url.searchParams.get('ar');
|
||||||
const fx = url.searchParams.get('fx');
|
const fx = url.searchParams.get('fx');
|
||||||
const fy = url.searchParams.get('fy');
|
const fy = url.searchParams.get('fy');
|
||||||
|
const dpr = url.searchParams.get('dpr');
|
||||||
|
|
||||||
if (w) {
|
if (w) {
|
||||||
const n = Number(w);
|
const n = Number(w);
|
||||||
@@ -59,7 +72,12 @@ export const GET: RequestHandler = async ({ url }) => {
|
|||||||
const n = Number(q);
|
const n = Number(q);
|
||||||
if (Number.isFinite(n)) params.q = n;
|
if (Number.isFinite(n)) params.q = n;
|
||||||
}
|
}
|
||||||
if (format) params.format = format;
|
// format=auto is resolved locally so the cache key binds to a concrete format.
|
||||||
|
if (format === 'auto') {
|
||||||
|
params.format = negotiateFormat(request.headers.get('accept'));
|
||||||
|
} else if (format) {
|
||||||
|
params.format = format;
|
||||||
|
}
|
||||||
if (fit) params.fit = fit;
|
if (fit) params.fit = fit;
|
||||||
if (ar) params.ar = ar;
|
if (ar) params.ar = ar;
|
||||||
if (fx) {
|
if (fx) {
|
||||||
@@ -70,6 +88,10 @@ export const GET: RequestHandler = async ({ url }) => {
|
|||||||
const n = Number(fy);
|
const n = Number(fy);
|
||||||
if (Number.isFinite(n)) params.fy = n;
|
if (Number.isFinite(n)) params.fy = n;
|
||||||
}
|
}
|
||||||
|
if (dpr) {
|
||||||
|
const n = Number(dpr);
|
||||||
|
if (Number.isFinite(n)) params.dpr = Math.min(4, Math.max(1, n));
|
||||||
|
}
|
||||||
|
|
||||||
const key = buildCacheKey(src, params);
|
const key = buildCacheKey(src, params);
|
||||||
|
|
||||||
@@ -92,6 +114,7 @@ export const GET: RequestHandler = async ({ url }) => {
|
|||||||
if (params.ar) transformParams.set('ar', params.ar);
|
if (params.ar) transformParams.set('ar', params.ar);
|
||||||
if (params.fx != null) transformParams.set('fx', String(params.fx));
|
if (params.fx != null) transformParams.set('fx', String(params.fx));
|
||||||
if (params.fy != null) transformParams.set('fy', String(params.fy));
|
if (params.fy != null) transformParams.set('fy', String(params.fy));
|
||||||
|
if (params.dpr != null) transformParams.set('dpr', String(params.dpr));
|
||||||
|
|
||||||
const transformUrl = `${cmsBase}/api/transform?${transformParams.toString()}`;
|
const transformUrl = `${cmsBase}/api/transform?${transformParams.toString()}`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user