@websolutespa/bom-mixer-image 1.8.2 → 1.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @websolutespa/bom-mixer-image
2
2
 
3
+ ## 1.8.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Added: ImageStrategy.Token
8
+ - Updated dependencies
9
+ - @websolutespa/bom-core@1.8.4
10
+
3
11
  ## 1.8.2
4
12
 
5
13
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,7 +1,15 @@
1
- declare function imageHandler({ domains, sizes, quality, }: {
1
+ import { IImageResize } from '@websolutespa/bom-core';
2
+
3
+ type IImageHandlerOptions = {
2
4
  domains: string[];
3
5
  sizes: number[];
4
6
  quality: number;
5
- }): any;
7
+ };
8
+ type IImageOptions = IImageResize & {
9
+ origin: string;
10
+ search: string;
11
+ url: string;
12
+ };
13
+ declare function imageHandler({ domains, sizes, quality, }: IImageHandlerOptions): any;
6
14
 
7
- export { imageHandler };
15
+ export { IImageHandlerOptions, IImageOptions, imageHandler };
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ module.exports = __toCommonJS(src_exports);
38
38
  var fs = __toESM(require("fs"));
39
39
  var path = __toESM(require("path"));
40
40
  var import_sharp = __toESM(require("sharp"));
41
+ var import_bom_core = require("@websolutespa/bom-core");
41
42
 
42
43
  // src/api/api.handler.ts
