pixel-serve-server 1.0.0 → 1.0.3
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/README.md +19 -18
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**A modern, type-safe middleware** for processing, resizing, and serving images in Node.js applications. Built with **TypeScript**, powered by **Sharp**, and designed for secure production use with ESM & CJS bundles.
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.npmjs.com/package/pixel-serve-server)
|
|
6
7
|
[](https://www.typescriptlang.org/)
|
|
7
8
|
[](https://nodejs.org/)
|
|
8
9
|
|
|
@@ -103,23 +104,23 @@ app.listen(3000);
|
|
|
103
104
|
|
|
104
105
|
## Configuration Options
|
|
105
106
|
|
|
106
|
-
| Option | Type | Default
|
|
107
|
-
| -------------------- | ----------------------------------------- |
|
|
108
|
-
| `baseDir` | `string` | **required**
|
|
109
|
-
| `idHandler` | `(id: string) => string` | `id => id`
|
|
110
|
-
| `getUserFolder` | `(req, id?) => string \| Promise<string>` | `undefined`
|
|
111
|
-
| `websiteURL` | `string` | `undefined`
|
|
112
|
-
| `apiRegex` | `RegExp` | `/^\/api\/v1\//`
|
|
113
|
-
| `allowedNetworkList` | `string[]` | `[]`
|
|
114
|
-
| `cacheControl` | `string` | `
|
|
115
|
-
| `etag` | `boolean` | `true`
|
|
116
|
-
| `minWidth` | `number` | `50`
|
|
117
|
-
| `maxWidth` | `number` | `4000`
|
|
118
|
-
| `minHeight` | `number` | `50`
|
|
119
|
-
| `maxHeight` | `number` | `4000`
|
|
120
|
-
| `defaultQuality` | `number` | `80`
|
|
121
|
-
| `requestTimeoutMs` | `number` | `5000`
|
|
122
|
-
| `maxDownloadBytes` | `number` | `5_000_000`
|
|
107
|
+
| Option | Type | Default | Description |
|
|
108
|
+
| -------------------- | ----------------------------------------- | ---------------- | ----------------------------------------------------------------------- |
|
|
109
|
+
| `baseDir` | `string` | **required** | Base directory for local images |
|
|
110
|
+
| `idHandler` | `(id: string) => string` | `id => id` | Transform user IDs before lookup |
|
|
111
|
+
| `getUserFolder` | `(req, id?) => string \| Promise<string>` | `undefined` | Resolve private folder path when `folder=private` |
|
|
112
|
+
| `websiteURL` | `string` | `undefined` | If set, internal URLs pointing to this host are treated as local assets |
|
|
113
|
+
| `apiRegex` | `RegExp` | `/^\/api\/v1\//` | Prefix stripped from internal URLs before lookup |
|
|
114
|
+
| `allowedNetworkList` | `string[]` | `[]` | Allowed remote hosts. Others immediately fall back |
|
|
115
|
+
| `cacheControl` | `string` | `undefined` | Cache-Control header value |
|
|
116
|
+
| `etag` | `boolean` | `true` | Emit ETag and honor If-None-Match |
|
|
117
|
+
| `minWidth` | `number` | `50` | Minimum accepted width |
|
|
118
|
+
| `maxWidth` | `number` | `4000` | Maximum accepted width |
|
|
119
|
+
| `minHeight` | `number` | `50` | Minimum accepted height |
|
|
120
|
+
| `maxHeight` | `number` | `4000` | Maximum accepted height |
|
|
121
|
+
| `defaultQuality` | `number` | `80` | Default JPEG/WebP/AVIF quality |
|
|
122
|
+
| `requestTimeoutMs` | `number` | `5000` | Network fetch timeout |
|
|
123
|
+
| `maxDownloadBytes` | `number` | `5_000_000` | Maximum remote download size |
|
|
123
124
|
|
|
124
125
|
## Query Parameters
|
|
125
126
|
|
|
@@ -253,7 +254,7 @@ const { registerServe } = require("pixel-serve-server");
|
|
|
253
254
|
## Requirements
|
|
254
255
|
|
|
255
256
|
- Node.js >= 18
|
|
256
|
-
- Express 5.x (
|
|
257
|
+
- Express 5.x (included as a dependency)
|
|
257
258
|
|
|
258
259
|
## Dependencies
|
|
259
260
|
|
package/dist/index.d.mts
CHANGED
|
@@ -37,7 +37,7 @@ type UserData = {
|
|
|
37
37
|
* @param {PixelServeOptions} options - The options object for image processing.
|
|
38
38
|
* @returns {function(Request, Response, NextFunction): Promise<void>} The middleware function.
|
|
39
39
|
*/
|
|
40
|
-
declare const registerServe: (options: PixelServeOptions) => (req: Request, res: Response, next: NextFunction) => Promise<void
|
|
40
|
+
declare const registerServe: (options: PixelServeOptions) => ((req: Request, res: Response, next: NextFunction) => Promise<void>);
|
|
41
41
|
|
|
42
42
|
declare const userDataSchema: z.ZodObject<{
|
|
43
43
|
src: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ type UserData = {
|
|
|
37
37
|
* @param {PixelServeOptions} options - The options object for image processing.
|
|
38
38
|
* @returns {function(Request, Response, NextFunction): Promise<void>} The middleware function.
|
|
39
39
|
*/
|
|
40
|
-
declare const registerServe: (options: PixelServeOptions) => (req: Request, res: Response, next: NextFunction) => Promise<void
|
|
40
|
+
declare const registerServe: (options: PixelServeOptions) => ((req: Request, res: Response, next: NextFunction) => Promise<void>);
|
|
41
41
|
|
|
42
42
|
declare const userDataSchema: z.ZodObject<{
|
|
43
43
|
src: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
'use strict';var
|
|
2
|
-
exports.isValidPath=
|
|
1
|
+
'use strict';var p=require('path'),crypto=require('crypto'),N=require('sharp'),d=require('fs/promises'),url=require('url'),$=require('axios'),zod=require('zod');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var p__default=/*#__PURE__*/_interopDefault(p);var N__default=/*#__PURE__*/_interopDefault(N);var d__namespace=/*#__PURE__*/_interopNamespace(d);var $__default=/*#__PURE__*/_interopDefault($);var j=()=>typeof document>"u"?new URL(`file:${__filename}`).href:document.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?document.currentScript.src:new URL("main.js",document.baseURI).href,f=j();var _=p__default.default.dirname(url.fileURLToPath(f)),D=e=>p__default.default.join(_,"assets",e),z=D("noimage.jpg"),k=D("noavatar.png"),u={normal:async()=>d.readFile(z),avatar:async()=>d.readFile(k)},U=/^\/api\/v1\//,x=["jpeg","jpg","png","webp","gif","tiff","avif","svg"],g={jpeg:"image/jpeg",jpg:"image/jpeg",png:"image/png",webp:"image/webp",gif:"image/gif",tiff:"image/tiff",avif:"image/avif",svg:"image/svg+xml"};var B=async(e,r)=>{try{if(!e||!r||r.includes("\0")||p__default.default.isAbsolute(r)||!/^[^\x00-\x1F]+$/.test(r))return !1;let i=p__default.default.resolve(e),o=p__default.default.resolve(i,r),[s,a]=await Promise.all([d__namespace.realpath(i),d__namespace.realpath(o)]);if(!(await d__namespace.stat(s)).isDirectory())return !1;let m=s+p__default.default.sep,c=(a+p__default.default.sep).startsWith(m)||a===s,h=p__default.default.relative(s,a);return !h.startsWith("..")&&!p__default.default.isAbsolute(h)&&c}catch{return false}},G=async(e,r="normal",{timeoutMs:i,maxBytes:o})=>{try{let s=await $__default.default.get(e,{responseType:"arraybuffer",timeout:i,maxContentLength:o,maxBodyLength:o,validateStatus:m=>m>=200&&m<300}),a=s.headers["content-type"]?.toLowerCase()?.split(";")[0]?.trim();return Object.values(g).includes(a??"")?Buffer.from(s.data):await u[r]()}catch{return await u[r]()}},b=async(e,r,i="normal",o)=>{if(!await B(r,e))return await u[i]();try{let a=p__default.default.resolve(r,e);return o&&(await d__namespace.stat(a)).size>o?await u[i]():await d__namespace.readFile(a)}catch{return await u[i]()}},C=(e,r,i,o="normal",s,a=[],{timeoutMs:n,maxBytes:m})=>{try{let l=new URL(e);if(i!==void 0&&[i,`www.${i}`].includes(l.hostname)){let w=l.pathname.replace(s,"");return b(w,r,o,m)}return a.includes(l.hostname)||a.includes(l.host)?["http:","https:"].includes(l.protocol)?G(e,o,{timeoutMs:n,maxBytes:m}):u[o]():u[o]()}catch{return b(e,r,o,m)}};var Q=zod.z.enum(x),V=zod.z.enum(["avatar","normal"]),T=zod.z.object({src:zod.z.string().min(1,"src is required").optional().default("/placeholder/noimage.jpg"),format:zod.z.string().optional().transform(e=>{let r=e?.toLowerCase();return r&&Q.options.includes(r)?r:void 0}).optional(),width:zod.z.union([zod.z.number(),zod.z.string()]).optional().transform(e=>e==null?void 0:Number(e)).pipe(zod.z.number().int().min(50,"width too small").max(4e3,"width too large").optional()),height:zod.z.union([zod.z.number(),zod.z.string()]).optional().transform(e=>e==null?void 0:Number(e)).pipe(zod.z.number().int().min(50,"height too small").max(4e3,"height too large").optional()),quality:zod.z.union([zod.z.number(),zod.z.string()]).optional().transform(e=>e==null?void 0:Number(e)).pipe(zod.z.number().int().min(1).max(100).default(80)),folder:zod.z.enum(["public","private"]).default("public"),type:V.default("normal"),userId:zod.z.union([zod.z.string(),zod.z.number()]).optional().transform(e=>e==null?void 0:String(e).trim()).pipe(zod.z.string().min(1,"userId cannot be empty").max(128,"userId too long").optional())}).strict(),R=zod.z.object({baseDir:zod.z.string().min(1,"baseDir is required"),idHandler:zod.z.custom(e=>typeof e=="function",{message:"idHandler must be a function"}).optional(),getUserFolder:zod.z.custom(e=>typeof e=="function",{message:"getUserFolder must be a function"}).optional(),websiteURL:zod.z.union([zod.z.url(),zod.z.string().regex(/^(?![-.])([\w]+[-.]?)*[\w]+$/)]).optional(),apiRegex:zod.z.instanceof(RegExp).default(U),allowedNetworkList:zod.z.array(zod.z.string()).default([]),cacheControl:zod.z.string().optional(),etag:zod.z.boolean().default(true),minWidth:zod.z.number().int().positive().default(50),maxWidth:zod.z.number().int().positive().default(4e3),minHeight:zod.z.number().int().positive().default(50),maxHeight:zod.z.number().int().positive().default(4e3),defaultQuality:zod.z.number().int().min(1).max(100).default(80),requestTimeoutMs:zod.z.number().int().positive().default(5e3),maxDownloadBytes:zod.z.number().int().positive().default(5e6)}).strict().refine(e=>e.minWidth<=e.maxWidth,{message:"minWidth must be less than or equal to maxWidth",path:["minWidth"]}).refine(e=>e.minHeight<=e.maxHeight,{message:"minHeight must be less than or equal to maxHeight",path:["minHeight"]});var E=e=>R.parse(e),O=(e,r)=>{let i=T.parse(e),o=(s,a,n)=>{if(s!==void 0)return Math.min(Math.max(s,a),n)};return {...i,width:o(i.width,r.minWidth,r.maxWidth),height:o(i.height,r.minHeight,r.maxHeight),quality:i.quality??r.defaultQuality,format:i.format??"jpeg"}};var X=async(e,r,i,o)=>{let s="normal";try{let a=E(o),n=O(e.query,{minWidth:a.minWidth,maxWidth:a.maxWidth,minHeight:a.minHeight,maxHeight:a.maxHeight,defaultQuality:a.defaultQuality});s=n.type??"normal";let m=a.baseDir,l;if(n.userId&&(l=a.idHandler?a.idHandler(n.userId):n.userId),n.folder==="private"&&a.getUserFolder){let F=Promise.resolve(a.getUserFolder(e,l)),q=new Promise((P,A)=>setTimeout(()=>A(new Error("getUserFolder timed out")),a.requestTimeoutMs));try{let P=await Promise.race([F,q]);P&&(m=P);}catch{}}let c=x.includes((n.format??"").toLowerCase())?n.format:"jpeg",w=await(async()=>n.src?n.src.startsWith("http://")||n.src.startsWith("https://")?C(n.src,m,a.websiteURL,n.type,a.apiRegex,a.allowedNetworkList,{timeoutMs:a.requestTimeoutMs,maxBytes:a.maxDownloadBytes}):b(n.src,m,n.type,a.maxDownloadBytes):u[n.type??"normal"]())(),I=N__default.default(w,{failOn:"truncated"});if(n.width||n.height){let F={width:n.width??void 0,height:n.height??void 0,fit:N__default.default.fit.cover,withoutEnlargement:!0};I=I.resize(F);}let v=await I.rotate().toFormat(c,{quality:n.quality}).toBuffer(),W=`${(n.src?p__default.default.basename(n.src,p__default.default.extname(n.src)):"image").replace(/["\\\x00-\x1F\x7F]/g,"_")}.${c}`,y=a.etag?`"${crypto.createHash("sha1").update(v).digest("hex")}"`:void 0;if(y&&e.headers["if-none-match"]===y){r.status(304).end();return}r.type(g[c]),r.setHeader("Content-Disposition",`inline; filename="${W}"`),r.setHeader("Cache-Control",a.cacheControl??"public, max-age=86400, stale-while-revalidate=604800"),y&&r.setHeader("ETag",y),r.setHeader("Content-Length",v.length.toString()),r.send(v);}catch{try{let n=await u[s==="avatar"?"avatar":"normal"]();r.type(g.jpeg),r.setHeader("Content-Disposition",'inline; filename="fallback.jpeg"'),r.setHeader("Cache-Control","public, max-age=60"),r.send(n);}catch(a){i(a);}}},J=e=>async(r,i,o)=>X(r,i,o,e),Y=J;
|
|
2
|
+
exports.isValidPath=B;exports.optionsSchema=R;exports.registerServe=Y;exports.userDataSchema=T;//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/tsup/assets/cjs_shims.js","../src/variables.ts","../src/functions.ts","../src/schema.ts","../src/renders.ts","../src/pixel.ts"],"names":["getImportMetaUrl","importMetaUrl","moduleDir","path","fileURLToPath","getAssetPath","filename","NOT_FOUND_IMAGE","NOT_FOUND_AVATAR","FALLBACKIMAGES","readFile","API_REGEX","allowedFormats","mimeTypes","isValidPath","basePath","specifiedPath","resolvedBase","resolvedPath","realBase","realPath","f","normalizedBase","isInside","relative","fetchFromNetwork","src","type","timeoutMs","maxBytes","response","axios","status","contentType","readLocalImage","filePath","baseDir","fetchImage","websiteURL","apiRegex","allowedNetworkList","url","localPath","imageFormatEnum","z","imageTypeEnum","userDataSchema","val","lower","value","optionsSchema","renderOptions","options","renderUserData","userData","bounds","parsed","clamp","min","max","serveImage","req","res","next","parsedOptions","parsedUserId","dir","outputFormat","imageBuffer","image","sharp","resizeOptions","processedImage","processedFileName","etag","createHash","fallback","fallbackError","registerServe","pixel_default"],"mappings":"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","file":"index.js","sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import type { ImageFormat } from \"./types\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\n\r\n/**\r\n * Get the directory path for the current module.\r\n * Uses import.meta.url for ESM (tsup provides shims for CJS compatibility).\r\n */\r\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\r\n\r\nconst getAssetPath = (filename: string): string => {\r\n return path.join(moduleDir, \"assets\", filename);\r\n};\r\n\r\nconst NOT_FOUND_IMAGE = getAssetPath(\"noimage.jpg\");\r\nconst NOT_FOUND_AVATAR = getAssetPath(\"noavatar.png\");\r\n\r\nexport const FALLBACKIMAGES: Record<\r\n \"normal\" | \"avatar\",\r\n () => Promise<Buffer>\r\n> = {\r\n normal: async (): Promise<Buffer> => readFile(NOT_FOUND_IMAGE),\r\n avatar: async (): Promise<Buffer> => readFile(NOT_FOUND_AVATAR),\r\n};\r\n\r\nexport const API_REGEX: RegExp = /^\\/api\\/v1\\//;\r\n\r\nexport const allowedFormats: ImageFormat[] = [\r\n \"jpeg\",\r\n \"jpg\",\r\n \"png\",\r\n \"webp\",\r\n \"gif\",\r\n \"tiff\",\r\n \"avif\",\r\n \"svg\",\r\n];\r\n\r\nexport const mimeTypes: Readonly<Record<string, string>> = {\r\n jpeg: \"image/jpeg\",\r\n jpg: \"image/jpeg\",\r\n png: \"image/png\",\r\n webp: \"image/webp\",\r\n gif: \"image/gif\",\r\n tiff: \"image/tiff\",\r\n avif: \"image/avif\",\r\n svg: \"image/svg+xml\",\r\n};\r\n","import path from \"node:path\";\r\nimport * as fs from \"node:fs/promises\";\r\nimport axios from \"axios\";\r\nimport { FALLBACKIMAGES, mimeTypes } from \"./variables\";\r\nimport type { ImageType } from \"./types\";\r\n\r\n/**\r\n * @typedef {(\"avatar\" | \"normal\")} ImageType\r\n * @description Defines the type of image being processed.\r\n */\r\n\r\n/**\r\n * Checks if a specified path is valid within a base path.\r\n *\r\n * @param {string} basePath - The base directory to resolve paths.\r\n * @param {string} specifiedPath - The path to check.\r\n * @returns {Promise<boolean>} True if the path is valid, false otherwise.\r\n */\r\nexport const isValidPath = async (\r\n basePath: string,\r\n specifiedPath: string\r\n): Promise<boolean> => {\r\n try {\r\n if (!basePath || !specifiedPath) return false;\r\n if (specifiedPath.includes(\"\\0\")) return false;\r\n if (path.isAbsolute(specifiedPath)) return false;\r\n if (!/^[^\\x00-\\x1F]+$/.test(specifiedPath)) return false;\r\n\r\n const resolvedBase = path.resolve(basePath);\r\n const resolvedPath = path.resolve(resolvedBase, specifiedPath);\r\n\r\n const [realBase, realPath] = await Promise.all([\r\n fs.realpath(resolvedBase),\r\n fs.realpath(resolvedPath),\r\n ]);\r\n\r\n const baseStats = await fs.stat(realBase);\r\n if (!baseStats.isDirectory()) return false;\r\n\r\n const normalizedBase = realBase + path.sep;\r\n const normalizedPath = realPath + path.sep;\r\n\r\n const isInside =\r\n normalizedPath.startsWith(normalizedBase) || realPath === realBase;\r\n\r\n const relative = path.relative(realBase, realPath);\r\n return !relative.startsWith(\"..\") && !path.isAbsolute(relative) && isInside;\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\n/**\r\n * Fetches an image from a network source.\r\n *\r\n * @param {string} src - The URL of the image.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image in case of an error.\r\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\r\n */\r\nconst fetchFromNetwork = async (\r\n src: string,\r\n type: ImageType = \"normal\",\r\n {\r\n timeoutMs,\r\n maxBytes,\r\n }: {\r\n timeoutMs: number;\r\n maxBytes: number;\r\n }\r\n): Promise<Buffer> => {\r\n try {\r\n const response = await axios.get(src, {\r\n responseType: \"arraybuffer\",\r\n timeout: timeoutMs,\r\n maxContentLength: maxBytes,\r\n maxBodyLength: maxBytes,\r\n validateStatus: (status) => status >= 200 && status < 300,\r\n });\r\n\r\n const contentType = response.headers[\"content-type\"]?.toLowerCase();\r\n const allowedMimeTypes = Object.values(mimeTypes);\r\n\r\n if (allowedMimeTypes.includes(contentType ?? \"\")) {\r\n return Buffer.from(response.data);\r\n }\r\n return await FALLBACKIMAGES[type]();\r\n } catch (error) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n};\r\n\r\n/**\r\n * Reads an image from the local file system.\r\n *\r\n * @param {string} filePath - Path to the image file.\r\n * @param {string} baseDir - Base directory to resolve paths.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\r\n * @returns {Promise<Buffer>} A buffer containing the image data.\r\n */\r\nexport const readLocalImage = async (\r\n filePath: string,\r\n baseDir: string,\r\n type: ImageType = \"normal\"\r\n) => {\r\n const isValid = await isValidPath(baseDir, filePath);\r\n if (!isValid) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n try {\r\n return await fs.readFile(path.resolve(baseDir, filePath));\r\n } catch (error) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n};\r\n\r\n/**\r\n * Fetches an image from either a local file or a network source.\r\n *\r\n * @param {string} src - The URL or local path of the image.\r\n * @param {string} baseDir - Base directory to resolve local paths.\r\n * @param {string} websiteURL - The URL of the website.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\r\n * @param {string[]} [allowedNetworkList=[]] - List of allowed network hosts.\r\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\r\n */\r\nexport const fetchImage = (\r\n src: string,\r\n baseDir: string,\r\n websiteURL: string | undefined,\r\n type: ImageType = \"normal\",\r\n apiRegex: RegExp,\r\n allowedNetworkList: string[] = [],\r\n {\r\n timeoutMs,\r\n maxBytes,\r\n }: {\r\n timeoutMs: number;\r\n maxBytes: number;\r\n }\r\n): Promise<Buffer> => {\r\n try {\r\n const url = new URL(src);\r\n const isInternal =\r\n websiteURL !== undefined &&\r\n [websiteURL, `www.${websiteURL}`].includes(url.host);\r\n\r\n if (isInternal) {\r\n const localPath = url.pathname.replace(apiRegex, \"\");\r\n return readLocalImage(localPath, baseDir, type);\r\n }\r\n\r\n const allowedCondition = allowedNetworkList.includes(url.host);\r\n if (!allowedCondition) {\r\n return FALLBACKIMAGES[type]();\r\n }\r\n if (![\"http:\", \"https:\"].includes(url.protocol)) {\r\n return FALLBACKIMAGES[type]();\r\n }\r\n return fetchFromNetwork(src, type, { timeoutMs, maxBytes });\r\n } catch {\r\n return readLocalImage(src, baseDir, type);\r\n }\r\n};\r\n","import { z } from \"zod\";\r\nimport { API_REGEX, allowedFormats } from \"./variables\";\r\n\r\nconst imageFormatEnum = z.enum(allowedFormats as [string, ...string[]]);\r\nconst imageTypeEnum = z.enum([\"avatar\", \"normal\"]);\r\n\r\nexport const userDataSchema = z\r\n .object({\r\n src: z\r\n .string()\r\n .min(1, \"src is required\")\r\n .optional()\r\n .default(\"/placeholder/noimage.jpg\"),\r\n format: z\r\n .string()\r\n .optional()\r\n .transform((val) => {\r\n const lower = val?.toLowerCase();\r\n return lower && imageFormatEnum.options.includes(lower as string)\r\n ? (lower as (typeof imageFormatEnum)[\"options\"][number])\r\n : undefined;\r\n })\r\n .optional(),\r\n width: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(\r\n z\r\n .number()\r\n .int()\r\n .min(50, \"width too small\")\r\n .max(4000, \"width too large\")\r\n .optional()\r\n ),\r\n height: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(\r\n z\r\n .number()\r\n .int()\r\n .min(50, \"height too small\")\r\n .max(4000, \"height too large\")\r\n .optional()\r\n ),\r\n quality: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(z.number().int().min(1).max(100).default(80)),\r\n folder: z.enum([\"public\", \"private\"]).default(\"public\"),\r\n type: imageTypeEnum.default(\"normal\"),\r\n userId: z\r\n .union([z.string(), z.number()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : String(value).trim()\r\n )\r\n .pipe(\r\n z\r\n .string()\r\n .min(1, \"userId cannot be empty\")\r\n .max(128, \"userId too long\")\r\n .optional()\r\n ),\r\n })\r\n .strict();\r\n\r\nexport const optionsSchema = z\r\n .object({\r\n baseDir: z.string().min(1, \"baseDir is required\"),\r\n idHandler: z\r\n .custom<\r\n (id: string) => string\r\n >((val) => typeof val === \"function\", { message: \"idHandler must be a function\" })\r\n .optional(),\r\n getUserFolder: z\r\n .custom<\r\n (req: unknown, id?: string) => Promise<string> | string\r\n >((val) => typeof val === \"function\", { message: \"getUserFolder must be a function\" })\r\n .optional(),\r\n websiteURL: z.union([z.url(), z.string().regex(/^[\\w.-]+$/)]).optional(),\r\n apiRegex: z.instanceof(RegExp).default(API_REGEX),\r\n allowedNetworkList: z.array(z.string()).default([]),\r\n cacheControl: z.string().optional(),\r\n etag: z.boolean().default(true),\r\n minWidth: z.number().int().positive().default(50),\r\n maxWidth: z.number().int().positive().default(4000),\r\n minHeight: z.number().int().positive().default(50),\r\n maxHeight: z.number().int().positive().default(4000),\r\n defaultQuality: z.number().int().min(1).max(100).default(80),\r\n requestTimeoutMs: z.number().int().positive().default(5000),\r\n maxDownloadBytes: z.number().int().positive().default(5_000_000),\r\n })\r\n .strict();\r\n\r\nexport type ParsedUserData = z.infer<typeof userDataSchema>;\r\nexport type ParsedOptions = z.infer<typeof optionsSchema>;\r\n","import { optionsSchema, userDataSchema } from \"./schema\";\r\nimport type { ParsedOptions, ParsedUserData } from \"./schema\";\r\nimport type { PixelServeOptions, UserData } from \"./types\";\r\n\r\n/**\r\n * @typedef {(\"avatar\" | \"normal\")} ImageType\r\n * @description Defines the type of image being processed.\r\n */\r\n\r\n/**\r\n * @typedef {(\"jpeg\" | \"jpg\" | \"png\" | \"webp\" | \"gif\" | \"tiff\" | \"avif\" | \"svg\")} ImageFormat\r\n * @description Supported formats for image processing.\r\n */\r\n\r\n/**\r\n * @typedef {Object} Options\r\n * @property {string} baseDir - The base directory for public image files.\r\n * @property {function(string): string} idHandler - A function to handle user IDs.\r\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\r\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\r\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\r\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\r\n */\r\n\r\n/**\r\n * @typedef {Object} UserData\r\n * @property {number|string} quality - Quality of the image (1–100).\r\n * @property {ImageFormat} format - Desired format of the image.\r\n * @property {string} [src] - Source path or URL for the image.\r\n * @property {string} [folder] - The folder type (\"public\" or \"private\").\r\n * @property {ImageType} [type] - Type of the image (\"avatar\" or \"normal\").\r\n * @property {string|null} [userId] - Optional user identifier.\r\n * @property {number|string} [width] - Desired image width.\r\n * @property {number|string} [height] - Desired image height.\r\n */\r\n\r\n/**\r\n * Renders the options object with default values and user-provided values.\r\n *\r\n * @param {Partial<Options>} options - The user-provided options.\r\n * @returns {Options} The rendered options object.\r\n */\r\nexport const renderOptions = (options: PixelServeOptions): ParsedOptions =>\r\n optionsSchema.parse(options);\r\n\r\n/**\r\n * Renders the user data object with default values and user-provided values.\r\n *\r\n * @param {Partial<UserData>} userData - The user-provided data.\r\n * @returns {UserData} The rendered user data object.\r\n */\r\nexport const renderUserData = (\r\n userData: Partial<UserData>,\r\n bounds: {\r\n minWidth: number;\r\n maxWidth: number;\r\n minHeight: number;\r\n maxHeight: number;\r\n defaultQuality: number;\r\n }\r\n): ParsedUserData => {\r\n const parsed = userDataSchema.parse(userData);\r\n\r\n const clamp = (value: number | undefined, min: number, max: number) => {\r\n if (value === undefined) return undefined;\r\n return Math.min(Math.max(value, min), max);\r\n };\r\n\r\n return {\r\n ...parsed,\r\n width: clamp(parsed.width, bounds.minWidth, bounds.maxWidth),\r\n height: clamp(parsed.height, bounds.minHeight, bounds.maxHeight),\r\n quality: parsed.quality ?? bounds.defaultQuality,\r\n format: parsed.format ?? \"jpeg\",\r\n };\r\n};\r\n","import path from \"node:path\";\r\nimport { createHash } from \"node:crypto\";\r\nimport sharp, { FormatEnum, ResizeOptions } from \"sharp\";\r\nimport type { Request, Response, NextFunction } from \"express\";\r\nimport type {\r\n PixelServeOptions,\r\n UserData,\r\n ImageFormat,\r\n ImageType,\r\n} from \"./types\";\r\nimport { allowedFormats, FALLBACKIMAGES, mimeTypes } from \"./variables\";\r\nimport { fetchImage, readLocalImage } from \"./functions\";\r\nimport { renderOptions, renderUserData } from \"./renders\";\r\n\r\n/**\r\n * @typedef {Object} Options\r\n * @property {string} baseDir - The base directory for public image files.\r\n * @property {function(string): string} idHandler - A function to handle user IDs.\r\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\r\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\r\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\r\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\r\n */\r\n\r\n/**\r\n * @function serveImage\r\n * @description Processes and serves an image based on user data and options.\r\n * @param {Request} req - The Express request object.\r\n * @param {Response} res - The Express response object.\r\n * @param {NextFunction} next - The Express next function.\r\n * @param {PixelServeOptions} options - The options object for image processing.\r\n * @returns {Promise<void>}\r\n */\r\nconst serveImage = async (\r\n req: Request,\r\n res: Response,\r\n next: NextFunction,\r\n options: PixelServeOptions\r\n) => {\r\n try {\r\n const parsedOptions = renderOptions(options);\r\n const userData = renderUserData(req.query as Partial<UserData>, {\r\n minWidth: parsedOptions.minWidth,\r\n maxWidth: parsedOptions.maxWidth,\r\n minHeight: parsedOptions.minHeight,\r\n maxHeight: parsedOptions.maxHeight,\r\n defaultQuality: parsedOptions.defaultQuality,\r\n });\r\n\r\n let baseDir = parsedOptions.baseDir;\r\n let parsedUserId: string | undefined;\r\n\r\n if (userData.userId) {\r\n parsedUserId = parsedOptions.idHandler\r\n ? parsedOptions.idHandler(userData.userId)\r\n : userData.userId;\r\n }\r\n\r\n if (userData.folder === \"private\" && parsedOptions.getUserFolder) {\r\n const dir = await parsedOptions.getUserFolder(req, parsedUserId);\r\n if (dir) {\r\n baseDir = dir;\r\n }\r\n }\r\n\r\n const outputFormat = allowedFormats.includes(\r\n (userData.format ?? \"\").toLowerCase() as ImageFormat\r\n )\r\n ? (userData.format as ImageFormat)\r\n : \"jpeg\";\r\n\r\n const resolveBuffer = async (): Promise<Buffer> => {\r\n if (!userData.src) {\r\n return FALLBACKIMAGES[userData.type ?? \"normal\"]();\r\n }\r\n if (userData.src.startsWith(\"http\")) {\r\n return fetchImage(\r\n userData.src,\r\n baseDir,\r\n parsedOptions.websiteURL,\r\n userData.type as ImageType,\r\n parsedOptions.apiRegex,\r\n parsedOptions.allowedNetworkList,\r\n {\r\n timeoutMs: parsedOptions.requestTimeoutMs,\r\n maxBytes: parsedOptions.maxDownloadBytes,\r\n }\r\n );\r\n }\r\n return readLocalImage(userData.src, baseDir, userData.type as ImageType);\r\n };\r\n\r\n const imageBuffer = await resolveBuffer();\r\n let image = sharp(imageBuffer, { failOn: \"truncated\" });\r\n\r\n if (userData.width || userData.height) {\r\n const resizeOptions: ResizeOptions = {\r\n width: userData.width ?? undefined,\r\n height: userData.height ?? undefined,\r\n fit: sharp.fit.cover,\r\n withoutEnlargement: true,\r\n };\r\n image = image.resize(resizeOptions);\r\n }\r\n\r\n const processedImage = await image\r\n .rotate()\r\n .toFormat(outputFormat as keyof FormatEnum, {\r\n quality: userData.quality,\r\n })\r\n .toBuffer();\r\n\r\n const sourceName = userData.src\r\n ? path.basename(userData.src, path.extname(userData.src))\r\n : \"image\";\r\n const processedFileName = `${sourceName}.${outputFormat}`;\r\n\r\n const etag = parsedOptions.etag\r\n ? `\"${createHash(\"sha1\").update(processedImage).digest(\"hex\")}\"`\r\n : undefined;\r\n\r\n if (etag && req.headers[\"if-none-match\"] === etag) {\r\n res.status(304).end();\r\n return;\r\n }\r\n\r\n res.type(mimeTypes[outputFormat]);\r\n res.setHeader(\r\n \"Content-Disposition\",\r\n `inline; filename=\"${processedFileName}\"`\r\n );\r\n res.setHeader(\r\n \"Cache-Control\",\r\n parsedOptions.cacheControl ??\r\n \"public, max-age=86400, stale-while-revalidate=604800\"\r\n );\r\n if (etag) {\r\n res.setHeader(\"ETag\", etag);\r\n }\r\n res.setHeader(\"Content-Length\", processedImage.length.toString());\r\n res.send(processedImage);\r\n /* c8 ignore next */\r\n } catch (error) {\r\n try {\r\n const fallback = await FALLBACKIMAGES.normal();\r\n res.type(mimeTypes.jpeg);\r\n res.setHeader(\"Content-Disposition\", `inline; filename=\"fallback.jpeg\"`);\r\n res.setHeader(\"Cache-Control\", \"public, max-age=60\");\r\n res.send(fallback);\r\n } catch (fallbackError) {\r\n next(fallbackError);\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * @function registerServe\r\n * @description A function to register the serveImage function as middleware for Express.\r\n * @param {PixelServeOptions} options - The options object for image processing.\r\n * @returns {function(Request, Response, NextFunction): Promise<void>} The middleware function.\r\n */\r\nconst registerServe = (options: PixelServeOptions) => {\r\n return async (req: Request, res: Response, next: NextFunction) =>\r\n serveImage(req, res, next, options);\r\n};\r\n\r\nexport default registerServe;\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../node_modules/tsup/assets/cjs_shims.js","../src/variables.ts","../src/functions.ts","../src/schema.ts","../src/renders.ts","../src/pixel.ts"],"names":["getImportMetaUrl","importMetaUrl","moduleDir","path","fileURLToPath","getAssetPath","filename","NOT_FOUND_IMAGE","NOT_FOUND_AVATAR","FALLBACKIMAGES","readFile","API_REGEX","allowedFormats","mimeTypes","isValidPath","basePath","specifiedPath","resolvedBase","resolvedPath","realBase","realPath","d","normalizedBase","isInside","relative","fetchFromNetwork","src","type","timeoutMs","maxBytes","response","axios","status","contentType","readLocalImage","filePath","baseDir","resolvedFile","fetchImage","websiteURL","apiRegex","allowedNetworkList","url","localPath","imageFormatEnum","z","imageTypeEnum","userDataSchema","val","lower","value","optionsSchema","data","renderOptions","options","renderUserData","userData","bounds","parsed","clamp","min","max","serveImage","req","res","next","requestedType","parsedOptions","parsedUserId","folderPromise","timeoutPromise","_","reject","dir","outputFormat","imageBuffer","image","sharp","resizeOptions","processedImage","processedFileName","etag","createHash","fallback","fallbackError","registerServe","pixel_default"],"mappings":"qtBAKA,IAAMA,EAAmB,IACvB,OAAO,QAAA,CAAa,GAAA,CAChB,IAAI,GAAA,CAAI,CAAA,KAAA,EAAQ,UAAU,CAAA,CAAE,EAAE,IAAA,CAC7B,QAAA,CAAS,eAAiB,QAAA,CAAS,aAAA,CAAc,QAAQ,WAAA,EAAY,GAAM,QAAA,CAC1E,QAAA,CAAS,cAAc,GAAA,CACvB,IAAI,IAAI,SAAA,CAAW,QAAA,CAAS,OAAO,CAAA,CAAE,IAAA,CAEhCC,CAAAA,CAAgCD,CAAAA,GCH7C,IAAME,CAAAA,CAAYC,mBAAK,OAAA,CAAQC,iBAAAA,CAAcH,CAAe,CAAC,CAAA,CAEvDI,CAAAA,CAAgBC,CAAAA,EACbH,mBAAK,IAAA,CAAKD,CAAAA,CAAW,SAAUI,CAAQ,CAAA,CAG1CC,EAAkBF,CAAAA,CAAa,aAAa,CAAA,CAC5CG,CAAAA,CAAmBH,EAAa,cAAc,CAAA,CAEvCI,EAGT,CACF,MAAA,CAAQ,SAA6BC,UAAAA,CAASH,CAAe,CAAA,CAC7D,MAAA,CAAQ,SAA6BG,UAAAA,CAASF,CAAgB,CAChE,CAAA,CAEaG,EAAoB,cAAA,CAEpBC,CAAAA,CAAgC,CAC3C,MAAA,CACA,MACA,KAAA,CACA,MAAA,CACA,MACA,MAAA,CACA,MAAA,CACA,KACF,CAAA,CAEaC,CAAAA,CAA8C,CACzD,IAAA,CAAM,aACN,GAAA,CAAK,YAAA,CACL,IAAK,WAAA,CACL,IAAA,CAAM,aACN,GAAA,CAAK,WAAA,CACL,IAAA,CAAM,YAAA,CACN,KAAM,YAAA,CACN,GAAA,CAAK,eACP,CAAA,CC9BO,IAAMC,CAAAA,CAAc,MACzBC,CAAAA,CACAC,CAAAA,GACqB,CACrB,GAAI,CAKF,GAJI,CAACD,GAAY,CAACC,CAAAA,EACdA,EAAc,QAAA,CAAS,IAAI,CAAA,EAC3Bb,kBAAAA,CAAK,WAAWa,CAAa,CAAA,EAE7B,CAAC,iBAAA,CAAkB,IAAA,CAAKA,CAAa,CAAA,CAAG,OAAO,CAAA,CAAA,CAEnD,IAAMC,EAAed,kBAAAA,CAAK,OAAA,CAAQY,CAAQ,CAAA,CACpCG,CAAAA,CAAef,mBAAK,OAAA,CAAQc,CAAAA,CAAcD,CAAa,CAAA,CAEvD,CAACG,CAAAA,CAAUC,CAAQ,CAAA,CAAI,MAAM,QAAQ,GAAA,CAAI,CAC1CC,YAAA,CAAA,QAAA,CAASJ,CAAY,EACrBI,YAAA,CAAA,QAAA,CAASH,CAAY,CAC1B,CAAC,CAAA,CAGD,GAAI,CAAA,CADc,MAASG,YAAA,CAAA,IAAA,CAAKF,CAAQ,GACzB,WAAA,EAAY,CAAG,OAAO,CAAA,CAAA,CAErC,IAAMG,EAAiBH,CAAAA,CAAWhB,kBAAAA,CAAK,GAAA,CAGjCoB,CAAAA,CAAAA,CAFiBH,EAAWjB,kBAAAA,CAAK,GAAA,EAGtB,WAAWmB,CAAc,CAAA,EAAKF,IAAaD,CAAAA,CAEtDK,CAAAA,CAAWrB,kBAAAA,CAAK,QAAA,CAASgB,EAAUC,CAAQ,CAAA,CACjD,OAAO,CAACI,EAAS,UAAA,CAAW,IAAI,CAAA,EAAK,CAACrB,mBAAK,UAAA,CAAWqB,CAAQ,GAAKD,CACrE,CAAA,KAAQ,CACN,OAAO,MACT,CACF,CAAA,CASME,EAAmB,MACvBC,CAAAA,CACAC,EAAkB,QAAA,CAClB,CACE,UAAAC,CAAAA,CACA,QAAA,CAAAC,CACF,CAAA,GAIoB,CACpB,GAAI,CACF,IAAMC,CAAAA,CAAW,MAAMC,mBAAM,GAAA,CAAIL,CAAAA,CAAK,CACpC,YAAA,CAAc,cACd,OAAA,CAASE,CAAAA,CACT,gBAAA,CAAkBC,CAAAA,CAClB,cAAeA,CAAAA,CACf,cAAA,CAAiBG,CAAAA,EAAWA,CAAAA,EAAU,KAAOA,CAAAA,CAAS,GACxD,CAAC,CAAA,CAEKC,CAAAA,CAAcH,EAAS,OAAA,CAAQ,cAAc,CAAA,EAC/C,WAAA,IACA,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA,EACZ,MAAK,CAGT,OAFyB,MAAA,CAAO,MAAA,CAAOjB,CAAS,CAAA,CAE3B,QAAA,CAASoB,GAAe,EAAE,CAAA,CACtC,OAAO,IAAA,CAAKH,CAAAA,CAAS,IAAI,CAAA,CAE3B,MAAMrB,CAAAA,CAAekB,CAAI,CAAA,EAClC,MAAQ,CACN,OAAO,MAAMlB,CAAAA,CAAekB,CAAI,CAAA,EAClC,CACF,CAAA,CAUaO,CAAAA,CAAiB,MAC5BC,CAAAA,CACAC,CAAAA,CACAT,CAAAA,CAAkB,QAAA,CAClBE,IACoB,CAEpB,GAAI,CADY,MAAMf,CAAAA,CAAYsB,EAASD,CAAQ,CAAA,CAEjD,OAAO,MAAM1B,EAAekB,CAAI,CAAA,GAElC,GAAI,CACF,IAAMU,CAAAA,CAAelC,kBAAAA,CAAK,OAAA,CAAQiC,CAAAA,CAASD,CAAQ,CAAA,CACnD,OAAIN,CAAAA,EAAAA,CACY,MAASR,kBAAKgB,CAAY,CAAA,EAC9B,IAAA,CAAOR,CAAAA,CACR,MAAMpB,CAAAA,CAAekB,CAAI,GAAE,CAG/B,MAASN,sBAASgB,CAAY,CACvC,CAAA,KAAQ,CACN,OAAO,MAAM5B,CAAAA,CAAekB,CAAI,CAAA,EAClC,CACF,CAAA,CAYaW,CAAAA,CAAa,CACxBZ,CAAAA,CACAU,EACAG,CAAAA,CACAZ,CAAAA,CAAkB,SAClBa,CAAAA,CACAC,CAAAA,CAA+B,EAAC,CAChC,CACE,SAAA,CAAAb,CAAAA,CACA,SAAAC,CACF,CAAA,GAIoB,CACpB,GAAI,CACF,IAAMa,CAAAA,CAAM,IAAI,GAAA,CAAIhB,CAAG,CAAA,CAKvB,GAHEa,IAAe,KAAA,CAAA,EACf,CAACA,EAAY,CAAA,IAAA,EAAOA,CAAU,CAAA,CAAE,CAAA,CAAE,SAASG,CAAAA,CAAI,QAAQ,EAEzC,CACd,IAAMC,EAAYD,CAAAA,CAAI,QAAA,CAAS,OAAA,CAAQF,CAAAA,CAAU,EAAE,CAAA,CACnD,OAAON,EAAeS,CAAAA,CAAWP,CAAAA,CAAST,EAAME,CAAQ,CAC1D,CAKA,OAFEY,EAAmB,QAAA,CAASC,CAAAA,CAAI,QAAQ,CAAA,EACxCD,EAAmB,QAAA,CAASC,CAAAA,CAAI,IAAI,CAAA,CAIjC,CAAC,OAAA,CAAS,QAAQ,EAAE,QAAA,CAASA,CAAAA,CAAI,QAAQ,CAAA,CAGvCjB,CAAAA,CAAiBC,CAAAA,CAAKC,CAAAA,CAAM,CAAE,SAAA,CAAAC,CAAAA,CAAW,SAAAC,CAAS,CAAC,EAFjDpB,CAAAA,CAAekB,CAAI,CAAA,EAAE,CAHrBlB,EAAekB,CAAI,CAAA,EAM9B,CAAA,KAAQ,CACN,OAAOO,CAAAA,CAAeR,CAAAA,CAAKU,CAAAA,CAAST,CAAAA,CAAME,CAAQ,CACpD,CACF,EC7KA,IAAMe,CAAAA,CAAkBC,MAAE,IAAA,CAAKjC,CAAuC,EAChEkC,CAAAA,CAAgBD,KAAAA,CAAE,KAAK,CAAC,QAAA,CAAU,QAAQ,CAAC,EAEpCE,CAAAA,CAAiBF,KAAAA,CAC3B,OAAO,CACN,GAAA,CAAKA,MACF,MAAA,EAAO,CACP,GAAA,CAAI,CAAA,CAAG,iBAAiB,CAAA,CACxB,QAAA,GACA,OAAA,CAAQ,0BAA0B,EACrC,MAAA,CAAQA,KAAAA,CACL,MAAA,EAAO,CACP,UAAS,CACT,SAAA,CAAWG,CAAAA,EAAQ,CAClB,IAAMC,CAAAA,CAAQD,CAAAA,EAAK,WAAA,EAAY,CAC/B,OAAOC,CAAAA,EAASL,CAAAA,CAAgB,QAAQ,QAAA,CAASK,CAAe,EAC3DA,CAAAA,CACD,MACN,CAAC,CAAA,CACA,UAAS,CACZ,KAAA,CAAOJ,MACJ,KAAA,CAAM,CAACA,MAAE,MAAA,EAAO,CAAGA,KAAAA,CAAE,MAAA,EAAQ,CAAC,CAAA,CAC9B,UAAS,CACT,SAAA,CAAWK,GACaA,CAAAA,EAAU,IAAA,CAAO,MAAA,CAAY,MAAA,CAAOA,CAAK,CAClE,CAAA,CACC,IAAA,CACCL,KAAAA,CACG,QAAO,CACP,GAAA,EAAI,CACJ,GAAA,CAAI,GAAI,iBAAiB,CAAA,CACzB,IAAI,GAAA,CAAM,iBAAiB,EAC3B,QAAA,EACL,CAAA,CACF,MAAA,CAAQA,MACL,KAAA,CAAM,CAACA,MAAE,MAAA,EAAO,CAAGA,MAAE,MAAA,EAAQ,CAAC,CAAA,CAC9B,UAAS,CACT,SAAA,CAAWK,GACaA,CAAAA,EAAU,IAAA,CAAO,OAAY,MAAA,CAAOA,CAAK,CAClE,CAAA,CACC,KACCL,KAAAA,CACG,MAAA,EAAO,CACP,GAAA,GACA,GAAA,CAAI,EAAA,CAAI,kBAAkB,CAAA,CAC1B,IAAI,GAAA,CAAM,kBAAkB,EAC5B,QAAA,EACL,EACF,OAAA,CAASA,KAAAA,CACN,KAAA,CAAM,CAACA,MAAE,MAAA,EAAO,CAAGA,MAAE,MAAA,EAAQ,CAAC,CAAA,CAC9B,QAAA,EAAS,CACT,SAAA,CAAWK,GACaA,CAAAA,EAAU,IAAA,CAAO,OAAY,MAAA,CAAOA,CAAK,CAClE,CAAA,CACC,IAAA,CAAKL,KAAAA,CAAE,MAAA,GAAS,GAAA,EAAI,CAAE,GAAA,CAAI,CAAC,EAAE,GAAA,CAAI,GAAG,CAAA,CAAE,OAAA,CAAQ,EAAE,CAAC,CAAA,CACpD,OAAQA,KAAAA,CAAE,IAAA,CAAK,CAAC,QAAA,CAAU,SAAS,CAAC,CAAA,CAAE,QAAQ,QAAQ,CAAA,CACtD,KAAMC,CAAAA,CAAc,OAAA,CAAQ,QAAQ,CAAA,CACpC,MAAA,CAAQD,KAAAA,CACL,KAAA,CAAM,CAACA,KAAAA,CAAE,MAAA,GAAUA,KAAAA,CAAE,MAAA,EAAQ,CAAC,CAAA,CAC9B,QAAA,EAAS,CACT,UAAWK,CAAAA,EACaA,CAAAA,EAAU,IAAA,CAAO,MAAA,CAAY,OAAOA,CAAK,CAAA,CAAE,IAAA,EACpE,EACC,IAAA,CACCL,KAAAA,CACG,QAAO,CACP,GAAA,CAAI,EAAG,wBAAwB,CAAA,CAC/B,GAAA,CAAI,GAAA,CAAK,iBAAiB,CAAA,CAC1B,QAAA,EACL,CACJ,CAAC,EACA,MAAA,EAAO,CAEGM,CAAAA,CAAgBN,KAAAA,CAC1B,OAAO,CACN,OAAA,CAASA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAA,CAAG,qBAAqB,CAAA,CAChD,SAAA,CAAWA,MACR,MAAA,CAEEG,CAAAA,EAAQ,OAAOA,CAAAA,EAAQ,WAAY,CAAE,OAAA,CAAS,8BAA+B,CAAC,EAChF,QAAA,EAAS,CACZ,cAAeH,KAAAA,CACZ,MAAA,CAEEG,GAAQ,OAAOA,CAAAA,EAAQ,UAAA,CAAY,CAAE,QAAS,kCAAmC,CAAC,EACpF,QAAA,EAAS,CACZ,WAAYH,KAAAA,CACT,KAAA,CAAM,CAACA,KAAAA,CAAE,KAAI,CAAGA,KAAAA,CAAE,QAAO,CAAE,KAAA,CAAM,8BAA8B,CAAC,CAAC,CAAA,CACjE,QAAA,GACH,QAAA,CAAUA,KAAAA,CAAE,UAAA,CAAW,MAAM,EAAE,OAAA,CAAQlC,CAAS,CAAA,CAChD,kBAAA,CAAoBkC,MAAE,KAAA,CAAMA,KAAAA,CAAE,QAAQ,CAAA,CAAE,QAAQ,EAAE,CAAA,CAClD,YAAA,CAAcA,MAAE,MAAA,EAAO,CAAE,UAAS,CAClC,IAAA,CAAMA,MAAE,OAAA,EAAQ,CAAE,OAAA,CAAQ,IAAI,EAC9B,QAAA,CAAUA,KAAAA,CAAE,QAAO,CAAE,GAAA,GAAM,QAAA,EAAS,CAAE,OAAA,CAAQ,EAAE,EAChD,QAAA,CAAUA,KAAAA,CAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,GAAI,CAAA,CAClD,SAAA,CAAWA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,EAAE,CAAA,CACjD,SAAA,CAAWA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,GAAI,CAAA,CACnD,cAAA,CAAgBA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,GAAA,CAAI,CAAC,CAAA,CAAE,IAAI,GAAG,CAAA,CAAE,OAAA,CAAQ,EAAE,EAC3D,gBAAA,CAAkBA,KAAAA,CAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,GAAW,OAAA,CAAQ,GAAI,EAC1D,gBAAA,CAAkBA,KAAAA,CAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,GAAW,OAAA,CAAQ,GAAS,CACjE,CAAC,CAAA,CACA,MAAA,EAAO,CACP,OAAQO,CAAAA,EAASA,CAAAA,CAAK,UAAYA,CAAAA,CAAK,QAAA,CAAU,CAChD,OAAA,CAAS,iDAAA,CACT,IAAA,CAAM,CAAC,UAAU,CACnB,CAAC,CAAA,CACA,MAAA,CAAQA,GAASA,CAAAA,CAAK,SAAA,EAAaA,CAAAA,CAAK,SAAA,CAAW,CAClD,OAAA,CAAS,mDAAA,CACT,KAAM,CAAC,WAAW,CACpB,CAAC,ECtEI,IAAMC,CAAAA,CAAiBC,GAC5BH,CAAAA,CAAc,KAAA,CAAMG,CAAO,CAAA,CAQhBC,CAAAA,CAAiB,CAC5BC,CAAAA,CACAC,CAAAA,GAOmB,CACnB,IAAMC,EAASX,CAAAA,CAAe,KAAA,CAAMS,CAAQ,CAAA,CAEtCG,CAAAA,CAAQ,CAACT,CAAAA,CAA2BU,CAAAA,CAAaC,CAAAA,GAAoC,CACzF,GAAIX,CAAAA,GAAU,MAAA,CACd,OAAO,IAAA,CAAK,IAAI,IAAA,CAAK,GAAA,CAAIA,CAAAA,CAAOU,CAAG,EAAGC,CAAG,CAC3C,EAEA,OAAO,CACL,GAAGH,CAAAA,CACH,KAAA,CAAOC,CAAAA,CAAMD,CAAAA,CAAO,MAAOD,CAAAA,CAAO,QAAA,CAAUA,EAAO,QAAQ,CAAA,CAC3D,OAAQE,CAAAA,CAAMD,CAAAA,CAAO,MAAA,CAAQD,CAAAA,CAAO,UAAWA,CAAAA,CAAO,SAAS,EAC/D,OAAA,CAASC,CAAAA,CAAO,SAAWD,CAAAA,CAAO,cAAA,CAClC,MAAA,CAAQC,CAAAA,CAAO,QAAU,MAC3B,CACF,CAAA,CC1CA,IAAMI,EAAa,MACjBC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAX,IACkB,CAClB,IAAIY,EAA2B,QAAA,CAC/B,GAAI,CACF,IAAMC,CAAAA,CAAgBd,CAAAA,CAAcC,CAAO,EACrCE,CAAAA,CAAWD,CAAAA,CAAeQ,EAAI,KAAA,CAA4B,CAC9D,SAAUI,CAAAA,CAAc,QAAA,CACxB,QAAA,CAAUA,CAAAA,CAAc,SACxB,SAAA,CAAWA,CAAAA,CAAc,UACzB,SAAA,CAAWA,CAAAA,CAAc,UACzB,cAAA,CAAgBA,CAAAA,CAAc,cAChC,CAAC,EAEDD,CAAAA,CAAiBV,CAAAA,CAAS,IAAA,EAAsB,QAAA,CAEhD,IAAIpB,CAAAA,CAAU+B,CAAAA,CAAc,OAAA,CACxBC,CAAAA,CAQJ,GANIZ,CAAAA,CAAS,MAAA,GACXY,EAAeD,CAAAA,CAAc,SAAA,CACzBA,EAAc,SAAA,CAAUX,CAAAA,CAAS,MAAM,CAAA,CACvCA,EAAS,MAAA,CAAA,CAGXA,CAAAA,CAAS,SAAW,SAAA,EAAaW,CAAAA,CAAc,cAAe,CAChE,IAAME,CAAAA,CAAgB,OAAA,CAAQ,QAC5BF,CAAAA,CAAc,aAAA,CAAcJ,EAAKK,CAAY,CAC/C,EACME,CAAAA,CAAiB,IAAI,OAAA,CAAe,CAACC,EAAGC,CAAAA,GAC5C,UAAA,CACE,IAAMA,CAAAA,CAAO,IAAI,KAAA,CAAM,yBAAyB,CAAC,CAAA,CACjDL,EAAc,gBAChB,CACF,EACA,GAAI,CACF,IAAMM,CAAAA,CAAM,MAAM,OAAA,CAAQ,IAAA,CAAK,CAACJ,CAAAA,CAAeC,CAAc,CAAC,CAAA,CAC1DG,CAAAA,GACFrC,EAAUqC,CAAAA,EAEd,CAAA,KAAQ,CAER,CACF,CAEA,IAAMC,CAAAA,CAAe9D,EAAe,QAAA,CAAA,CACjC4C,CAAAA,CAAS,QAAU,EAAA,EAAI,WAAA,EAC1B,CAAA,CACKA,EAAS,MAAA,CACV,MAAA,CA+BEmB,CAAAA,CAAc,KAAA,CA7BE,SACfnB,CAAAA,CAAS,GAAA,CAIZA,CAAAA,CAAS,GAAA,CAAI,WAAW,SAAS,CAAA,EACjCA,EAAS,GAAA,CAAI,UAAA,CAAW,UAAU,CAAA,CAE3BlB,CAAAA,CACLkB,CAAAA,CAAS,GAAA,CACTpB,EACA+B,CAAAA,CAAc,UAAA,CACdX,EAAS,IAAA,CACTW,CAAAA,CAAc,SACdA,CAAAA,CAAc,kBAAA,CACd,CACE,SAAA,CAAWA,EAAc,gBAAA,CACzB,QAAA,CAAUA,EAAc,gBAC1B,CACF,EAEKjC,CAAAA,CACLsB,CAAAA,CAAS,GAAA,CACTpB,CAAAA,CACAoB,EAAS,IAAA,CACTW,CAAAA,CAAc,gBAChB,CAAA,CAxBS1D,EAAe+C,CAAAA,CAAS,IAAA,EAAQ,QAAQ,CAAA,KA2BX,CACpCoB,CAAAA,CAAQC,mBAAMF,CAAAA,CAAa,CAAE,OAAQ,WAAY,CAAC,CAAA,CAEtD,GAAInB,EAAS,KAAA,EAASA,CAAAA,CAAS,OAAQ,CACrC,IAAMsB,EAA+B,CACnC,KAAA,CAAOtB,CAAAA,CAAS,KAAA,EAAS,OACzB,MAAA,CAAQA,CAAAA,CAAS,QAAU,KAAA,CAAA,CAC3B,GAAA,CAAKqB,mBAAM,GAAA,CAAI,KAAA,CACf,kBAAA,CAAoB,CAAA,CACtB,EACAD,CAAAA,CAAQA,CAAAA,CAAM,MAAA,CAAOE,CAAa,EACpC,CAEA,IAAMC,CAAAA,CAAiB,MAAMH,EAC1B,MAAA,EAAO,CACP,SAASF,CAAAA,CAAkC,CAC1C,QAASlB,CAAAA,CAAS,OACpB,CAAC,CAAA,CACA,UAAS,CAONwB,CAAAA,CAAoB,IALVxB,CAAAA,CAAS,GAAA,CACrBrD,mBAAK,QAAA,CAASqD,CAAAA,CAAS,GAAA,CAAKrD,kBAAAA,CAAK,QAAQqD,CAAAA,CAAS,GAAG,CAAC,CAAA,CACtD,OAAA,EAEuB,QAAQ,qBAAA,CAAuB,GAAG,CACtB,CAAA,CAAA,EAAIkB,CAAY,CAAA,CAAA,CAEjDO,CAAAA,CAAOd,CAAAA,CAAc,IAAA,CACvB,IAAIe,iBAAAA,CAAW,MAAM,CAAA,CAAE,MAAA,CAAOH,CAAc,CAAA,CAAE,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA,CAAA,CAC3D,OAEJ,GAAIE,CAAAA,EAAQlB,CAAAA,CAAI,OAAA,CAAQ,eAAe,CAAA,GAAMkB,CAAAA,CAAM,CACjDjB,CAAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,GAAA,EAAI,CACpB,MACF,CAEAA,CAAAA,CAAI,IAAA,CAAKnD,EAAU6D,CAAY,CAAC,EAChCV,CAAAA,CAAI,SAAA,CACF,qBAAA,CACA,CAAA,kBAAA,EAAqBgB,CAAiB,CAAA,CAAA,CACxC,CAAA,CACAhB,CAAAA,CAAI,SAAA,CACF,gBACAG,CAAAA,CAAc,YAAA,EACZ,sDACJ,CAAA,CACIc,GACFjB,CAAAA,CAAI,SAAA,CAAU,OAAQiB,CAAI,CAAA,CAE5BjB,EAAI,SAAA,CAAU,gBAAA,CAAkBe,CAAAA,CAAe,MAAA,CAAO,UAAU,CAAA,CAChEf,EAAI,IAAA,CAAKe,CAAc,EACzB,CAAA,KAAQ,CACN,GAAI,CAGF,IAAMI,CAAAA,CAAW,MAAM1E,EADrByD,CAAAA,GAAkB,QAAA,CAAW,SAAW,QACQ,CAAA,EAAE,CACpDF,CAAAA,CAAI,KAAKnD,CAAAA,CAAU,IAAI,CAAA,CACvBmD,CAAAA,CAAI,UAAU,qBAAA,CAAuB,kCAAkC,CAAA,CACvEA,CAAAA,CAAI,UAAU,eAAA,CAAiB,oBAAoB,EACnDA,CAAAA,CAAI,IAAA,CAAKmB,CAAQ,EACnB,CAAA,MAASC,CAAAA,CAAe,CACtBnB,EAAKmB,CAAa,EACpB,CACF,CACF,CAAA,CAQMC,EACJ/B,CAAAA,EAEO,MAAOS,CAAAA,CAAcC,CAAAA,CAAeC,IACzCH,CAAAA,CAAWC,CAAAA,CAAKC,EAAKC,CAAAA,CAAMX,CAAO,EAG/BgC,CAAAA,CAAQD","file":"index.js","sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import type { ImageFormat } from \"./types\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\n\r\n/**\r\n * Get the directory path for the current module.\r\n * Uses import.meta.url for ESM (tsup provides shims for CJS compatibility).\r\n */\r\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\r\n\r\nconst getAssetPath = (filename: string): string => {\r\n return path.join(moduleDir, \"assets\", filename);\r\n};\r\n\r\nconst NOT_FOUND_IMAGE = getAssetPath(\"noimage.jpg\");\r\nconst NOT_FOUND_AVATAR = getAssetPath(\"noavatar.png\");\r\n\r\nexport const FALLBACKIMAGES: Record<\r\n \"normal\" | \"avatar\",\r\n () => Promise<Buffer>\r\n> = {\r\n normal: async (): Promise<Buffer> => readFile(NOT_FOUND_IMAGE),\r\n avatar: async (): Promise<Buffer> => readFile(NOT_FOUND_AVATAR),\r\n};\r\n\r\nexport const API_REGEX: RegExp = /^\\/api\\/v1\\//;\r\n\r\nexport const allowedFormats: ImageFormat[] = [\r\n \"jpeg\",\r\n \"jpg\",\r\n \"png\",\r\n \"webp\",\r\n \"gif\",\r\n \"tiff\",\r\n \"avif\",\r\n \"svg\",\r\n];\r\n\r\nexport const mimeTypes: Readonly<Record<string, string>> = {\r\n jpeg: \"image/jpeg\",\r\n jpg: \"image/jpeg\",\r\n png: \"image/png\",\r\n webp: \"image/webp\",\r\n gif: \"image/gif\",\r\n tiff: \"image/tiff\",\r\n avif: \"image/avif\",\r\n svg: \"image/svg+xml\",\r\n};\r\n","import path from \"node:path\";\r\nimport * as fs from \"node:fs/promises\";\r\nimport axios from \"axios\";\r\nimport { FALLBACKIMAGES, mimeTypes } from \"./variables\";\r\nimport type { ImageType } from \"./types\";\r\n\r\n/**\r\n * @typedef {(\"avatar\" | \"normal\")} ImageType\r\n * @description Defines the type of image being processed.\r\n */\r\n\r\n/**\r\n * Checks if a specified path is valid within a base path.\r\n *\r\n * @param {string} basePath - The base directory to resolve paths.\r\n * @param {string} specifiedPath - The path to check.\r\n * @returns {Promise<boolean>} True if the path is valid, false otherwise.\r\n */\r\nexport const isValidPath = async (\r\n basePath: string,\r\n specifiedPath: string\r\n): Promise<boolean> => {\r\n try {\r\n if (!basePath || !specifiedPath) return false;\r\n if (specifiedPath.includes(\"\\0\")) return false;\r\n if (path.isAbsolute(specifiedPath)) return false;\r\n // eslint-disable-next-line no-control-regex\r\n if (!/^[^\\x00-\\x1F]+$/.test(specifiedPath)) return false;\r\n\r\n const resolvedBase = path.resolve(basePath);\r\n const resolvedPath = path.resolve(resolvedBase, specifiedPath);\r\n\r\n const [realBase, realPath] = await Promise.all([\r\n fs.realpath(resolvedBase),\r\n fs.realpath(resolvedPath),\r\n ]);\r\n\r\n const baseStats = await fs.stat(realBase);\r\n if (!baseStats.isDirectory()) return false;\r\n\r\n const normalizedBase = realBase + path.sep;\r\n const normalizedPath = realPath + path.sep;\r\n\r\n const isInside =\r\n normalizedPath.startsWith(normalizedBase) || realPath === realBase;\r\n\r\n const relative = path.relative(realBase, realPath);\r\n return !relative.startsWith(\"..\") && !path.isAbsolute(relative) && isInside;\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\n/**\r\n * Fetches an image from a network source.\r\n *\r\n * @param {string} src - The URL of the image.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image in case of an error.\r\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\r\n */\r\nconst fetchFromNetwork = async (\r\n src: string,\r\n type: ImageType = \"normal\",\r\n {\r\n timeoutMs,\r\n maxBytes,\r\n }: {\r\n timeoutMs: number;\r\n maxBytes: number;\r\n }\r\n): Promise<Buffer> => {\r\n try {\r\n const response = await axios.get(src, {\r\n responseType: \"arraybuffer\",\r\n timeout: timeoutMs,\r\n maxContentLength: maxBytes,\r\n maxBodyLength: maxBytes,\r\n validateStatus: (status) => status >= 200 && status < 300,\r\n });\r\n\r\n const contentType = response.headers[\"content-type\"]\r\n ?.toLowerCase()\r\n ?.split(\";\")[0]\r\n ?.trim();\r\n const allowedMimeTypes = Object.values(mimeTypes);\r\n\r\n if (allowedMimeTypes.includes(contentType ?? \"\")) {\r\n return Buffer.from(response.data);\r\n }\r\n return await FALLBACKIMAGES[type]();\r\n } catch {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n};\r\n\r\n/**\r\n * Reads an image from the local file system.\r\n *\r\n * @param {string} filePath - Path to the image file.\r\n * @param {string} baseDir - Base directory to resolve paths.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\r\n * @returns {Promise<Buffer>} A buffer containing the image data.\r\n */\r\nexport const readLocalImage = async (\r\n filePath: string,\r\n baseDir: string,\r\n type: ImageType = \"normal\",\r\n maxBytes?: number\r\n): Promise<Buffer> => {\r\n const isValid = await isValidPath(baseDir, filePath);\r\n if (!isValid) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n try {\r\n const resolvedFile = path.resolve(baseDir, filePath);\r\n if (maxBytes) {\r\n const stats = await fs.stat(resolvedFile);\r\n if (stats.size > maxBytes) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n }\r\n return await fs.readFile(resolvedFile);\r\n } catch {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n};\r\n\r\n/**\r\n * Fetches an image from either a local file or a network source.\r\n *\r\n * @param {string} src - The URL or local path of the image.\r\n * @param {string} baseDir - Base directory to resolve local paths.\r\n * @param {string} websiteURL - The URL of the website.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\r\n * @param {string[]} [allowedNetworkList=[]] - List of allowed network hosts.\r\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\r\n */\r\nexport const fetchImage = (\r\n src: string,\r\n baseDir: string,\r\n websiteURL: string | undefined,\r\n type: ImageType = \"normal\",\r\n apiRegex: RegExp,\r\n allowedNetworkList: string[] = [],\r\n {\r\n timeoutMs,\r\n maxBytes,\r\n }: {\r\n timeoutMs: number;\r\n maxBytes: number;\r\n }\r\n): Promise<Buffer> => {\r\n try {\r\n const url = new URL(src);\r\n const isInternal =\r\n websiteURL !== undefined &&\r\n [websiteURL, `www.${websiteURL}`].includes(url.hostname);\r\n\r\n if (isInternal) {\r\n const localPath = url.pathname.replace(apiRegex, \"\");\r\n return readLocalImage(localPath, baseDir, type, maxBytes);\r\n }\r\n\r\n const allowedCondition =\r\n allowedNetworkList.includes(url.hostname) ||\r\n allowedNetworkList.includes(url.host);\r\n if (!allowedCondition) {\r\n return FALLBACKIMAGES[type]();\r\n }\r\n if (![\"http:\", \"https:\"].includes(url.protocol)) {\r\n return FALLBACKIMAGES[type]();\r\n }\r\n return fetchFromNetwork(src, type, { timeoutMs, maxBytes });\r\n } catch {\r\n return readLocalImage(src, baseDir, type, maxBytes);\r\n }\r\n};\r\n","import { z } from \"zod\";\r\nimport { API_REGEX, allowedFormats } from \"./variables\";\r\n\r\nconst imageFormatEnum = z.enum(allowedFormats as [string, ...string[]]);\r\nconst imageTypeEnum = z.enum([\"avatar\", \"normal\"]);\r\n\r\nexport const userDataSchema = z\r\n .object({\r\n src: z\r\n .string()\r\n .min(1, \"src is required\")\r\n .optional()\r\n .default(\"/placeholder/noimage.jpg\"),\r\n format: z\r\n .string()\r\n .optional()\r\n .transform((val) => {\r\n const lower = val?.toLowerCase();\r\n return lower && imageFormatEnum.options.includes(lower as string)\r\n ? (lower as (typeof imageFormatEnum)[\"options\"][number])\r\n : undefined;\r\n })\r\n .optional(),\r\n width: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(\r\n z\r\n .number()\r\n .int()\r\n .min(50, \"width too small\")\r\n .max(4000, \"width too large\")\r\n .optional()\r\n ),\r\n height: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(\r\n z\r\n .number()\r\n .int()\r\n .min(50, \"height too small\")\r\n .max(4000, \"height too large\")\r\n .optional()\r\n ),\r\n quality: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(z.number().int().min(1).max(100).default(80)),\r\n folder: z.enum([\"public\", \"private\"]).default(\"public\"),\r\n type: imageTypeEnum.default(\"normal\"),\r\n userId: z\r\n .union([z.string(), z.number()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : String(value).trim()\r\n )\r\n .pipe(\r\n z\r\n .string()\r\n .min(1, \"userId cannot be empty\")\r\n .max(128, \"userId too long\")\r\n .optional()\r\n ),\r\n })\r\n .strict();\r\n\r\nexport const optionsSchema = z\r\n .object({\r\n baseDir: z.string().min(1, \"baseDir is required\"),\r\n idHandler: z\r\n .custom<\r\n (id: string) => string\r\n >((val) => typeof val === \"function\", { message: \"idHandler must be a function\" })\r\n .optional(),\r\n getUserFolder: z\r\n .custom<\r\n (req: unknown, id?: string) => Promise<string> | string\r\n >((val) => typeof val === \"function\", { message: \"getUserFolder must be a function\" })\r\n .optional(),\r\n websiteURL: z\r\n .union([z.url(), z.string().regex(/^(?![-.])([\\w]+[-.]?)*[\\w]+$/)])\r\n .optional(),\r\n apiRegex: z.instanceof(RegExp).default(API_REGEX),\r\n allowedNetworkList: z.array(z.string()).default([]),\r\n cacheControl: z.string().optional(),\r\n etag: z.boolean().default(true),\r\n minWidth: z.number().int().positive().default(50),\r\n maxWidth: z.number().int().positive().default(4000),\r\n minHeight: z.number().int().positive().default(50),\r\n maxHeight: z.number().int().positive().default(4000),\r\n defaultQuality: z.number().int().min(1).max(100).default(80),\r\n requestTimeoutMs: z.number().int().positive().default(5000),\r\n maxDownloadBytes: z.number().int().positive().default(5_000_000),\r\n })\r\n .strict()\r\n .refine((data) => data.minWidth <= data.maxWidth, {\r\n message: \"minWidth must be less than or equal to maxWidth\",\r\n path: [\"minWidth\"],\r\n })\r\n .refine((data) => data.minHeight <= data.maxHeight, {\r\n message: \"minHeight must be less than or equal to maxHeight\",\r\n path: [\"minHeight\"],\r\n });\r\n\r\nexport type ParsedUserData = z.infer<typeof userDataSchema>;\r\nexport type ParsedOptions = z.infer<typeof optionsSchema>;\r\n","import { optionsSchema, userDataSchema } from \"./schema\";\r\nimport type { ParsedOptions, ParsedUserData } from \"./schema\";\r\nimport type { PixelServeOptions, UserData } from \"./types\";\r\n\r\n/**\r\n * @typedef {(\"avatar\" | \"normal\")} ImageType\r\n * @description Defines the type of image being processed.\r\n */\r\n\r\n/**\r\n * @typedef {(\"jpeg\" | \"jpg\" | \"png\" | \"webp\" | \"gif\" | \"tiff\" | \"avif\" | \"svg\")} ImageFormat\r\n * @description Supported formats for image processing.\r\n */\r\n\r\n/**\r\n * @typedef {Object} Options\r\n * @property {string} baseDir - The base directory for public image files.\r\n * @property {function(string): string} idHandler - A function to handle user IDs.\r\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\r\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\r\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\r\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\r\n */\r\n\r\n/**\r\n * @typedef {Object} UserData\r\n * @property {number|string} quality - Quality of the image (1–100).\r\n * @property {ImageFormat} format - Desired format of the image.\r\n * @property {string} [src] - Source path or URL for the image.\r\n * @property {string} [folder] - The folder type (\"public\" or \"private\").\r\n * @property {ImageType} [type] - Type of the image (\"avatar\" or \"normal\").\r\n * @property {string|null} [userId] - Optional user identifier.\r\n * @property {number|string} [width] - Desired image width.\r\n * @property {number|string} [height] - Desired image height.\r\n */\r\n\r\n/**\r\n * Renders the options object with default values and user-provided values.\r\n *\r\n * @param {Partial<Options>} options - The user-provided options.\r\n * @returns {Options} The rendered options object.\r\n */\r\nexport const renderOptions = (options: PixelServeOptions): ParsedOptions =>\r\n optionsSchema.parse(options);\r\n\r\n/**\r\n * Renders the user data object with default values and user-provided values.\r\n *\r\n * @param {Partial<UserData>} userData - The user-provided data.\r\n * @returns {UserData} The rendered user data object.\r\n */\r\nexport const renderUserData = (\r\n userData: Partial<UserData>,\r\n bounds: {\r\n minWidth: number;\r\n maxWidth: number;\r\n minHeight: number;\r\n maxHeight: number;\r\n defaultQuality: number;\r\n }\r\n): ParsedUserData => {\r\n const parsed = userDataSchema.parse(userData);\r\n\r\n const clamp = (value: number | undefined, min: number, max: number): number | undefined => {\r\n if (value === undefined) return undefined;\r\n return Math.min(Math.max(value, min), max);\r\n };\r\n\r\n return {\r\n ...parsed,\r\n width: clamp(parsed.width, bounds.minWidth, bounds.maxWidth),\r\n height: clamp(parsed.height, bounds.minHeight, bounds.maxHeight),\r\n quality: parsed.quality ?? bounds.defaultQuality,\r\n format: parsed.format ?? \"jpeg\",\r\n };\r\n};\r\n","import path from \"node:path\";\r\nimport { createHash } from \"node:crypto\";\r\nimport sharp, { FormatEnum, ResizeOptions } from \"sharp\";\r\nimport type { Request, Response, NextFunction } from \"express\";\r\nimport type {\r\n PixelServeOptions,\r\n UserData,\r\n ImageFormat,\r\n ImageType,\r\n} from \"./types\";\r\nimport { allowedFormats, FALLBACKIMAGES, mimeTypes } from \"./variables\";\r\nimport { fetchImage, readLocalImage } from \"./functions\";\r\nimport { renderOptions, renderUserData } from \"./renders\";\r\n\r\n/**\r\n * @typedef {Object} Options\r\n * @property {string} baseDir - The base directory for public image files.\r\n * @property {function(string): string} idHandler - A function to handle user IDs.\r\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\r\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\r\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\r\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\r\n */\r\n\r\n/**\r\n * @function serveImage\r\n * @description Processes and serves an image based on user data and options.\r\n * @param {Request} req - The Express request object.\r\n * @param {Response} res - The Express response object.\r\n * @param {NextFunction} next - The Express next function.\r\n * @param {PixelServeOptions} options - The options object for image processing.\r\n * @returns {Promise<void>}\r\n */\r\nconst serveImage = async (\r\n req: Request,\r\n res: Response,\r\n next: NextFunction,\r\n options: PixelServeOptions\r\n): Promise<void> => {\r\n let requestedType: ImageType = \"normal\";\r\n try {\r\n const parsedOptions = renderOptions(options);\r\n const userData = renderUserData(req.query as Partial<UserData>, {\r\n minWidth: parsedOptions.minWidth,\r\n maxWidth: parsedOptions.maxWidth,\r\n minHeight: parsedOptions.minHeight,\r\n maxHeight: parsedOptions.maxHeight,\r\n defaultQuality: parsedOptions.defaultQuality,\r\n });\r\n\r\n requestedType = (userData.type as ImageType) ?? \"normal\";\r\n\r\n let baseDir = parsedOptions.baseDir;\r\n let parsedUserId: string | undefined;\r\n\r\n if (userData.userId) {\r\n parsedUserId = parsedOptions.idHandler\r\n ? parsedOptions.idHandler(userData.userId)\r\n : userData.userId;\r\n }\r\n\r\n if (userData.folder === \"private\" && parsedOptions.getUserFolder) {\r\n const folderPromise = Promise.resolve(\r\n parsedOptions.getUserFolder(req, parsedUserId)\r\n );\r\n const timeoutPromise = new Promise<never>((_, reject) =>\r\n setTimeout(\r\n () => reject(new Error(\"getUserFolder timed out\")),\r\n parsedOptions.requestTimeoutMs\r\n )\r\n );\r\n try {\r\n const dir = await Promise.race([folderPromise, timeoutPromise]);\r\n if (dir) {\r\n baseDir = dir;\r\n }\r\n } catch {\r\n // getUserFolder timed out or failed — use default baseDir\r\n }\r\n }\r\n\r\n const outputFormat = allowedFormats.includes(\r\n (userData.format ?? \"\").toLowerCase() as ImageFormat\r\n )\r\n ? (userData.format as ImageFormat)\r\n : \"jpeg\";\r\n\r\n const resolveBuffer = async (): Promise<Buffer> => {\r\n if (!userData.src) {\r\n return FALLBACKIMAGES[userData.type ?? \"normal\"]();\r\n }\r\n if (\r\n userData.src.startsWith(\"http://\") ||\r\n userData.src.startsWith(\"https://\")\r\n ) {\r\n return fetchImage(\r\n userData.src,\r\n baseDir,\r\n parsedOptions.websiteURL,\r\n userData.type as ImageType,\r\n parsedOptions.apiRegex,\r\n parsedOptions.allowedNetworkList,\r\n {\r\n timeoutMs: parsedOptions.requestTimeoutMs,\r\n maxBytes: parsedOptions.maxDownloadBytes,\r\n }\r\n );\r\n }\r\n return readLocalImage(\r\n userData.src,\r\n baseDir,\r\n userData.type as ImageType,\r\n parsedOptions.maxDownloadBytes\r\n );\r\n };\r\n\r\n const imageBuffer = await resolveBuffer();\r\n let image = sharp(imageBuffer, { failOn: \"truncated\" });\r\n\r\n if (userData.width || userData.height) {\r\n const resizeOptions: ResizeOptions = {\r\n width: userData.width ?? undefined,\r\n height: userData.height ?? undefined,\r\n fit: sharp.fit.cover,\r\n withoutEnlargement: true,\r\n };\r\n image = image.resize(resizeOptions);\r\n }\r\n\r\n const processedImage = await image\r\n .rotate()\r\n .toFormat(outputFormat as keyof FormatEnum, {\r\n quality: userData.quality,\r\n })\r\n .toBuffer();\r\n\r\n const rawName = userData.src\r\n ? path.basename(userData.src, path.extname(userData.src))\r\n : \"image\";\r\n // eslint-disable-next-line no-control-regex\r\n const sourceName = rawName.replace(/[\"\\\\\\x00-\\x1F\\x7F]/g, \"_\");\r\n const processedFileName = `${sourceName}.${outputFormat}`;\r\n\r\n const etag = parsedOptions.etag\r\n ? `\"${createHash(\"sha1\").update(processedImage).digest(\"hex\")}\"`\r\n : undefined;\r\n\r\n if (etag && req.headers[\"if-none-match\"] === etag) {\r\n res.status(304).end();\r\n return;\r\n }\r\n\r\n res.type(mimeTypes[outputFormat]);\r\n res.setHeader(\r\n \"Content-Disposition\",\r\n `inline; filename=\"${processedFileName}\"`\r\n );\r\n res.setHeader(\r\n \"Cache-Control\",\r\n parsedOptions.cacheControl ??\r\n \"public, max-age=86400, stale-while-revalidate=604800\"\r\n );\r\n if (etag) {\r\n res.setHeader(\"ETag\", etag);\r\n }\r\n res.setHeader(\"Content-Length\", processedImage.length.toString());\r\n res.send(processedImage);\r\n } catch {\r\n try {\r\n const fallbackType =\r\n requestedType === \"avatar\" ? \"avatar\" : \"normal\";\r\n const fallback = await FALLBACKIMAGES[fallbackType]();\r\n res.type(mimeTypes.jpeg);\r\n res.setHeader(\"Content-Disposition\", `inline; filename=\"fallback.jpeg\"`);\r\n res.setHeader(\"Cache-Control\", \"public, max-age=60\");\r\n res.send(fallback);\r\n } catch (fallbackError) {\r\n next(fallbackError);\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * @function registerServe\r\n * @description A function to register the serveImage function as middleware for Express.\r\n * @param {PixelServeOptions} options - The options object for image processing.\r\n * @returns {function(Request, Response, NextFunction): Promise<void>} The middleware function.\r\n */\r\nconst registerServe = (\r\n options: PixelServeOptions\r\n): ((req: Request, res: Response, next: NextFunction) => Promise<void>) => {\r\n return async (req: Request, res: Response, next: NextFunction) =>\r\n serveImage(req, res, next, options);\r\n};\r\n\r\nexport default registerServe;\r\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import p from'path';import {createHash}from'crypto';import
|
|
2
|
-
export{
|
|
1
|
+
import p from'path';import {createHash}from'crypto';import C from'sharp';import*as d from'fs/promises';import {readFile}from'fs/promises';import {fileURLToPath}from'url';import $ from'axios';import {z as z$1}from'zod';var z=p.dirname(fileURLToPath(import.meta.url)),B=e=>p.join(z,"assets",e),_=B("noimage.jpg"),k=B("noavatar.png"),u={normal:async()=>readFile(_),avatar:async()=>readFile(k)},E=/^\/api\/v1\//,b=["jpeg","jpg","png","webp","gif","tiff","avif","svg"],h={jpeg:"image/jpeg",jpg:"image/jpeg",png:"image/png",webp:"image/webp",gif:"image/gif",tiff:"image/tiff",avif:"image/avif",svg:"image/svg+xml"};var O=async(e,r)=>{try{if(!e||!r||r.includes("\0")||p.isAbsolute(r)||!/^[^\x00-\x1F]+$/.test(r))return !1;let n=p.resolve(e),o=p.resolve(n,r),[s,a]=await Promise.all([d.realpath(n),d.realpath(o)]);if(!(await d.stat(s)).isDirectory())return !1;let m=s+p.sep,f=(a+p.sep).startsWith(m)||a===s,y=p.relative(s,a);return !y.startsWith("..")&&!p.isAbsolute(y)&&f}catch{return false}},G=async(e,r="normal",{timeoutMs:n,maxBytes:o})=>{try{let s=await $.get(e,{responseType:"arraybuffer",timeout:n,maxContentLength:o,maxBodyLength:o,validateStatus:m=>m>=200&&m<300}),a=s.headers["content-type"]?.toLowerCase()?.split(";")[0]?.trim();return Object.values(h).includes(a??"")?Buffer.from(s.data):await u[r]()}catch{return await u[r]()}},w=async(e,r,n="normal",o)=>{if(!await O(r,e))return await u[n]();try{let a=p.resolve(r,e);return o&&(await d.stat(a)).size>o?await u[n]():await d.readFile(a)}catch{return await u[n]()}},U=(e,r,n,o="normal",s,a=[],{timeoutMs:i,maxBytes:m})=>{try{let l=new URL(e);if(n!==void 0&&[n,`www.${n}`].includes(l.hostname)){let v=l.pathname.replace(s,"");return w(v,r,o,m)}return a.includes(l.hostname)||a.includes(l.host)?["http:","https:"].includes(l.protocol)?G(e,o,{timeoutMs:i,maxBytes:m}):u[o]():u[o]()}catch{return w(e,r,o,m)}};var Q=z$1.enum(b),V=z$1.enum(["avatar","normal"]),H=z$1.object({src:z$1.string().min(1,"src is required").optional().default("/placeholder/noimage.jpg"),format:z$1.string().optional().transform(e=>{let r=e?.toLowerCase();return r&&Q.options.includes(r)?r:void 0}).optional(),width:z$1.union([z$1.number(),z$1.string()]).optional().transform(e=>e==null?void 0:Number(e)).pipe(z$1.number().int().min(50,"width too small").max(4e3,"width too large").optional()),height:z$1.union([z$1.number(),z$1.string()]).optional().transform(e=>e==null?void 0:Number(e)).pipe(z$1.number().int().min(50,"height too small").max(4e3,"height too large").optional()),quality:z$1.union([z$1.number(),z$1.string()]).optional().transform(e=>e==null?void 0:Number(e)).pipe(z$1.number().int().min(1).max(100).default(80)),folder:z$1.enum(["public","private"]).default("public"),type:V.default("normal"),userId:z$1.union([z$1.string(),z$1.number()]).optional().transform(e=>e==null?void 0:String(e).trim()).pipe(z$1.string().min(1,"userId cannot be empty").max(128,"userId too long").optional())}).strict(),D=z$1.object({baseDir:z$1.string().min(1,"baseDir is required"),idHandler:z$1.custom(e=>typeof e=="function",{message:"idHandler must be a function"}).optional(),getUserFolder:z$1.custom(e=>typeof e=="function",{message:"getUserFolder must be a function"}).optional(),websiteURL:z$1.union([z$1.url(),z$1.string().regex(/^(?![-.])([\w]+[-.]?)*[\w]+$/)]).optional(),apiRegex:z$1.instanceof(RegExp).default(E),allowedNetworkList:z$1.array(z$1.string()).default([]),cacheControl:z$1.string().optional(),etag:z$1.boolean().default(true),minWidth:z$1.number().int().positive().default(50),maxWidth:z$1.number().int().positive().default(4e3),minHeight:z$1.number().int().positive().default(50),maxHeight:z$1.number().int().positive().default(4e3),defaultQuality:z$1.number().int().min(1).max(100).default(80),requestTimeoutMs:z$1.number().int().positive().default(5e3),maxDownloadBytes:z$1.number().int().positive().default(5e6)}).strict().refine(e=>e.minWidth<=e.maxWidth,{message:"minWidth must be less than or equal to maxWidth",path:["minWidth"]}).refine(e=>e.minHeight<=e.maxHeight,{message:"minHeight must be less than or equal to maxHeight",path:["minHeight"]});var W=e=>D.parse(e),q=(e,r)=>{let n=H.parse(e),o=(s,a,i)=>{if(s!==void 0)return Math.min(Math.max(s,a),i)};return {...n,width:o(n.width,r.minWidth,r.maxWidth),height:o(n.height,r.minHeight,r.maxHeight),quality:n.quality??r.defaultQuality,format:n.format??"jpeg"}};var X=async(e,r,n,o)=>{let s="normal";try{let a=W(o),i=q(e.query,{minWidth:a.minWidth,maxWidth:a.maxWidth,minHeight:a.minHeight,maxHeight:a.maxHeight,defaultQuality:a.defaultQuality});s=i.type??"normal";let m=a.baseDir,l;if(i.userId&&(l=a.idHandler?a.idHandler(i.userId):i.userId),i.folder==="private"&&a.getUserFolder){let P=Promise.resolve(a.getUserFolder(e,l)),L=new Promise((T,j)=>setTimeout(()=>j(new Error("getUserFolder timed out")),a.requestTimeoutMs));try{let T=await Promise.race([P,L]);T&&(m=T);}catch{}}let f=b.includes((i.format??"").toLowerCase())?i.format:"jpeg",v=await(async()=>i.src?i.src.startsWith("http://")||i.src.startsWith("https://")?U(i.src,m,a.websiteURL,i.type,a.apiRegex,a.allowedNetworkList,{timeoutMs:a.requestTimeoutMs,maxBytes:a.maxDownloadBytes}):w(i.src,m,i.type,a.maxDownloadBytes):u[i.type??"normal"]())(),F=C(v,{failOn:"truncated"});if(i.width||i.height){let P={width:i.width??void 0,height:i.height??void 0,fit:C.fit.cover,withoutEnlargement:!0};F=F.resize(P);}let I=await F.rotate().toFormat(f,{quality:i.quality}).toBuffer(),N=`${(i.src?p.basename(i.src,p.extname(i.src)):"image").replace(/["\\\x00-\x1F\x7F]/g,"_")}.${f}`,x=a.etag?`"${createHash("sha1").update(I).digest("hex")}"`:void 0;if(x&&e.headers["if-none-match"]===x){r.status(304).end();return}r.type(h[f]),r.setHeader("Content-Disposition",`inline; filename="${N}"`),r.setHeader("Cache-Control",a.cacheControl??"public, max-age=86400, stale-while-revalidate=604800"),x&&r.setHeader("ETag",x),r.setHeader("Content-Length",I.length.toString()),r.send(I);}catch{try{let i=await u[s==="avatar"?"avatar":"normal"]();r.type(h.jpeg),r.setHeader("Content-Disposition",'inline; filename="fallback.jpeg"'),r.setHeader("Cache-Control","public, max-age=60"),r.send(i);}catch(a){n(a);}}},J=e=>async(r,n,o)=>X(r,n,o,e),Y=J;
|
|
2
|
+
export{O as isValidPath,D as optionsSchema,Y as registerServe,H as userDataSchema};//# sourceMappingURL=index.mjs.map
|
|
3
3
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/variables.ts","../src/functions.ts","../src/schema.ts","../src/renders.ts","../src/pixel.ts"],"names":["moduleDir","path","fileURLToPath","getAssetPath","filename","NOT_FOUND_IMAGE","NOT_FOUND_AVATAR","FALLBACKIMAGES","readFile","API_REGEX","allowedFormats","mimeTypes","isValidPath","basePath","specifiedPath","resolvedBase","resolvedPath","realBase","realPath","normalizedBase","isInside","relative","fetchFromNetwork","src","type","timeoutMs","maxBytes","response","axios","status","contentType","readLocalImage","filePath","baseDir","fetchImage","websiteURL","apiRegex","allowedNetworkList","url","localPath","imageFormatEnum","z","imageTypeEnum","userDataSchema","val","lower","value","optionsSchema","renderOptions","options","renderUserData","userData","bounds","parsed","clamp","min","max","serveImage","req","res","next","parsedOptions","parsedUserId","dir","outputFormat","imageBuffer","image","sharp","resizeOptions","processedImage","processedFileName","etag","createHash","fallback","fallbackError","registerServe","pixel_default"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ImageFormat } from \"./types\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\n\r\n/**\r\n * Get the directory path for the current module.\r\n * Uses import.meta.url for ESM (tsup provides shims for CJS compatibility).\r\n */\r\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\r\n\r\nconst getAssetPath = (filename: string): string => {\r\n return path.join(moduleDir, \"assets\", filename);\r\n};\r\n\r\nconst NOT_FOUND_IMAGE = getAssetPath(\"noimage.jpg\");\r\nconst NOT_FOUND_AVATAR = getAssetPath(\"noavatar.png\");\r\n\r\nexport const FALLBACKIMAGES: Record<\r\n \"normal\" | \"avatar\",\r\n () => Promise<Buffer>\r\n> = {\r\n normal: async (): Promise<Buffer> => readFile(NOT_FOUND_IMAGE),\r\n avatar: async (): Promise<Buffer> => readFile(NOT_FOUND_AVATAR),\r\n};\r\n\r\nexport const API_REGEX: RegExp = /^\\/api\\/v1\\//;\r\n\r\nexport const allowedFormats: ImageFormat[] = [\r\n \"jpeg\",\r\n \"jpg\",\r\n \"png\",\r\n \"webp\",\r\n \"gif\",\r\n \"tiff\",\r\n \"avif\",\r\n \"svg\",\r\n];\r\n\r\nexport const mimeTypes: Readonly<Record<string, string>> = {\r\n jpeg: \"image/jpeg\",\r\n jpg: \"image/jpeg\",\r\n png: \"image/png\",\r\n webp: \"image/webp\",\r\n gif: \"image/gif\",\r\n tiff: \"image/tiff\",\r\n avif: \"image/avif\",\r\n svg: \"image/svg+xml\",\r\n};\r\n","import path from \"node:path\";\r\nimport * as fs from \"node:fs/promises\";\r\nimport axios from \"axios\";\r\nimport { FALLBACKIMAGES, mimeTypes } from \"./variables\";\r\nimport type { ImageType } from \"./types\";\r\n\r\n/**\r\n * @typedef {(\"avatar\" | \"normal\")} ImageType\r\n * @description Defines the type of image being processed.\r\n */\r\n\r\n/**\r\n * Checks if a specified path is valid within a base path.\r\n *\r\n * @param {string} basePath - The base directory to resolve paths.\r\n * @param {string} specifiedPath - The path to check.\r\n * @returns {Promise<boolean>} True if the path is valid, false otherwise.\r\n */\r\nexport const isValidPath = async (\r\n basePath: string,\r\n specifiedPath: string\r\n): Promise<boolean> => {\r\n try {\r\n if (!basePath || !specifiedPath) return false;\r\n if (specifiedPath.includes(\"\\0\")) return false;\r\n if (path.isAbsolute(specifiedPath)) return false;\r\n if (!/^[^\\x00-\\x1F]+$/.test(specifiedPath)) return false;\r\n\r\n const resolvedBase = path.resolve(basePath);\r\n const resolvedPath = path.resolve(resolvedBase, specifiedPath);\r\n\r\n const [realBase, realPath] = await Promise.all([\r\n fs.realpath(resolvedBase),\r\n fs.realpath(resolvedPath),\r\n ]);\r\n\r\n const baseStats = await fs.stat(realBase);\r\n if (!baseStats.isDirectory()) return false;\r\n\r\n const normalizedBase = realBase + path.sep;\r\n const normalizedPath = realPath + path.sep;\r\n\r\n const isInside =\r\n normalizedPath.startsWith(normalizedBase) || realPath === realBase;\r\n\r\n const relative = path.relative(realBase, realPath);\r\n return !relative.startsWith(\"..\") && !path.isAbsolute(relative) && isInside;\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\n/**\r\n * Fetches an image from a network source.\r\n *\r\n * @param {string} src - The URL of the image.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image in case of an error.\r\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\r\n */\r\nconst fetchFromNetwork = async (\r\n src: string,\r\n type: ImageType = \"normal\",\r\n {\r\n timeoutMs,\r\n maxBytes,\r\n }: {\r\n timeoutMs: number;\r\n maxBytes: number;\r\n }\r\n): Promise<Buffer> => {\r\n try {\r\n const response = await axios.get(src, {\r\n responseType: \"arraybuffer\",\r\n timeout: timeoutMs,\r\n maxContentLength: maxBytes,\r\n maxBodyLength: maxBytes,\r\n validateStatus: (status) => status >= 200 && status < 300,\r\n });\r\n\r\n const contentType = response.headers[\"content-type\"]?.toLowerCase();\r\n const allowedMimeTypes = Object.values(mimeTypes);\r\n\r\n if (allowedMimeTypes.includes(contentType ?? \"\")) {\r\n return Buffer.from(response.data);\r\n }\r\n return await FALLBACKIMAGES[type]();\r\n } catch (error) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n};\r\n\r\n/**\r\n * Reads an image from the local file system.\r\n *\r\n * @param {string} filePath - Path to the image file.\r\n * @param {string} baseDir - Base directory to resolve paths.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\r\n * @returns {Promise<Buffer>} A buffer containing the image data.\r\n */\r\nexport const readLocalImage = async (\r\n filePath: string,\r\n baseDir: string,\r\n type: ImageType = \"normal\"\r\n) => {\r\n const isValid = await isValidPath(baseDir, filePath);\r\n if (!isValid) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n try {\r\n return await fs.readFile(path.resolve(baseDir, filePath));\r\n } catch (error) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n};\r\n\r\n/**\r\n * Fetches an image from either a local file or a network source.\r\n *\r\n * @param {string} src - The URL or local path of the image.\r\n * @param {string} baseDir - Base directory to resolve local paths.\r\n * @param {string} websiteURL - The URL of the website.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\r\n * @param {string[]} [allowedNetworkList=[]] - List of allowed network hosts.\r\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\r\n */\r\nexport const fetchImage = (\r\n src: string,\r\n baseDir: string,\r\n websiteURL: string | undefined,\r\n type: ImageType = \"normal\",\r\n apiRegex: RegExp,\r\n allowedNetworkList: string[] = [],\r\n {\r\n timeoutMs,\r\n maxBytes,\r\n }: {\r\n timeoutMs: number;\r\n maxBytes: number;\r\n }\r\n): Promise<Buffer> => {\r\n try {\r\n const url = new URL(src);\r\n const isInternal =\r\n websiteURL !== undefined &&\r\n [websiteURL, `www.${websiteURL}`].includes(url.host);\r\n\r\n if (isInternal) {\r\n const localPath = url.pathname.replace(apiRegex, \"\");\r\n return readLocalImage(localPath, baseDir, type);\r\n }\r\n\r\n const allowedCondition = allowedNetworkList.includes(url.host);\r\n if (!allowedCondition) {\r\n return FALLBACKIMAGES[type]();\r\n }\r\n if (![\"http:\", \"https:\"].includes(url.protocol)) {\r\n return FALLBACKIMAGES[type]();\r\n }\r\n return fetchFromNetwork(src, type, { timeoutMs, maxBytes });\r\n } catch {\r\n return readLocalImage(src, baseDir, type);\r\n }\r\n};\r\n","import { z } from \"zod\";\r\nimport { API_REGEX, allowedFormats } from \"./variables\";\r\n\r\nconst imageFormatEnum = z.enum(allowedFormats as [string, ...string[]]);\r\nconst imageTypeEnum = z.enum([\"avatar\", \"normal\"]);\r\n\r\nexport const userDataSchema = z\r\n .object({\r\n src: z\r\n .string()\r\n .min(1, \"src is required\")\r\n .optional()\r\n .default(\"/placeholder/noimage.jpg\"),\r\n format: z\r\n .string()\r\n .optional()\r\n .transform((val) => {\r\n const lower = val?.toLowerCase();\r\n return lower && imageFormatEnum.options.includes(lower as string)\r\n ? (lower as (typeof imageFormatEnum)[\"options\"][number])\r\n : undefined;\r\n })\r\n .optional(),\r\n width: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(\r\n z\r\n .number()\r\n .int()\r\n .min(50, \"width too small\")\r\n .max(4000, \"width too large\")\r\n .optional()\r\n ),\r\n height: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(\r\n z\r\n .number()\r\n .int()\r\n .min(50, \"height too small\")\r\n .max(4000, \"height too large\")\r\n .optional()\r\n ),\r\n quality: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(z.number().int().min(1).max(100).default(80)),\r\n folder: z.enum([\"public\", \"private\"]).default(\"public\"),\r\n type: imageTypeEnum.default(\"normal\"),\r\n userId: z\r\n .union([z.string(), z.number()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : String(value).trim()\r\n )\r\n .pipe(\r\n z\r\n .string()\r\n .min(1, \"userId cannot be empty\")\r\n .max(128, \"userId too long\")\r\n .optional()\r\n ),\r\n })\r\n .strict();\r\n\r\nexport const optionsSchema = z\r\n .object({\r\n baseDir: z.string().min(1, \"baseDir is required\"),\r\n idHandler: z\r\n .custom<\r\n (id: string) => string\r\n >((val) => typeof val === \"function\", { message: \"idHandler must be a function\" })\r\n .optional(),\r\n getUserFolder: z\r\n .custom<\r\n (req: unknown, id?: string) => Promise<string> | string\r\n >((val) => typeof val === \"function\", { message: \"getUserFolder must be a function\" })\r\n .optional(),\r\n websiteURL: z.union([z.url(), z.string().regex(/^[\\w.-]+$/)]).optional(),\r\n apiRegex: z.instanceof(RegExp).default(API_REGEX),\r\n allowedNetworkList: z.array(z.string()).default([]),\r\n cacheControl: z.string().optional(),\r\n etag: z.boolean().default(true),\r\n minWidth: z.number().int().positive().default(50),\r\n maxWidth: z.number().int().positive().default(4000),\r\n minHeight: z.number().int().positive().default(50),\r\n maxHeight: z.number().int().positive().default(4000),\r\n defaultQuality: z.number().int().min(1).max(100).default(80),\r\n requestTimeoutMs: z.number().int().positive().default(5000),\r\n maxDownloadBytes: z.number().int().positive().default(5_000_000),\r\n })\r\n .strict();\r\n\r\nexport type ParsedUserData = z.infer<typeof userDataSchema>;\r\nexport type ParsedOptions = z.infer<typeof optionsSchema>;\r\n","import { optionsSchema, userDataSchema } from \"./schema\";\r\nimport type { ParsedOptions, ParsedUserData } from \"./schema\";\r\nimport type { PixelServeOptions, UserData } from \"./types\";\r\n\r\n/**\r\n * @typedef {(\"avatar\" | \"normal\")} ImageType\r\n * @description Defines the type of image being processed.\r\n */\r\n\r\n/**\r\n * @typedef {(\"jpeg\" | \"jpg\" | \"png\" | \"webp\" | \"gif\" | \"tiff\" | \"avif\" | \"svg\")} ImageFormat\r\n * @description Supported formats for image processing.\r\n */\r\n\r\n/**\r\n * @typedef {Object} Options\r\n * @property {string} baseDir - The base directory for public image files.\r\n * @property {function(string): string} idHandler - A function to handle user IDs.\r\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\r\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\r\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\r\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\r\n */\r\n\r\n/**\r\n * @typedef {Object} UserData\r\n * @property {number|string} quality - Quality of the image (1–100).\r\n * @property {ImageFormat} format - Desired format of the image.\r\n * @property {string} [src] - Source path or URL for the image.\r\n * @property {string} [folder] - The folder type (\"public\" or \"private\").\r\n * @property {ImageType} [type] - Type of the image (\"avatar\" or \"normal\").\r\n * @property {string|null} [userId] - Optional user identifier.\r\n * @property {number|string} [width] - Desired image width.\r\n * @property {number|string} [height] - Desired image height.\r\n */\r\n\r\n/**\r\n * Renders the options object with default values and user-provided values.\r\n *\r\n * @param {Partial<Options>} options - The user-provided options.\r\n * @returns {Options} The rendered options object.\r\n */\r\nexport const renderOptions = (options: PixelServeOptions): ParsedOptions =>\r\n optionsSchema.parse(options);\r\n\r\n/**\r\n * Renders the user data object with default values and user-provided values.\r\n *\r\n * @param {Partial<UserData>} userData - The user-provided data.\r\n * @returns {UserData} The rendered user data object.\r\n */\r\nexport const renderUserData = (\r\n userData: Partial<UserData>,\r\n bounds: {\r\n minWidth: number;\r\n maxWidth: number;\r\n minHeight: number;\r\n maxHeight: number;\r\n defaultQuality: number;\r\n }\r\n): ParsedUserData => {\r\n const parsed = userDataSchema.parse(userData);\r\n\r\n const clamp = (value: number | undefined, min: number, max: number) => {\r\n if (value === undefined) return undefined;\r\n return Math.min(Math.max(value, min), max);\r\n };\r\n\r\n return {\r\n ...parsed,\r\n width: clamp(parsed.width, bounds.minWidth, bounds.maxWidth),\r\n height: clamp(parsed.height, bounds.minHeight, bounds.maxHeight),\r\n quality: parsed.quality ?? bounds.defaultQuality,\r\n format: parsed.format ?? \"jpeg\",\r\n };\r\n};\r\n","import path from \"node:path\";\r\nimport { createHash } from \"node:crypto\";\r\nimport sharp, { FormatEnum, ResizeOptions } from \"sharp\";\r\nimport type { Request, Response, NextFunction } from \"express\";\r\nimport type {\r\n PixelServeOptions,\r\n UserData,\r\n ImageFormat,\r\n ImageType,\r\n} from \"./types\";\r\nimport { allowedFormats, FALLBACKIMAGES, mimeTypes } from \"./variables\";\r\nimport { fetchImage, readLocalImage } from \"./functions\";\r\nimport { renderOptions, renderUserData } from \"./renders\";\r\n\r\n/**\r\n * @typedef {Object} Options\r\n * @property {string} baseDir - The base directory for public image files.\r\n * @property {function(string): string} idHandler - A function to handle user IDs.\r\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\r\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\r\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\r\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\r\n */\r\n\r\n/**\r\n * @function serveImage\r\n * @description Processes and serves an image based on user data and options.\r\n * @param {Request} req - The Express request object.\r\n * @param {Response} res - The Express response object.\r\n * @param {NextFunction} next - The Express next function.\r\n * @param {PixelServeOptions} options - The options object for image processing.\r\n * @returns {Promise<void>}\r\n */\r\nconst serveImage = async (\r\n req: Request,\r\n res: Response,\r\n next: NextFunction,\r\n options: PixelServeOptions\r\n) => {\r\n try {\r\n const parsedOptions = renderOptions(options);\r\n const userData = renderUserData(req.query as Partial<UserData>, {\r\n minWidth: parsedOptions.minWidth,\r\n maxWidth: parsedOptions.maxWidth,\r\n minHeight: parsedOptions.minHeight,\r\n maxHeight: parsedOptions.maxHeight,\r\n defaultQuality: parsedOptions.defaultQuality,\r\n });\r\n\r\n let baseDir = parsedOptions.baseDir;\r\n let parsedUserId: string | undefined;\r\n\r\n if (userData.userId) {\r\n parsedUserId = parsedOptions.idHandler\r\n ? parsedOptions.idHandler(userData.userId)\r\n : userData.userId;\r\n }\r\n\r\n if (userData.folder === \"private\" && parsedOptions.getUserFolder) {\r\n const dir = await parsedOptions.getUserFolder(req, parsedUserId);\r\n if (dir) {\r\n baseDir = dir;\r\n }\r\n }\r\n\r\n const outputFormat = allowedFormats.includes(\r\n (userData.format ?? \"\").toLowerCase() as ImageFormat\r\n )\r\n ? (userData.format as ImageFormat)\r\n : \"jpeg\";\r\n\r\n const resolveBuffer = async (): Promise<Buffer> => {\r\n if (!userData.src) {\r\n return FALLBACKIMAGES[userData.type ?? \"normal\"]();\r\n }\r\n if (userData.src.startsWith(\"http\")) {\r\n return fetchImage(\r\n userData.src,\r\n baseDir,\r\n parsedOptions.websiteURL,\r\n userData.type as ImageType,\r\n parsedOptions.apiRegex,\r\n parsedOptions.allowedNetworkList,\r\n {\r\n timeoutMs: parsedOptions.requestTimeoutMs,\r\n maxBytes: parsedOptions.maxDownloadBytes,\r\n }\r\n );\r\n }\r\n return readLocalImage(userData.src, baseDir, userData.type as ImageType);\r\n };\r\n\r\n const imageBuffer = await resolveBuffer();\r\n let image = sharp(imageBuffer, { failOn: \"truncated\" });\r\n\r\n if (userData.width || userData.height) {\r\n const resizeOptions: ResizeOptions = {\r\n width: userData.width ?? undefined,\r\n height: userData.height ?? undefined,\r\n fit: sharp.fit.cover,\r\n withoutEnlargement: true,\r\n };\r\n image = image.resize(resizeOptions);\r\n }\r\n\r\n const processedImage = await image\r\n .rotate()\r\n .toFormat(outputFormat as keyof FormatEnum, {\r\n quality: userData.quality,\r\n })\r\n .toBuffer();\r\n\r\n const sourceName = userData.src\r\n ? path.basename(userData.src, path.extname(userData.src))\r\n : \"image\";\r\n const processedFileName = `${sourceName}.${outputFormat}`;\r\n\r\n const etag = parsedOptions.etag\r\n ? `\"${createHash(\"sha1\").update(processedImage).digest(\"hex\")}\"`\r\n : undefined;\r\n\r\n if (etag && req.headers[\"if-none-match\"] === etag) {\r\n res.status(304).end();\r\n return;\r\n }\r\n\r\n res.type(mimeTypes[outputFormat]);\r\n res.setHeader(\r\n \"Content-Disposition\",\r\n `inline; filename=\"${processedFileName}\"`\r\n );\r\n res.setHeader(\r\n \"Cache-Control\",\r\n parsedOptions.cacheControl ??\r\n \"public, max-age=86400, stale-while-revalidate=604800\"\r\n );\r\n if (etag) {\r\n res.setHeader(\"ETag\", etag);\r\n }\r\n res.setHeader(\"Content-Length\", processedImage.length.toString());\r\n res.send(processedImage);\r\n /* c8 ignore next */\r\n } catch (error) {\r\n try {\r\n const fallback = await FALLBACKIMAGES.normal();\r\n res.type(mimeTypes.jpeg);\r\n res.setHeader(\"Content-Disposition\", `inline; filename=\"fallback.jpeg\"`);\r\n res.setHeader(\"Cache-Control\", \"public, max-age=60\");\r\n res.send(fallback);\r\n } catch (fallbackError) {\r\n next(fallbackError);\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * @function registerServe\r\n * @description A function to register the serveImage function as middleware for Express.\r\n * @param {PixelServeOptions} options - The options object for image processing.\r\n * @returns {function(Request, Response, NextFunction): Promise<void>} The middleware function.\r\n */\r\nconst registerServe = (options: PixelServeOptions) => {\r\n return async (req: Request, res: Response, next: NextFunction) =>\r\n serveImage(req, res, next, options);\r\n};\r\n\r\nexport default registerServe;\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/variables.ts","../src/functions.ts","../src/schema.ts","../src/renders.ts","../src/pixel.ts"],"names":["moduleDir","path","fileURLToPath","getAssetPath","filename","NOT_FOUND_IMAGE","NOT_FOUND_AVATAR","FALLBACKIMAGES","readFile","API_REGEX","allowedFormats","mimeTypes","isValidPath","basePath","specifiedPath","resolvedBase","resolvedPath","realBase","realPath","normalizedBase","isInside","relative","fetchFromNetwork","src","type","timeoutMs","maxBytes","response","axios","status","contentType","readLocalImage","filePath","baseDir","resolvedFile","fetchImage","websiteURL","apiRegex","allowedNetworkList","url","localPath","imageFormatEnum","z","imageTypeEnum","userDataSchema","val","lower","value","optionsSchema","data","renderOptions","options","renderUserData","userData","bounds","parsed","clamp","min","max","serveImage","req","res","next","requestedType","parsedOptions","parsedUserId","folderPromise","timeoutPromise","_","reject","dir","outputFormat","imageBuffer","image","sharp","resizeOptions","processedImage","processedFileName","etag","createHash","fallback","fallbackError","registerServe","pixel_default"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ImageFormat } from \"./types\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\n\r\n/**\r\n * Get the directory path for the current module.\r\n * Uses import.meta.url for ESM (tsup provides shims for CJS compatibility).\r\n */\r\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\r\n\r\nconst getAssetPath = (filename: string): string => {\r\n return path.join(moduleDir, \"assets\", filename);\r\n};\r\n\r\nconst NOT_FOUND_IMAGE = getAssetPath(\"noimage.jpg\");\r\nconst NOT_FOUND_AVATAR = getAssetPath(\"noavatar.png\");\r\n\r\nexport const FALLBACKIMAGES: Record<\r\n \"normal\" | \"avatar\",\r\n () => Promise<Buffer>\r\n> = {\r\n normal: async (): Promise<Buffer> => readFile(NOT_FOUND_IMAGE),\r\n avatar: async (): Promise<Buffer> => readFile(NOT_FOUND_AVATAR),\r\n};\r\n\r\nexport const API_REGEX: RegExp = /^\\/api\\/v1\\//;\r\n\r\nexport const allowedFormats: ImageFormat[] = [\r\n \"jpeg\",\r\n \"jpg\",\r\n \"png\",\r\n \"webp\",\r\n \"gif\",\r\n \"tiff\",\r\n \"avif\",\r\n \"svg\",\r\n];\r\n\r\nexport const mimeTypes: Readonly<Record<string, string>> = {\r\n jpeg: \"image/jpeg\",\r\n jpg: \"image/jpeg\",\r\n png: \"image/png\",\r\n webp: \"image/webp\",\r\n gif: \"image/gif\",\r\n tiff: \"image/tiff\",\r\n avif: \"image/avif\",\r\n svg: \"image/svg+xml\",\r\n};\r\n","import path from \"node:path\";\r\nimport * as fs from \"node:fs/promises\";\r\nimport axios from \"axios\";\r\nimport { FALLBACKIMAGES, mimeTypes } from \"./variables\";\r\nimport type { ImageType } from \"./types\";\r\n\r\n/**\r\n * @typedef {(\"avatar\" | \"normal\")} ImageType\r\n * @description Defines the type of image being processed.\r\n */\r\n\r\n/**\r\n * Checks if a specified path is valid within a base path.\r\n *\r\n * @param {string} basePath - The base directory to resolve paths.\r\n * @param {string} specifiedPath - The path to check.\r\n * @returns {Promise<boolean>} True if the path is valid, false otherwise.\r\n */\r\nexport const isValidPath = async (\r\n basePath: string,\r\n specifiedPath: string\r\n): Promise<boolean> => {\r\n try {\r\n if (!basePath || !specifiedPath) return false;\r\n if (specifiedPath.includes(\"\\0\")) return false;\r\n if (path.isAbsolute(specifiedPath)) return false;\r\n // eslint-disable-next-line no-control-regex\r\n if (!/^[^\\x00-\\x1F]+$/.test(specifiedPath)) return false;\r\n\r\n const resolvedBase = path.resolve(basePath);\r\n const resolvedPath = path.resolve(resolvedBase, specifiedPath);\r\n\r\n const [realBase, realPath] = await Promise.all([\r\n fs.realpath(resolvedBase),\r\n fs.realpath(resolvedPath),\r\n ]);\r\n\r\n const baseStats = await fs.stat(realBase);\r\n if (!baseStats.isDirectory()) return false;\r\n\r\n const normalizedBase = realBase + path.sep;\r\n const normalizedPath = realPath + path.sep;\r\n\r\n const isInside =\r\n normalizedPath.startsWith(normalizedBase) || realPath === realBase;\r\n\r\n const relative = path.relative(realBase, realPath);\r\n return !relative.startsWith(\"..\") && !path.isAbsolute(relative) && isInside;\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\n/**\r\n * Fetches an image from a network source.\r\n *\r\n * @param {string} src - The URL of the image.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image in case of an error.\r\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\r\n */\r\nconst fetchFromNetwork = async (\r\n src: string,\r\n type: ImageType = \"normal\",\r\n {\r\n timeoutMs,\r\n maxBytes,\r\n }: {\r\n timeoutMs: number;\r\n maxBytes: number;\r\n }\r\n): Promise<Buffer> => {\r\n try {\r\n const response = await axios.get(src, {\r\n responseType: \"arraybuffer\",\r\n timeout: timeoutMs,\r\n maxContentLength: maxBytes,\r\n maxBodyLength: maxBytes,\r\n validateStatus: (status) => status >= 200 && status < 300,\r\n });\r\n\r\n const contentType = response.headers[\"content-type\"]\r\n ?.toLowerCase()\r\n ?.split(\";\")[0]\r\n ?.trim();\r\n const allowedMimeTypes = Object.values(mimeTypes);\r\n\r\n if (allowedMimeTypes.includes(contentType ?? \"\")) {\r\n return Buffer.from(response.data);\r\n }\r\n return await FALLBACKIMAGES[type]();\r\n } catch {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n};\r\n\r\n/**\r\n * Reads an image from the local file system.\r\n *\r\n * @param {string} filePath - Path to the image file.\r\n * @param {string} baseDir - Base directory to resolve paths.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\r\n * @returns {Promise<Buffer>} A buffer containing the image data.\r\n */\r\nexport const readLocalImage = async (\r\n filePath: string,\r\n baseDir: string,\r\n type: ImageType = \"normal\",\r\n maxBytes?: number\r\n): Promise<Buffer> => {\r\n const isValid = await isValidPath(baseDir, filePath);\r\n if (!isValid) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n try {\r\n const resolvedFile = path.resolve(baseDir, filePath);\r\n if (maxBytes) {\r\n const stats = await fs.stat(resolvedFile);\r\n if (stats.size > maxBytes) {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n }\r\n return await fs.readFile(resolvedFile);\r\n } catch {\r\n return await FALLBACKIMAGES[type]();\r\n }\r\n};\r\n\r\n/**\r\n * Fetches an image from either a local file or a network source.\r\n *\r\n * @param {string} src - The URL or local path of the image.\r\n * @param {string} baseDir - Base directory to resolve local paths.\r\n * @param {string} websiteURL - The URL of the website.\r\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\r\n * @param {string[]} [allowedNetworkList=[]] - List of allowed network hosts.\r\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\r\n */\r\nexport const fetchImage = (\r\n src: string,\r\n baseDir: string,\r\n websiteURL: string | undefined,\r\n type: ImageType = \"normal\",\r\n apiRegex: RegExp,\r\n allowedNetworkList: string[] = [],\r\n {\r\n timeoutMs,\r\n maxBytes,\r\n }: {\r\n timeoutMs: number;\r\n maxBytes: number;\r\n }\r\n): Promise<Buffer> => {\r\n try {\r\n const url = new URL(src);\r\n const isInternal =\r\n websiteURL !== undefined &&\r\n [websiteURL, `www.${websiteURL}`].includes(url.hostname);\r\n\r\n if (isInternal) {\r\n const localPath = url.pathname.replace(apiRegex, \"\");\r\n return readLocalImage(localPath, baseDir, type, maxBytes);\r\n }\r\n\r\n const allowedCondition =\r\n allowedNetworkList.includes(url.hostname) ||\r\n allowedNetworkList.includes(url.host);\r\n if (!allowedCondition) {\r\n return FALLBACKIMAGES[type]();\r\n }\r\n if (![\"http:\", \"https:\"].includes(url.protocol)) {\r\n return FALLBACKIMAGES[type]();\r\n }\r\n return fetchFromNetwork(src, type, { timeoutMs, maxBytes });\r\n } catch {\r\n return readLocalImage(src, baseDir, type, maxBytes);\r\n }\r\n};\r\n","import { z } from \"zod\";\r\nimport { API_REGEX, allowedFormats } from \"./variables\";\r\n\r\nconst imageFormatEnum = z.enum(allowedFormats as [string, ...string[]]);\r\nconst imageTypeEnum = z.enum([\"avatar\", \"normal\"]);\r\n\r\nexport const userDataSchema = z\r\n .object({\r\n src: z\r\n .string()\r\n .min(1, \"src is required\")\r\n .optional()\r\n .default(\"/placeholder/noimage.jpg\"),\r\n format: z\r\n .string()\r\n .optional()\r\n .transform((val) => {\r\n const lower = val?.toLowerCase();\r\n return lower && imageFormatEnum.options.includes(lower as string)\r\n ? (lower as (typeof imageFormatEnum)[\"options\"][number])\r\n : undefined;\r\n })\r\n .optional(),\r\n width: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(\r\n z\r\n .number()\r\n .int()\r\n .min(50, \"width too small\")\r\n .max(4000, \"width too large\")\r\n .optional()\r\n ),\r\n height: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(\r\n z\r\n .number()\r\n .int()\r\n .min(50, \"height too small\")\r\n .max(4000, \"height too large\")\r\n .optional()\r\n ),\r\n quality: z\r\n .union([z.number(), z.string()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : Number(value)\r\n )\r\n .pipe(z.number().int().min(1).max(100).default(80)),\r\n folder: z.enum([\"public\", \"private\"]).default(\"public\"),\r\n type: imageTypeEnum.default(\"normal\"),\r\n userId: z\r\n .union([z.string(), z.number()])\r\n .optional()\r\n .transform((value) =>\r\n value === undefined || value === null ? undefined : String(value).trim()\r\n )\r\n .pipe(\r\n z\r\n .string()\r\n .min(1, \"userId cannot be empty\")\r\n .max(128, \"userId too long\")\r\n .optional()\r\n ),\r\n })\r\n .strict();\r\n\r\nexport const optionsSchema = z\r\n .object({\r\n baseDir: z.string().min(1, \"baseDir is required\"),\r\n idHandler: z\r\n .custom<\r\n (id: string) => string\r\n >((val) => typeof val === \"function\", { message: \"idHandler must be a function\" })\r\n .optional(),\r\n getUserFolder: z\r\n .custom<\r\n (req: unknown, id?: string) => Promise<string> | string\r\n >((val) => typeof val === \"function\", { message: \"getUserFolder must be a function\" })\r\n .optional(),\r\n websiteURL: z\r\n .union([z.url(), z.string().regex(/^(?![-.])([\\w]+[-.]?)*[\\w]+$/)])\r\n .optional(),\r\n apiRegex: z.instanceof(RegExp).default(API_REGEX),\r\n allowedNetworkList: z.array(z.string()).default([]),\r\n cacheControl: z.string().optional(),\r\n etag: z.boolean().default(true),\r\n minWidth: z.number().int().positive().default(50),\r\n maxWidth: z.number().int().positive().default(4000),\r\n minHeight: z.number().int().positive().default(50),\r\n maxHeight: z.number().int().positive().default(4000),\r\n defaultQuality: z.number().int().min(1).max(100).default(80),\r\n requestTimeoutMs: z.number().int().positive().default(5000),\r\n maxDownloadBytes: z.number().int().positive().default(5_000_000),\r\n })\r\n .strict()\r\n .refine((data) => data.minWidth <= data.maxWidth, {\r\n message: \"minWidth must be less than or equal to maxWidth\",\r\n path: [\"minWidth\"],\r\n })\r\n .refine((data) => data.minHeight <= data.maxHeight, {\r\n message: \"minHeight must be less than or equal to maxHeight\",\r\n path: [\"minHeight\"],\r\n });\r\n\r\nexport type ParsedUserData = z.infer<typeof userDataSchema>;\r\nexport type ParsedOptions = z.infer<typeof optionsSchema>;\r\n","import { optionsSchema, userDataSchema } from \"./schema\";\r\nimport type { ParsedOptions, ParsedUserData } from \"./schema\";\r\nimport type { PixelServeOptions, UserData } from \"./types\";\r\n\r\n/**\r\n * @typedef {(\"avatar\" | \"normal\")} ImageType\r\n * @description Defines the type of image being processed.\r\n */\r\n\r\n/**\r\n * @typedef {(\"jpeg\" | \"jpg\" | \"png\" | \"webp\" | \"gif\" | \"tiff\" | \"avif\" | \"svg\")} ImageFormat\r\n * @description Supported formats for image processing.\r\n */\r\n\r\n/**\r\n * @typedef {Object} Options\r\n * @property {string} baseDir - The base directory for public image files.\r\n * @property {function(string): string} idHandler - A function to handle user IDs.\r\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\r\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\r\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\r\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\r\n */\r\n\r\n/**\r\n * @typedef {Object} UserData\r\n * @property {number|string} quality - Quality of the image (1–100).\r\n * @property {ImageFormat} format - Desired format of the image.\r\n * @property {string} [src] - Source path or URL for the image.\r\n * @property {string} [folder] - The folder type (\"public\" or \"private\").\r\n * @property {ImageType} [type] - Type of the image (\"avatar\" or \"normal\").\r\n * @property {string|null} [userId] - Optional user identifier.\r\n * @property {number|string} [width] - Desired image width.\r\n * @property {number|string} [height] - Desired image height.\r\n */\r\n\r\n/**\r\n * Renders the options object with default values and user-provided values.\r\n *\r\n * @param {Partial<Options>} options - The user-provided options.\r\n * @returns {Options} The rendered options object.\r\n */\r\nexport const renderOptions = (options: PixelServeOptions): ParsedOptions =>\r\n optionsSchema.parse(options);\r\n\r\n/**\r\n * Renders the user data object with default values and user-provided values.\r\n *\r\n * @param {Partial<UserData>} userData - The user-provided data.\r\n * @returns {UserData} The rendered user data object.\r\n */\r\nexport const renderUserData = (\r\n userData: Partial<UserData>,\r\n bounds: {\r\n minWidth: number;\r\n maxWidth: number;\r\n minHeight: number;\r\n maxHeight: number;\r\n defaultQuality: number;\r\n }\r\n): ParsedUserData => {\r\n const parsed = userDataSchema.parse(userData);\r\n\r\n const clamp = (value: number | undefined, min: number, max: number): number | undefined => {\r\n if (value === undefined) return undefined;\r\n return Math.min(Math.max(value, min), max);\r\n };\r\n\r\n return {\r\n ...parsed,\r\n width: clamp(parsed.width, bounds.minWidth, bounds.maxWidth),\r\n height: clamp(parsed.height, bounds.minHeight, bounds.maxHeight),\r\n quality: parsed.quality ?? bounds.defaultQuality,\r\n format: parsed.format ?? \"jpeg\",\r\n };\r\n};\r\n","import path from \"node:path\";\r\nimport { createHash } from \"node:crypto\";\r\nimport sharp, { FormatEnum, ResizeOptions } from \"sharp\";\r\nimport type { Request, Response, NextFunction } from \"express\";\r\nimport type {\r\n PixelServeOptions,\r\n UserData,\r\n ImageFormat,\r\n ImageType,\r\n} from \"./types\";\r\nimport { allowedFormats, FALLBACKIMAGES, mimeTypes } from \"./variables\";\r\nimport { fetchImage, readLocalImage } from \"./functions\";\r\nimport { renderOptions, renderUserData } from \"./renders\";\r\n\r\n/**\r\n * @typedef {Object} Options\r\n * @property {string} baseDir - The base directory for public image files.\r\n * @property {function(string): string} idHandler - A function to handle user IDs.\r\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\r\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\r\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\r\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\r\n */\r\n\r\n/**\r\n * @function serveImage\r\n * @description Processes and serves an image based on user data and options.\r\n * @param {Request} req - The Express request object.\r\n * @param {Response} res - The Express response object.\r\n * @param {NextFunction} next - The Express next function.\r\n * @param {PixelServeOptions} options - The options object for image processing.\r\n * @returns {Promise<void>}\r\n */\r\nconst serveImage = async (\r\n req: Request,\r\n res: Response,\r\n next: NextFunction,\r\n options: PixelServeOptions\r\n): Promise<void> => {\r\n let requestedType: ImageType = \"normal\";\r\n try {\r\n const parsedOptions = renderOptions(options);\r\n const userData = renderUserData(req.query as Partial<UserData>, {\r\n minWidth: parsedOptions.minWidth,\r\n maxWidth: parsedOptions.maxWidth,\r\n minHeight: parsedOptions.minHeight,\r\n maxHeight: parsedOptions.maxHeight,\r\n defaultQuality: parsedOptions.defaultQuality,\r\n });\r\n\r\n requestedType = (userData.type as ImageType) ?? \"normal\";\r\n\r\n let baseDir = parsedOptions.baseDir;\r\n let parsedUserId: string | undefined;\r\n\r\n if (userData.userId) {\r\n parsedUserId = parsedOptions.idHandler\r\n ? parsedOptions.idHandler(userData.userId)\r\n : userData.userId;\r\n }\r\n\r\n if (userData.folder === \"private\" && parsedOptions.getUserFolder) {\r\n const folderPromise = Promise.resolve(\r\n parsedOptions.getUserFolder(req, parsedUserId)\r\n );\r\n const timeoutPromise = new Promise<never>((_, reject) =>\r\n setTimeout(\r\n () => reject(new Error(\"getUserFolder timed out\")),\r\n parsedOptions.requestTimeoutMs\r\n )\r\n );\r\n try {\r\n const dir = await Promise.race([folderPromise, timeoutPromise]);\r\n if (dir) {\r\n baseDir = dir;\r\n }\r\n } catch {\r\n // getUserFolder timed out or failed — use default baseDir\r\n }\r\n }\r\n\r\n const outputFormat = allowedFormats.includes(\r\n (userData.format ?? \"\").toLowerCase() as ImageFormat\r\n )\r\n ? (userData.format as ImageFormat)\r\n : \"jpeg\";\r\n\r\n const resolveBuffer = async (): Promise<Buffer> => {\r\n if (!userData.src) {\r\n return FALLBACKIMAGES[userData.type ?? \"normal\"]();\r\n }\r\n if (\r\n userData.src.startsWith(\"http://\") ||\r\n userData.src.startsWith(\"https://\")\r\n ) {\r\n return fetchImage(\r\n userData.src,\r\n baseDir,\r\n parsedOptions.websiteURL,\r\n userData.type as ImageType,\r\n parsedOptions.apiRegex,\r\n parsedOptions.allowedNetworkList,\r\n {\r\n timeoutMs: parsedOptions.requestTimeoutMs,\r\n maxBytes: parsedOptions.maxDownloadBytes,\r\n }\r\n );\r\n }\r\n return readLocalImage(\r\n userData.src,\r\n baseDir,\r\n userData.type as ImageType,\r\n parsedOptions.maxDownloadBytes\r\n );\r\n };\r\n\r\n const imageBuffer = await resolveBuffer();\r\n let image = sharp(imageBuffer, { failOn: \"truncated\" });\r\n\r\n if (userData.width || userData.height) {\r\n const resizeOptions: ResizeOptions = {\r\n width: userData.width ?? undefined,\r\n height: userData.height ?? undefined,\r\n fit: sharp.fit.cover,\r\n withoutEnlargement: true,\r\n };\r\n image = image.resize(resizeOptions);\r\n }\r\n\r\n const processedImage = await image\r\n .rotate()\r\n .toFormat(outputFormat as keyof FormatEnum, {\r\n quality: userData.quality,\r\n })\r\n .toBuffer();\r\n\r\n const rawName = userData.src\r\n ? path.basename(userData.src, path.extname(userData.src))\r\n : \"image\";\r\n // eslint-disable-next-line no-control-regex\r\n const sourceName = rawName.replace(/[\"\\\\\\x00-\\x1F\\x7F]/g, \"_\");\r\n const processedFileName = `${sourceName}.${outputFormat}`;\r\n\r\n const etag = parsedOptions.etag\r\n ? `\"${createHash(\"sha1\").update(processedImage).digest(\"hex\")}\"`\r\n : undefined;\r\n\r\n if (etag && req.headers[\"if-none-match\"] === etag) {\r\n res.status(304).end();\r\n return;\r\n }\r\n\r\n res.type(mimeTypes[outputFormat]);\r\n res.setHeader(\r\n \"Content-Disposition\",\r\n `inline; filename=\"${processedFileName}\"`\r\n );\r\n res.setHeader(\r\n \"Cache-Control\",\r\n parsedOptions.cacheControl ??\r\n \"public, max-age=86400, stale-while-revalidate=604800\"\r\n );\r\n if (etag) {\r\n res.setHeader(\"ETag\", etag);\r\n }\r\n res.setHeader(\"Content-Length\", processedImage.length.toString());\r\n res.send(processedImage);\r\n } catch {\r\n try {\r\n const fallbackType =\r\n requestedType === \"avatar\" ? \"avatar\" : \"normal\";\r\n const fallback = await FALLBACKIMAGES[fallbackType]();\r\n res.type(mimeTypes.jpeg);\r\n res.setHeader(\"Content-Disposition\", `inline; filename=\"fallback.jpeg\"`);\r\n res.setHeader(\"Cache-Control\", \"public, max-age=60\");\r\n res.send(fallback);\r\n } catch (fallbackError) {\r\n next(fallbackError);\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * @function registerServe\r\n * @description A function to register the serveImage function as middleware for Express.\r\n * @param {PixelServeOptions} options - The options object for image processing.\r\n * @returns {function(Request, Response, NextFunction): Promise<void>} The middleware function.\r\n */\r\nconst registerServe = (\r\n options: PixelServeOptions\r\n): ((req: Request, res: Response, next: NextFunction) => Promise<void>) => {\r\n return async (req: Request, res: Response, next: NextFunction) =>\r\n serveImage(req, res, next, options);\r\n};\r\n\r\nexport default registerServe;\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixel-serve-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A robust Node.js utility for handling and processing images. This package provides features like resizing, format conversion and etc.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"supertest": "^7.1.4",
|
|
65
65
|
"tsup": "^8.5.1",
|
|
66
66
|
"typescript": "^5.9.3",
|
|
67
|
+
"typescript-eslint": "^8.56.0",
|
|
67
68
|
"vitest": "^4.0.15"
|
|
68
69
|
},
|
|
69
70
|
"engines": {
|