@websolutespa/bom-mixer-image 2.0.1-next.0 → 2.0.1-next.2

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,115 +1,121 @@
1
- # @websolutespa/bom-mixer-image
2
-
3
- ## 2.0.1-next.0
4
-
5
- ### Major Changes
6
-
7
- - e74b305: Release: Payload 3.7.0
8
-
9
- ### Patch Changes
10
-
11
- - Updated dependencies [e74b305]
12
- - @websolutespa/bom-core@2.0.1-next.0
13
-
14
- ## 1.8.4
15
-
16
- ### Patch Changes
17
-
18
- - Fixing: handling latin1 src
19
-
20
- ## 1.8.3
21
-
22
- ### Patch Changes
23
-
24
- - Added: ImageStrategy.Token
25
- - Updated dependencies
26
- - @websolutespa/bom-core@1.8.4
27
-
28
- ## 1.8.2
29
-
30
- ### Patch Changes
31
-
32
- - Modified: imageHandler
33
-
34
- ## 1.8.1
35
-
36
- ### Patch Changes
37
-
38
- - Modified: imageHandler
39
-
40
- ## 1.8.0
41
-
42
- ### Patch Changes
43
-
44
- - Updated dependencies [f8a1d97]
45
- - @websolutespa/bom-core@1.8.0
46
-
47
- ## 1.0.9
48
-
49
- ### Patch Changes
50
-
51
- - Fixing: downgrading eslint to v8.56.0
52
- - Updated dependencies
53
- - @websolutespa/bom-core@1.7.8
54
-
55
- ## 1.0.8
56
-
57
- ### Patch Changes
58
-
59
- - Added: Cache-Control
60
-
61
- ## 1.0.7
62
-
63
- ### Patch Changes
64
-
65
- - Fixing: imageHandler Vercel support
66
- - Updated dependencies
67
- - @websolutespa/bom-core@1.7.7
68
-
69
- ## 1.0.6
70
-
71
- ### Patch Changes
72
-
73
- - Modified: imageHandler, re-route not found to origin
74
-
75
- ## 1.0.5
76
-
77
- ### Patch Changes
78
-
79
- - Modified: imageHandler
80
- - Updated dependencies
81
- - @websolutespa/bom-core@1.7.6
82
-
83
- ## 1.0.4
84
-
85
- ### Patch Changes
86
-
87
- - Modified: imageHandler getOriginalImage
88
- - Updated dependencies
89
- - @websolutespa/bom-core@1.7.5
90
-
91
- ## 1.0.3
92
-
93
- ### Patch Changes
94
-
95
- - 43957d4: Fixing: production local asset public path
96
- - Updated dependencies
97
- - @websolutespa/bom-core@1.7.4
98
-
99
- ## 1.0.2
100
-
101
- ### Patch Changes
102
-
103
- - 75b1311: Fixing: relative paths
104
-
105
- ## 1.0.1
106
-
107
- ### Patch Changes
108
-
109
- - Updating: dependencies
110
-
111
- ## 1.0.0
112
-
113
- ### Patch Changes
114
-
115
- - Added: first release
1
+ # @websolutespa/bom-mixer-image
2
+
3
+ ## 2.0.1-next.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updating: dependencies and build step
8
+
9
+ ## 2.0.1-next.0
10
+
11
+ ### Major Changes
12
+
13
+ - e74b305: Release: Payload 3.7.0
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [e74b305]
18
+ - @websolutespa/bom-core@2.0.1-next.0
19
+
20
+ ## 1.8.4
21
+
22
+ ### Patch Changes
23
+
24
+ - Fixing: handling latin1 src
25
+
26
+ ## 1.8.3
27
+
28
+ ### Patch Changes
29
+
30
+ - Added: ImageStrategy.Token
31
+ - Updated dependencies
32
+ - @websolutespa/bom-core@1.8.4
33
+
34
+ ## 1.8.2
35
+
36
+ ### Patch Changes
37
+
38
+ - Modified: imageHandler
39
+
40
+ ## 1.8.1
41
+
42
+ ### Patch Changes
43
+
44
+ - Modified: imageHandler
45
+
46
+ ## 1.8.0
47
+
48
+ ### Patch Changes
49
+
50
+ - Updated dependencies [f8a1d97]
51
+ - @websolutespa/bom-core@1.8.0
52
+
53
+ ## 1.0.9
54
+
55
+ ### Patch Changes
56
+
57
+ - Fixing: downgrading eslint to v8.56.0
58
+ - Updated dependencies
59
+ - @websolutespa/bom-core@1.7.8
60
+
61
+ ## 1.0.8
62
+
63
+ ### Patch Changes
64
+
65
+ - Added: Cache-Control
66
+
67
+ ## 1.0.7
68
+
69
+ ### Patch Changes
70
+
71
+ - Fixing: imageHandler Vercel support
72
+ - Updated dependencies
73
+ - @websolutespa/bom-core@1.7.7
74
+
75
+ ## 1.0.6
76
+
77
+ ### Patch Changes
78
+
79
+ - Modified: imageHandler, re-route not found to origin
80
+
81
+ ## 1.0.5
82
+
83
+ ### Patch Changes
84
+
85
+ - Modified: imageHandler
86
+ - Updated dependencies
87
+ - @websolutespa/bom-core@1.7.6
88
+
89
+ ## 1.0.4
90
+
91
+ ### Patch Changes
92
+
93
+ - Modified: imageHandler getOriginalImage
94
+ - Updated dependencies
95
+ - @websolutespa/bom-core@1.7.5
96
+
97
+ ## 1.0.3
98
+
99
+ ### Patch Changes
100
+
101
+ - 43957d4: Fixing: production local asset public path
102
+ - Updated dependencies
103
+ - @websolutespa/bom-core@1.7.4
104
+
105
+ ## 1.0.2
106
+
107
+ ### Patch Changes
108
+
109
+ - 75b1311: Fixing: relative paths
110
+
111
+ ## 1.0.1
112
+
113
+ ### Patch Changes
114
+
115
+ - Updating: dependencies
116
+
117
+ ## 1.0.0
118
+
119
+ ### Patch Changes
120
+
121
+ - Added: first release
package/dist/index.d.ts CHANGED
@@ -1,15 +1,23 @@
1
- import { IImageResize } from '@websolutespa/bom-core';
1
+ import { IImageResize } from "@websolutespa/bom-core";
2
2
 
3
+ //#region src/image/image.handler.d.ts
3
4
  type IImageHandlerOptions = {
4
- domains: string[];
5
- sizes: number[];
6
- quality: number;
5
+ domains: string[];
6
+ sizes: number[];
7
+ quality: number;
7
8
  };
8
9
  type IImageOptions = IImageResize & {
9
- origin: string;
10
- search: string;
11
- url: string;
10
+ origin: string;
11
+ search: string;
12
+ url: string;
12
13
  };
13
- declare function imageHandler({ domains, sizes, quality, }: IImageHandlerOptions): any;
14
+ declare function imageHandler({
15
+ domains,
16
+ sizes,
17
+ quality
18
+ }: IImageHandlerOptions): any;
19
+ //# sourceMappingURL=image.handler.d.ts.map
14
20
 
15
- export { type IImageHandlerOptions, type IImageOptions, imageHandler };
21
+ //#endregion
22
+ export { IImageHandlerOptions, IImageOptions, imageHandler };
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/image/image.handler.ts"],"sourcesContent":[],"mappings":";;;KAsBY,oBAAA;;EAAA,KAAA,EAAA,MAAA,EAAA;EAMA,OAAA,EAAA,MAAA;AAOZ,CAAA;AAA4B,KAPhB,aAAA,GAAgB,YAOA,GAAA;EAAA,MAC1B,EAAA,MAAA;EAAY,MACZ,EAAA,MAAA;EAAU,GACV,EAAA,MAAA;CAAuB;AACF,iBAJP,YAAA,CAIO;EAAA,OAAA;EAAA,KAAA;EAAA;AAAA,CAAA,EAApB,oBAAoB,CAAA,EAAA,GAAA"}
package/dist/index.js CHANGED
@@ -1,299 +1,248 @@
1
- // src/image/image.handler.ts
2
1
  import * as fs from "fs";
3
2
  import * as path from "path";
4
3
  import sharp from "sharp";
5
- import { decryptImageResize, ImageStrategy } from "@websolutespa/bom-core";
4
+ import { ImageStrategy, decryptImageResize } from "@websolutespa/bom-core";
6
5
 
7
- // src/api/api.handler.ts
6
+ //#region src/api/api.handler.ts
8
7
  function apiHandler(handler) {
9
- return async (request, response) => {
10
- const method = (request.method || "GET").toLowerCase();
11
- if (!handler[method]) {
12
- return response.status(405).end(`Method ${method} Not Allowed`);
13
- }
14
- try {
15
- await handler[method](request, response);
16
- } catch (error) {
17
- return errorHandler(error, response);
18
- }
19
- };
8
+ return async (request, response) => {
9
+ const method = (request.method || "GET").toLowerCase();
10
+ if (!handler[method]) return response.status(405).end(`Method ${method} Not Allowed`);
11
+ try {
12
+ await handler[method](request, response);
13
+ } catch (error) {
14
+ return errorHandler(error, response);
15
+ }
16
+ };
20
17
  }
21
18
  function errorHandler(error, response) {
22
- if (typeof error === "string") {
23
- const is404 = error.toLowerCase().endsWith("not found");
24
- const statusCode = is404 ? 404 : 400;
25
- return response.status(statusCode).json({ message: error });
26
- }
27
- if (error.name === "UnauthorizedError") {
28
- return response.status(401).json({ message: "Invalid Token" });
29
- }
30
- console.error(error);
31
- return response.status(500).json({ message: error.message });
19
+ if (typeof error === "string") {
20
+ const is404 = error.toLowerCase().endsWith("not found");
21
+ const statusCode = is404 ? 404 : 400;
22
+ return response.status(statusCode).json({ message: error });
23
+ }
24
+ if (error.name === "UnauthorizedError") return response.status(401).json({ message: "Invalid Token" });
25
+ console.error(error);
26
+ return response.status(500).json({ message: error.message });
32
27
  }
33
28
 
