"use strict";exports.__esModule=true;exports.imageOptimizer=imageOptimizer;exports.detectContentType=detectContentType;exports.getMaxAge=getMaxAge;var _accept=require("@hapi/accept");var _crypto=require("crypto");var _fs=require("fs");var _getOrientation=require("get-orientation");var _isAnimated=_interopRequireDefault(require("next/dist/compiled/is-animated"));var _path=require("path");var _stream=_interopRequireDefault(require("stream"));var _url=_interopRequireDefault(require("url"));var _fileExists=require("../lib/file-exists");var _imageConfig=require("./image-config");var _main=require("./lib/squoosh/main");var _sendPayload=require("./send-payload");var _serveStatic=require("./serve-static");function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}// @ts-ignore no types for is-animated
//const AVIF = 'image/avif'
const WEBP='image/webp';const PNG='image/png';const JPEG='image/jpeg';const GIF='image/gif';const SVG='image/svg+xml';const CACHE_VERSION=3;const MODERN_TYPES=[/* AVIF, */WEBP];const ANIMATABLE_TYPES=[WEBP,PNG,GIF];const VECTOR_TYPES=[SVG];const inflightRequests=new Map();async function imageOptimizer(server,req,res,parsedUrl,nextConfig,distDir,isDev=false){const imageData=nextConfig.images||_imageConfig.imageConfigDefault;const{deviceSizes=[],imageSizes=[],domains=[],loader}=imageData;if(loader!=='default'){await server.render404(req,res,parsedUrl);return{finished:true};}const{headers}=req;const{url,w,q}=parsedUrl.query;const mimeType=getSupportedMimeType(MODERN_TYPES,headers.accept);let href;if(!url){res.statusCode=400;res.end('"url" parameter is required');return{finished:true};}else if(Array.isArray(url)){res.statusCode=400;res.end('"url" parameter cannot be an array');return{finished:true};}let isAbsolute;if(url.startsWith('/')){href=url;isAbsolute=false;}else{let hrefParsed;try{hrefParsed=new URL(url);href=hrefParsed.toString();isAbsolute=true;}catch(_error){res.statusCode=400;res.end('"url" parameter is invalid');return{finished:true};}if(!['http:','https:'].includes(hrefParsed.protocol)){res.statusCode=400;res.end('"url" parameter is invalid');return{finished:true};}if(!domains.includes(hrefParsed.hostname)){res.statusCode=400;res.end('"url" parameter is not allowed');return{finished:true};}}if(!w){res.statusCode=400;res.end('"w" parameter (width) is required');return{finished:true};}else if(Array.isArray(w)){res.statusCode=400;res.end('"w" parameter (width) cannot be an array');return{finished:true};}if(!q){res.statusCode=400;res.end('"q" parameter (quality) is required');return{finished:true};}else if(Array.isArray(q)){res.statusCode=400;res.end('"q" parameter (quality) cannot be an array');return{finished:true};}// Should match output from next-image-loader
const isStatic=url.startsWith('/_next/static/image');const width=parseInt(w,10);if(!width||isNaN(width)){res.statusCode=400;res.end('"w" parameter (width) must be a number greater than 0');return{finished:true};}const sizes=[...deviceSizes,...imageSizes];if(!sizes.includes(width)){res.statusCode=400;res.end(`"w" parameter (width) of ${width} is not allowed`);return{finished:true};}const quality=parseInt(q);if(isNaN(quality)||quality<1||quality>100){res.statusCode=400;res.end('"q" parameter (quality) must be a number between 1 and 100');return{finished:true};}const hash=getHash([CACHE_VERSION,href,width,quality,mimeType]);const imagesDir=(0,_path.join)(distDir,'cache','images');const hashDir=(0,_path.join)(imagesDir,hash);const now=Date.now();// If there're concurrent requests hitting the same resource and it's still
// being optimized, wait before accessing the cache.
if(inflightRequests.has(hash)){await inflightRequests.get(hash);}let dedupeResolver;inflightRequests.set(hash,new Promise(resolve=>dedupeResolver=resolve));try{var _upstreamType;if(await(0,_fileExists.fileExists)(hashDir,'directory')){const files=await _fs.promises.readdir(hashDir);for(let file of files){const[maxAgeStr,expireAtSt,etag,extension]=file.split('.');const maxAge=Number(maxAgeStr);const expireAt=Number(expireAtSt);const contentType=(0,_serveStatic.getContentType)(extension);const fsPath=(0,_path.join)(hashDir,file);if(now<expireAt){const result=setResponseHeaders(req,res,etag,maxAge,contentType,isStatic,isDev);if(!result.finished){(0,_fs.createReadStream)(fsPath).pipe(res);}return{finished:true};}else{await _fs.promises.unlink(fsPath);}}}let upstreamBuffer;let upstreamType;let maxAge;if(isAbsolute){const upstreamRes=await fetch(href);if(!upstreamRes.ok){res.statusCode=upstreamRes.status;res.end('"url" parameter is valid but upstream response is invalid');return{finished:true};}res.statusCode=upstreamRes.status;upstreamBuffer=Buffer.from(await upstreamRes.arrayBuffer());upstreamType=detectContentType(upstreamBuffer)||upstreamRes.headers.get('Content-Type');maxAge=getMaxAge(upstreamRes.headers.get('Cache-Control'));}else{try{const resBuffers=[];const mockRes=new _stream.default.Writable();const isStreamFinished=new Promise(function(resolve,reject){mockRes.on('finish',()=>resolve(true));mockRes.on('end',()=>resolve(true));mockRes.on('error',()=>reject());});mockRes.write=chunk=>{resBuffers.push(Buffer.isBuffer(chunk)?chunk:Buffer.from(chunk));};mockRes._write=chunk=>{mockRes.write(chunk);};const mockHeaders={};mockRes.writeHead=(_status,_headers)=>Object.assign(mockHeaders,_headers);mockRes.getHeader=name=>mockHeaders[name.toLowerCase()];mockRes.getHeaders=()=>mockHeaders;mockRes.getHeaderNames=()=>Object.keys(mockHeaders);mockRes.setHeader=(name,value)=>mockHeaders[name.toLowerCase()]=value;mockRes._implicitHeader=()=>{};mockRes.finished=false;mockRes.statusCode=200;const mockReq=new _stream.default.Readable();mockReq._read=()=>{mockReq.emit('end');mockReq.emit('close');return Buffer.from('');};mockReq.headers=req.headers;mockReq.method=req.method;mockReq.url=href;await server.getRequestHandler()(mockReq,mockRes,_url.default.parse(href,true));await isStreamFinished;res.statusCode=mockRes.statusCode;upstreamBuffer=Buffer.concat(resBuffers);upstreamType=detectContentType(upstreamBuffer)||mockRes.getHeader('Content-Type');maxAge=getMaxAge(mockRes.getHeader('Cache-Control'));}catch(err){res.statusCode=500;res.end('"url" parameter is valid but upstream response is invalid');return{finished:true};}}const expireAt=maxAge*1000+now;if(upstreamType){const vector=VECTOR_TYPES.includes(upstreamType);const animate=ANIMATABLE_TYPES.includes(upstreamType)&&(0,_isAnimated.default)(upstreamBuffer);if(vector||animate){await writeToCacheDir(hashDir,upstreamType,maxAge,expireAt,upstreamBuffer);sendResponse(req,res,maxAge,upstreamType,upstreamBuffer,isStatic,isDev);return{finished:true};}if(!upstreamType.startsWith('image/')){res.statusCode=400;res.end("The requested resource isn't a valid image.");return{finished:true};}}let contentType;if(mimeType){contentType=mimeType;}else if((_upstreamType=upstreamType)!=null&&_upstreamType.startsWith('image/')&&(0,_serveStatic.getExtension)(upstreamType)){contentType=upstreamType;}else{contentType=JPEG;}try{const orientation=await(0,_getOrientation.getOrientation)(upstreamBuffer);const operations=[];if(orientation===_getOrientation.Orientation.RIGHT_TOP){operations.push({type:'rotate',numRotations:1});}else if(orientation===_getOrientation.Orientation.BOTTOM_RIGHT){operations.push({type:'rotate',numRotations:2});}else if(orientation===_getOrientation.Orientation.LEFT_BOTTOM){operations.push({type:'rotate',numRotations:3});}else{// TODO: support more orientations
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// const _: never = orientation
}operations.push({type:'resize',width});let optimizedBuffer;//if (contentType === AVIF) {
//} else
if(contentType===WEBP){optimizedBuffer=await(0,_main.processBuffer)(upstreamBuffer,operations,'webp',quality);}else if(contentType===PNG){optimizedBuffer=await(0,_main.processBuffer)(upstreamBuffer,operations,'png',quality);}else if(contentType===JPEG){optimizedBuffer=await(0,_main.processBuffer)(upstreamBuffer,operations,'jpeg',quality);}if(optimizedBuffer){await writeToCacheDir(hashDir,contentType,maxAge,expireAt,optimizedBuffer);sendResponse(req,res,maxAge,contentType,optimizedBuffer,isStatic,isDev);}else{throw new Error('Unable to optimize buffer');}}catch(error){sendResponse(req,res,maxAge,upstreamType,upstreamBuffer,isStatic,isDev);}return{finished:true};}finally{// Make sure to remove the hash in the end.
dedupeResolver();inflightRequests.delete(hash);}}async function writeToCacheDir(dir,contentType,maxAge,expireAt,buffer){await _fs.promises.mkdir(dir,{recursive:true});const extension=(0,_serveStatic.getExtension)(contentType);const etag=getHash([buffer]);const filename=(0,_path.join)(dir,`${maxAge}.${expireAt}.${etag}.${extension}`);await _fs.promises.writeFile(filename,buffer);}function setResponseHeaders(req,res,etag,maxAge,contentType,isStatic,isDev){res.setHeader('Vary','Accept');res.setHeader('Cache-Control',isStatic?'public, max-age=315360000, immutable':`public, max-age=${isDev?0:maxAge}, must-revalidate`);if((0,_sendPayload.sendEtagResponse)(req,res,etag)){// already called res.end() so we're finished
return{finished:true};}if(contentType){res.setHeader('Content-Type',contentType);}return{finished:false};}function sendResponse(req,res,maxAge,contentType,buffer,isStatic,isDev){const etag=getHash([buffer]);const result=setResponseHeaders(req,res,etag,maxAge,contentType,isStatic,isDev);if(!result.finished){res.end(buffer);}}function getSupportedMimeType(options,accept=''){const mimeType=(0,_accept.mediaType)(accept,options);return accept.includes(mimeType)?mimeType:'';}function getHash(items){const hash=(0,_crypto.createHash)('sha256');for(let item of items){if(typeof item==='number')hash.update(String(item));else{hash.update(item);}}// See https://en.wikipedia.org/wiki/Base64#Filenames
return hash.digest('base64').replace(/\//g,'-');}function parseCacheControl(str){const map=new Map();if(!str){return map;}for(let directive of str.split(',')){let[key,value]=directive.trim().split('=');key=key.toLowerCase();if(value){value=value.toLowerCase();}map.set(key,value);}return map;}/**
 * Inspects the first few bytes of a buffer to determine if
 * it matches the "magic number" of known file signatures.
 * https://en.wikipedia.org/wiki/List_of_file_signatures
 */function detectContentType(buffer){if([0xff,0xd8,0xff].every((b,i)=>buffer[i]===b)){return JPEG;}if([0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a].every((b,i)=>buffer[i]===b)){return PNG;}if([0x47,0x49,0x46,0x38].every((b,i)=>buffer[i]===b)){return GIF;}if([0x52,0x49,0x46,0x46,0,0,0,0,0x57,0x45,0x42,0x50].every((b,i)=>!b||buffer[i]===b)){return WEBP;}if([0x3c,0x3f,0x78,0x6d,0x6c].every((b,i)=>buffer[i]===b)){return SVG;}return null;}function getMaxAge(str){const minimum=60;const map=parseCacheControl(str);if(map){let age=map.get('s-maxage')||map.get('max-age')||'';if(age.startsWith('"')&&age.endsWith('"')){age=age.slice(1,-1);}const n=parseInt(age,10);if(!isNaN(n)){return Math.max(n,minimum);}}return minimum;}
//# sourceMappingURL=image-optimizer.js.map