@websolutespa/bom-mixer-image 1.8.3 → 2.0.1-next.0
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 +115 -98
- package/dist/{index.mjs → index.cjs} +76 -24
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +39 -61
- package/package.json +20 -18
- package/src/__tests/example.test.ts +1 -1
- package/src/image/image.handler.ts +35 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,98 +1,115 @@
|
|
|
1
|
-
# @websolutespa/bom-mixer-image
|
|
2
|
-
|
|
3
|
-
## 1.
|
|
4
|
-
|
|
5
|
-
###
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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,8 +1,44 @@
|
|
|
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
|
+
|
|
1
37
|
// src/image/image.handler.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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");
|
|
6
42
|
|
|
7
43
|
// src/api/api.handler.ts
|
|
8
44
|
function apiHandler(handler) {
|
|
@@ -32,7 +68,7 @@ function errorHandler(error, response) {
|
|
|
32
68
|
}
|
|
33
69
|
|
|
34
70
|
// src/image/image.handler.ts
|
|
35
|
-
var IMAGE_STRATEGY = process.env.IMAGE_STRATEGY ? process.env.IMAGE_STRATEGY : ImageStrategy.Mixer;
|
|
71
|
+
var IMAGE_STRATEGY = process.env.IMAGE_STRATEGY ? process.env.IMAGE_STRATEGY : import_bom_core.ImageStrategy.Mixer;
|
|
36
72
|
var IMAGE_MAX_AGE = 604800;
|
|
37
73
|
var IMAGE_FOCAL_X = 50;
|
|
38
74
|
var IMAGE_FOCAL_Y = 50;
|
|
@@ -48,7 +84,7 @@ function imageHandler({
|
|
|
48
84
|
try {
|
|
49
85
|
let imageOptions;
|
|
50
86
|
try {
|
|
51
|
-
imageOptions = IMAGE_STRATEGY === ImageStrategy.Token ? getEncryptedImageOptions(request, { domains, sizes, quality }) : getImageOptions(request, { domains, sizes, quality });
|
|
87
|
+
imageOptions = IMAGE_STRATEGY === import_bom_core.ImageStrategy.Token ? getEncryptedImageOptions(request, { domains, sizes, quality }) : getImageOptions(request, { domains, sizes, quality });
|
|
52
88
|
} catch (error) {
|
|
53
89
|
return response.status(400).json({
|
|
54
90
|
errors: [{
|
|
@@ -64,10 +100,7 @@ function imageHandler({
|
|
|
64
100
|
response.setHeader("Cache-Control", `public, max-age=${IMAGE_MAX_AGE}`);
|
|
65
101
|
return response.send(cachedImage);
|
|
66
102
|
}
|
|
67
|
-
|
|
68
|
-
const referer = headers.referer;
|
|
69
|
-
const publicUrl = process.env.NEXT_PUBLIC_URL || "";
|
|
70
|
-
if (referer && publicUrl !== "https://" && referer.indexOf(publicUrl) !== 0) {
|
|
103
|
+
if (isInvalidReferer(request)) {
|
|
71
104
|
return response.status(401).send("you are not authorized to perform this action");
|
|
72
105
|
}
|
|
73
106
|
if (imageOptions.url.indexOf("http") === -1 && isVercel()) {
|
|
@@ -75,7 +108,9 @@ function imageHandler({
|
|
|
75
108
|
}
|
|
76
109
|
const originalImageBuffer = await getOriginalImage(imageOptions.url, domains, request.headers);
|
|
77
110
|
if (originalImageBuffer) {
|
|
78
|
-
const originalImage =
|
|
111
|
+
const originalImage = (0, import_sharp.default)(originalImageBuffer, {
|
|
112
|
+
animated: isGIF(imageOptions.url)
|
|
113
|
+
});
|
|
79
114
|
const resizedImage = await resizeImage(originalImage, imageOptions);
|
|
80
115
|
const compressedImage = resizedImage.webp({
|
|
81
116
|
quality: imageOptions.q
|
|
@@ -99,6 +134,18 @@ function imageHandler({
|
|
|
99
134
|
}
|
|
100
135
|
});
|
|
101
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
|
+
}
|
|
102
149
|
function getImageOptions(request, options) {
|
|
103
150
|
const { slugs, origin = "", search = "", w, h, x, y, q } = request.query;
|
|
104
151
|
if (!Array.isArray(slugs) || slugs.length === 0) {
|
|
@@ -146,10 +193,11 @@ function getEncryptedImageOptions(request, options) {
|
|
|
146
193
|
if (typeof t !== "string") {
|
|
147
194
|
throw "missing t parameter";
|
|
148
195
|
}
|
|
149
|
-
const imageResize = decryptImageResize(t);
|
|
150
|
-
|
|
196
|
+
const imageResize = (0, import_bom_core.decryptImageResize)(t);
|
|
197
|
+
imageResize.src = decodeURIComponent(imageResize.src);
|
|
198
|
+
const url = decodeURIComponent(origin + "/" + slugs.join("/") + search);
|
|
151
199
|
if (imageResize.src !== url) {
|
|
152
|
-
throw
|
|
200
|
+
throw `invalid src parameter ${imageResize.src} ${url}`;
|
|
153
201
|
}
|
|
154
202
|
return {
|
|
155
203
|
...imageResize,
|
|
@@ -164,8 +212,8 @@ async function resizeImage(originalImage, imageOptions) {
|
|
|
164
212
|
return originalImage.resize({
|
|
165
213
|
width: resizeWidth,
|
|
166
214
|
height: resizeHeight ? resizeHeight : void 0,
|
|
167
|
-
fit:
|
|
168
|
-
position:
|
|
215
|
+
fit: import_sharp.default.fit.cover,
|
|
216
|
+
position: import_sharp.default.strategy.attention,
|
|
169
217
|
withoutEnlargement: true
|
|
170
218
|
});
|
|
171
219
|
};
|
|
@@ -178,10 +226,10 @@ async function resizeImage(originalImage, imageOptions) {
|
|
|
178
226
|
const imageToResize = originalImage.clone();
|
|
179
227
|
const scaledImage = imageToResize.resize(prioritizeHeight ? {
|
|
180
228
|
height: resizeHeight,
|
|
181
|
-
fit:
|
|
229
|
+
fit: import_sharp.default.fit.cover
|
|
182
230
|
} : {
|
|
183
231
|
width: resizeWidth,
|
|
184
|
-
fit:
|
|
232
|
+
fit: import_sharp.default.fit.cover
|
|
185
233
|
});
|
|
186
234
|
const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true });
|
|
187
235
|
const extractWidth = resizeWidth ?? scaledImageInfo.width;
|
|
@@ -209,10 +257,10 @@ async function resizeImage(originalImage, imageOptions) {
|
|
|
209
257
|
return defaultResizeOptions();
|
|
210
258
|
}
|
|
211
259
|
}
|
|
212
|
-
function getOriginalImage(url, domains, headers
|
|
260
|
+
function getOriginalImage(url, domains, headers) {
|
|
213
261
|
return new Promise((resolve, reject) => {
|
|
214
262
|
if (url.indexOf("http") === 0) {
|
|
215
|
-
if (
|
|
263
|
+
if (isInvalidDomain(url, domains)) {
|
|
216
264
|
return reject({ status: 401, message: "invalid origin" });
|
|
217
265
|
}
|
|
218
266
|
fetch(url, {
|
|
@@ -275,10 +323,14 @@ function urlToCachedName(options) {
|
|
|
275
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}`);
|
|
276
324
|
return cachedName;
|
|
277
325
|
}
|
|
278
|
-
function
|
|
326
|
+
function isInvalidDomain(src, domains) {
|
|
327
|
+
if (IMAGE_STRATEGY === import_bom_core.ImageStrategy.Token) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
279
330
|
const url = new URL(src);
|
|
280
|
-
return domains.includes(url.hostname);
|
|
331
|
+
return !domains.includes(url.hostname);
|
|
281
332
|
}
|
|
282
|
-
export
|
|
333
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
334
|
+
0 && (module.exports = {
|
|
283
335
|
imageHandler
|
|
284
|
-
};
|
|
336
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ type IImageOptions = IImageResize & {
|
|
|
12
12
|
};
|
|
13
13
|
declare function imageHandler({ domains, sizes, quality, }: IImageHandlerOptions): any;
|
|
14
14
|
|
|
15
|
-
export { IImageHandlerOptions, IImageOptions, imageHandler };
|
|
15
|
+
export { type IImageHandlerOptions, type IImageOptions, imageHandler };
|
package/dist/index.js
CHANGED
|
@@ -1,44 +1,8 @@
|
|
|
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 src_exports = {};
|
|
32
|
-
__export(src_exports, {
|
|
33
|
-
imageHandler: () => imageHandler
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(src_exports);
|
|
36
|
-
|
|
37
1
|
// src/image/image.handler.ts
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import sharp from "sharp";
|
|
5
|
+
import { decryptImageResize, ImageStrategy } from "@websolutespa/bom-core";
|
|
42
6
|
|
|
43
7
|
// src/api/api.handler.ts
|
|
44
8
|
function apiHandler(handler) {
|
|
@@ -68,7 +32,7 @@ function errorHandler(error, response) {
|
|
|
68
32
|
}
|
|
69
33
|
|
|
70
34
|
// src/image/image.handler.ts
|
|
71
|
-
var IMAGE_STRATEGY = process.env.IMAGE_STRATEGY ? process.env.IMAGE_STRATEGY :
|
|
35
|
+
var IMAGE_STRATEGY = process.env.IMAGE_STRATEGY ? process.env.IMAGE_STRATEGY : ImageStrategy.Mixer;
|
|
72
36
|
var IMAGE_MAX_AGE = 604800;
|
|
73
37
|
var IMAGE_FOCAL_X = 50;
|
|
74
38
|
var IMAGE_FOCAL_Y = 50;
|
|
@@ -84,7 +48,7 @@ function imageHandler({
|
|
|
84
48
|
try {
|
|
85
49
|
let imageOptions;
|
|
86
50
|
try {
|
|
87
|
-
imageOptions = IMAGE_STRATEGY ===
|
|
51
|
+
imageOptions = IMAGE_STRATEGY === ImageStrategy.Token ? getEncryptedImageOptions(request, { domains, sizes, quality }) : getImageOptions(request, { domains, sizes, quality });
|
|
88
52
|
} catch (error) {
|
|
89
53
|
return response.status(400).json({
|
|
90
54
|
errors: [{
|
|
@@ -100,10 +64,7 @@ function imageHandler({
|
|
|
100
64
|
response.setHeader("Cache-Control", `public, max-age=${IMAGE_MAX_AGE}`);
|
|
101
65
|
return response.send(cachedImage);
|
|
102
66
|
}
|
|
103
|
-
|
|
104
|
-
const referer = headers.referer;
|
|
105
|
-
const publicUrl = process.env.NEXT_PUBLIC_URL || "";
|
|
106
|
-
if (referer && publicUrl !== "https://" && referer.indexOf(publicUrl) !== 0) {
|
|
67
|
+
if (isInvalidReferer(request)) {
|
|
107
68
|
return response.status(401).send("you are not authorized to perform this action");
|
|
108
69
|
}
|
|
109
70
|
if (imageOptions.url.indexOf("http") === -1 && isVercel()) {
|
|
@@ -111,7 +72,9 @@ function imageHandler({
|
|
|
111
72
|
}
|
|
112
73
|
const originalImageBuffer = await getOriginalImage(imageOptions.url, domains, request.headers);
|
|
113
74
|
if (originalImageBuffer) {
|
|
114
|
-
const originalImage = (
|
|
75
|
+
const originalImage = sharp(originalImageBuffer, {
|
|
76
|
+
animated: isGIF(imageOptions.url)
|
|
77
|
+
});
|
|
115
78
|
const resizedImage = await resizeImage(originalImage, imageOptions);
|
|
116
79
|
const compressedImage = resizedImage.webp({
|
|
117
80
|
quality: imageOptions.q
|
|
@@ -135,6 +98,18 @@ function imageHandler({
|
|
|
135
98
|
}
|
|
136
99
|
});
|
|
137
100
|
}
|
|
101
|
+
function isGIF(src) {
|
|
102
|
+
return src.indexOf(".gif") !== -1;
|
|
103
|
+
}
|
|
104
|
+
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;
|
|
112
|
+
}
|
|
138
113
|
function getImageOptions(request, options) {
|
|
139
114
|
const { slugs, origin = "", search = "", w, h, x, y, q } = request.query;
|
|
140
115
|
if (!Array.isArray(slugs) || slugs.length === 0) {
|
|
@@ -182,10 +157,11 @@ function getEncryptedImageOptions(request, options) {
|
|
|
182
157
|
if (typeof t !== "string") {
|
|
183
158
|
throw "missing t parameter";
|
|
184
159
|
}
|
|
185
|
-
const imageResize =
|
|
186
|
-
|
|
160
|
+
const imageResize = decryptImageResize(t);
|
|
161
|
+
imageResize.src = decodeURIComponent(imageResize.src);
|
|
162
|
+
const url = decodeURIComponent(origin + "/" + slugs.join("/") + search);
|
|
187
163
|
if (imageResize.src !== url) {
|
|
188
|
-
throw
|
|
164
|
+
throw `invalid src parameter ${imageResize.src} ${url}`;
|
|
189
165
|
}
|
|
190
166
|
return {
|
|
191
167
|
...imageResize,
|
|
@@ -200,8 +176,8 @@ async function resizeImage(originalImage, imageOptions) {
|
|
|
200
176
|
return originalImage.resize({
|
|
201
177
|
width: resizeWidth,
|
|
202
178
|
height: resizeHeight ? resizeHeight : void 0,
|
|
203
|
-
fit:
|
|
204
|
-
position:
|
|
179
|
+
fit: sharp.fit.cover,
|
|
180
|
+
position: sharp.strategy.attention,
|
|
205
181
|
withoutEnlargement: true
|
|
206
182
|
});
|
|
207
183
|
};
|
|
@@ -214,10 +190,10 @@ async function resizeImage(originalImage, imageOptions) {
|
|
|
214
190
|
const imageToResize = originalImage.clone();
|
|
215
191
|
const scaledImage = imageToResize.resize(prioritizeHeight ? {
|
|
216
192
|
height: resizeHeight,
|
|
217
|
-
fit:
|
|
193
|
+
fit: sharp.fit.cover
|
|
218
194
|
} : {
|
|
219
195
|
width: resizeWidth,
|
|
220
|
-
fit:
|
|
196
|
+
fit: sharp.fit.cover
|
|
221
197
|
});
|
|
222
198
|
const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true });
|
|
223
199
|
const extractWidth = resizeWidth ?? scaledImageInfo.width;
|
|
@@ -245,10 +221,10 @@ async function resizeImage(originalImage, imageOptions) {
|
|
|
245
221
|
return defaultResizeOptions();
|
|
246
222
|
}
|
|
247
223
|
}
|
|
248
|
-
function getOriginalImage(url, domains, headers
|
|
224
|
+
function getOriginalImage(url, domains, headers) {
|
|
249
225
|
return new Promise((resolve, reject) => {
|
|
250
226
|
if (url.indexOf("http") === 0) {
|
|
251
|
-
if (
|
|
227
|
+
if (isInvalidDomain(url, domains)) {
|
|
252
228
|
return reject({ status: 401, message: "invalid origin" });
|
|
253
229
|
}
|
|
254
230
|
fetch(url, {
|
|
@@ -311,11 +287,13 @@ function urlToCachedName(options) {
|
|
|
311
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}`);
|
|
312
288
|
return cachedName;
|
|
313
289
|
}
|
|
314
|
-
function
|
|
290
|
+
function isInvalidDomain(src, domains) {
|
|
291
|
+
if (IMAGE_STRATEGY === ImageStrategy.Token) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
315
294
|
const url = new URL(src);
|
|
316
|
-
return domains.includes(url.hostname);
|
|
295
|
+
return !domains.includes(url.hostname);
|
|
317
296
|
}
|
|
318
|
-
|
|
319
|
-
0 && (module.exports = {
|
|
297
|
+
export {
|
|
320
298
|
imageHandler
|
|
321
|
-
}
|
|
299
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@websolutespa/bom-mixer-image",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.0.1-next.0",
|
|
4
4
|
"description": "Mixer Image module of the BOM Repository",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bom",
|
|
@@ -14,37 +14,39 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"lint": "eslint *.ts*",
|
|
17
|
-
"test": "
|
|
18
|
-
"test-watch": "
|
|
19
|
-
"coverage:integration": "npm run test -- --coverage",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test-watch": "vitest",
|
|
19
|
+
"coverage:integration": "npm run test -- --coverage.enabled --coverage.provider=istanbul --coverage.all",
|
|
20
20
|
"coverage": "run-s -c --silent coverage:*",
|
|
21
21
|
"compile": "tsup ./src/index.ts --format esm,cjs --dts",
|
|
22
22
|
"prepack": "bom prepack",
|
|
23
|
-
"postpack": "bom postpack"
|
|
23
|
+
"postpack": "bom postpack",
|
|
24
|
+
"types": "tsc --noemit"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {},
|
|
26
27
|
"peerDependencies": {
|
|
27
|
-
"@websolutespa/bom-core": "
|
|
28
|
-
"next": "
|
|
29
|
-
"sharp": "
|
|
28
|
+
"@websolutespa/bom-core": "2.0.1-next.0",
|
|
29
|
+
"next": "15.4.10",
|
|
30
|
+
"sharp": "0.34.2"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@types/sharp": "^0.30.4",
|
|
33
|
-
"@websolutespa/bom-cli": "
|
|
34
|
-
"@websolutespa/test": "
|
|
35
|
-
"@websolutespa/tsconfig": "
|
|
36
|
-
"eslint": "
|
|
37
|
-
"eslint-config
|
|
38
|
-
"npm-run-all": "
|
|
39
|
-
"sharp": "0.
|
|
40
|
-
"ts-node": "^10.9.
|
|
41
|
-
"tsup": "
|
|
42
|
-
"typescript": "
|
|
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",
|
|
37
|
+
"eslint": "9.31.0",
|
|
38
|
+
"@websolutespa/eslint-config": "2.0.1-next.0",
|
|
39
|
+
"npm-run-all": "4.1.5",
|
|
40
|
+
"sharp": "0.34.2",
|
|
41
|
+
"ts-node": "^10.9.2",
|
|
42
|
+
"tsup": "8.5.0",
|
|
43
|
+
"typescript": "5.8.3"
|
|
43
44
|
},
|
|
44
45
|
"publishConfig": {
|
|
45
46
|
"access": "public"
|
|
46
47
|
},
|
|
47
48
|
"sideEffects": false,
|
|
49
|
+
"type": "module",
|
|
48
50
|
"prepack": {
|
|
49
51
|
"types": "./dist/index.d.ts",
|
|
50
52
|
"main": "./dist/index.js",
|
|
@@ -72,15 +72,7 @@ export function imageHandler({
|
|
|
72
72
|
return response.send(cachedImage);
|
|
73
73
|
}
|
|
74
74
|
// Check referrer
|
|
75
|
-
|
|
76
|
-
const referer = headers.referer;
|
|
77
|
-
const publicUrl = process.env.NEXT_PUBLIC_URL || '';
|
|
78
|
-
// console.log('referef', referer, 'publicUrl', publicUrl);
|
|
79
|
-
if (
|
|
80
|
-
referer &&
|
|
81
|
-
publicUrl !== 'https://' &&
|
|
82
|
-
referer.indexOf(publicUrl) !== 0
|
|
83
|
-
) {
|
|
75
|
+
if (isInvalidReferer(request)) {
|
|
84
76
|
return response.status(401).send('you are not authorized to perform this action');
|
|
85
77
|
}
|
|
86
78
|
// Temporarily fix Vercel local image issue
|
|
@@ -92,7 +84,9 @@ export function imageHandler({
|
|
|
92
84
|
// Checking if the original image exists
|
|
93
85
|
if (originalImageBuffer) {
|
|
94
86
|
// Original image
|
|
95
|
-
const originalImage = sharp(originalImageBuffer
|
|
87
|
+
const originalImage = sharp(originalImageBuffer, {
|
|
88
|
+
animated: isGIF(imageOptions.url),
|
|
89
|
+
});
|
|
96
90
|
// Resizing the original image and converting it to WebP format
|
|
97
91
|
const resizedImage = await resizeImage(originalImage, imageOptions);
|
|
98
92
|
// Converting resized image to WebP format
|
|
@@ -129,6 +123,26 @@ export function imageHandler({
|
|
|
129
123
|
});
|
|
130
124
|
}
|
|
131
125
|
|
|
126
|
+
function isGIF(src: string): boolean {
|
|
127
|
+
return src.indexOf('.gif') !== -1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isInvalidReferer(request: NextApiRequest): boolean {
|
|
131
|
+
const { headers } = request;
|
|
132
|
+
const referer = headers.referer;
|
|
133
|
+
const publicUrl = process.env.NEXT_PUBLIC_URL || '';
|
|
134
|
+
// console.log('referef', referer, 'publicUrl', publicUrl, 'IMAGE_STRATEGY', IMAGE_STRATEGY, ImageStrategy.Token);
|
|
135
|
+
if (
|
|
136
|
+
IMAGE_STRATEGY !== ImageStrategy.Token &&
|
|
137
|
+
referer &&
|
|
138
|
+
publicUrl !== 'https://' &&
|
|
139
|
+
referer.indexOf(publicUrl) !== 0
|
|
140
|
+
) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
132
146
|
function getImageOptions(request: NextApiRequest, options: IImageHandlerOptions): IImageOptions {
|
|
133
147
|
// Destructuring 'slugs', 'origin', 'search', 'w', 'h', 'x', 'y', 'q' from the request query
|
|
134
148
|
const { slugs, origin = '', search = '', w, h, x, y, q } = request.query;
|
|
@@ -211,11 +225,12 @@ function getEncryptedImageOptions(request: NextApiRequest, options: IImageHandle
|
|
|
211
225
|
throw 'invalid q parameter';
|
|
212
226
|
}
|
|
213
227
|
*/
|
|
228
|
+
imageResize.src = decodeURIComponent(imageResize.src);
|
|
214
229
|
// Generating url
|
|
215
|
-
const url = origin + '/' + slugs.join('/') + search;
|
|
230
|
+
const url = decodeURIComponent(origin + '/' + slugs.join('/') + search);
|
|
216
231
|
//
|
|
217
232
|
if (imageResize.src !== url) {
|
|
218
|
-
throw
|
|
233
|
+
throw `invalid src parameter ${imageResize.src} ${url}`;
|
|
219
234
|
}
|
|
220
235
|
//
|
|
221
236
|
return {
|
|
@@ -285,16 +300,12 @@ async function resizeImage(originalImage: sharp.Sharp, imageOptions: IImageOptio
|
|
|
285
300
|
}
|
|
286
301
|
|
|
287
302
|
// Defining the 'getOriginalImage' function
|
|
288
|
-
function getOriginalImage(url: string, domains: string[], headers: IncomingHttpHeaders
|
|
303
|
+
function getOriginalImage(url: string, domains: string[], headers: IncomingHttpHeaders): Promise<Buffer> {
|
|
289
304
|
// Returning a new promise
|
|
290
305
|
return new Promise((resolve, reject) => {
|
|
291
306
|
// Checking if 'url' starts with 'http'
|
|
292
307
|
if (url.indexOf('http') === 0) {
|
|
293
|
-
if (
|
|
294
|
-
!skipUrlValidity &&
|
|
295
|
-
!checkUrlValidity(url, domains)
|
|
296
|
-
) {
|
|
297
|
-
// console.error('imageHandler.getOriginalImage.error invalid origin');
|
|
308
|
+
if (isInvalidDomain(url, domains)) {
|
|
298
309
|
return reject({ status: 401, message: 'invalid origin' });
|
|
299
310
|
}
|
|
300
311
|
// Fetching the image from the URL
|
|
@@ -330,21 +341,6 @@ function getOriginalImage(url: string, domains: string[], headers: IncomingHttpH
|
|
|
330
341
|
// Resolving the promise with the image data
|
|
331
342
|
return resolve(data);
|
|
332
343
|
});
|
|
333
|
-
/*
|
|
334
|
-
// Check if the file exists
|
|
335
|
-
if (fs.existsSync(filePath)) {
|
|
336
|
-
fs.readFile(filePath, (error, data) => {
|
|
337
|
-
if (error) {
|
|
338
|
-
return reject(error);
|
|
339
|
-
}
|
|
340
|
-
// Resolving the promise with the image data
|
|
341
|
-
return resolve(data);
|
|
342
|
-
});
|
|
343
|
-
} else {
|
|
344
|
-
const origin = getOrigin(headers);
|
|
345
|
-
return getOriginalImage(`${origin}${url}`, domains, headers, true);
|
|
346
|
-
}
|
|
347
|
-
*/
|
|
348
344
|
}
|
|
349
345
|
});
|
|
350
346
|
}
|
|
@@ -369,7 +365,7 @@ function getCachedImage(pathname: string): Promise<Buffer> {
|
|
|
369
365
|
function urlToFileName(url: string): string {
|
|
370
366
|
// Replacing useless characters with UNDERSCORE
|
|
371
367
|
const segments = url.split('?');
|
|
372
|
-
const path = segments[0]
|
|
368
|
+
const path = segments[0]!;
|
|
373
369
|
const query = segments[1];
|
|
374
370
|
let uniqueName = path.replace('://', '_').replace(/\.|\/|\?|=/g, '_');
|
|
375
371
|
// Replacing last UNDERSCORE with a DOT
|
|
@@ -407,7 +403,10 @@ function urlToCachedName(options: IImageOptions): string {
|
|
|
407
403
|
return cachedName;
|
|
408
404
|
}
|
|
409
405
|
|
|
410
|
-
function
|
|
406
|
+
function isInvalidDomain(src: string, domains: string[]): boolean {
|
|
407
|
+
if (IMAGE_STRATEGY === ImageStrategy.Token) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
411
410
|
const url = new URL(src);
|
|
412
|
-
return domains.includes(url.hostname);
|
|
411
|
+
return !domains.includes(url.hostname);
|
|
413
412
|
}
|