34
- // src/image/image.handler.ts
35
- var IMAGE_STRATEGY = process.env.IMAGE_STRATEGY ? process.env.IMAGE_STRATEGY : ImageStrategy.Mixer;
36
- var IMAGE_MAX_AGE = 604800;
37
- var IMAGE_FOCAL_X = 50;
38
- var IMAGE_FOCAL_Y = 50;
39
- var IMAGE_QUALITY = 90;
40
- function imageHandler({
41
- domains = [],
42
- sizes = [],
43
- quality = IMAGE_QUALITY
44
- }) {
45
- return apiHandler({
46
- // Defining the 'get' method for handling GET requests
47
- get: async (request, response) => {
48
- try {
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
- });
58
- }
59
- const cachedName = urlToCachedName(imageOptions);
60
- if (fs.existsSync(cachedName)) {
61
- const cachedImage = await getCachedImage(cachedName);
62
- response.setHeader("Content-Type", "image/webp");
63
- response.setHeader("Expires", new Date(Date.now() + IMAGE_MAX_AGE * 1e3).toUTCString());
64
- response.setHeader("Cache-Control", `public, max-age=${IMAGE_MAX_AGE}`);
65
- return response.send(cachedImage);
66
- }
67
- if (isInvalidReferer(request)) {
68
- return response.status(401).send("you are not authorized to perform this action");
69
- }
70
- if (imageOptions.url.indexOf("http") === -1 && isVercel()) {
71
- response.redirect(307, imageOptions.url);
72
- }
73
- const originalImageBuffer = await getOriginalImage(imageOptions.url, domains, request.headers);
74
- if (originalImageBuffer) {
75
- const originalImage = sharp(originalImageBuffer, {
76
- animated: isGIF(imageOptions.url)
77
- });
78
- const resizedImage = await resizeImage(originalImage, imageOptions);
79
- const compressedImage = resizedImage.webp({
80
- quality: imageOptions.q
81
- // nearLossless: true,
82
- });
83
- compressedImage.toFile(cachedName).then(() => console.log("imageHandler.sharp.cached", cachedName)).catch((error) => console.error("imageHandler.sharp.error", cachedName, error));
84
- const data = await compressedImage.toBuffer();
85
- response.setHeader("Content-Type", "image/webp");
86
- response.setHeader("Expires", new Date(Date.now() + IMAGE_MAX_AGE * 1e3).toUTCString());
87
- response.setHeader("Cache-Control", `public, max-age=${IMAGE_MAX_AGE}`);
88
- return response.end(data);
89
- }
90
- } catch (error) {
91
- console.error("imageHandler.error", error);
92
- return response.status(error.status || 500).json({
93
- errors: [{
94
- message: error.message || error.name || error.statusText || "An error occurred"
95
- }]
96
- });
97
- }
98
- }
99
- });
29
+ //#endregion
30
+ //#region src/image/image.handler.ts
31
+ const IMAGE_STRATEGY = process.env.IMAGE_STRATEGY ? process.env.IMAGE_STRATEGY : ImageStrategy.Mixer;
32
+ const IMAGE_MAX_AGE = 604800;
33
+ const IMAGE_FOCAL_X = 50;
34
+ const IMAGE_FOCAL_Y = 50;
35
+ const IMAGE_QUALITY = 90;
36
+ function imageHandler({ domains = [], sizes = [], quality = IMAGE_QUALITY }) {
37
+ return apiHandler({ get: async (request, response) => {
38
+ try {
39
+ let imageOptions;
40
+ try {
41
+ imageOptions = IMAGE_STRATEGY === ImageStrategy.Token ? getEncryptedImageOptions(request, {
42
+ domains,
43
+ sizes,
44
+ quality
45
+ }) : getImageOptions(request, {
46
+ domains,
47
+ sizes,
48
+ quality
49
+ });
50
+ } catch (error) {
51
+ return response.status(400).json({ errors: [{ message: error }] });
52
+ }
53
+ const cachedName = urlToCachedName(imageOptions);
54
+ if (fs.existsSync(cachedName)) {
55
+ const cachedImage = await getCachedImage(cachedName);
56
+ response.setHeader("Content-Type", "image/webp");
57
+ response.setHeader("Expires", new Date(Date.now() + IMAGE_MAX_AGE * 1e3).toUTCString());
58
+ response.setHeader("Cache-Control", `public, max-age=${IMAGE_MAX_AGE}`);
59
+ return response.send(cachedImage);
60
+ }
61
+ if (isInvalidReferer(request)) return response.status(401).send("you are not authorized to perform this action");
62
+ if (imageOptions.url.indexOf("http") === -1 && isVercel()) response.redirect(307, imageOptions.url);
63
+ const originalImageBuffer = await getOriginalImage(imageOptions.url, domains, request.headers);
64
+ if (originalImageBuffer) {
65
+ const originalImage = sharp(originalImageBuffer, { animated: isGIF(imageOptions.url) });
66
+ const resizedImage = await resizeImage(originalImage, imageOptions);
67
+ const compressedImage = resizedImage.webp({ quality: imageOptions.q });
68
+ compressedImage.toFile(cachedName).then(() => console.log("imageHandler.sharp.cached", cachedName)).catch((error) => console.error("imageHandler.sharp.error", cachedName, error));
69
+ const data = await compressedImage.toBuffer();
70
+ response.setHeader("Content-Type", "image/webp");
71
+ response.setHeader("Expires", new Date(Date.now() + IMAGE_MAX_AGE * 1e3).toUTCString());
72
+ response.setHeader("Cache-Control", `public, max-age=${IMAGE_MAX_AGE}`);
73
+ return response.end(data);
74
+ }
75
+ } catch (error) {
76
+ console.error("imageHandler.error", error);
77
+ return response.status(error.status || 500).json({ errors: [{ message: error.message || error.name || error.statusText || "An error occurred" }] });
78
+ }
79
+ } });
100
80
  }
101
81
  function isGIF(src) {
102
- return src.indexOf(".gif") !== -1;
82
+ return src.indexOf(".gif") !== -1;
103
83
  }
104
84
  function isInvalidReferer(request) {
105
- const { headers } = request;
106
- const referer = headers.referer;
107
- const publicUrl = process.env.NEXT_PUBLIC_URL || "";
108
- if (IMAGE_STRATEGY !== ImageStrategy.Token && referer && publicUrl !== "https://" && referer.indexOf(publicUrl) !== 0) {
109
- return true;
110
- }
111
- return false;
85
+ const { headers } = request;
86
+ const referer = headers.referer;
87
+ const publicUrl = process.env.NEXT_PUBLIC_URL || "";
88
+ if (IMAGE_STRATEGY !== ImageStrategy.Token && referer && publicUrl !== "https://" && referer.indexOf(publicUrl) !== 0) return true;
89
+ return false;
112
90
  }
113
91
  function getImageOptions(request, options) {
114
- const { slugs, origin = "", search = "", w, h, x, y, q } = request.query;
115
- if (!Array.isArray(slugs) || slugs.length === 0) {
116
- throw "missing slugs parameter";
117
- }
118
- const width = typeof w === "string" ? parseInt(w) : void 0;
119
- if (!width) {
120
- throw "missing w parameter";
121
- }
122
- if (!options.sizes.includes(width)) {
123
- throw "invalid w parameter";
124
- }
125
- const height = typeof h === "string" ? parseInt(h) : void 0;
126
- const focalX = typeof x === "string" ? parseInt(x) : IMAGE_FOCAL_X;
127
- const focalY = typeof y === "string" ? parseInt(y) : IMAGE_FOCAL_Y;
128
- const imageQuality = Math.max(30, Math.min(100, typeof q === "string" ? parseInt(q) : options.quality));
129
- if (imageQuality !== options.quality) {
130
- throw "invalid q parameter";
131
- }
132
- const url = origin + "/" + slugs.join("/") + search;
133
- return height ? {
134
- w: width,
135
- h: height,
136
- x: focalX,
137
- y: focalY,
138
- q: imageQuality,
139
- src: url,
140
- url,
141
- origin: String(origin),
142
- search: String(search)
143
- } : {
144
- w: width,
145
- q: imageQuality,
146
- src: url,
147
- url,
148
- origin: String(origin),
149
- search: String(search)
150
- };
92
+ const { slugs, origin = "", search = "", w, h, x, y, q } = request.query;
93
+ if (!Array.isArray(slugs) || slugs.length === 0) throw "missing slugs parameter";
94
+ const width = typeof w === "string" ? parseInt(w) : void 0;
95
+ if (!width) throw "missing w parameter";
96
+ if (!options.sizes.includes(width)) throw "invalid w parameter";
97
+ const height = typeof h === "string" ? parseInt(h) : void 0;
98
+ const focalX = typeof x === "string" ? parseInt(x) : IMAGE_FOCAL_X;
99
+ const focalY = typeof y === "string" ? parseInt(y) : IMAGE_FOCAL_Y;
100
+ const imageQuality = Math.max(30, Math.min(100, typeof q === "string" ? parseInt(q) : options.quality));
101
+ if (imageQuality !== options.quality) throw "invalid q parameter";
102
+ const url = origin + "/" + slugs.join("/") + search;
103
+ return height ? {
104
+ w: width,
105
+ h: height,
106
+ x: focalX,
107
+ y: focalY,
108
+ q: imageQuality,
109
+ src: url,
110
+ url,
111
+ origin: String(origin),
112
+ search: String(search)
113
+ } : {
114
+ w: width,
115
+ q: imageQuality,
116
+ src: url,
117
+ url,
118
+ origin: String(origin),
119
+ search: String(search)
120
+ };
151
121
  }
152
122
  function getEncryptedImageOptions(request, options) {
153
- const { slugs, origin = "", search = "", t } = request.query;
154
- if (!Array.isArray(slugs) || slugs.length === 0) {
155
- throw "missing slugs parameter";
156
- }
157
- if (typeof t !== "string") {
158
- throw "missing t parameter";
159
- }
160
- const imageResize = decryptImageResize(t);
161
- imageResize.src = decodeURIComponent(imageResize.src);
162
- const url = decodeURIComponent(origin + "/" + slugs.join("/") + search);
163
- if (imageResize.src !== url) {
164
- throw `invalid src parameter ${imageResize.src} ${url}`;
165
- }
166
- return {
167
- ...imageResize,
168
- url,
169
- origin: String(origin),
170
- search: String(search)
171
- };
123
+ const { slugs, origin = "", search = "", t } = request.query;
124
+ if (!Array.isArray(slugs) || slugs.length === 0) throw "missing slugs parameter";
125
+ if (typeof t !== "string") throw "missing t parameter";
126
+ const imageResize = decryptImageResize(t);
127
+ imageResize.src = decodeURIComponent(imageResize.src);
128
+ const url = decodeURIComponent(origin + "/" + slugs.join("/") + search);
129
+ if (imageResize.src !== url) throw `invalid src parameter ${imageResize.src} ${url}`;
130
+ return {
131
+ ...imageResize,
132
+ url,
133
+ origin: String(origin),
134
+ search: String(search)
135
+ };
172
136
  }
