b2b-platform-utils 1.1.56 → 1.1.58

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/errorsMap.js CHANGED
@@ -880,6 +880,54 @@ const STATIC_ERRORS = [
880
880
  httpCode: 503,
881
881
  description: "Service is temporarily unavailable.",
882
882
  },
883
+ {
884
+ errorCode: 1144,
885
+ errorKey: "promocodeInvalid",
886
+ httpCode: 422,
887
+ description: "Promo code is invalid or cannot be applied.",
888
+ },
889
+ {
890
+ errorCode: 1145,
891
+ errorKey: "promocodeExpired",
892
+ httpCode: 422,
893
+ description: "Promo code is expired.",
894
+ },
895
+ {
896
+ errorCode: 1146,
897
+ errorKey: "promocodeUsageLimitReached",
898
+ httpCode: 422,
899
+ description: "Promo code usage limit has been reached.",
900
+ },
901
+ {
902
+ errorCode: 1147,
903
+ errorKey: "promocodeApplicationModeMismatch",
904
+ httpCode: 422,
905
+ description: "Promo code application mode does not match the requested operation.",
906
+ },
907
+ {
908
+ errorCode: 1148,
909
+ errorKey: "promocodeTargetBonusAlreadyOwned",
910
+ httpCode: 422,
911
+ description: "Target bonus is already owned by the user.",
912
+ },
913
+ {
914
+ errorCode: 1149,
915
+ errorKey: "promocodeTargetBonusUnavailable",
916
+ httpCode: 422,
917
+ description: "Target bonus is not available for this promo code.",
918
+ },
919
+ {
920
+ errorCode: 1150,
921
+ errorKey: "promocodeAlreadyApplied",
922
+ httpCode: 422,
923
+ description: "Promo code has already been applied.",
924
+ },
925
+ {
926
+ errorCode: 1151,
927
+ errorKey: "promocodeNotActive",
928
+ httpCode: 422,
929
+ description: "Promo code is not active.",
930
+ },
883
931
  ];
884
932
 
885
933
  const STATIC_BY_KEY = Object.fromEntries(
package/optimizeMedia.js CHANGED
@@ -82,8 +82,185 @@ async function saveGameImageBufferWEBPWithOptions(fileDataBuffer, options = {})
82
82
  .toBuffer();
83
83
  }
84
84
 
