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;
|
||||
fx?: number;
|
||||
fy?: number;
|
||||
dpr?: number;
|
||||
}
|
||||
|
||||
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.fx != null) parts.push(`fx=${roundFocal(params.fx)}`);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,19 @@ function resolveSourceUrl(src: string): string {
|
||||
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');
|
||||
if (!src) {
|
||||
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 fx = url.searchParams.get('fx');
|
||||
const fy = url.searchParams.get('fy');
|
||||
const dpr = url.searchParams.get('dpr');
|
||||
|
||||
if (w) {
|
||||
const n = Number(w);
|
||||
@@ -59,7 +72,12 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
const n = Number(q);
|
||||
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 (ar) params.ar = ar;
|
||||
if (fx) {
|
||||
@@ -70,6 +88,10 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
const n = Number(fy);
|
||||
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);
|
||||
|
||||
@@ -92,6 +114,7 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
if (params.ar) transformParams.set('ar', params.ar);
|
||||
if (params.fx != null) transformParams.set('fx', String(params.fx));
|
||||
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()}`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user