173
137
  async function resizeImage(originalImage, imageOptions) {
174
- const { w: resizeWidth, h: resizeHeight, x: focalX = IMAGE_FOCAL_X, y: focalY = IMAGE_FOCAL_Y } = imageOptions;
175
- const defaultResizeOptions = () => {
176
- return originalImage.resize({
177
- width: resizeWidth,
178
- height: resizeHeight ? resizeHeight : void 0,
179
- fit: sharp.fit.cover,
180
- position: sharp.strategy.attention,
181
- withoutEnlargement: true
182
- });
183
- };
184
- if (resizeHeight && (focalX !== IMAGE_FOCAL_X || focalY !== IMAGE_FOCAL_Y)) {
185
- const { width: originalWidth, height: originalHeight } = await originalImage.metadata();
186
- if (originalWidth && originalHeight) {
187
- const resizeAspectRatio = resizeWidth / resizeHeight;
188
- const originalAspectRatio = originalWidth / originalHeight;
189
- const prioritizeHeight = resizeAspectRatio < originalAspectRatio;
190
- const imageToResize = originalImage.clone();
191
- const scaledImage = imageToResize.resize(prioritizeHeight ? {
192
- height: resizeHeight,
193
- fit: sharp.fit.cover
194
- } : {
195
- width: resizeWidth,
196
- fit: sharp.fit.cover
197
- });
198
- const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true });
199
- const extractWidth = resizeWidth ?? scaledImageInfo.width;
200
- const maxOffsetX = scaledImageInfo.width - extractWidth;
201
- const leftFocalEdge = Math.round(
202
- scaledImageInfo.width * (focalX / 100) - extractWidth / 2
203
- );
204
- const extractX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX);
205
- const extractHeight = resizeHeight ?? scaledImageInfo.height;
206
- const maxOffsetY = scaledImageInfo.height - extractHeight;
207
- const topFocalEdge = Math.round(
208
- scaledImageInfo.height * (focalY / 100) - extractHeight / 2
209
- );
210
- const extractY = Math.min(Math.max(0, topFocalEdge), maxOffsetY);
211
- return scaledImage.extract({
212
- left: extractX,
213
- top: extractY,
214
- width: extractWidth,
215
- height: extractHeight
216
- });
217
- } else {
218
- return defaultResizeOptions();
219
- }
220
- } else {
221
- return defaultResizeOptions();
222
- }
138
+ const { w: resizeWidth, h: resizeHeight, x: focalX = IMAGE_FOCAL_X, y: focalY = IMAGE_FOCAL_Y } = imageOptions;
139
+ const defaultResizeOptions = () => {
140
+ return originalImage.resize({
141
+ width: resizeWidth,
142
+ height: resizeHeight ? resizeHeight : void 0,
143
+ fit: sharp.fit.cover,
144
+ position: sharp.strategy.attention,
145
+ withoutEnlargement: true
146
+ });
147
+ };
148
+ if (resizeHeight && (focalX !== IMAGE_FOCAL_X || focalY !== IMAGE_FOCAL_Y)) {
149
+ const { width: originalWidth, height: originalHeight } = await originalImage.metadata();
150
+ if (originalWidth && originalHeight) {
151
+ const resizeAspectRatio = resizeWidth / resizeHeight;
152
+ const originalAspectRatio = originalWidth / originalHeight;
153
+ const prioritizeHeight = resizeAspectRatio < originalAspectRatio;
154
+ const imageToResize = originalImage.clone();
155
+ const scaledImage = imageToResize.resize(prioritizeHeight ? {
156
+ height: resizeHeight,
157
+ fit: sharp.fit.cover
158
+ } : {
159
+ width: resizeWidth,
160
+ fit: sharp.fit.cover
161
+ });
162
+ const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true });
163
+ const extractWidth = resizeWidth ?? scaledImageInfo.width;
164
+ const maxOffsetX = scaledImageInfo.width - extractWidth;
165
+ const leftFocalEdge = Math.round(scaledImageInfo.width * (focalX / 100) - extractWidth / 2);
166
+ const extractX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX);
167
+ const extractHeight = resizeHeight ?? scaledImageInfo.height;
168
+ const maxOffsetY = scaledImageInfo.height - extractHeight;
169
+ const topFocalEdge = Math.round(scaledImageInfo.height * (focalY / 100) - extractHeight / 2);
170
+ const extractY = Math.min(Math.max(0, topFocalEdge), maxOffsetY);
171
+ return scaledImage.extract({
172
+ left: extractX,
173
+ top: extractY,
174
+ width: extractWidth,
175
+ height: extractHeight
176
+ });
177
+ } else return defaultResizeOptions();
178
+ } else return defaultResizeOptions();
223
179
  }
224
180
  function getOriginalImage(url, domains, headers) {
225
- return new Promise((resolve, reject) => {
226
- if (url.indexOf("http") === 0) {
227
- if (isInvalidDomain(url, domains)) {
228
- return reject({ status: 401, message: "invalid origin" });
229
- }
230
- fetch(url, {
231
- method: "GET",
232
- mode: "cors",
233
- headers: {
234
- "Access-Control-Allow-Origin": "*"
235
- }
236
- }).then((response) => {
237
- if (!response.ok) {
238
- return reject({ status: response.status, message: response.statusText });
239
- }
240
- response.arrayBuffer().then((buffer) => {
241
- return resolve(Buffer.from(buffer));
242
- }).catch((error) => {
243
- return reject(error);
244
- });
245
- }).catch((error) => {
246
- return reject(error);
247
- });
248
- } else {
249
- const filePath = path.join(process.cwd(), "public", url);
250
- fs.readFile(filePath, (error, data) => {
251
- if (error) {
252
- return reject(error);
253
- }
254
- return resolve(data);
255
- });
256
- }
257
- });
181
+ return new Promise((resolve, reject) => {
182
+ if (url.indexOf("http") === 0) {
183
+ if (isInvalidDomain(url, domains)) return reject({
184
+ status: 401,
185
+ message: "invalid origin"
186
+ });
187
+ fetch(url, {
188
+ method: "GET",
189
+ mode: "cors",
190
+ headers: { "Access-Control-Allow-Origin": "*" }
191
+ }).then((response) => {
192
+ if (!response.ok) return reject({
193
+ status: response.status,
194
+ message: response.statusText
195
+ });
196
+ response.arrayBuffer().then((buffer) => {
197
+ return resolve(Buffer.from(buffer));
198
+ }).catch((error) => {
199
+ return reject(error);
200
+ });
201
+ }).catch((error) => {
202
+ return reject(error);
203
+ });
204
+ } else {
205
+ const filePath = path.join(process.cwd(), "public", url);
206
+ fs.readFile(filePath, (error, data) => {
207
+ if (error) return reject(error);
208
+ return resolve(data);
209
+ });
210
+ }
211
+ });
258
212
  }
259
213
  function getCachedImage(pathname) {
260
- return new Promise((resolve, reject) => {
261
- fs.readFile(pathname, (error, data) => {
262
- if (error) {
263
- return reject(error);
264
- }
265
- return resolve(data);
266
- });
267
- });
214
+ return new Promise((resolve, reject) => {
215
+ fs.readFile(pathname, (error, data) => {
216
+ if (error) return reject(error);
217
+ return resolve(data);
218
+ });
219
+ });
268
220
  }
269
221
  function urlToFileName(url) {
270
- const segments = url.split("?");
271
- const path2 = segments[0];
272
- const query = segments[1];
273
- let uniqueName = path2.replace("://", "_").replace(/\.|\/|\?|=/g, "_");
274
- uniqueName = uniqueName.substring(0, uniqueName.lastIndexOf("_")) + (query ? "_" + query.replace(/\.|\/|\?|=/g, "_") : "") + "." + uniqueName.substring(uniqueName.lastIndexOf("_") + 1, uniqueName.length);
275
- return uniqueName;
222
+ const segments = url.split("?");
223
+ const path$1 = segments[0];
224
+ const query = segments[1];
225
+ let uniqueName = path$1.replace("://", "_").replace(/\.|\/|\?|=/g, "_");
226
+ uniqueName = uniqueName.substring(0, uniqueName.lastIndexOf("_")) + (query ? "_" + query.replace(/\.|\/|\?|=/g, "_") : "") + "." + uniqueName.substring(uniqueName.lastIndexOf("_") + 1, uniqueName.length);
227
+ return uniqueName;
276
228
  }
277
229
  function isVercel() {
278
- return process && process.env && String(process.env.VERCEL) === "1";
230
+ return process && process.env && String(process.env.VERCEL) === "1";
279
231
  }
280
232
  function urlToCachedName(options) {
281
- const fileName = urlToFileName(options.url);
282
- const parsedName = path.parse(fileName);
283
- const cacheDir = isVercel() ? path.join("/tmp", ".cache") : path.join(process.cwd(), ".cache");
284
- if (!fs.existsSync(cacheDir)) {
285
- fs.mkdirSync(cacheDir);
286
- }
287
- 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}`);
288
- return cachedName;
233
+ const fileName = urlToFileName(options.url);
234
+ const parsedName = path.parse(fileName);
235
+ const cacheDir = isVercel() ? path.join("/tmp", ".cache") : path.join(process.cwd(), ".cache");
236
+ if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir);
237
+ 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}`);
238
+ return cachedName;
289
239
  }
290
240
  function isInvalidDomain(src, domains) {
291
- if (IMAGE_STRATEGY === ImageStrategy.Token) {
292
- return false;
293
- }
294
- const url = new URL(src);
295
- return !domains.includes(url.hostname);
241
+ if (IMAGE_STRATEGY === ImageStrategy.Token) return false;
242
+ const url = new URL(src);
243
+ return !domains.includes(url.hostname);
296
244
  }