85
+ const WEBP_CONVERSION_FORMATS = new Set(['jpeg', 'png', 'webp']);
86
+
87
+ function normalizeImageFileType(originalFileType) {
88
+ if (!originalFileType) {
89
+ return null;
90
+ }
91
+
92
+ let type = String(originalFileType).toLowerCase().replace(/^\./, '');
93
+
94
+ if (type === 'jpg') {
95
+ type = 'jpeg';
96
+ }
97
+
98
+ return type;
99
+ }
100
+
101
+ function isKnownFormat(fileType) {
102
+ return fileType === 'gif' || fileType === 'avif' || WEBP_CONVERSION_FORMATS.has(fileType);
103
+ }
104
+
105
+ function hasResizeOptions(options) {
106
+ const { maxWidth, width, height } = options;
107
+
108
+ return width != null || height != null || maxWidth != null;
109
+ }
110
+
111
+ function buildResizeOptions(options) {
112
+ const {
113
+ maxWidth,
114
+ width,
115
+ height,
116
+ fit = 'inside',
117
+ withoutEnlargement = true
118
+ } = options;
119
+
120
+ const resizeOptions = { fit, withoutEnlargement };
121
+
122
+ if (width != null) {
123
+ resizeOptions.width = width;
124
+ } else if (maxWidth != null) {
125
+ resizeOptions.width = maxWidth;
126
+ }
127
+
128
+ if (height != null) {
129
+ resizeOptions.height = height;
130
+ }
131
+
132
+ return resizeOptions;
133
+ }
134
+
135
+ async function resizePipelineIfNeeded(pipeline, fileDataBuffer, options) {
136
+ const { resize = 1 } = options;
137
+
138
+ if (hasResizeOptions(options)) {
139
+ return pipeline.resize(buildResizeOptions(options));
140
+ }
141
+
142
+ if (resize !== 1) {
143
+ const metadata = await sharp(fileDataBuffer).metadata();
144
+ const newWidth = Math.round(metadata.width * resize);
145
+ const newHeight = Math.round(metadata.height * resize);
146
+
147
+ return pipeline.resize(newWidth, newHeight);
148
+ }
149
+
150
+ return pipeline;
151
+ }
152
+
153
+ async function encodeWebpBuffer(fileDataBuffer, options) {
154
+ let pipeline = sharp(fileDataBuffer);
155
+ pipeline = await resizePipelineIfNeeded(pipeline, fileDataBuffer, options);
156
+
157
+ return pipeline
158
+ .webp({
159
+ quality: options.quality,
160
+ lossless: false,
161
+ smartSubsample: options.smartSubsample,
162
+ effort: options.effort,
163
+ alphaQuality: options.alphaQuality
164
+ })
165
+ .toBuffer();
166
+ }
167
+
168
+ /**
169
+ * Format-aware image optimization: preserves GIF/AVIF, converts JPEG/PNG/WebP to WebP, safe fallback on failure.
170
+ * @param {Buffer} fileDataBuffer
171
+ * @param {string} originalFileType
172
+ * @param {object} [options]
173
+ * @returns {Promise<{ buffer: Buffer, fileType: string, optimized: boolean }>}
174
+ */
175
+ async function optimizeImageBufferByFormat(fileDataBuffer, originalFileType, options = {}) {
176
+ const opts = {
177
+ quality: 80,
178
+ resize: 1,
179
+ alphaQuality: 80,
180
+ maxWidth: null,
181
+ width: null,
182
+ height: null,
183
+ fit: 'inside',
184
+ withoutEnlargement: true,
185
+ effort: 5,
186
+ smartSubsample: true,
187
+ ...options
188
+ };
189
+
190
+ let normalizedType = normalizeImageFileType(originalFileType);
191
+
192
+ try {
193
+ if (normalizedType === 'gif') {
194
+ return { buffer: fileDataBuffer, fileType: 'gif', optimized: false };
195
+ }
196
+
197
+ if (!normalizedType || !isKnownFormat(normalizedType)) {
198
+ const metadata = await sharp(fileDataBuffer).metadata();
199
+ const metadataType = normalizeImageFileType(metadata.format);
200
+
201
+ if (metadataType) {
202
+ normalizedType = metadataType;
203
+ }
204
+ }
205
+
206
+ if (normalizedType === 'gif') {
207
+ return { buffer: fileDataBuffer, fileType: 'gif', optimized: false };
208
+ }
209
+
210
+ if (normalizedType === 'avif') {
211
+ try {
212
+ let pipeline = sharp(fileDataBuffer);
213
+
214
+ if (hasResizeOptions(opts)) {
215
+ pipeline = pipeline.resize(buildResizeOptions(opts));
216
+ }
217
+
218
+ const buffer = await pipeline
219
+ .avif({
220
+ quality: opts.quality,
221
+ effort: opts.effort
222
+ })
223
+ .toBuffer();
224
+
225
+ return { buffer, fileType: 'avif', optimized: true };
226
+ } catch {
227
+ return { buffer: fileDataBuffer, fileType: 'avif', optimized: false };
228
+ }
229
+ }
230
+
231
+ if (WEBP_CONVERSION_FORMATS.has(normalizedType)) {
232
+ try {
233
+ const buffer = await encodeWebpBuffer(fileDataBuffer, opts);
234
+
235
+ return { buffer, fileType: 'webp', optimized: true };
236
+ } catch {
237
+ return { buffer: fileDataBuffer, fileType: normalizedType, optimized: false };
238
+ }
239
+ }
240
+
241
+ try {
242
+ const buffer = await encodeWebpBuffer(fileDataBuffer, opts);
243
+
244
+ return { buffer, fileType: 'webp', optimized: true };
245
+ } catch {
246
+ return {
247
+ buffer: fileDataBuffer,
248
+ fileType: normalizedType || 'bin',
249
+ optimized: false
250
+ };
251
+ }
252
+ } catch {
253
+ return {
254
+ buffer: fileDataBuffer,
255
+ fileType: normalizedType || normalizeImageFileType(originalFileType) || 'bin',
256
+ optimized: false
257
+ };
258
+ }
259
+ }
260
+
85
261
  module.exports = {
86
262
  optimizeImageBufferWEBP,
87
263
  saveGameImageBufferWEBP,
88
- saveGameImageBufferWEBPWithOptions
264
+ saveGameImageBufferWEBPWithOptions,
265
+ optimizeImageBufferByFormat
89
266
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "b2b-platform-utils",
3
- "version": "1.1.56",
3
+ "version": "1.1.58",
4
4
  "description": "Shared utilities for Node.js microservices: errors map, local cache, logger, numbers, dates, filesystem, media optimization, paginator, slugger, crypto wrapper, sanitize HTML, sorting.",
5
5
  "type": "commonjs",
6
6
  "license": "KingSizer",