43
44
  function apiHandler(handler) {
@@ -67,7 +68,10 @@ function errorHandler(error, response) {
67
68
  }
68
69
 
69
70
  // src/image/image.handler.ts
71
+ var IMAGE_STRATEGY = process.env.IMAGE_STRATEGY ? process.env.IMAGE_STRATEGY : import_bom_core.ImageStrategy.Mixer;
70
72
  var IMAGE_MAX_AGE = 604800;
73
+ var IMAGE_FOCAL_X = 50;
74
+ var IMAGE_FOCAL_Y = 50;
71
75
  var IMAGE_QUALITY = 90;
72
76
  function imageHandler({
73
77
  domains = [],
@@ -78,26 +82,17 @@ function imageHandler({
78
82
  // Defining the 'get' method for handling GET requests
79
83
  get: async (request, response) => {
80
84
  try {
81
- const { slugs, origin = "", search = "", w, h, x, y, q } = request.query;
82
- if (!Array.isArray(slugs) || slugs.length === 0) {
83
- return response.status(400).send("missing slugs parameter");
84
- }
85
- const width = typeof w === "string" ? parseInt(w) : void 0;
86
- if (!width) {
87
- return response.status(400).send("missing w parameter");
88
- }
89
- if (!sizes.includes(width)) {
90
- return response.status(400).send("invalid w parameter");
91
- }
92
- const height = typeof h === "string" ? parseInt(h) : void 0;
93
- const focalX = typeof x === "string" ? parseInt(x) : 50;
94
- const focalY = typeof y === "string" ? parseInt(y) : 50;
95
- const imageQuality = Math.max(30, Math.min(100, typeof q === "string" ? parseInt(q) : quality));
96
- if (imageQuality !== quality) {
97
- return response.status(400).send("invalid q parameter");
85
+ let imageOptions;
86
+ try {
87
+ imageOptions = IMAGE_STRATEGY === import_bom_core.ImageStrategy.Token ? getEncryptedImageOptions(request, { domains, sizes, quality }) : getImageOptions(request, { domains, sizes, quality });
88
+ } catch (error) {
89
+ return response.status(400).json({
90
+ errors: [{
91
+ message: error
92
+ }]
93
+ });
98
94
  }
99
- const url = origin + "/" + slugs.join("/") + search;
100
- const cachedName = urlToCachedName(url, width, height, focalX, focalY, imageQuality);
95
+ const cachedName = urlToCachedName(imageOptions);
101
96
  if (fs.existsSync(cachedName)) {
102
97
  const cachedImage = await getCachedImage(cachedName);
103
98
  response.setHeader("Content-Type", "image/webp");
@@ -111,15 +106,15 @@ function imageHandler({
111
106
  if (referer && publicUrl !== "https://" && referer.indexOf(publicUrl) !== 0) {
112
107
  return response.status(401).send("you are not authorized to perform this action");
113
108
  }
114
- if (url.indexOf("http") === -1 && isVercel()) {
115
- response.redirect(307, url);
109
+ if (imageOptions.url.indexOf("http") === -1 && isVercel()) {
110
+ response.redirect(307, imageOptions.url);
116
111
  }
117
- const originalImageBuffer = await getOriginalImage(url, domains, request.headers);
112
+ const originalImageBuffer = await getOriginalImage(imageOptions.url, domains, request.headers);
118
113
  if (originalImageBuffer) {
119
114
  const originalImage = (0, import_sharp.default)(originalImageBuffer);
120
- const resizedImage = await resizeImage(originalImage, width, height, focalX, focalY);
115
+ const resizedImage = await resizeImage(originalImage, imageOptions);
121
116
  const compressedImage = resizedImage.webp({
122
- quality: imageQuality
117
+ quality: imageOptions.q
123
118
  // nearLossless: true,
124
119
  });
125
120
  compressedImage.toFile(cachedName).then(() => console.log("imageHandler.sharp.cached", cachedName)).catch((error) => console.error("imageHandler.sharp.error", cachedName, error));
@@ -131,12 +126,76 @@ function imageHandler({
131
126
  }
132
127
  } catch (error) {
133
128
  console.error("imageHandler.error", error);
134
- throw error;
129
+ return response.status(error.status || 500).json({
130
+ errors: [{
131
+ message: error.message || error.name || error.statusText || "An error occurred"
132
+ }]
133
+ });
135
134
  }
136
135
  }
137
136
  });
138
137
  }
139
- async function resizeImage(originalImage, resizeWidth, resizeHeight, focalX, focalY) {
138
+ function getImageOptions(request, options) {
139
+ const { slugs, origin = "", search = "", w, h, x, y, q } = request.query;
140
+ if (!Array.isArray(slugs) || slugs.length === 0) {
141
+ throw "missing slugs parameter";
142
+ }
143
+ const width = typeof w === "string" ? parseInt(w) : void 0;
144
+ if (!width) {
145
+ throw "missing w parameter";
146
+ }
147
+ if (!options.sizes.includes(width)) {
148
+ throw "invalid w parameter";
149
+ }
150
+ const height = typeof h === "string" ? parseInt(h) : void 0;
151
+ const focalX = typeof x === "string" ? parseInt(x) : IMAGE_FOCAL_X;
152
+ const focalY = typeof y === "string" ? parseInt(y) : IMAGE_FOCAL_Y;
153
+ const imageQuality = Math.max(30, Math.min(100, typeof q === "string" ? parseInt(q) : options.quality));
154
+ if (imageQuality !== options.quality) {
155
+ throw "invalid q parameter";
156
+ }
157
+ const url = origin + "/" + slugs.join("/") + search;
158
+ return height ? {
159
+ w: width,
160
+ h: height,
161
+ x: focalX,
162
+ y: focalY,
163
+ q: imageQuality,
164
+ src: url,
165
+ url,
166
+ origin: String(origin),
167
+ search: String(search)
168
+ } : {
169
+ w: width,
170
+ q: imageQuality,
171
+ src: url,
172
+ url,
173
+ origin: String(origin),
174
+ search: String(search)
175
+ };
176
+ }
177
+ function getEncryptedImageOptions(request, options) {
178
+ const { slugs, origin = "", search = "", t } = request.query;
179
+ if (!Array.isArray(slugs) || slugs.length === 0) {
180
+ throw "missing slugs parameter";
181
+ }
182
+ if (typeof t !== "string") {
183
+ throw "missing t parameter";
184
+ }
185
+ const imageResize = (0, import_bom_core.decryptImageResize)(t);
186
+ const url = origin + "/" + slugs.join("/") + search;
187
+ if (imageResize.src !== url) {
188
+ throw "invalid src parameter";
189
+ }
190
+ return {
191
+ ...imageResize,
192
+ url,
193
+ origin: String(origin),
194
+ search: String(search)
195
+ };
196
+ }
197
+ async function resizeImage(originalImage, imageOptions) {
198
+ const { w: resizeWidth, h: resizeHeight, x: focalX = IMAGE_FOCAL_X, y: focalY = IMAGE_FOCAL_Y } = imageOptions;
140
199
  const defaultResizeOptions = () => {
141
200
  return originalImage.resize({
142
201
  width: resizeWidth,
@@ -146,7 +205,7 @@ async function resizeImage(originalImage, resizeWidth, resizeHeight, focalX, foc
146
205
  withoutEnlargement: true
147
206
  });
148
207
  };
149
- if (resizeHeight && (focalX !== 50 || focalY !== 50)) {
208
+ if (resizeHeight && (focalX !== IMAGE_FOCAL_X || focalY !== IMAGE_FOCAL_Y)) {
150
209
  const { width: originalWidth, height: originalHeight } = await originalImage.metadata();
151
210
  if (originalWidth && originalHeight) {
152
211
  const resizeAspectRatio = resizeWidth / resizeHeight;
@@ -242,14 +301,14 @@ function urlToFileName(url) {
242
301
  function isVercel() {
243
302
  return process && process.env && String(process.env.VERCEL) === "1";
244
303
  }
245
- function urlToCachedName(url, width, height, focalX, focalY, quality) {
246
- const fileName = urlToFileName(url);
304
+ function urlToCachedName(options) {
305
+ const fileName = urlToFileName(options.url);
247
306
  const parsedName = path.parse(fileName);
248
307
  const cacheDir = isVercel() ? path.join("/tmp", ".cache") : path.join(process.cwd(), ".cache");
249
308
  if (!fs.existsSync(cacheDir)) {
250
309
  fs.mkdirSync(cacheDir);
251
310
  }
252
- const cachedName = path.join(cacheDir, `${parsedName.name}_w_${width}_h_${height || 0}_x_${focalX || 50}_y_${focalY || 50}_q_${quality}${parsedName.ext}`);
311
+ const cachedName = options.h ? path.join(cacheDir, `${parsedName.name}_w_${options.w}_h_${options.h || 0}_x_${options.x || IMAGE_FOCAL_X}_y_${options.y || IMAGE_FOCAL_Y}_q_${options.q}${parsedName.ext}`) : path.join(cacheDir, `${parsedName.name}_w_${options.w}_q_${options.q}${parsedName.ext}`);
253
312
  return cachedName;
254
313
  }
255
314
  function checkUrlValidity(src, domains) {
package/dist/index.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import sharp from "sharp";
5
+ import { decryptImageResize, ImageStrategy } from "@websolutespa/bom-core";
5
6
 
6
7
  // src/api/api.handler.ts
7
8
  function apiHandler(handler) {
@@ -31,7 +32,10 @@ function errorHandler(error, response) {
31
32
  }
32
33
 
33
34
  // src/image/image.handler.ts
35
+ var IMAGE_STRATEGY = process.env.IMAGE_STRATEGY ? process.env.IMAGE_STRATEGY : ImageStrategy.Mixer;
34
36
  var IMAGE_MAX_AGE = 604800;
37
+ var IMAGE_FOCAL_X = 50;
38
+ var IMAGE_FOCAL_Y = 50;
35
39
  var IMAGE_QUALITY = 90;
36
40
  function imageHandler({
37
41
  domains = [],
@@ -42,26 +46,17 @@ function imageHandler({
42
46
  // Defining the 'get' method for handling GET requests
43
47
  get: async (request, response) => {
44
48
  try {
45
- const { slugs, origin = "", search = "", w, h, x, y, q } = request.query;
46
- if (!Array.isArray(slugs) || slugs.length === 0) {
47
- return response.status(400).send("missing slugs parameter");
48
- }
49
- const width = typeof w === "string" ? parseInt(w) : void 0;
50
- if (!width) {
51
- return response.status(400).send("missing w parameter");
52
- }
53
- if (!sizes.includes(width)) {
54
- return response.status(400).send("invalid w parameter");
55
- }
56
- const height = typeof h === "string" ? parseInt(h) : void 0;
57
- const focalX = typeof x === "string" ? parseInt(x) : 50;
58
- const focalY = typeof y === "string" ? parseInt(y) : 50;
59
- const imageQuality = Math.max(30, Math.min(100, typeof q === "string" ? parseInt(q) : quality));
60
- if (imageQuality !== quality) {
61
- return response.status(400).send("invalid q parameter");
49
+ let imageOptions;
50
+ try {
51
+ imageOptions = IMAGE_STRATEGY === ImageStrategy.Token ? getEncryptedImageOptions(request, { domains, sizes, quality }) : getImageOptions(request, { domains, sizes, quality });
52
+ } catch (error) {
53
+ return response.status(400).json({
54
+ errors: [{
55
+ message: error
56
+ }]
57
+ });
62
58
  }
63
- const url = origin + "/" + slugs.join("/") + search;
64
- const cachedName = urlToCachedName(url, width, height, focalX, focalY, imageQuality);
59
+ const cachedName = urlToCachedName(imageOptions);
65
60
  if (fs.existsSync(cachedName)) {
66
61
  const cachedImage = await getCachedImage(cachedName);
67
62
  response.setHeader("Content-Type", "image/webp");
@@ -75,15 +70,15 @@ function imageHandler({
75
70
  if (referer && publicUrl !== "https://" && referer.indexOf(publicUrl) !== 0) {
76
71
  return response.status(401).send("you are not authorized to perform this action");
77
72
  }
78
- if (url.indexOf("http") === -1 && isVercel()) {
79
- response.redirect(307, url);
73
+ if (imageOptions.url.indexOf("http") === -1 && isVercel()) {
74
+ response.redirect(307, imageOptions.url);
80
75
  }
81
- const originalImageBuffer = await getOriginalImage(url, domains, request.headers);
76
+ const originalImageBuffer = await getOriginalImage(imageOptions.url, domains, request.headers);
82
77
  if (originalImageBuffer) {
83
78
  const originalImage = sharp(originalImageBuffer);
84
- const resizedImage = await resizeImage(originalImage, width, height, focalX, focalY);
79
+ const resizedImage = await resizeImage(originalImage, imageOptions);
85
80
  const compressedImage = resizedImage.webp({
86
- quality: imageQuality
81
+ quality: imageOptions.q
87
82
  // nearLossless: true,
88
83
  });
89
84
  compressedImage.toFile(cachedName).then(() => console.log("imageHandler.sharp.cached", cachedName)).catch((error) => console.error("imageHandler.sharp.error", cachedName, error));
@@ -95,12 +90,76 @@ function imageHandler({
95
90
  }
96
91
  } catch (error) {
97
92
  console.error("imageHandler.error", error);
98
- throw error;
93
+ return response.status(error.status || 500).json({
94
+ errors: [{
95
+ message: error.message || error.name || error.statusText || "An error occurred"
96
+ }]
97
+ });
99
98
  }
100
99
  }
101
100
  });
102
101
  }
103
- async function resizeImage(originalImage, resizeWidth, resizeHeight, focalX, focalY) {
102
+ function getImageOptions(request, options) {
103
+ const { slugs, origin = "", search = "", w, h, x, y, q } = request.query;
104
+ if (!Array.isArray(slugs) || slugs.length === 0) {
105
+ throw "missing slugs parameter";
106
+ }
107
+ const width = typeof w === "string" ? parseInt(w) : void 0;
108
+ if (!width) {
109
+ throw "missing w parameter";
110
+ }
111
+ if (!options.sizes.includes(width)) {
112
+ throw "invalid w parameter";
113
+ }
114
+ const height = typeof h === "string" ? parseInt(h) : void 0;
115
+ const focalX = typeof x === "string" ? parseInt(x) : IMAGE_FOCAL_X;
116
+ const focalY = typeof y === "string" ? parseInt(y) : IMAGE_FOCAL_Y;
117
+ const imageQuality = Math.max(30, Math.min(100, typeof q === "string" ? parseInt(q) : options.quality));
118
+ if (imageQuality !== options.quality) {
119
+ throw "invalid q parameter";
120
+ }
121
+ const url = origin + "/" + slugs.join("/") + search;
122
+ return height ? {
123
+ w: width,
124
+ h: height,
125
+ x: focalX,
126
+ y: focalY,
127
+ q: imageQuality,
128
+ src: url,
129
+ url,
130
+ origin: String(origin),
131
+ search: String(search)
132
+ } : {
133
+ w: width,
134
+ q: imageQuality,
135
+ src: url,
136
+ url,
137
+ origin: String(origin),
138
+ search: String(search)
139
+ };
140
+ }
141
+ function getEncryptedImageOptions(request, options) {
142
+ const { slugs, origin = "", search = "", t } = request.query;
143
+ if (!Array.isArray(slugs) || slugs.length === 0) {
144
+ throw "missing slugs parameter";
145
+ }
146
+ if (typeof t !== "string") {
147
+ throw "missing t parameter";
148
+ }
149
+ const imageResize = decryptImageResize(t);
150
+ const url = origin + "/" + slugs.join("/") + search;
151
+ if (imageResize.src !== url) {
152
+ throw "invalid src parameter";
153
+ }
154
+ return {
155
+ ...imageResize,
156
+ url,
157
+ origin: String(origin),
158
+ search: String(search)
159
+ };
160
+ }
161
+ async function resizeImage(originalImage, imageOptions) {
162
+ const { w: resizeWidth, h: resizeHeight, x: focalX = IMAGE_FOCAL_X, y: focalY = IMAGE_FOCAL_Y } = imageOptions;
104
163
  const defaultResizeOptions = () => {
105
164
  return originalImage.resize({
106
165
  width: resizeWidth,
@@ -110,7 +169,7 @@ async function resizeImage(originalImage, resizeWidth, resizeHeight, focalX, foc
110
169
  withoutEnlargement: true
111
170
  });
112
171
  };
113
- if (resizeHeight && (focalX !== 50 || focalY !== 50)) {
172
+ if (resizeHeight && (focalX !== IMAGE_FOCAL_X || focalY !== IMAGE_FOCAL_Y)) {
114
173
  const { width: originalWidth, height: originalHeight } = await originalImage.metadata();
115
174
  if (originalWidth && originalHeight) {
116
175
  const resizeAspectRatio = resizeWidth / resizeHeight;
@@ -206,14 +265,14 @@ function urlToFileName(url) {
206
265
  function isVercel() {
207
266
  return process && process.env && String(process.env.VERCEL) === "1";
208
267
  }
209
- function urlToCachedName(url, width, height, focalX, focalY, quality) {
210
- const fileName = urlToFileName(url);
268
+ function urlToCachedName(options) {
269
+ const fileName = urlToFileName(options.url);
211
270
  const parsedName = path.parse(fileName);
212
271
  const cacheDir = isVercel() ? path.join("/tmp", ".cache") : path.join(process.cwd(), ".cache");
213
272
  if (!fs.existsSync(cacheDir)) {
214
273
  fs.mkdirSync(cacheDir);
215
274
  }
216
- const cachedName = path.join(cacheDir, `${parsedName.name}_w_${width}_h_${height || 0}_x_${focalX || 50}_y_${focalY || 50}_q_${quality}${parsedName.ext}`);
275
+ const cachedName = options.h ? path.join(cacheDir, `${parsedName.name}_w_${options.w}_h_${options.h || 0}_x_${options.x || IMAGE_FOCAL_X}_y_${options.y || IMAGE_FOCAL_Y}_q_${options.q}${parsedName.ext}`) : path.join(cacheDir, `${parsedName.name}_w_${options.w}_q_${options.q}${parsedName.ext}`);
217
276
  return cachedName;
218
277
  }
219
278
  function checkUrlValidity(src, domains) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@websolutespa/bom-mixer-image",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "description": "Mixer Image module of the BOM Repository",
5
5
  "keywords": [
6
6
  "bom",
@@ -8,63 +8,55 @@ import * as path from 'path';
8
8
  // Importing 'sharp' for image processing
9
9
  import sharp from 'sharp';
10
10
  // Importing 'apiHandler' from '../api/api.handler'
11
+ import { decryptImageResize, IImageResize, ImageStrategy } from '@websolutespa/bom-core';
11
12
  import { apiHandler } from '../api/api.handler';
12
13
 
14
+ const IMAGE_STRATEGY: ImageStrategy = process.env.IMAGE_STRATEGY ?
15
+ process.env.IMAGE_STRATEGY as ImageStrategy :
16
+ ImageStrategy.Mixer;
17
+
13
18
  const IMAGE_MAX_AGE = 604800;
14
19
  const IMAGE_FOCAL_X = 50;
15
20
  const IMAGE_FOCAL_Y = 50;
16
21
  const IMAGE_QUALITY = 90;
17
22
 
23
+ export type IImageHandlerOptions = {
24
+ domains: string[];
25
+ sizes: number[];
26
+ quality: number;
27
+ };
28
+
29
+ export type IImageOptions = IImageResize & {
30
+ origin: string;
31
+ search: string;
32
+ url: string;
33
+ };
34
+
18
35
  // Exporting the 'imageHandler' function
19
36
  export function imageHandler({
20
37
  domains = [],
21
38
  sizes = [],
22
39
  quality = IMAGE_QUALITY,
23
- }: {
24
- domains: string[];
25
- sizes: number[];
26
- quality: number;
27
- }) {
40
+ }: IImageHandlerOptions) {
28
41
  // Returning the 'apiHandler' function with a configuration object
29
42
  return apiHandler({
30
43
  // Defining the 'get' method for handling GET requests
31
44
  get: async (request: NextApiRequest, response: NextApiResponse) => {
32
45
  try {
33
- // Destructuring 'url', 'w', 'h', 'x', 'y', 'q' from the request query
34
- const { slugs, origin = '', search = '', w, h, x, y, q } = request.query;
35
- // Checking slugs
36
- if (!Array.isArray(slugs) || slugs.length === 0) {
37
- // console.error('imageHandler', 'missing slugs parameter');
38
- return response.status(400).send('missing slugs parameter');
39
- }
40
- // Parsing 'w' to an integer and assigning it to 'width'
41
- const width = typeof w === 'string' ? parseInt(w) : undefined;
42
- // Checking if 'width' is not defined
43
- if (!width) {
44
- // Logging an error message and returning a 400 status code with a message
45
- // console.error('imageHandler', 'missing w parameter');
46
- return response.status(400).send('missing w parameter');
47
- }
48
- // if (width > 2048) {
49
- if (!sizes.includes(width)) {
50
- return response.status(400).send('invalid w parameter');
51
- }
52
- // Parsing 'h' to an integer and assigning it to 'height'
53
- const height = typeof h === 'string' ? parseInt(h) : undefined;
54
- // Parsing 'x' to an integer and assigning it to 'focalX'
55
- const focalX = typeof x === 'string' ? parseInt(x) : 50;
56
- // Parsing 'y' to an integer and assigning it to 'focalY'
57
- const focalY = typeof y === 'string' ? parseInt(y) : 50;
58
- // Parsing 'q' to an integer, ensuring it's between 30 and 100, and assigning it to 'imageQuality'
59
- const imageQuality = Math.max(30, Math.min(100, (typeof q === 'string' ? parseInt(q) : quality)));
60
- // if (width > 2048) {
61
- if (imageQuality !== quality) {
62
- return response.status(400).send('invalid q parameter');
46
+ let imageOptions: IImageOptions;
47
+ try {
48
+ imageOptions = (IMAGE_STRATEGY === ImageStrategy.Token ?
49
+ getEncryptedImageOptions(request, { domains, sizes, quality }) :
50
+ getImageOptions(request, { domains, sizes, quality }));
51
+ } catch (error) {
52
+ return response.status(400).json({
53
+ errors: [{
54
+ message: error,
55
+ }],
56
+ });
63
57
  }
64
- // Generating url
65
- const url = origin + '/' + slugs.join('/') + search;
66
58
  // Generating a cached name for the image
67
- const cachedName = urlToCachedName(url, width, height, focalX, focalY, imageQuality);
59
+ const cachedName = urlToCachedName(imageOptions);
68
60
  // Checking if the cached image exists
69
61
  if (fs.existsSync(cachedName)) {
70
62
  // console.log('imageHandler.fromCache', cachedName);
@@ -92,21 +84,21 @@ export function imageHandler({
92
84
  return response.status(401).send('you are not authorized to perform this action');
93
85
  }
94
86
  // Temporarily fix Vercel local image issue
95
- if (url.indexOf('http') === -1 && isVercel()) {
96
- response.redirect(307, url);
87
+ if (imageOptions.url.indexOf('http') === -1 && isVercel()) {
88
+ response.redirect(307, imageOptions.url);
97
89
  }
98
90
  // Reading the original image
99
- const originalImageBuffer = await getOriginalImage(url, domains, request.headers);
91
+ const originalImageBuffer = await getOriginalImage(imageOptions.url, domains, request.headers);
100
92
  // Checking if the original image exists
101
93
  if (originalImageBuffer) {
102
94
  // Original image
103
95
  const originalImage = sharp(originalImageBuffer);
104
96
  // Resizing the original image and converting it to WebP format
105
- const resizedImage = await resizeImage(originalImage, width, height, focalX, focalY);
97
+ const resizedImage = await resizeImage(originalImage, imageOptions);
106
98
  // Converting resized image to WebP format
107
99
  const compressedImage = resizedImage
108
100
  .webp({
109
- quality: imageQuality,
101
+ quality: imageOptions.q,
110
102
  // nearLossless: true,
111
103
  });
112
104
  // Saving the resized image to the cache
@@ -124,16 +116,118 @@ export function imageHandler({
124
116
  // Sending data in the response
125
117
  return response.end(data);
126
118
  }
127
- } catch (error) {
119
+ } catch (error: any) {
128
120
  // Logging an error message and throwing the error
129
121
  console.error('imageHandler.error', error);
130
- throw error;
122
+ return response.status(error.status || 500).json({
123
+ errors: [{
124
+ message: error.message || error.name || error.statusText || 'An error occurred',
125
+ }],
126
+ });
131
127
  }
132
128
  },
133
129
  });
134
130
  }
135
131
 
136
- async function resizeImage(originalImage: sharp.Sharp, resizeWidth: number, resizeHeight: number | undefined, focalX: number, focalY: number): Promise<sharp.Sharp> {
132
+ function getImageOptions(request: NextApiRequest, options: IImageHandlerOptions): IImageOptions {
133
+ // Destructuring 'slugs', 'origin', 'search', 'w', 'h', 'x', 'y', 'q' from the request query
134
+ const { slugs, origin = '', search = '', w, h, x, y, q } = request.query;
135
+ // Checking slugs
136
+ if (!Array.isArray(slugs) || slugs.length === 0) {
137
+ // console.error('imageHandler', 'missing slugs parameter');
138
+ throw 'missing slugs parameter';
139
+ }
140
+ // Parsing 'w' to an integer and assigning it to 'width'
141
+ const width = typeof w === 'string' ? parseInt(w) : undefined;
142
+ // Checking if 'width' is not defined
143
+ if (!width) {
144
+ // Logging an error message and returning a 400 status code with a message
145
+ // console.error('imageHandler', 'missing w parameter');
146
+ throw 'missing w parameter';
147
+ }
148
+ // if (width > 2048) {
149
+ if (!options.sizes.includes(width)) {
150
+ throw 'invalid w parameter';
151
+ }
152
+ // Parsing 'h' to an integer and assigning it to 'height'
153
+ const height = typeof h === 'string' ? parseInt(h) : undefined;
154
+ // Parsing 'x' to an integer and assigning it to 'focalX'
155
+ const focalX = typeof x === 'string' ? parseInt(x) : IMAGE_FOCAL_X;
156
+ // Parsing 'y' to an integer and assigning it to 'focalY'
157
+ const focalY = typeof y === 'string' ? parseInt(y) : IMAGE_FOCAL_Y;
158
+ // Parsing 'q' to an integer, ensuring it's between 30 and 100, and assigning it to 'imageQuality'
159
+ const imageQuality = Math.max(30, Math.min(100, (typeof q === 'string' ? parseInt(q) : options.quality)));
160
+ // if (width > 2048) {
161
+ if (imageQuality !== options.quality) {
162
+ throw 'invalid q parameter';
163
+ }
164
+ // Generating url
165
+ const url = origin + '/' + slugs.join('/') + search;
166
+ //
167
+ return height ? {
168
+ w: width,
169
+ h: height,
170
+ x: focalX,
171
+ y: focalY,
172
+ q: imageQuality,
173
+ src: url,
174
+ url: url,
175
+ origin: String(origin),
176
+ search: String(search),
177
+ } : {
178
+ w: width,
179
+ q: imageQuality,
180
+ src: url,
181
+ url: url,
182
+ origin: String(origin),
183
+ search: String(search),
184
+ };
185
+ }
186
+
187
+ function getEncryptedImageOptions(request: NextApiRequest, options: IImageHandlerOptions): IImageOptions {
188
+ // Destructuring 'slugs', 'origin', 'search', 't' from the request query
189
+ const { slugs, origin = '', search = '', t } = request.query;
190
+ // Checking slugs
191
+ if (!Array.isArray(slugs) || slugs.length === 0) {
192
+ // console.error('imageHandler', 'missing slugs parameter');
193
+ throw 'missing slugs parameter';
194
+ }
195
+ if (typeof t !== 'string') {
196
+ throw 'missing t parameter';
197
+ }
198
+ const imageResize = decryptImageResize(t);
199
+ /*
200
+ // Checking if 'w' is not defined
201
+ if (!imageResize.w) {
202
+ throw 'missing w parameter';
203
+ }
204
+ // if (width > 2048) {
205
+ if (!options.sizes.includes(imageResize.w)) {
206
+ throw 'invalid w parameter';
207
+ }
208
+ // Ensuring 'q' is between 30 and 100, and assigning it to 'imageQuality'
209
+ const imageQuality = Math.max(30, Math.min(100, imageResize.q || options.quality));
210
+ if (imageQuality !== options.quality) {
211
+ throw 'invalid q parameter';
212
+ }
213
+ */
214
+ // Generating url
215
+ const url = origin + '/' + slugs.join('/') + search;
216
+ //
217
+ if (imageResize.src !== url) {
218
+ throw 'invalid src parameter';
219
+ }
220
+ //
221
+ return {
222
+ ...imageResize,
223
+ url: url,
224
+ origin: String(origin),
225
+ search: String(search),
226
+ };
227
+ }
228
+
229
+ async function resizeImage(originalImage: sharp.Sharp, imageOptions: IImageOptions): Promise<sharp.Sharp> {
230
+ const { w: resizeWidth, h: resizeHeight, x: focalX = IMAGE_FOCAL_X, y: focalY = IMAGE_FOCAL_Y } = imageOptions;
137
231
  const defaultResizeOptions = () => {
138
232
  return originalImage.resize({
139
233
  width: resizeWidth,
@@ -143,7 +237,7 @@ async function resizeImage(originalImage: sharp.Sharp, resizeWidth: number, resi
143
237
  withoutEnlargement: true,
144
238
  });
145
239
  };
146
- if (resizeHeight && (focalX !== 50 || focalY !== 50)) {
240
+ if (resizeHeight && (focalX !== IMAGE_FOCAL_X || focalY !== IMAGE_FOCAL_Y)) {
147
241
  const { width: originalWidth, height: originalHeight } = await originalImage.metadata();
148
242
  if (originalWidth && originalHeight) {
149
243
  const resizeAspectRatio = resizeWidth / resizeHeight;
@@ -291,9 +385,9 @@ function isVercel(): boolean {
291
385
  }
292
386
 
293
387
  // Defining the 'urlToCachedName' function
294
- function urlToCachedName(url: string, width: number, height: number | undefined, focalX: number | undefined, focalY: number | undefined, quality: number): string {
388
+ function urlToCachedName(options: IImageOptions): string {
295
389
  // Converting the URL to a file name
296
- const fileName = urlToFileName(url);
390
+ const fileName = urlToFileName(options.url);
297
391
  // Parsing the file name
298
392
  const parsedName = path.parse(fileName);
299
393
  // Defining the cache directory
@@ -306,7 +400,9 @@ function urlToCachedName(url: string, width: number, height: number | undefined,
306
400
  fs.mkdirSync(cacheDir);
307
401
  }
308
402
  // Generating the cached name
309
- const cachedName = path.join(cacheDir, `${parsedName.name}_w_${width}_h_${height || 0}_x_${focalX || 50}_y_${focalY || 50}_q_${quality}${parsedName.ext}`);
403
+ const cachedName = options.h ?
404
+ path.join(cacheDir, `${parsedName.name}_w_${options.w}_h_${options.h || 0}_x_${options.x || IMAGE_FOCAL_X}_y_${options.y || IMAGE_FOCAL_Y}_q_${options.q}${parsedName.ext}`) :
405
+ path.join(cacheDir, `${parsedName.name}_w_${options.w}_q_${options.q}${parsedName.ext}`);
310
406
  // Returning the cached name
311
407
  return cachedName;
312
408
  }