297
- export {
298
- imageHandler
299
- };
245
+
246
+ //#endregion
247
+ export { imageHandler };
248
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["handler: IApiHandler","request: NextApiRequest","response: NextApiResponse","method: ICrudMethod","error: any","IMAGE_STRATEGY: ImageStrategy","request: NextApiRequest","response: NextApiResponse","imageOptions: IImageOptions","error: any","src: string","options: IImageHandlerOptions","originalImage: sharp.Sharp","url: string","domains: string[]","headers: IncomingHttpHeaders","pathname: string","path","options: IImageOptions"],"sources":["../src/api/api.handler.ts","../src/image/image.handler.ts"],"sourcesContent":["import { NextApiRequest, NextApiResponse } from 'next';\r\n\r\nexport type ICrudMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | string;\r\n\r\nexport type IHandler = (request: NextApiRequest, response: NextApiResponse) => Promise<any>;\r\n\r\nexport type IApiHandler = Record<ICrudMethod, IHandler>;\r\n\r\nexport function apiHandler(handler: IApiHandler): any {\r\n return async (request: NextApiRequest, response: NextApiResponse): Promise<any> => {\r\n const method: ICrudMethod = (request.method || 'GET').toLowerCase() as ICrudMethod;\r\n // check handler supports HTTP method\r\n if (!handler[method]) {\r\n return response.status(405).end(`Method ${method} Not Allowed`);\r\n }\r\n try {\r\n // global middleware\r\n // await jwtMiddleware(request, response);\r\n // route handler\r\n await handler[method](request, response);\r\n } catch (error) {\r\n // console.log('error', error);\r\n // global error handler\r\n return errorHandler(error, response);\r\n }\r\n };\r\n}\r\n\r\nexport function errorHandler(error: any, response: NextApiResponse): Promise<any> | void {\r\n if (typeof (error) === 'string') {\r\n // custom application error\r\n const is404 = error.toLowerCase().endsWith('not found');\r\n const statusCode = is404 ? 404 : 400;\r\n return response.status(statusCode).json({ message: error });\r\n }\r\n if (error.name === 'UnauthorizedError') {\r\n // jwt authentication error\r\n return response.status(401).json({ message: 'Invalid Token' });\r\n }\r\n // default to 500 server error\r\n console.error(error);\r\n return response.status(500).json({ message: error.message });\r\n}\r\n","import { IncomingHttpHeaders } from 'http';\r\n// Importing the 'fs' module to work with the file system\r\nimport * as fs from 'fs';\r\n// Importing the 'NextApiRequest' and 'NextApiResponse' from 'next' for handling API requests and responses\r\nimport { NextApiRequest, NextApiResponse } from 'next';\r\n// Importing the 'path' module to work with file and directory paths\r\nimport * as path from 'path';\r\n// Importing 'sharp' for image processing\r\nimport sharp from 'sharp';\r\n// Importing 'apiHandler' from '../api/api.handler'\r\nimport { decryptImageResize, IImageResize, ImageStrategy } from '@websolutespa/bom-core';\r\nimport { apiHandler } from '../api/api.handler';\r\n\r\nconst IMAGE_STRATEGY: ImageStrategy = process.env.IMAGE_STRATEGY ?\r\n process.env.IMAGE_STRATEGY as ImageStrategy :\r\n ImageStrategy.Mixer;\r\n\r\nconst IMAGE_MAX_AGE = 604800;\r\nconst IMAGE_FOCAL_X = 50;\r\nconst IMAGE_FOCAL_Y = 50;\r\nconst IMAGE_QUALITY = 90;\r\n\r\nexport type IImageHandlerOptions = {\r\n domains: string[];\r\n sizes: number[];\r\n quality: number;\r\n};\r\n\r\nexport type IImageOptions = IImageResize & {\r\n origin: string;\r\n search: string;\r\n url: string;\r\n};\r\n\r\n// Exporting the 'imageHandler' function\r\nexport function imageHandler({\r\n domains = [],\r\n sizes = [],\r\n quality = IMAGE_QUALITY,\r\n}: IImageHandlerOptions) {\r\n // Returning the 'apiHandler' function with a configuration object\r\n return apiHandler({\r\n // Defining the 'get' method for handling GET requests\r\n get: async (request: NextApiRequest, response: NextApiResponse) => {\r\n try {\r\n let imageOptions: IImageOptions;\r\n try {\r\n imageOptions = (IMAGE_STRATEGY === ImageStrategy.Token ?\r\n getEncryptedImageOptions(request, { domains, sizes, quality }) :\r\n getImageOptions(request, { domains, sizes, quality }));\r\n } catch (error) {\r\n return response.status(400).json({\r\n errors: [{\r\n message: error,\r\n }],\r\n });\r\n }\r\n // Generating a cached name for the image\r\n const cachedName = urlToCachedName(imageOptions);\r\n // Checking if the cached image exists\r\n if (fs.existsSync(cachedName)) {\r\n // console.log('imageHandler.fromCache', cachedName);\r\n // Reading the cached image\r\n const cachedImage = await getCachedImage(cachedName);\r\n // Setting the 'Content-Type' header to 'image/webp'\r\n response.setHeader('Content-Type', 'image/webp');\r\n // Setting the 'Expires' header to the current time plus the maximum age of the image (in seconds)\r\n response.setHeader('Expires', new Date(Date.now() + IMAGE_MAX_AGE * 1000).toUTCString());\r\n // Setting the 'Cache-Control' header to 'public' and setting 'max-age' to the maximum age of the image\r\n response.setHeader('Cache-Control', `public, max-age=${IMAGE_MAX_AGE}`);\r\n // Ending the response and sending the cached image\r\n return response.send(cachedImage);\r\n }\r\n // Check referrer\r\n if (isInvalidReferer(request)) {\r\n return response.status(401).send('you are not authorized to perform this action');\r\n }\r\n // Temporarily fix Vercel local image issue\r\n if (imageOptions.url.indexOf('http') === -1 && isVercel()) {\r\n response.redirect(307, imageOptions.url);\r\n }\r\n // Reading the original image\r\n const originalImageBuffer = await getOriginalImage(imageOptions.url, domains, request.headers);\r\n // Checking if the original image exists\r\n if (originalImageBuffer) {\r\n // Original image\r\n const originalImage = sharp(originalImageBuffer, {\r\n animated: isGIF(imageOptions.url),\r\n });\r\n // Resizing the original image and converting it to WebP format\r\n const resizedImage = await resizeImage(originalImage, imageOptions);\r\n // Converting resized image to WebP format\r\n const compressedImage = resizedImage\r\n .webp({\r\n quality: imageOptions.q,\r\n // nearLossless: true,\r\n });\r\n // Saving the resized image to the cache\r\n compressedImage.toFile(cachedName)\r\n .then(() => console.log('imageHandler.sharp.cached', cachedName))\r\n .catch((error: any) => console.error('imageHandler.sharp.error', cachedName, error));\r\n // Converting the resized image to a buffer\r\n const data = await compressedImage.toBuffer();\r\n // Setting the 'Content-Type' header to 'image/webp'\r\n response.setHeader('Content-Type', 'image/webp');\r\n // Setting the 'Expires' header to the current time plus the maximum age of the image (in seconds)\r\n response.setHeader('Expires', new Date(Date.now() + IMAGE_MAX_AGE * 1000).toUTCString());\r\n // Setting the 'Cache-Control' header to 'public' and setting 'max-age' to the maximum age of the image\r\n response.setHeader('Cache-Control', `public, max-age=${IMAGE_MAX_AGE}`);\r\n // Sending data in the response\r\n return response.end(data);\r\n }\r\n } catch (error: any) {\r\n // Logging an error message and throwing the error\r\n console.error('imageHandler.error', error);\r\n return response.status(error.status || 500).json({\r\n errors: [{\r\n message: error.message || error.name || error.statusText || 'An error occurred',\r\n }],\r\n });\r\n }\r\n },\r\n });\r\n}\r\n\r\nfunction isGIF(src: string): boolean {\r\n return src.indexOf('.gif') !== -1;\r\n}\r\n\r\nfunction isInvalidReferer(request: NextApiRequest): boolean {\r\n const { headers } = request;\r\n const referer = headers.referer;\r\n const publicUrl = process.env.NEXT_PUBLIC_URL || '';\r\n // console.log('referef', referer, 'publicUrl', publicUrl, 'IMAGE_STRATEGY', IMAGE_STRATEGY, ImageStrategy.Token);\r\n if (\r\n IMAGE_STRATEGY !== ImageStrategy.Token &&\r\n referer &&\r\n publicUrl !== 'https://' &&\r\n referer.indexOf(publicUrl) !== 0\r\n ) {\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction getImageOptions(request: NextApiRequest, options: IImageHandlerOptions): IImageOptions {\r\n // Destructuring 'slugs', 'origin', 'search', 'w', 'h', 'x', 'y', 'q' from the request query\r\n const { slugs, origin = '', search = '', w, h, x, y, q } = request.query;\r\n // Checking slugs\r\n if (!Array.isArray(slugs) || slugs.length === 0) {\r\n // console.error('imageHandler', 'missing slugs parameter');\r\n throw 'missing slugs parameter';\r\n }\r\n // Parsing 'w' to an integer and assigning it to 'width'\r\n const width = typeof w === 'string' ? parseInt(w) : undefined;\r\n // Checking if 'width' is not defined\r\n if (!width) {\r\n // Logging an error message and returning a 400 status code with a message\r\n // console.error('imageHandler', 'missing w parameter');\r\n throw 'missing w parameter';\r\n }\r\n // if (width > 2048) {\r\n if (!options.sizes.includes(width)) {\r\n throw 'invalid w parameter';\r\n }\r\n // Parsing 'h' to an integer and assigning it to 'height'\r\n const height = typeof h === 'string' ? parseInt(h) : undefined;\r\n // Parsing 'x' to an integer and assigning it to 'focalX'\r\n const focalX = typeof x === 'string' ? parseInt(x) : IMAGE_FOCAL_X;\r\n // Parsing 'y' to an integer and assigning it to 'focalY'\r\n const focalY = typeof y === 'string' ? parseInt(y) : IMAGE_FOCAL_Y;\r\n // Parsing 'q' to an integer, ensuring it's between 30 and 100, and assigning it to 'imageQuality'\r\n const imageQuality = Math.max(30, Math.min(100, (typeof q === 'string' ? parseInt(q) : options.quality)));\r\n // if (width > 2048) {\r\n if (imageQuality !== options.quality) {\r\n throw 'invalid q parameter';\r\n }\r\n // Generating url\r\n const url = origin + '/' + slugs.join('/') + search;\r\n //\r\n return height ? {\r\n w: width,\r\n h: height,\r\n x: focalX,\r\n y: focalY,\r\n q: imageQuality,\r\n src: url,\r\n url: url,\r\n origin: String(origin),\r\n search: String(search),\r\n } : {\r\n w: width,\r\n q: imageQuality,\r\n src: url,\r\n url: url,\r\n origin: String(origin),\r\n search: String(search),\r\n };\r\n}\r\n\r\nfunction getEncryptedImageOptions(request: NextApiRequest, options: IImageHandlerOptions): IImageOptions {\r\n // Destructuring 'slugs', 'origin', 'search', 't' from the request query\r\n const { slugs, origin = '', search = '', t } = request.query;\r\n // Checking slugs\r\n if (!Array.isArray(slugs) || slugs.length === 0) {\r\n // console.error('imageHandler', 'missing slugs parameter');\r\n throw 'missing slugs parameter';\r\n }\r\n if (typeof t !== 'string') {\r\n throw 'missing t parameter';\r\n }\r\n const imageResize = decryptImageResize(t);\r\n /*\r\n // Checking if 'w' is not defined\r\n if (!imageResize.w) {\r\n throw 'missing w parameter';\r\n }\r\n // if (width > 2048) {\r\n if (!options.sizes.includes(imageResize.w)) {\r\n throw 'invalid w parameter';\r\n }\r\n // Ensuring 'q' is between 30 and 100, and assigning it to 'imageQuality'\r\n const imageQuality = Math.max(30, Math.min(100, imageResize.q || options.quality));\r\n if (imageQuality !== options.quality) {\r\n throw 'invalid q parameter';\r\n }\r\n */\r\n imageResize.src = decodeURIComponent(imageResize.src);\r\n // Generating url\r\n const url = decodeURIComponent(origin + '/' + slugs.join('/') + search);\r\n //\r\n if (imageResize.src !== url) {\r\n throw `invalid src parameter ${imageResize.src} ${url}`;\r\n }\r\n //\r\n return {\r\n ...imageResize,\r\n url: url,\r\n origin: String(origin),\r\n search: String(search),\r\n };\r\n}\r\n\r\nasync function resizeImage(originalImage: sharp.Sharp, imageOptions: IImageOptions): Promise<sharp.Sharp> {\r\n const { w: resizeWidth, h: resizeHeight, x: focalX = IMAGE_FOCAL_X, y: focalY = IMAGE_FOCAL_Y } = imageOptions;\r\n const defaultResizeOptions = () => {\r\n return originalImage.resize({\r\n width: resizeWidth,\r\n height: resizeHeight ? resizeHeight : undefined,\r\n fit: sharp.fit.cover,\r\n position: sharp.strategy.attention,\r\n withoutEnlargement: true,\r\n });\r\n };\r\n if (resizeHeight && (focalX !== IMAGE_FOCAL_X || focalY !== IMAGE_FOCAL_Y)) {\r\n const { width: originalWidth, height: originalHeight } = await originalImage.metadata();\r\n if (originalWidth && originalHeight) {\r\n const resizeAspectRatio = resizeWidth / resizeHeight;\r\n const originalAspectRatio = originalWidth / originalHeight;\r\n const prioritizeHeight = resizeAspectRatio < originalAspectRatio;\r\n const imageToResize = originalImage.clone();\r\n // Scale the image up or down to fit the resize dimensions\r\n const scaledImage = imageToResize.resize(prioritizeHeight ? {\r\n height: resizeHeight,\r\n fit: sharp.fit.cover,\r\n } : {\r\n width: resizeWidth,\r\n fit: sharp.fit.cover,\r\n });\r\n const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true });\r\n // extractWidth\r\n const extractWidth = resizeWidth ?? scaledImageInfo.width;\r\n const maxOffsetX = scaledImageInfo.width - extractWidth;\r\n const leftFocalEdge = Math.round(\r\n scaledImageInfo.width * (focalX / 100) - extractWidth / 2\r\n );\r\n // extractX\r\n const extractX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX);\r\n // extractHeight\r\n const extractHeight = resizeHeight ?? scaledImageInfo.height;\r\n const maxOffsetY = scaledImageInfo.height - extractHeight;\r\n const topFocalEdge = Math.round(\r\n scaledImageInfo.height * (focalY / 100) - extractHeight / 2\r\n );\r\n // extractY\r\n const extractY = Math.min(Math.max(0, topFocalEdge), maxOffsetY);\r\n // Extract the focal area from the scaled image\r\n return scaledImage.extract({\r\n left: extractX,\r\n top: extractY,\r\n width: extractWidth,\r\n height: extractHeight,\r\n });\r\n } else {\r\n return defaultResizeOptions();\r\n }\r\n } else {\r\n return defaultResizeOptions();\r\n }\r\n}\r\n\r\n// Defining the 'getOriginalImage' function\r\nfunction getOriginalImage(url: string, domains: string[], headers: IncomingHttpHeaders): Promise<Buffer> {\r\n // Returning a new promise\r\n return new Promise((resolve, reject) => {\r\n // Checking if 'url' starts with 'http'\r\n if (url.indexOf('http') === 0) {\r\n if (isInvalidDomain(url, domains)) {\r\n return reject({ status: 401, message: 'invalid origin' });\r\n }\r\n // Fetching the image from the URL\r\n fetch(url, {\r\n method: 'GET',\r\n mode: 'cors',\r\n headers: {\r\n 'Access-Control-Allow-Origin': '*',\r\n },\r\n })\r\n .then(response => {\r\n if (!response.ok) {\r\n return reject({ status: response.status, message: response.statusText });\r\n }\r\n response.arrayBuffer().then(buffer => {\r\n return resolve(Buffer.from(buffer));\r\n }).catch(error => {\r\n return reject(error);\r\n });\r\n })\r\n .catch(error => {\r\n // Rejecting the promise with the error\r\n return reject(error);\r\n });\r\n } else {\r\n // Reading the image from the file system\r\n const filePath = path.join(process.cwd(), 'public', url);\r\n // console.log('imageHandler.getOriginalImage.filePath', filePath);\r\n fs.readFile(filePath, (error, data) => {\r\n if (error) {\r\n return reject(error);\r\n }\r\n // Resolving the promise with the image data\r\n return resolve(data);\r\n });\r\n }\r\n });\r\n}\r\n\r\n// Defining the 'getCachedImage' function\r\nfunction getCachedImage(pathname: string): Promise<Buffer> {\r\n // Returning a new promise\r\n return new Promise((resolve, reject) => {\r\n // Reading the image from the file system\r\n fs.readFile(pathname, (error, data) => {\r\n if (error) {\r\n // Rejecting the promise with the error\r\n return reject(error);\r\n }\r\n // Resolving the promise with the image data\r\n return resolve(data);\r\n });\r\n });\r\n}\r\n\r\n// Defining the 'urlToFileName' function\r\nfunction urlToFileName(url: string): string {\r\n // Replacing useless characters with UNDERSCORE\r\n const segments = url.split('?');\r\n const path = segments[0]!;\r\n const query = segments[1];\r\n let uniqueName = path.replace('://', '_').replace(/\\.|\\/|\\?|=/g, '_');\r\n // Replacing last UNDERSCORE with a DOT\r\n uniqueName = uniqueName.substring(0, uniqueName.lastIndexOf('_'))\r\n + (query ? '_' + query.replace(/\\.|\\/|\\?|=/g, '_') : '')\r\n + '.' + uniqueName.substring(uniqueName.lastIndexOf('_') + 1, uniqueName.length);\r\n // Returning the unique name\r\n return uniqueName;\r\n}\r\n\r\nfunction isVercel(): boolean {\r\n return process && process.env && String(process.env.VERCEL) === '1';\r\n}\r\n\r\n// Defining the 'urlToCachedName' function\r\nfunction urlToCachedName(options: IImageOptions): string {\r\n // Converting the URL to a file name\r\n const fileName = urlToFileName(options.url);\r\n // Parsing the file name\r\n const parsedName = path.parse(fileName);\r\n // Defining the cache directory\r\n const cacheDir = isVercel() ?\r\n path.join('/tmp', '.cache') :\r\n path.join(process.cwd(), '.cache');\r\n // Checking if the cache directory exists\r\n if (!fs.existsSync(cacheDir)) {\r\n // Creating the cache directory\r\n fs.mkdirSync(cacheDir);\r\n }\r\n // Generating the cached name\r\n const cachedName = options.h ?\r\n 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}`) :\r\n path.join(cacheDir, `${parsedName.name}_w_${options.w}_q_${options.q}${parsedName.ext}`);\r\n // Returning the cached name\r\n return cachedName;\r\n}\r\n\r\nfunction isInvalidDomain(src: string, domains: string[]): boolean {\r\n if (IMAGE_STRATEGY === ImageStrategy.Token) {\r\n return false;\r\n }\r\n const url = new URL(src);\r\n return !domains.includes(url.hostname);\r\n}\r\n"],"mappings":";;;;;;AAQA,SAAgB,WAAWA,SAA2B;AACpD,QAAO,OAAOC,SAAyBC,aAA4C;EACjF,MAAMC,UAAuB,QAAQ,UAAU,OAAO,aAAa;AAEnE,MAAI,CAAC,QAAQ,QACX,QAAO,SAAS,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,YAAY,CAAC,CAAC;AAEjE,MAAI;GAIF,MAAM,QAAQ,QAAQ,SAAS,SAAS;EACzC,SAAQ,OAAO;AAGd,UAAO,aAAa,OAAO,SAAS;EACrC;CACF;AACF;AAED,SAAgB,aAAaC,OAAYF,UAAgD;AACvF,KAAI,OAAQ,UAAW,UAAU;EAE/B,MAAM,QAAQ,MAAM,aAAa,CAAC,SAAS,YAAY;EACvD,MAAM,aAAa,QAAQ,MAAM;AACjC,SAAO,SAAS,OAAO,WAAW,CAAC,KAAK,EAAE,SAAS,MAAO,EAAC;CAC5D;AACD,KAAI,MAAM,SAAS,oBAEjB,QAAO,SAAS,OAAO,IAAI,CAAC,KAAK,EAAE,SAAS,gBAAiB,EAAC;CAGhE,QAAQ,MAAM,MAAM;AACpB,QAAO,SAAS,OAAO,IAAI,CAAC,KAAK,EAAE,SAAS,MAAM,QAAS,EAAC;AAC7D;;;;AC7BD,MAAMG,iBAAgC,QAAQ,IAAI,iBAChD,QAAQ,IAAI,iBACZ,cAAc;AAEhB,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;AAetB,SAAgB,aAAa,EAC3B,UAAU,CAAE,GACZ,QAAQ,CAAE,GACV,UAAU,eACW,EAAE;AAEvB,QAAO,WAAW,EAEhB,KAAK,OAAOC,SAAyBC,aAA8B;AACjE,MAAI;GACF,IAAIC;AACJ,OAAI;IACF,eAAgB,mBAAmB,cAAc,QAC/C,yBAAyB,SAAS;KAAE;KAAS;KAAO;IAAS,EAAC,GAC9D,gBAAgB,SAAS;KAAE;KAAS;KAAO;IAAS,EAAC;GACxD,SAAQ,OAAO;AACd,WAAO,SAAS,OAAO,IAAI,CAAC,KAAK,EAC/B,QAAQ,CAAC,EACP,SAAS,MACV,CAAC,EACH,EAAC;GACH;GAED,MAAM,aAAa,gBAAgB,aAAa;AAEhD,OAAI,GAAG,WAAW,WAAW,EAAE;IAG7B,MAAM,cAAc,MAAM,eAAe,WAAW;IAEpD,SAAS,UAAU,gBAAgB,aAAa;IAEhD,SAAS,UAAU,WAAW,IAAI,KAAK,KAAK,KAAK,GAAG,gBAAgB,KAAM,aAAa,CAAC;IAExF,SAAS,UAAU,iBAAiB,CAAC,gBAAgB,EAAE,eAAe,CAAC;AAEvE,WAAO,SAAS,KAAK,YAAY;GAClC;AAED,OAAI,iBAAiB,QAAQ,CAC3B,QAAO,SAAS,OAAO,IAAI,CAAC,KAAK,gDAAgD;AAGnF,OAAI,aAAa,IAAI,QAAQ,OAAO,KAAK,MAAM,UAAU,EACvD,SAAS,SAAS,KAAK,aAAa,IAAI;GAG1C,MAAM,sBAAsB,MAAM,iBAAiB,aAAa,KAAK,SAAS,QAAQ,QAAQ;AAE9F,OAAI,qBAAqB;IAEvB,MAAM,gBAAgB,MAAM,qBAAqB,EAC/C,UAAU,MAAM,aAAa,IAAI,CAClC,EAAC;IAEF,MAAM,eAAe,MAAM,YAAY,eAAe,aAAa;IAEnE,MAAM,kBAAkB,aACrB,KAAK,EACJ,SAAS,aAAa,EAEvB,EAAC;IAEJ,gBAAgB,OAAO,WAAW,CAC/B,KAAK,MAAM,QAAQ,IAAI,6BAA6B,WAAW,CAAC,CAChE,MAAM,CAACC,UAAe,QAAQ,MAAM,4BAA4B,YAAY,MAAM,CAAC;IAEtF,MAAM,OAAO,MAAM,gBAAgB,UAAU;IAE7C,SAAS,UAAU,gBAAgB,aAAa;IAEhD,SAAS,UAAU,WAAW,IAAI,KAAK,KAAK,KAAK,GAAG,gBAAgB,KAAM,aAAa,CAAC;IAExF,SAAS,UAAU,iBAAiB,CAAC,gBAAgB,EAAE,eAAe,CAAC;AAEvE,WAAO,SAAS,IAAI,KAAK;GAC1B;EACF,SAAQA,OAAY;GAEnB,QAAQ,MAAM,sBAAsB,MAAM;AAC1C,UAAO,SAAS,OAAO,MAAM,UAAU,IAAI,CAAC,KAAK,EAC/C,QAAQ,CAAC,EACP,SAAS,MAAM,WAAW,MAAM,QAAQ,MAAM,cAAc,oBAC7D,CAAC,EACH,EAAC;EACH;CACF,EACF,EAAC;AACH;AAED,SAAS,MAAMC,KAAsB;AACnC,QAAO,IAAI,QAAQ,OAAO,KAAK;AAChC;AAED,SAAS,iBAAiBJ,SAAkC;CAC1D,MAAM,EAAE,SAAS,GAAG;CACpB,MAAM,UAAU,QAAQ;CACxB,MAAM,YAAY,QAAQ,IAAI,mBAAmB;AAEjD,KACE,mBAAmB,cAAc,SACjC,WACA,cAAc,cACd,QAAQ,QAAQ,UAAU,KAAK,EAE/B,QAAO;AAET,QAAO;AACR;AAED,SAAS,gBAAgBA,SAAyBK,SAA8C;CAE9F,MAAM,EAAE,OAAO,SAAS,IAAI,SAAS,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,QAAQ;AAEnE,KAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,MAAM,WAAW,EAE5C,OAAM;CAGR,MAAM,QAAQ,OAAO,MAAM,WAAW,SAAS,EAAE,GAAG;AAEpD,KAAI,CAAC,MAGH,OAAM;AAGR,KAAI,CAAC,QAAQ,MAAM,SAAS,MAAM,CAChC,OAAM;CAGR,MAAM,SAAS,OAAO,MAAM,WAAW,SAAS,EAAE,GAAG;CAErD,MAAM,SAAS,OAAO,MAAM,WAAW,SAAS,EAAE,GAAG;CAErD,MAAM,SAAS,OAAO,MAAM,WAAW,SAAS,EAAE,GAAG;CAErD,MAAM,eAAe,KAAK,IAAI,IAAI,KAAK,IAAI,KAAM,OAAO,MAAM,WAAW,SAAS,EAAE,GAAG,QAAQ,QAAS,CAAC;AAEzG,KAAI,iBAAiB,QAAQ,QAC3B,OAAM;CAGR,MAAM,MAAM,SAAS,MAAM,MAAM,KAAK,IAAI,GAAG;AAE7C,QAAO,SAAS;EACd,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;EACH,KAAK;EACA;EACL,QAAQ,OAAO,OAAO;EACtB,QAAQ,OAAO,OAAO;CACvB,IAAG;EACF,GAAG;EACH,GAAG;EACH,KAAK;EACA;EACL,QAAQ,OAAO,OAAO;EACtB,QAAQ,OAAO,OAAO;CACvB;AACF;AAED,SAAS,yBAAyBL,SAAyBK,SAA8C;CAEvG,MAAM,EAAE,OAAO,SAAS,IAAI,SAAS,IAAI,GAAG,GAAG,QAAQ;AAEvD,KAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,MAAM,WAAW,EAE5C,OAAM;AAER,KAAI,OAAO,MAAM,SACf,OAAM;CAER,MAAM,cAAc,mBAAmB,EAAE;CAgBzC,YAAY,MAAM,mBAAmB,YAAY,IAAI;CAErD,MAAM,MAAM,mBAAmB,SAAS,MAAM,MAAM,KAAK,IAAI,GAAG,OAAO;AAEvE,KAAI,YAAY,QAAQ,IACtB,OAAM,CAAC,sBAAsB,EAAE,YAAY,IAAI,CAAC,EAAE,KAAK;AAGzD,QAAO;EACL,GAAG;EACE;EACL,QAAQ,OAAO,OAAO;EACtB,QAAQ,OAAO,OAAO;CACvB;AACF;AAED,eAAe,YAAYC,eAA4BJ,cAAmD;CACxG,MAAM,EAAE,GAAG,aAAa,GAAG,cAAc,GAAG,SAAS,eAAe,GAAG,SAAS,eAAe,GAAG;CAClG,MAAM,uBAAuB,MAAM;AACjC,SAAO,cAAc,OAAO;GAC1B,OAAO;GACP,QAAQ,eAAe,eAAe;GACtC,KAAK,MAAM,IAAI;GACf,UAAU,MAAM,SAAS;GACzB,oBAAoB;EACrB,EAAC;CACH;AACD,KAAI,iBAAiB,WAAW,iBAAiB,WAAW,gBAAgB;EAC1E,MAAM,EAAE,OAAO,eAAe,QAAQ,gBAAgB,GAAG,MAAM,cAAc,UAAU;AACvF,MAAI,iBAAiB,gBAAgB;GACnC,MAAM,oBAAoB,cAAc;GACxC,MAAM,sBAAsB,gBAAgB;GAC5C,MAAM,mBAAmB,oBAAoB;GAC7C,MAAM,gBAAgB,cAAc,OAAO;GAE3C,MAAM,cAAc,cAAc,OAAO,mBAAmB;IAC1D,QAAQ;IACR,KAAK,MAAM,IAAI;GAChB,IAAG;IACF,OAAO;IACP,KAAK,MAAM,IAAI;GAChB,EAAC;GACF,MAAM,EAAE,MAAM,iBAAiB,GAAG,MAAM,YAAY,SAAS,EAAE,mBAAmB,KAAM,EAAC;GAEzF,MAAM,eAAe,eAAe,gBAAgB;GACpD,MAAM,aAAa,gBAAgB,QAAQ;GAC3C,MAAM,gBAAgB,KAAK,MACzB,gBAAgB,SAAS,SAAS,OAAO,eAAe,EACzD;GAED,MAAM,WAAW,KAAK,IAAI,KAAK,IAAI,GAAG,cAAc,EAAE,WAAW;GAEjE,MAAM,gBAAgB,gBAAgB,gBAAgB;GACtD,MAAM,aAAa,gBAAgB,SAAS;GAC5C,MAAM,eAAe,KAAK,MACxB,gBAAgB,UAAU,SAAS,OAAO,gBAAgB,EAC3D;GAED,MAAM,WAAW,KAAK,IAAI,KAAK,IAAI,GAAG,aAAa,EAAE,WAAW;AAEhE,UAAO,YAAY,QAAQ;IACzB,MAAM;IACN,KAAK;IACL,OAAO;IACP,QAAQ;GACT,EAAC;EACH,MACC,QAAO,sBAAsB;CAEhC,MACC,QAAO,sBAAsB;AAEhC;AAGD,SAAS,iBAAiBK,KAAaC,SAAmBC,SAA+C;AAEvG,QAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAEtC,MAAI,IAAI,QAAQ,OAAO,KAAK,GAAG;AAC7B,OAAI,gBAAgB,KAAK,QAAQ,CAC/B,QAAO,OAAO;IAAE,QAAQ;IAAK,SAAS;GAAkB,EAAC;GAG3D,MAAM,KAAK;IACT,QAAQ;IACR,MAAM;IACN,SAAS,EACP,+BAA+B,IAChC;GACF,EAAC,CACC,KAAK,cAAY;AAChB,QAAI,CAAC,SAAS,GACZ,QAAO,OAAO;KAAE,QAAQ,SAAS;KAAQ,SAAS,SAAS;IAAY,EAAC;IAE1E,SAAS,aAAa,CAAC,KAAK,YAAU;AACpC,YAAO,QAAQ,OAAO,KAAK,OAAO,CAAC;IACpC,EAAC,CAAC,MAAM,WAAS;AAChB,YAAO,OAAO,MAAM;IACrB,EAAC;GACH,EAAC,CACD,MAAM,WAAS;AAEd,WAAO,OAAO,MAAM;GACrB,EAAC;EACL,OAAM;GAEL,MAAM,WAAW,KAAK,KAAK,QAAQ,KAAK,EAAE,UAAU,IAAI;GAExD,GAAG,SAAS,UAAU,CAAC,OAAO,SAAS;AACrC,QAAI,MACF,QAAO,OAAO,MAAM;AAGtB,WAAO,QAAQ,KAAK;GACrB,EAAC;EACH;CACF;AACF;AAGD,SAAS,eAAeC,UAAmC;AAEzD,QAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;EAEtC,GAAG,SAAS,UAAU,CAAC,OAAO,SAAS;AACrC,OAAI,MAEF,QAAO,OAAO,MAAM;AAGtB,UAAO,QAAQ,KAAK;EACrB,EAAC;CACH;AACF;AAGD,SAAS,cAAcH,KAAqB;CAE1C,MAAM,WAAW,IAAI,MAAM,IAAI;CAC/B,MAAMI,SAAO,SAAS;CACtB,MAAM,QAAQ,SAAS;CACvB,IAAI,aAAaA,OAAK,QAAQ,OAAO,IAAI,CAAC,QAAQ,eAAe,IAAI;CAErE,aAAa,WAAW,UAAU,GAAG,WAAW,YAAY,IAAI,CAAC,IAC5D,QAAQ,MAAM,MAAM,QAAQ,eAAe,IAAI,GAAG,MACnD,MAAM,WAAW,UAAU,WAAW,YAAY,IAAI,GAAG,GAAG,WAAW,OAAO;AAElF,QAAO;AACR;AAED,SAAS,WAAoB;AAC3B,QAAO,WAAW,QAAQ,OAAO,OAAO,QAAQ,IAAI,OAAO,KAAK;AACjE;AAGD,SAAS,gBAAgBC,SAAgC;CAEvD,MAAM,WAAW,cAAc,QAAQ,IAAI;CAE3C,MAAM,aAAa,KAAK,MAAM,SAAS;CAEvC,MAAM,WAAW,UAAU,GACzB,KAAK,KAAK,QAAQ,SAAS,GAC3B,KAAK,KAAK,QAAQ,KAAK,EAAE,SAAS;AAEpC,KAAI,CAAC,GAAG,WAAW,SAAS,EAE1B,GAAG,UAAU,SAAS;CAGxB,MAAM,aAAa,QAAQ,IACzB,KAAK,KAAK,UAAU,GAAG,WAAW,KAAK,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,KAAK,EAAE,GAAG,EAAE,QAAQ,KAAK,cAAc,GAAG,EAAE,QAAQ,KAAK,cAAc,GAAG,EAAE,QAAQ,IAAI,WAAW,KAAK,CAAC,GAC5K,KAAK,KAAK,UAAU,GAAG,WAAW,KAAK,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,WAAW,KAAK,CAAC;AAE1F,QAAO;AACR;AAED,SAAS,gBAAgBR,KAAaI,SAA4B;AAChE,KAAI,mBAAmB,cAAc,MACnC,QAAO;CAET,MAAM,MAAM,IAAI,IAAI;AACpB,QAAO,CAAC,QAAQ,SAAS,IAAI,SAAS;AACvC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@websolutespa/bom-mixer-image",
3
- "version": "2.0.1-next.0",
3
+ "version": "2.0.1-next.2",
4
4
  "description": "Mixer Image module of the BOM Repository",
5
5
  "keywords": [
6
6
  "bom",
@@ -18,28 +18,26 @@
18
18
  "test-watch": "vitest",
19
19
  "coverage:integration": "npm run test -- --coverage.enabled --coverage.provider=istanbul --coverage.all",
20
20
  "coverage": "run-s -c --silent coverage:*",
21
- "compile": "tsup ./src/index.ts --format esm,cjs --dts",
21
+ "compile": "tsdown",
22
22
  "prepack": "bom prepack",
23
23
  "postpack": "bom postpack",
24
24
  "types": "tsc --noemit"
25
25
  },
26
26
  "dependencies": {},
27
27
  "peerDependencies": {
28
- "@websolutespa/bom-core": "2.0.1-next.0",
28
+ "@websolutespa/bom-core": "2.0.1-next.2",
29
29
  "next": "15.4.10",
30
30
  "sharp": "0.34.2"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/sharp": "^0.30.4",
34
- "@websolutespa/bom-cli": "2.0.1-next.0",
35
- "@websolutespa/test": "2.0.1-next.0",
36
- "@websolutespa/tsconfig": "2.0.1-next.0",
34
+ "@websolutespa/bom-cli": "2.0.1-next.2",
35
+ "@websolutespa/test": "2.0.1-next.2",
36
+ "@websolutespa/tsconfig": "2.0.1-next.2",
37
37
  "eslint": "9.31.0",
38
- "@websolutespa/eslint-config": "2.0.1-next.0",
38
+ "@websolutespa/eslint-config": "2.0.1-next.2",
39
39
  "npm-run-all": "4.1.5",
40
40
  "sharp": "0.34.2",
41
- "ts-node": "^10.9.2",
42
- "tsup": "8.5.0",
43
41
  "typescript": "5.8.3"
44
42
  },
45
43
  "publishConfig": {
@@ -50,13 +48,34 @@
50
48
  "prepack": {
51
49
  "types": "./dist/index.d.ts",
52
50
  "main": "./dist/index.js",
53
- "source": "./src/index.ts"
51
+ "source": "./src/index.ts",
52
+ "exports": {
53
+ ".": {
54
+ "import": "./dist/index.js",
55
+ "types": "./dist/index.d.ts",
56
+ "default": "./dist/index.js"
57
+ }
58
+ }
54
59
  },
55
60
  "postpack": {
56
61
  "types": "./src/index.ts",
57
- "main": "./src/index.ts"
62
+ "main": "./src/index.ts",
63
+ "exports": {
64
+ ".": {
65
+ "import": "./src/index.ts",
66
+ "types": "./src/index.ts",
67
+ "default": "./src/index.ts"
68
+ }
69
+ }
58
70
  },
59
71
  "types": "./dist/index.d.ts",
60
72
  "main": "./dist/index.js",
61
- "source": "./src/index.ts"
73
+ "source": "./src/index.ts",
74
+ "exports": {
75
+ ".": {
76
+ "import": "./dist/index.js",
77
+ "types": "./dist/index.d.ts",
78
+ "default": "./dist/index.js"
79
+ }
80
+ }
62
81
  }
package/dist/index.cjs DELETED
@@ -1,336 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- imageHandler: () => imageHandler
34
- });
35
- module.exports = __toCommonJS(index_exports);
36
-
37
- // src/image/image.handler.ts
38
- var fs = __toESM(require("fs"), 1);
39
- var path = __toESM(require("path"), 1);
40
- var import_sharp = __toESM(require("sharp"), 1);
41
- var import_bom_core = require("@websolutespa/bom-core");
42
-
43
- // src/api/api.handler.ts
44
- function apiHandler(handler) {
45
- return async (request, response) => {
46
- const method = (request.method || "GET").toLowerCase();
47
- if (!handler[method]) {
48
- return response.status(405).end(`Method ${method} Not Allowed`);
49
- }
50
- try {
51
- await handler[method](request, response);
52
- } catch (error) {
53
- return errorHandler(error, response);
54
- }
55
- };
56
- }
57
- function errorHandler(error, response) {
58
- if (typeof error === "string") {
59
- const is404 = error.toLowerCase().endsWith("not found");
60
- const statusCode = is404 ? 404 : 400;
61
- return response.status(statusCode).json({ message: error });
62
- }
63
- if (error.name === "UnauthorizedError") {
64
- return response.status(401).json({ message: "Invalid Token" });
65
- }
66
- console.error(error);
67
- return response.status(500).json({ message: error.message });
68
- }
69
-
70
- // src/image/image.handler.ts
71
- var IMAGE_STRATEGY = process.env.IMAGE_STRATEGY ? process.env.IMAGE_STRATEGY : import_bom_core.ImageStrategy.Mixer;
72
- var IMAGE_MAX_AGE = 604800;
73
- var IMAGE_FOCAL_X = 50;
74
- var IMAGE_FOCAL_Y = 50;
75
- var IMAGE_QUALITY = 90;
76
- function imageHandler({
77
- domains = [],
78
- sizes = [],
79
- quality = IMAGE_QUALITY
80
- }) {
81
- return apiHandler({
82
- // Defining the 'get' method for handling GET requests
83
- get: async (request, response) => {
84
- try {
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
- });
94
- }
95
- const cachedName = urlToCachedName(imageOptions);
96
- if (fs.existsSync(cachedName)) {
97
- const cachedImage = await getCachedImage(cachedName);
98
- response.setHeader("Content-Type", "image/webp");
99
- response.setHeader("Expires", new Date(Date.now() + IMAGE_MAX_AGE * 1e3).toUTCString());
100
- response.setHeader("Cache-Control", `public, max-age=${IMAGE_MAX_AGE}`);
101
- return response.send(cachedImage);
102
- }
103
- if (isInvalidReferer(request)) {
104
- return response.status(401).send("you are not authorized to perform this action");
105
- }
106
- if (imageOptions.url.indexOf("http") === -1 && isVercel()) {
107
- response.redirect(307, imageOptions.url);
108
- }
109
- const originalImageBuffer = await getOriginalImage(imageOptions.url, domains, request.headers);
110
- if (originalImageBuffer) {
111
- const originalImage = (0, import_sharp.default)(originalImageBuffer, {
112
- animated: isGIF(imageOptions.url)
113
- });
114
- const resizedImage = await resizeImage(originalImage, imageOptions);
115
- const compressedImage = resizedImage.webp({
116
- quality: imageOptions.q
117
- // nearLossless: true,
118
- });
119
- compressedImage.toFile(cachedName).then(() => console.log("imageHandler.sharp.cached", cachedName)).catch((error) => console.error("imageHandler.sharp.error", cachedName, error));
120
- const data = await compressedImage.toBuffer();
121
- response.setHeader("Content-Type", "image/webp");
122
- response.setHeader("Expires", new Date(Date.now() + IMAGE_MAX_AGE * 1e3).toUTCString());
123
- response.setHeader("Cache-Control", `public, max-age=${IMAGE_MAX_AGE}`);
124
- return response.end(data);
125
- }
126
- } catch (error) {
127
- console.error("imageHandler.error", error);
128
- return response.status(error.status || 500).json({
129
- errors: [{
130
- message: error.message || error.name || error.statusText || "An error occurred"
131
- }]
132
- });
133
- }
134
- }
135
- });
136
- }
137
- function isGIF(src) {
138
- return src.indexOf(".gif") !== -1;
139
- }
140
- function isInvalidReferer(request) {
141
- const { headers } = request;
142
- const referer = headers.referer;
143
- const publicUrl = process.env.NEXT_PUBLIC_URL || "";
144
- if (IMAGE_STRATEGY !== import_bom_core.ImageStrategy.Token && referer && publicUrl !== "https://" && referer.indexOf(publicUrl) !== 0) {
145
- return true;
146
- }
147
- return false;
148
- }
149
- function getImageOptions(request, options) {
150
- const { slugs, origin = "", search = "", w, h, x, y, q } = request.query;
151
- if (!Array.isArray(slugs) || slugs.length === 0) {
152
- throw "missing slugs parameter";
153
- }
154
- const width = typeof w === "string" ? parseInt(w) : void 0;
155
- if (!width) {
156
- throw "missing w parameter";
157
- }
158
- if (!options.sizes.includes(width)) {
159
- throw "invalid w parameter";
160
- }
161
- const height = typeof h === "string" ? parseInt(h) : void 0;
162
- const focalX = typeof x === "string" ? parseInt(x) : IMAGE_FOCAL_X;
163
- const focalY = typeof y === "string" ? parseInt(y) : IMAGE_FOCAL_Y;
164
- const imageQuality = Math.max(30, Math.min(100, typeof q === "string" ? parseInt(q) : options.quality));
165
- if (imageQuality !== options.quality) {
166
- throw "invalid q parameter";
167
- }
168
- const url = origin + "/" + slugs.join("/") + search;
169
- return height ? {
170
- w: width,
171
- h: height,
172
- x: focalX,
173
- y: focalY,
174
- q: imageQuality,
175
- src: url,
176
- url,
177
- origin: String(origin),
178
- search: String(search)
179
- } : {
180
- w: width,
181
- q: imageQuality,
182
- src: url,
183
- url,
184
- origin: String(origin),
185
- search: String(search)
186
- };
187
- }
188
- function getEncryptedImageOptions(request, options) {
189
- const { slugs, origin = "", search = "", t } = request.query;
190
- if (!Array.isArray(slugs) || slugs.length === 0) {
191
- throw "missing slugs parameter";
192
- }
193
- if (typeof t !== "string") {
194
- throw "missing t parameter";
195
- }
196
- const imageResize = (0, import_bom_core.decryptImageResize)(t);
197
- imageResize.src = decodeURIComponent(imageResize.src);
198
- const url = decodeURIComponent(origin + "/" + slugs.join("/") + search);
199
- if (imageResize.src !== url) {
200
- throw `invalid src parameter ${imageResize.src} ${url}`;
201
- }
202
- return {
203
- ...imageResize,
204
- url,
205
- origin: String(origin),
206
- search: String(search)
207
- };
208
- }
209
- async function resizeImage(originalImage, imageOptions) {
210
- const { w: resizeWidth, h: resizeHeight, x: focalX = IMAGE_FOCAL_X, y: focalY = IMAGE_FOCAL_Y } = imageOptions;
211
- const defaultResizeOptions = () => {
212
- return originalImage.resize({
213
- width: resizeWidth,
214
- height: resizeHeight ? resizeHeight : void 0,
215
- fit: import_sharp.default.fit.cover,
216
- position: import_sharp.default.strategy.attention,
217
- withoutEnlargement: true
218
- });
219
- };
220
- if (resizeHeight && (focalX !== IMAGE_FOCAL_X || focalY !== IMAGE_FOCAL_Y)) {
221
- const { width: originalWidth, height: originalHeight } = await originalImage.metadata();
222
- if (originalWidth && originalHeight) {
223
- const resizeAspectRatio = resizeWidth / resizeHeight;
224
- const originalAspectRatio = originalWidth / originalHeight;
225
- const prioritizeHeight = resizeAspectRatio < originalAspectRatio;
226
- const imageToResize = originalImage.clone();
227
- const scaledImage = imageToResize.resize(prioritizeHeight ? {
228
- height: resizeHeight,
229
- fit: import_sharp.default.fit.cover
230
- } : {
231
- width: resizeWidth,
232
- fit: import_sharp.default.fit.cover
233
- });
234
- const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true });
235
- const extractWidth = resizeWidth ?? scaledImageInfo.width;
236
- const maxOffsetX = scaledImageInfo.width - extractWidth;
237
- const leftFocalEdge = Math.round(
238
- scaledImageInfo.width * (focalX / 100) - extractWidth / 2
239
- );
240
- const extractX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX);
241
- const extractHeight = resizeHeight ?? scaledImageInfo.height;
242
- const maxOffsetY = scaledImageInfo.height - extractHeight;
243
- const topFocalEdge = Math.round(
244
- scaledImageInfo.height * (focalY / 100) - extractHeight / 2
245
- );
246
- const extractY = Math.min(Math.max(0, topFocalEdge), maxOffsetY);
247
- return scaledImage.extract({
248
- left: extractX,
249
- top: extractY,
250
- width: extractWidth,
251
- height: extractHeight
252
- });
253
- } else {
254
- return defaultResizeOptions();
255
- }
256
- } else {
257
- return defaultResizeOptions();
258
- }
259
- }
260
- function getOriginalImage(url, domains, headers) {
261
- return new Promise((resolve, reject) => {
262
- if (url.indexOf("http") === 0) {
263
- if (isInvalidDomain(url, domains)) {
264
- return reject({ status: 401, message: "invalid origin" });
265
- }
266
- fetch(url, {
267
- method: "GET",
268
- mode: "cors",
269
- headers: {
270
- "Access-Control-Allow-Origin": "*"
271
- }
272
- }).then((response) => {
273
- if (!response.ok) {
274
- return reject({ status: response.status, message: response.statusText });
275
- }
276
- response.arrayBuffer().then((buffer) => {
277
- return resolve(Buffer.from(buffer));
278
- }).catch((error) => {
279
- return reject(error);
280
- });
281
- }).catch((error) => {
282
- return reject(error);
283
- });
284
- } else {
285
- const filePath = path.join(process.cwd(), "public", url);
286
- fs.readFile(filePath, (error, data) => {
287
- if (error) {
288
- return reject(error);
289
- }
290
- return resolve(data);
291
- });
292
- }
293
- });
294
- }
295
- function getCachedImage(pathname) {
296
- return new Promise((resolve, reject) => {
297
- fs.readFile(pathname, (error, data) => {
298
- if (error) {
299
- return reject(error);
300
- }
301
- return resolve(data);
302
- });
303
- });
304
- }
305
- function urlToFileName(url) {
306
- const segments = url.split("?");
307
- const path2 = segments[0];
308
- const query = segments[1];
309
- let uniqueName = path2.replace("://", "_").replace(/\.|\/|\?|=/g, "_");
310
- uniqueName = uniqueName.substring(0, uniqueName.lastIndexOf("_")) + (query ? "_" + query.replace(/\.|\/|\?|=/g, "_") : "") + "." + uniqueName.substring(uniqueName.lastIndexOf("_") + 1, uniqueName.length);
311
- return uniqueName;
312
- }
313
- function isVercel() {
314
- return process && process.env && String(process.env.VERCEL) === "1";
315
- }
316
- function urlToCachedName(options) {
317
- const fileName = urlToFileName(options.url);
318
- const parsedName = path.parse(fileName);
319
- const cacheDir = isVercel() ? path.join("/tmp", ".cache") : path.join(process.cwd(), ".cache");
320
- if (!fs.existsSync(cacheDir)) {
321
- fs.mkdirSync(cacheDir);
322
- }
323
- 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}`);
324
- return cachedName;
325
- }
326
- function isInvalidDomain(src, domains) {
327
- if (IMAGE_STRATEGY === import_bom_core.ImageStrategy.Token) {
328
- return false;
329
- }
330
- const url = new URL(src);
331
- return !domains.includes(url.hostname);
332
- }
333
- // Annotate the CommonJS export names for ESM import in node:
334
- 0 && (module.exports = {
335
- imageHandler
336
- });
package/dist/index.d.cts DELETED
@@ -1,15 +0,0 @@
1
- import { IImageResize } from '@websolutespa/bom-core';
2
-
3
- type IImageHandlerOptions = {
4
- domains: string[];
5
- sizes: number[];
6
- quality: number;
7
- };
8
- type IImageOptions = IImageResize & {
9
- origin: string;
10
- search: string;
11
- url: string;
12
- };
13
- declare function imageHandler({ domains, sizes, quality, }: IImageHandlerOptions): any;
14
-
15
- export { type IImageHandlerOptions, type IImageOptions, imageHandler };